Compare commits
No commits in common. "9f2c5ad9e3e7f0bc22ee53a3e0ec4f447732cd57" and "51fa3efc328f99ff028a3ad016e9560c9813478b" have entirely different histories.
9f2c5ad9e3
...
51fa3efc32
4
TODO
4
TODO
@ -12,7 +12,3 @@
|
|||||||
- Syscalls.
|
- Syscalls.
|
||||||
- Error message with an empty file wrongly says that a ")" is expected.
|
- Error message with an empty file wrongly says that a ")" is expected.
|
||||||
- Support any expressions for constants.
|
- Support any expressions for constants.
|
||||||
- Name analysis should fail if there are undefined symbols.
|
|
||||||
|
|
||||||
# Toolchain
|
|
||||||
- Try to guess the build platform (x86_64-slackware-linux is hard coded).
|
|
||||||
|
@ -53,8 +53,8 @@ namespace elna::cli
|
|||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
auto global_scope = add_builtin_symbols();
|
auto global_scope = add_builtin_symbols();
|
||||||
source::name_analysis_visitor(global_scope, in_file).visit(ast.get());
|
source::name_analysis_visitor(global_scope).visit(ast.get());
|
||||||
source::type_analysis_visitor(global_scope, in_file, 4).visit(ast.get());
|
source::type_analysis_visitor().visit(ast.get());
|
||||||
source::allocator_visitor(global_scope).visit(ast.get());
|
source::allocator_visitor(global_scope).visit(ast.get());
|
||||||
|
|
||||||
source::intermediate_code_generator intermediate_code_generator{ global_scope };
|
source::intermediate_code_generator intermediate_code_generator{ global_scope };
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
#include <boost/core/noncopyable.hpp>
|
#include <boost/core/noncopyable.hpp>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <elna/source/lexer.hpp>
|
#include <elna/source/lexer.hpp>
|
||||||
#include "elna/source/types.hpp"
|
|
||||||
|
|
||||||
namespace elna::source
|
namespace elna::source
|
||||||
{
|
{
|
||||||
@ -174,7 +173,6 @@ namespace elna::source
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
std::shared_ptr<operand> place;
|
std::shared_ptr<operand> place;
|
||||||
std::shared_ptr<const type> data_type;
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/**
|
/**
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include "elna/source/types.hpp"
|
|
||||||
|
|
||||||
namespace elna::source
|
namespace elna::source
|
||||||
{
|
{
|
||||||
@ -124,32 +123,6 @@ namespace elna::source
|
|||||||
std::string what() const override;
|
std::string what() const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct type_mismatch final : public error
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Kind of the operation on the type.
|
|
||||||
*/
|
|
||||||
enum class operation
|
|
||||||
{
|
|
||||||
dereference,
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* \param name Given type.
|
|
||||||
* \param kind Kind of the operation on the type.
|
|
||||||
* \param path Source file name.
|
|
||||||
* \param position Operation position.
|
|
||||||
*/
|
|
||||||
type_mismatch(std::shared_ptr<const type> got, operation kind, const std::filesystem::path& path,
|
|
||||||
const struct position position);
|
|
||||||
|
|
||||||
std::string what() const override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::shared_ptr<const type> got;
|
|
||||||
operation kind;
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
struct writer
|
struct writer
|
||||||
{
|
{
|
||||||
|
@ -7,23 +7,12 @@ namespace elna::source
|
|||||||
{
|
{
|
||||||
class name_analysis_visitor final : public empty_visitor
|
class name_analysis_visitor final : public empty_visitor
|
||||||
{
|
{
|
||||||
std::shared_ptr<symbol_table> table;
|
std::shared_ptr<symbol_table> table = std::make_shared<symbol_table>();
|
||||||
const std::filesystem::path filename;
|
|
||||||
std::list<std::unique_ptr<error>> m_errors;
|
|
||||||
|
|
||||||
std::shared_ptr<const type> convert_declaration_type(const type_expression& ast_type) const;
|
std::shared_ptr<const type> convert_declaration_type(const type_expression& ast_type) const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
name_analysis_visitor(std::shared_ptr<symbol_table> table);
|
||||||
* \param table Symbol table.
|
|
||||||
* \param path Source filename.
|
|
||||||
*/
|
|
||||||
name_analysis_visitor(std::shared_ptr<symbol_table> table, const std::filesystem::path& filename);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* \return Collected errors.
|
|
||||||
*/
|
|
||||||
const std::list<std::unique_ptr<error>>& errors() const noexcept;
|
|
||||||
|
|
||||||
void visit(constant_definition *definition) override;
|
void visit(constant_definition *definition) override;
|
||||||
void visit(declaration *declaration) override;
|
void visit(declaration *declaration) override;
|
||||||
@ -55,29 +44,5 @@ namespace elna::source
|
|||||||
*/
|
*/
|
||||||
class type_analysis_visitor final : public empty_visitor
|
class type_analysis_visitor final : public empty_visitor
|
||||||
{
|
{
|
||||||
std::shared_ptr<symbol_table> table;
|
|
||||||
const std::filesystem::path filename;
|
|
||||||
const std::size_t pointer_size;
|
|
||||||
std::list<std::unique_ptr<error>> m_errors;
|
|
||||||
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* \param table Symbol table.
|
|
||||||
* \param path Source filename.
|
|
||||||
* \param target_pointer_size Pointer size on the target platform.
|
|
||||||
*/
|
|
||||||
type_analysis_visitor(std::shared_ptr<symbol_table> table, const std::filesystem::path& filename,
|
|
||||||
std::size_t target_pointer_size);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* \return Collected errors.
|
|
||||||
*/
|
|
||||||
const std::list<std::unique_ptr<error>>& errors() const noexcept;
|
|
||||||
|
|
||||||
void visit(program *program) override;
|
|
||||||
void visit(procedure_definition *definition) override;
|
|
||||||
void visit(integer_literal *literal) override;
|
|
||||||
void visit(boolean_literal *literal) override;
|
|
||||||
void visit(unary_expression *expression) override;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -8,23 +8,12 @@ namespace elna::source
|
|||||||
/**
|
/**
|
||||||
* Type representation.
|
* Type representation.
|
||||||
*/
|
*/
|
||||||
class type
|
struct type
|
||||||
{
|
{
|
||||||
const std::size_t byte_size;
|
const std::size_t byte_size;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/**
|
|
||||||
* Constructor.
|
|
||||||
*
|
|
||||||
* \param byte_size The type size in bytes.
|
|
||||||
*/
|
|
||||||
explicit type(const std::size_t byte_size);
|
explicit type(const std::size_t byte_size);
|
||||||
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* \return The type size in bytes.
|
|
||||||
*/
|
|
||||||
virtual std::size_t size() const noexcept;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -32,15 +21,8 @@ namespace elna::source
|
|||||||
*/
|
*/
|
||||||
struct primitive_type : public type
|
struct primitive_type : public type
|
||||||
{
|
{
|
||||||
/// Type name.
|
|
||||||
const std::string type_name;
|
const std::string type_name;
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor.
|
|
||||||
*
|
|
||||||
* \param type_name Type name.
|
|
||||||
* \param byte_size The type size in bytes.
|
|
||||||
*/
|
|
||||||
primitive_type(const std::string& type_name, const std::size_t byte_size);
|
primitive_type(const std::string& type_name, const std::size_t byte_size);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -49,15 +31,8 @@ namespace elna::source
|
|||||||
*/
|
*/
|
||||||
struct pointer_type : public type
|
struct pointer_type : public type
|
||||||
{
|
{
|
||||||
/// Pointer target type.
|
|
||||||
std::shared_ptr<const type> base_type;
|
std::shared_ptr<const type> base_type;
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor.
|
|
||||||
*
|
|
||||||
* \param base_type Pointer target type.
|
|
||||||
* \param byte_size The type size in bytes.
|
|
||||||
*/
|
|
||||||
pointer_type(std::shared_ptr<const type> base_type, const std::size_t byte_size);
|
pointer_type(std::shared_ptr<const type> base_type, const std::size_t byte_size);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -16,7 +16,9 @@ namespace elna
|
|||||||
{
|
{
|
||||||
successful,
|
successful,
|
||||||
compile_failed,
|
compile_failed,
|
||||||
|
build_failed,
|
||||||
expectation_failed,
|
expectation_failed,
|
||||||
|
expectation_not_found
|
||||||
};
|
};
|
||||||
|
|
||||||
class test_results final
|
class test_results final
|
||||||
|
12
package.json
12
package.json
@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "elna",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "",
|
|
||||||
"main": "tools/index.js",
|
|
||||||
"type": "module",
|
|
||||||
"scripts": {
|
|
||||||
"start": "node tools/index.js"
|
|
||||||
},
|
|
||||||
"author": "Eugen Wissner <belka@caraus.de>",
|
|
||||||
"license": "MPL-2.0"
|
|
||||||
}
|
|
@ -1,4 +1,5 @@
|
|||||||
#include "elna/source/result.hpp"
|
#include "elna/source/result.hpp"
|
||||||
|
#include "elna/source/types.hpp"
|
||||||
|
|
||||||
namespace elna::source
|
namespace elna::source
|
||||||
{
|
{
|
||||||
@ -32,15 +33,4 @@ namespace elna::source
|
|||||||
{
|
{
|
||||||
return "Name '" + name + "' was already defined";
|
return "Name '" + name + "' was already defined";
|
||||||
}
|
}
|
||||||
|
|
||||||
type_mismatch::type_mismatch(std::shared_ptr<const type> got, operation kind, const std::filesystem::path& path,
|
|
||||||
const struct position position)
|
|
||||||
: error(path, position), kind(kind), got(got)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string type_mismatch::what() const
|
|
||||||
{
|
|
||||||
return "Type cannot be used here.";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
#include "elna/source/semantic.hpp"
|
#include "elna/source/semantic.hpp"
|
||||||
#include "elna/source/result.hpp"
|
#include "elna/source/types.hpp"
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
|
||||||
namespace elna::source
|
namespace elna::source
|
||||||
{
|
{
|
||||||
name_analysis_visitor::name_analysis_visitor(std::shared_ptr<symbol_table> table,
|
name_analysis_visitor::name_analysis_visitor(std::shared_ptr<symbol_table> table)
|
||||||
const std::filesystem::path& filename)
|
: table(table)
|
||||||
: table(table), filename(filename)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,11 +63,6 @@ namespace elna::source
|
|||||||
this->table = info->scope()->scope();
|
this->table = info->scope()->scope();
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::list<std::unique_ptr<error>>& name_analysis_visitor::errors() const noexcept
|
|
||||||
{
|
|
||||||
return m_errors;
|
|
||||||
}
|
|
||||||
|
|
||||||
allocator_visitor::allocator_visitor(std::shared_ptr<symbol_table> table)
|
allocator_visitor::allocator_visitor(std::shared_ptr<symbol_table> table)
|
||||||
: table(table)
|
: table(table)
|
||||||
{
|
{
|
||||||
@ -121,69 +115,4 @@ namespace elna::source
|
|||||||
this->argument_offset = std::max(static_cast<std::size_t>(this->argument_offset),
|
this->argument_offset = std::max(static_cast<std::size_t>(this->argument_offset),
|
||||||
call_info->parameter_stack_size());
|
call_info->parameter_stack_size());
|
||||||
}
|
}
|
||||||
|
|
||||||
type_analysis_visitor::type_analysis_visitor(std::shared_ptr<symbol_table> table,
|
|
||||||
const std::filesystem::path& filename, std::size_t target_pointer_size)
|
|
||||||
: table(table), filename(filename), pointer_size(target_pointer_size)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void type_analysis_visitor::visit(program *program)
|
|
||||||
{
|
|
||||||
for (auto& definition : program->definitions())
|
|
||||||
{
|
|
||||||
if (dynamic_cast<procedure_definition *>(definition.get()) != nullptr)
|
|
||||||
{
|
|
||||||
definition->accept(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
program->body().accept(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void type_analysis_visitor::visit(procedure_definition *definition)
|
|
||||||
{
|
|
||||||
definition->body().accept(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void type_analysis_visitor::visit(integer_literal *literal)
|
|
||||||
{
|
|
||||||
literal->data_type = std::dynamic_pointer_cast<type_info>(table->lookup("Int"))->type();
|
|
||||||
}
|
|
||||||
|
|
||||||
void type_analysis_visitor::visit(boolean_literal *literal)
|
|
||||||
{
|
|
||||||
literal->data_type = std::dynamic_pointer_cast<type_info>(table->lookup("Boolean"))->type();
|
|
||||||
}
|
|
||||||
|
|
||||||
void type_analysis_visitor::visit(unary_expression *expression)
|
|
||||||
{
|
|
||||||
empty_visitor::visit(expression);
|
|
||||||
|
|
||||||
switch (expression->operation())
|
|
||||||
{
|
|
||||||
case unary_operator::reference:
|
|
||||||
expression->data_type = std::make_shared<const pointer_type>(expression->operand().data_type,
|
|
||||||
this->pointer_size);
|
|
||||||
break;
|
|
||||||
case unary_operator::dereference:
|
|
||||||
auto operand_type = expression->operand().data_type;
|
|
||||||
|
|
||||||
if (auto referenced_type = std::dynamic_pointer_cast<const pointer_type>(operand_type))
|
|
||||||
{
|
|
||||||
expression->data_type = referenced_type;
|
|
||||||
}
|
|
||||||
else if (operand_type != nullptr)
|
|
||||||
{
|
|
||||||
auto new_error = std::make_unique<type_mismatch>(operand_type,
|
|
||||||
type_mismatch::operation::dereference, this->filename, expression->position());
|
|
||||||
m_errors.push_back(std::move(new_error));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::list<std::unique_ptr<error>>& type_analysis_visitor::errors() const noexcept
|
|
||||||
{
|
|
||||||
return m_errors;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -7,11 +7,6 @@ namespace elna::source
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
std::size_t type::size() const noexcept
|
|
||||||
{
|
|
||||||
return this->byte_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
primitive_type::primitive_type(const std::string& type_name, const std::size_t byte_size)
|
primitive_type::primitive_type(const std::string& type_name, const std::size_t byte_size)
|
||||||
: type(byte_size), type_name(type_name)
|
: type(byte_size), type_name(type_name)
|
||||||
{
|
{
|
||||||
|
109
tests/tester.cpp
109
tests/tester.cpp
@ -1,6 +1,5 @@
|
|||||||
#include "elna/tester.hpp"
|
#include "elna/tester.hpp"
|
||||||
|
|
||||||
#include <filesystem>
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
@ -52,16 +51,6 @@ namespace elna
|
|||||||
return "build/tests";
|
return "build/tests";
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::filesystem::path in_root_directory()
|
|
||||||
{
|
|
||||||
return "build/root";
|
|
||||||
}
|
|
||||||
|
|
||||||
static std::filesystem::path in_root_directory(const std::filesystem::path& path)
|
|
||||||
{
|
|
||||||
return in_root_directory() / path;
|
|
||||||
}
|
|
||||||
|
|
||||||
boost::process::v2::process_stdio get_output_streams(const std::uint8_t stream_number,
|
boost::process::v2::process_stdio get_output_streams(const std::uint8_t stream_number,
|
||||||
boost::asio::readable_pipe& read_pipe)
|
boost::asio::readable_pipe& read_pipe)
|
||||||
{
|
{
|
||||||
@ -125,9 +114,9 @@ namespace elna
|
|||||||
{
|
{
|
||||||
const std::filesystem::path test_filename = test_entry.path().filename();
|
const std::filesystem::path test_filename = test_entry.path().filename();
|
||||||
|
|
||||||
std::filesystem::path test_binary = in_root_directory("tests" / test_filename);
|
std::filesystem::path test_binary = in_build_directory(test_filename);
|
||||||
std::filesystem::path test_object = in_build_directory(test_filename);
|
std::filesystem::path test_object = test_binary;
|
||||||
std::filesystem::path test_log = in_build_directory(test_filename);
|
std::filesystem::path test_log = test_binary;
|
||||||
test_binary.replace_extension();
|
test_binary.replace_extension();
|
||||||
test_object.replace_extension(".o");
|
test_object.replace_extension(".o");
|
||||||
test_log.replace_extension(".log");
|
test_log.replace_extension(".log");
|
||||||
@ -194,74 +183,6 @@ namespace elna
|
|||||||
{ test_binary.string() });
|
{ test_binary.string() });
|
||||||
}
|
}
|
||||||
|
|
||||||
static void cpio_archive(boost::asio::io_context& context)
|
|
||||||
{
|
|
||||||
boost::asio::readable_pipe read_pipe{ context };
|
|
||||||
boost::asio::writable_pipe write_pipe{ context };
|
|
||||||
std::string output;
|
|
||||||
boost::asio::dynamic_string_buffer buffer = boost::asio::dynamic_buffer(output);
|
|
||||||
boost::system::error_code ec;
|
|
||||||
std::ofstream archive_output{ "build/root.cpio", std::ios::binary };
|
|
||||||
boost::process::v2::process_stdio process_stdio;
|
|
||||||
auto current_path = std::filesystem::current_path();
|
|
||||||
|
|
||||||
std::filesystem::current_path(in_root_directory());
|
|
||||||
boost::process::v2::process cpio_child(context, boost::process::search_path("cpio"),
|
|
||||||
{ "-o", "--format=newc" },
|
|
||||||
boost::process::v2::process_stdio{ write_pipe, read_pipe, nullptr });
|
|
||||||
|
|
||||||
for (const auto& entry : std::filesystem::recursive_directory_iterator("."))
|
|
||||||
{
|
|
||||||
auto entry_path = entry.path().string() + "\n";
|
|
||||||
auto entry_iterator = std::cbegin(entry_path);
|
|
||||||
std::size_t written{ 0 };
|
|
||||||
|
|
||||||
while (entry_iterator != std::cend(entry_path))
|
|
||||||
{
|
|
||||||
std::size_t written = write_pipe.write_some(boost::asio::buffer(entry_iterator.base(),
|
|
||||||
std::distance(entry_iterator, std::cend(entry_path))));
|
|
||||||
std::advance(entry_iterator, written);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
write_pipe.close();
|
|
||||||
do
|
|
||||||
{
|
|
||||||
std::size_t transferred = read_pipe.read_some(buffer.prepare(512), ec);
|
|
||||||
|
|
||||||
if (transferred == 0)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
buffer.commit(transferred);
|
|
||||||
archive_output.write(boost::asio::buffer_cast<const char *>(buffer.data()), buffer.size());
|
|
||||||
buffer.consume(transferred);
|
|
||||||
}
|
|
||||||
while (ec == boost::system::errc::success);
|
|
||||||
|
|
||||||
std::filesystem::current_path(current_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void run_vm(boost::asio::io_context& context)
|
|
||||||
{
|
|
||||||
std::vector<std::string> arguments = {
|
|
||||||
"-nographic",
|
|
||||||
"-M", "virt",
|
|
||||||
"-bios", "default",
|
|
||||||
"-kernel", "build/tools/linux-5.15.158/arch/riscv/boot/Image",
|
|
||||||
"-append", "quiet",
|
|
||||||
"-initrd", "build/root.cpio"
|
|
||||||
};
|
|
||||||
auto process = boost::process::v2::process(context,
|
|
||||||
boost::process::search_path("qemu-system-riscv32"), arguments);
|
|
||||||
boost::process::v2::execute(std::move(process));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void create_init()
|
|
||||||
{
|
|
||||||
std::filesystem::copy("build/tools/init", in_root_directory());
|
|
||||||
}
|
|
||||||
|
|
||||||
static test_results run_in_path(const std::filesystem::path test_directory)
|
static test_results run_in_path(const std::filesystem::path test_directory)
|
||||||
{
|
{
|
||||||
test_results results;
|
test_results results;
|
||||||
@ -275,13 +196,14 @@ namespace elna
|
|||||||
}
|
}
|
||||||
test_status result;
|
test_status result;
|
||||||
|
|
||||||
std::cout << "Compiling " << test_entry << std::endl;
|
std::cout << "Running " << test_entry << std::endl;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
build_test(context, test_entry);
|
build_test(context, test_entry);
|
||||||
}
|
}
|
||||||
catch (const boost::process::process_error& exception)
|
catch (const boost::process::process_error& exception)
|
||||||
{
|
{
|
||||||
|
test_status::build_failed;
|
||||||
std::cout << exception.what() << std::endl;
|
std::cout << exception.what() << std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -302,12 +224,15 @@ namespace elna
|
|||||||
print_result(test_entry, result);
|
print_result(test_entry, result);
|
||||||
results.add_exit_code(result);
|
results.add_exit_code(result);
|
||||||
}
|
}
|
||||||
std::filesystem::copy(test_directory / "expectations", in_root_directory("expectations"),
|
for (const auto& expectation_entry : std::filesystem::directory_iterator(test_directory / "expectations"))
|
||||||
std::filesystem::copy_options::recursive);
|
{
|
||||||
create_init();
|
auto test_entry = test_directory / expectation_entry.path().filename();
|
||||||
cpio_archive(context);
|
test_entry.replace_extension(".eln");
|
||||||
run_vm(context);
|
auto result = check_expectation(expectation_entry.path(), in_actual_directory());
|
||||||
|
|
||||||
|
print_result(test_entry, result);
|
||||||
|
results.add_exit_code(result);
|
||||||
|
}
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -325,14 +250,6 @@ int main()
|
|||||||
std::filesystem::create_directory(elna::in_build_directory());
|
std::filesystem::create_directory(elna::in_build_directory());
|
||||||
std::filesystem::create_directory(elna::in_actual_directory());
|
std::filesystem::create_directory(elna::in_actual_directory());
|
||||||
|
|
||||||
std::filesystem::remove_all(elna::in_root_directory());
|
|
||||||
std::filesystem::create_directory(elna::in_root_directory());
|
|
||||||
std::filesystem::create_directory(elna::in_root_directory("expectations"));
|
|
||||||
std::filesystem::create_directory(elna::in_root_directory("tests"));
|
|
||||||
|
|
||||||
auto current_environment = boost::this_process::environment();
|
|
||||||
current_environment["PATH"] += "./build/tools/sysroot/bin";
|
|
||||||
|
|
||||||
std::cout << "Run all tests and check the results" << std::endl;
|
std::cout << "Run all tests and check the results" << std::endl;
|
||||||
std::filesystem::path test_directory{ "tests" };
|
std::filesystem::path test_directory{ "tests" };
|
||||||
const auto results = elna::run_in_path(test_directory);
|
const auto results = elna::run_in_path(test_directory);
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
/dev/vda / ext4 defaults 1 1
|
|
||||||
proc /proc proc defaults 0 0
|
|
@ -1,3 +0,0 @@
|
|||||||
::sysinit:/etc/init.d/rcS
|
|
||||||
::shutdown:/etc/init.d/rcK
|
|
||||||
::askfirst:-/bin/sh
|
|
@ -1,7 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
PATH=/usr/local/sbin:/usr/sbin:/sbin:/usr/local/bin:/usr/bin:/bin
|
|
||||||
|
|
||||||
killall5 -15
|
|
||||||
sleep 2
|
|
||||||
umount -v -a -r -t no,proc,sysfs,devtmpfs
|
|
@ -1,7 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
PATH=/usr/local/sbin:/usr/sbin:/sbin:/usr/local/bin:/usr/bin:/bin
|
|
||||||
|
|
||||||
mount -v proc /proc -n -t proc
|
|
||||||
mount -w -v -n -o remount /
|
|
||||||
mount -a
|
|
373
tools/index.js
373
tools/index.js
@ -1,373 +0,0 @@
|
|||||||
import fs from 'fs/promises'
|
|
||||||
import path from 'node:path'
|
|
||||||
import childProcess from 'node:child_process'
|
|
||||||
import process from 'process'
|
|
||||||
import os from 'os'
|
|
||||||
|
|
||||||
// Define constants.
|
|
||||||
const tmp = path.resolve('./build/tools')
|
|
||||||
const target = 'riscv32-unknown-linux-gnu'
|
|
||||||
const build = 'x86_64-slackware-linux'
|
|
||||||
const baseDirectory = path.resolve('./tools')
|
|
||||||
|
|
||||||
const busyboxVersion = '1.36.1'
|
|
||||||
const kernelVersion = '5.15.158'
|
|
||||||
const gccVersion = '13.2.0'
|
|
||||||
const binutilsVersion = '2.42'
|
|
||||||
const glibcVersion = '2.39'
|
|
||||||
|
|
||||||
function createImage (rootfs) {
|
|
||||||
const rootExt4 = path.join(tmp, 'rootfs.ext4')
|
|
||||||
|
|
||||||
childProcess.execFileSync('dd', ['if=/dev/zero', `of=${rootExt4}`, 'bs=1M', 'count=1024'], { stdio: 'inherit' })
|
|
||||||
childProcess.execFileSync('/sbin/mkfs.ext4', ['-d', rootfs, rootExt4], { stdio: 'inherit' })
|
|
||||||
}
|
|
||||||
|
|
||||||
async function downloadAndUnarchive (url) {
|
|
||||||
const response = await fetch(url)
|
|
||||||
const bodyReader = response.body.getReader()
|
|
||||||
const basename = path.basename(url.pathname)
|
|
||||||
let archiveType = ''
|
|
||||||
let rootDirectory = ''
|
|
||||||
|
|
||||||
switch (path.extname(basename)) {
|
|
||||||
case '.bz2':
|
|
||||||
archiveType = '-j'
|
|
||||||
rootDirectory = path.basename(basename, '.tar.bz2')
|
|
||||||
break
|
|
||||||
case '.xz':
|
|
||||||
archiveType = '-J'
|
|
||||||
rootDirectory = path.basename(basename, '.tar.xz')
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise(async function (resolve, reject) {
|
|
||||||
const untar = childProcess.spawn('tar', ['-C', tmp, archiveType, '-xv'], { stdio: ['pipe', 'inherit', 'inherit'] })
|
|
||||||
let done = false
|
|
||||||
|
|
||||||
untar.on('exit', function () {
|
|
||||||
resolve(path.join(tmp, rootDirectory))
|
|
||||||
})
|
|
||||||
|
|
||||||
do {
|
|
||||||
const chunk = await bodyReader.read()
|
|
||||||
|
|
||||||
done = chunk.done
|
|
||||||
if (chunk.value !== undefined) {
|
|
||||||
untar.stdin.write(chunk.value)
|
|
||||||
}
|
|
||||||
} while (!done)
|
|
||||||
|
|
||||||
untar.stdin.end()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async function buildBusyBox (sysroot, rootfs) {
|
|
||||||
const cwd = await downloadAndUnarchive(new URL(`https://busybox.net/downloads/busybox-${busyboxVersion}.tar.bz2`))
|
|
||||||
const env = {
|
|
||||||
...process.env,
|
|
||||||
CROSS_COMPILE: path.join(sysroot, 'bin', `${target}-`)
|
|
||||||
}
|
|
||||||
const configuration = [
|
|
||||||
`CONFIG_PREFIX=${rootfs}`,
|
|
||||||
`CONFIG_SYSROOT=${rootfs}`
|
|
||||||
]
|
|
||||||
childProcess.execFileSync('make', ['defconfig'], { stdio: 'inherit', env, cwd })
|
|
||||||
childProcess.execFileSync('make', ['-j', os.availableParallelism()], { stdio: 'inherit', env, cwd })
|
|
||||||
childProcess.execFileSync('make', [...configuration, 'install'], { stdio: 'inherit', env, cwd })
|
|
||||||
}
|
|
||||||
|
|
||||||
async function buildKernel (sysroot, rootfs) {
|
|
||||||
const cwd = await downloadAndUnarchive(
|
|
||||||
new URL(`https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-${kernelVersion}.tar.xz`)
|
|
||||||
)
|
|
||||||
const env = {
|
|
||||||
...process.env,
|
|
||||||
CROSS_COMPILE: `${target}-`,
|
|
||||||
ARCH: 'riscv',
|
|
||||||
PATH: `${path.join(sysroot, 'bin')}:${process.env['PATH']}`
|
|
||||||
}
|
|
||||||
childProcess.execFileSync('make', ['rv32_defconfig'], { stdio: 'inherit', env, cwd })
|
|
||||||
childProcess.execFileSync('make', ['-j', os.availableParallelism()], { stdio: 'inherit', env, cwd })
|
|
||||||
childProcess.execFileSync('make', ['headers'], { stdio: 'inherit', env, cwd })
|
|
||||||
|
|
||||||
const userDirectory = path.join(rootfs, 'usr')
|
|
||||||
await fs.cp(path.join(cwd, 'usr/include'), path.join(userDirectory, 'include'), { recursive: true })
|
|
||||||
|
|
||||||
return path.join(cwd, 'arch/riscv/boot/Image')
|
|
||||||
}
|
|
||||||
|
|
||||||
async function buildInit (sysroot) {
|
|
||||||
const env = {
|
|
||||||
...process.env,
|
|
||||||
PATH: `${path.join(sysroot, 'bin')}:${process.env['PATH']}`
|
|
||||||
}
|
|
||||||
const compilerArguments = [
|
|
||||||
'-ffreestanding', '-static',
|
|
||||||
'-o', path.join(tmp, 'init'),
|
|
||||||
path.join(baseDirectory, 'init.cpp')
|
|
||||||
]
|
|
||||||
childProcess.execFileSync('riscv32-unknown-linux-gnu-g++', compilerArguments, { stdio: 'inherit', env })
|
|
||||||
}
|
|
||||||
|
|
||||||
async function buildGlibc (sysroot, rootfs) {
|
|
||||||
const sourceDirectory = await downloadAndUnarchive(
|
|
||||||
new URL(`https://ftp.gnu.org/gnu/glibc/glibc-${glibcVersion}.tar.xz`)
|
|
||||||
)
|
|
||||||
const configureOptions = [
|
|
||||||
'--prefix=/usr',
|
|
||||||
'--libdir=/usr/lib',
|
|
||||||
`--host=${target}`,
|
|
||||||
`--build=${build}`,
|
|
||||||
`--enable-kernel=${kernelVersion}`,
|
|
||||||
`--with-headers=${path.join(rootfs, 'usr/include')}`,
|
|
||||||
'--disable-nscd'
|
|
||||||
]
|
|
||||||
const env = {
|
|
||||||
...process.env,
|
|
||||||
PATH: `${path.join(sysroot, 'bin')}:${process.env['PATH']}`
|
|
||||||
}
|
|
||||||
const cwd = path.join(path.dirname(sourceDirectory), 'build-glibc');
|
|
||||||
await fs.mkdir(cwd)
|
|
||||||
|
|
||||||
childProcess.execFileSync(path.join(path.relative(cwd, sourceDirectory), './configure'), configureOptions, {
|
|
||||||
stdio: 'inherit',
|
|
||||||
env,
|
|
||||||
cwd
|
|
||||||
})
|
|
||||||
childProcess.execFileSync('make', ['-j', os.availableParallelism()], { stdio: 'inherit', env, cwd })
|
|
||||||
childProcess.execFileSync('make', [`DESTDIR=${rootfs}`, 'install'], { stdio: 'inherit', env, cwd })
|
|
||||||
}
|
|
||||||
|
|
||||||
async function buildCrossBinutils (sysroot) {
|
|
||||||
const sourceDirectory = await downloadAndUnarchive(
|
|
||||||
new URL(`https://ftp.gnu.org/gnu/binutils/binutils-${binutilsVersion}.tar.xz`)
|
|
||||||
)
|
|
||||||
const cwd = path.join(path.dirname(sourceDirectory), 'build-binutils');
|
|
||||||
await fs.mkdir(cwd)
|
|
||||||
|
|
||||||
const configureOptions = [
|
|
||||||
`--prefix=${sysroot}`,
|
|
||||||
`--target=${target}`,
|
|
||||||
'--disable-nls',
|
|
||||||
'--enable-gprofng=no',
|
|
||||||
'--disable-werror',
|
|
||||||
'--enable-default-hash-style=gnu'
|
|
||||||
]
|
|
||||||
childProcess.execFileSync(path.join(path.relative(cwd, sourceDirectory), 'configure'), configureOptions, {
|
|
||||||
stdio: 'inherit',
|
|
||||||
cwd
|
|
||||||
})
|
|
||||||
childProcess.execFileSync('make', ['-j', os.availableParallelism()], { stdio: 'inherit', cwd })
|
|
||||||
childProcess.execFileSync('make', ['install'], { stdio: 'inherit', cwd })
|
|
||||||
|
|
||||||
return sourceDirectory
|
|
||||||
}
|
|
||||||
|
|
||||||
async function buildGCC1 (sysroot, rootfs) {
|
|
||||||
const sourceDirectory = await downloadAndUnarchive(
|
|
||||||
new URL(`https://download.dlackware.com/slackware/slackware64-current/source/d/gcc/gcc-${gccVersion}.tar.xz`)
|
|
||||||
)
|
|
||||||
const cwd = path.join(path.dirname(sourceDirectory), 'build-gcc');
|
|
||||||
await fs.mkdir(cwd)
|
|
||||||
|
|
||||||
childProcess.execFileSync(path.join(sourceDirectory, 'contrib/download_prerequisites'), {
|
|
||||||
stdio: 'inherit',
|
|
||||||
cwd: sourceDirectory
|
|
||||||
})
|
|
||||||
const configureOptions = [
|
|
||||||
`--prefix=${sysroot}`,
|
|
||||||
`--with-sysroot=${rootfs}`,
|
|
||||||
'--enable-languages=c,c++',
|
|
||||||
'--disable-shared',
|
|
||||||
'--with-arch=rv32imafdc',
|
|
||||||
'--with-abi=ilp32d',
|
|
||||||
'--with-tune=rocket',
|
|
||||||
'--with-isa-spec=20191213',
|
|
||||||
'--disable-bootstrap',
|
|
||||||
'--disable-multilib',
|
|
||||||
'--disable-libmudflap',
|
|
||||||
'--disable-libssp',
|
|
||||||
'--disable-libquadmath',
|
|
||||||
'--disable-libsanitizer',
|
|
||||||
'--disable-threads',
|
|
||||||
'--disable-libatomic',
|
|
||||||
'--disable-libgomp',
|
|
||||||
'--disable-libvtv',
|
|
||||||
'--disable-libstdcxx',
|
|
||||||
'--disable-nls',
|
|
||||||
'--with-newlib',
|
|
||||||
'--without-headers',
|
|
||||||
`--target=${target}`,
|
|
||||||
`--build=${build}`,
|
|
||||||
`--host=${build}`
|
|
||||||
]
|
|
||||||
const flags = '-O2 -fPIC'
|
|
||||||
const env = {
|
|
||||||
...process.env,
|
|
||||||
CFLAGS: flags,
|
|
||||||
CXXFLAGS: flags,
|
|
||||||
PATH: `${path.join(sysroot, 'bin')}:${process.env['PATH']}`
|
|
||||||
}
|
|
||||||
childProcess.execFileSync(path.join(path.relative(cwd, sourceDirectory), 'configure'), configureOptions, {
|
|
||||||
stdio: 'inherit',
|
|
||||||
env,
|
|
||||||
cwd
|
|
||||||
})
|
|
||||||
childProcess.execFileSync('make', ['-j', os.availableParallelism()], { stdio: 'inherit', env, cwd })
|
|
||||||
childProcess.execFileSync('make', ['install'], { stdio: 'inherit', env, cwd })
|
|
||||||
|
|
||||||
return sourceDirectory
|
|
||||||
}
|
|
||||||
|
|
||||||
async function buildBinutils (sourceDirectory, sysroot, rootfs) {
|
|
||||||
const cwd = path.join(path.dirname(sourceDirectory), 'build-binutils');
|
|
||||||
await fs.rm(cwd, { recursive: true, force: true })
|
|
||||||
await fs.mkdir(cwd)
|
|
||||||
|
|
||||||
const configureOptions = [
|
|
||||||
'--prefix=/usr',
|
|
||||||
`--build=${build}`,
|
|
||||||
`--host=${target}`,
|
|
||||||
`--with-build-sysroot=${rootfs}`,
|
|
||||||
'--disable-nls',
|
|
||||||
'--enable-shared',
|
|
||||||
'--enable-gprofng=no',
|
|
||||||
'--disable-werror',
|
|
||||||
'--enable-default-hash-style=gnu'
|
|
||||||
]
|
|
||||||
const env = {
|
|
||||||
...process.env,
|
|
||||||
PATH: `${path.join(sysroot, 'bin')}:${process.env['PATH']}`
|
|
||||||
}
|
|
||||||
childProcess.execFileSync(path.join(path.relative(cwd, sourceDirectory), 'configure'), configureOptions, {
|
|
||||||
stdio: 'inherit',
|
|
||||||
env,
|
|
||||||
cwd
|
|
||||||
})
|
|
||||||
childProcess.execFileSync('make', ['-j', os.availableParallelism()], { stdio: 'inherit', env, cwd })
|
|
||||||
childProcess.execFileSync('make', [`DESTDIR=${rootfs}`, 'install'], { stdio: 'inherit', env, cwd })
|
|
||||||
|
|
||||||
return sourceDirectory
|
|
||||||
}
|
|
||||||
|
|
||||||
async function buildGCC2 (sourceDirectory, sysroot, rootfs) {
|
|
||||||
const cwd = path.join(path.dirname(sourceDirectory), 'build-gcc');
|
|
||||||
await fs.rm(cwd, { recursive: true, force: true })
|
|
||||||
await fs.mkdir(cwd)
|
|
||||||
|
|
||||||
const configureOptions = [
|
|
||||||
`--prefix=${sysroot}`,
|
|
||||||
`--with-sysroot=${rootfs}`,
|
|
||||||
'--enable-languages=c,c++,lto',
|
|
||||||
'--enable-lto',
|
|
||||||
'--enable-shared',
|
|
||||||
'--with-arch=rv32imafdc',
|
|
||||||
'--with-abi=ilp32d',
|
|
||||||
'--with-tune=rocket',
|
|
||||||
'--with-isa-spec=20191213',
|
|
||||||
'--disable-bootstrap',
|
|
||||||
'--disable-multilib',
|
|
||||||
'--enable-checking=release',
|
|
||||||
'--disable-libssp',
|
|
||||||
'--enable-threads=posix',
|
|
||||||
'--with-default-libstdcxx-abi=new',
|
|
||||||
'--disable-nls',
|
|
||||||
`--target=${target}`,
|
|
||||||
`--build=${build}`,
|
|
||||||
`--host=${build}`
|
|
||||||
]
|
|
||||||
const flags = '-O2 -fPIC'
|
|
||||||
const env = {
|
|
||||||
...process.env,
|
|
||||||
CFLAGS: flags,
|
|
||||||
CXXFLAGS: flags,
|
|
||||||
PATH: `${path.join(sysroot, 'bin')}:${process.env['PATH']}`
|
|
||||||
}
|
|
||||||
childProcess.execFileSync(path.join(path.relative(cwd, sourceDirectory), 'configure'), configureOptions, {
|
|
||||||
stdio: 'inherit',
|
|
||||||
env,
|
|
||||||
cwd
|
|
||||||
})
|
|
||||||
childProcess.execFileSync('make', ['-j', os.availableParallelism()], { stdio: 'inherit', env, cwd })
|
|
||||||
childProcess.execFileSync('make', ['install'], { stdio: 'inherit', env, cwd })
|
|
||||||
}
|
|
||||||
|
|
||||||
async function buildGCC3 (sourceDirectory, sysroot, rootfs) {
|
|
||||||
const cwd = path.join(path.dirname(sourceDirectory), 'build-gcc');
|
|
||||||
await fs.rm(cwd, { recursive: true, force: true })
|
|
||||||
await fs.mkdir(cwd)
|
|
||||||
|
|
||||||
const configureOptions = [
|
|
||||||
`--prefix=/usr`,
|
|
||||||
`--libdir=/usr/lib`,
|
|
||||||
'--enable-languages=c,c++,lto',
|
|
||||||
'--enable-lto',
|
|
||||||
'--enable-shared',
|
|
||||||
'--with-arch=rv32imafdc',
|
|
||||||
'--with-abi=ilp32d',
|
|
||||||
'--with-tune=rocket',
|
|
||||||
'--with-isa-spec=20191213',
|
|
||||||
'--disable-bootstrap',
|
|
||||||
'--disable-multilib',
|
|
||||||
'--enable-checking=release',
|
|
||||||
'--disable-libssp',
|
|
||||||
'--enable-threads=posix',
|
|
||||||
'--with-default-libstdcxx-abi=new',
|
|
||||||
'--disable-nls',
|
|
||||||
`--target=${target}`,
|
|
||||||
`--build=${build}`,
|
|
||||||
`--host=${target}`
|
|
||||||
]
|
|
||||||
const flags = '-O2 -fPIC'
|
|
||||||
const env = {
|
|
||||||
...process.env,
|
|
||||||
CFLAGS: flags,
|
|
||||||
CXXFLAGS: flags,
|
|
||||||
PATH: `${path.join(sysroot, 'bin')}:${process.env['PATH']}`
|
|
||||||
}
|
|
||||||
childProcess.execFileSync(path.join(path.relative(cwd, sourceDirectory), 'configure'), configureOptions, {
|
|
||||||
stdio: 'inherit',
|
|
||||||
env,
|
|
||||||
cwd
|
|
||||||
})
|
|
||||||
childProcess.execFileSync('make', ['-j', os.availableParallelism()], { stdio: 'inherit', env, cwd })
|
|
||||||
childProcess.execFileSync('make', [`DESTDIR=${rootfs}`, 'install'], { stdio: 'inherit', env, cwd })
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
async function createRoot (rootfs) {
|
|
||||||
for (const directory of ['proc', 'sys', 'dev', 'etc/init.d', 'usr/local', 'usr/local/bin']) {
|
|
||||||
await fs.mkdir(path.join(rootfs, directory))
|
|
||||||
}
|
|
||||||
await fs.cp(path.join(baseDirectory, 'files/fstab'), path.join(rootfs, 'etc/fstab'))
|
|
||||||
await fs.cp(path.join(baseDirectory, 'files/rcS'), path.join(rootfs, 'etc/init.d/rcS'))
|
|
||||||
await fs.cp(path.join(baseDirectory, 'files/rcK'), path.join(rootfs, 'etc/init.d/rcK'))
|
|
||||||
await fs.cp(path.join(baseDirectory, 'files/inittab'), path.join(rootfs, 'etc/inittab'))
|
|
||||||
}
|
|
||||||
|
|
||||||
const sysroot = path.join(tmp, 'sysroot')
|
|
||||||
const rootfs = path.join(tmp, 'rootfs')
|
|
||||||
|
|
||||||
for (const targetDirectory of [tmp, sysroot, rootfs]) {
|
|
||||||
await fs.rm(targetDirectory, { recursive: true, force: true })
|
|
||||||
await fs.mkdir(targetDirectory)
|
|
||||||
}
|
|
||||||
|
|
||||||
const binutilsSource = await buildCrossBinutils(sysroot)
|
|
||||||
const gccSource = await buildGCC1(sysroot, rootfs)
|
|
||||||
|
|
||||||
const kernelImage = await buildKernel(sysroot, rootfs)
|
|
||||||
await buildGlibc(sysroot, rootfs)
|
|
||||||
await buildBinutils(binutilsSource, sysroot, rootfs)
|
|
||||||
await buildGCC2(gccSource, sysroot, rootfs)
|
|
||||||
buildInit (sysroot)
|
|
||||||
|
|
||||||
await buildBusyBox(sysroot, rootfs)
|
|
||||||
await createRoot(rootfs)
|
|
||||||
|
|
||||||
await buildGCC3(gccSource, sysroot, rootfs)
|
|
||||||
createImage(rootfs)
|
|
||||||
console.log(kernelImage)
|
|
185
tools/init.cpp
185
tools/init.cpp
@ -1,185 +0,0 @@
|
|||||||
#include <algorithm>
|
|
||||||
#include <memory>
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <cstdio>
|
|
||||||
#include <cstring>
|
|
||||||
#include <dirent.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <sys/wait.h>
|
|
||||||
#include <sys/reboot.h>
|
|
||||||
|
|
||||||
template<std::size_t size>
|
|
||||||
std::size_t read_command(int descriptor, char *command_buffer)
|
|
||||||
{
|
|
||||||
std::ptrdiff_t bytes_read{ 0 };
|
|
||||||
std::size_t read_so_far{ 0 };
|
|
||||||
|
|
||||||
while ((bytes_read = read(descriptor, command_buffer + read_so_far, size - read_so_far - 1)) > 0)
|
|
||||||
{
|
|
||||||
read_so_far += bytes_read;
|
|
||||||
if (read_so_far >= size - 1)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
command_buffer[read_so_far] = 0;
|
|
||||||
return read_so_far;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class status
|
|
||||||
{
|
|
||||||
success,
|
|
||||||
failure,
|
|
||||||
warning,
|
|
||||||
fatal
|
|
||||||
};
|
|
||||||
|
|
||||||
template<std::size_t size>
|
|
||||||
void make_path(char *destination, const char *directory, const char *filename, const char *extension)
|
|
||||||
{
|
|
||||||
memcpy(destination, directory, strlen(directory));
|
|
||||||
std::size_t remaining_space = size - strlen(directory);
|
|
||||||
|
|
||||||
if (extension != nullptr)
|
|
||||||
{
|
|
||||||
remaining_space -= strlen(extension) + 1;
|
|
||||||
}
|
|
||||||
strncpy(destination + strlen(directory), filename, remaining_space);
|
|
||||||
|
|
||||||
if (extension != nullptr)
|
|
||||||
{
|
|
||||||
strncpy(destination + strlen(destination), extension, strlen(extension));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
status run_test(const char *file_entry_name)
|
|
||||||
{
|
|
||||||
printf("Running %s. ", file_entry_name);
|
|
||||||
|
|
||||||
char filename[256];
|
|
||||||
char command_buffer[256];
|
|
||||||
char file_buffer[256];
|
|
||||||
int pipe_ends[2];
|
|
||||||
|
|
||||||
if (pipe(pipe_ends) == -1)
|
|
||||||
{
|
|
||||||
perror("pipe");
|
|
||||||
return status::fatal;
|
|
||||||
}
|
|
||||||
make_path<sizeof(filename)>(filename, "./tests/", file_entry_name, nullptr);
|
|
||||||
|
|
||||||
auto child_pid = fork();
|
|
||||||
if (child_pid == -1)
|
|
||||||
{
|
|
||||||
return status::fatal;
|
|
||||||
}
|
|
||||||
else if (child_pid == 0)
|
|
||||||
{
|
|
||||||
close(STDIN_FILENO);
|
|
||||||
close(STDERR_FILENO);
|
|
||||||
close(pipe_ends[0]); // Close the read end.
|
|
||||||
|
|
||||||
if (dup2(pipe_ends[1], STDOUT_FILENO) == -1)
|
|
||||||
{
|
|
||||||
perror("dup2");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
execl(filename, filename);
|
|
||||||
perror("execl");
|
|
||||||
}
|
|
||||||
close(STDOUT_FILENO);
|
|
||||||
close(pipe_ends[1]);
|
|
||||||
std::exit(1);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
close(pipe_ends[1]); // Close the write end.
|
|
||||||
auto read_from_command = read_command<sizeof(command_buffer)>(pipe_ends[0], command_buffer);
|
|
||||||
close(pipe_ends[0]);
|
|
||||||
|
|
||||||
int wait_status{ 0 };
|
|
||||||
wait(&wait_status);
|
|
||||||
|
|
||||||
make_path<sizeof(filename)>(filename, "./expectations/", file_entry_name, ".txt");
|
|
||||||
|
|
||||||
std::unique_ptr<FILE, int(*)(FILE *)> expectation_descriptor(fopen(filename, "r"), &fclose);
|
|
||||||
|
|
||||||
if (expectation_descriptor == nullptr)
|
|
||||||
{
|
|
||||||
return status::warning;
|
|
||||||
}
|
|
||||||
auto read_from_file = fread(file_buffer, 1, sizeof(file_buffer) - 1, expectation_descriptor.get());
|
|
||||||
file_buffer[std::max<std::ptrdiff_t>(0, read_from_file)] = 0;
|
|
||||||
|
|
||||||
if (strcmp(command_buffer, file_buffer) != 0)
|
|
||||||
{
|
|
||||||
printf("Failed. Got:\n%s", command_buffer);
|
|
||||||
|
|
||||||
return status::failure;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
fwrite("\n", 1, 1, stdout);
|
|
||||||
return status::success;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct summary
|
|
||||||
{
|
|
||||||
std::size_t total{ 0 };
|
|
||||||
std::size_t failure{ 0 };
|
|
||||||
std::size_t success{ 0 };
|
|
||||||
};
|
|
||||||
|
|
||||||
void walk()
|
|
||||||
{
|
|
||||||
auto directory_stream = opendir("./tests");
|
|
||||||
dirent *file_entry;
|
|
||||||
|
|
||||||
summary test_summary;
|
|
||||||
|
|
||||||
while ((file_entry = readdir(directory_stream)) != nullptr)
|
|
||||||
{
|
|
||||||
if (file_entry->d_name[0] == '.')
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
++test_summary.total;
|
|
||||||
switch (run_test(file_entry->d_name))
|
|
||||||
{
|
|
||||||
case status::failure:
|
|
||||||
++test_summary.failure;
|
|
||||||
break;
|
|
||||||
case status::success:
|
|
||||||
++test_summary.success;
|
|
||||||
break;
|
|
||||||
case status::warning:
|
|
||||||
break;
|
|
||||||
case status::fatal:
|
|
||||||
goto end_walk;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
printf("Successful: %lu, Failed: %lu, Total: %lu.\n",
|
|
||||||
test_summary.success, test_summary.failure, test_summary.total);
|
|
||||||
end_walk:
|
|
||||||
closedir(directory_stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
int main()
|
|
||||||
{
|
|
||||||
auto dev_console = open("/dev/console", O_WRONLY);
|
|
||||||
if (dev_console != -1)
|
|
||||||
{
|
|
||||||
dup2(dev_console, STDOUT_FILENO);
|
|
||||||
walk();
|
|
||||||
close(dev_console);
|
|
||||||
}
|
|
||||||
sync();
|
|
||||||
reboot(RB_POWER_OFF);
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user