Support failure tests
This commit is contained in:
parent
7e5f7f492d
commit
f2a20c2825
2
TODO
2
TODO
@ -14,6 +14,8 @@
|
||||
- Support immediates greater than 12 bits.
|
||||
- It seems instructions are correctly encoded only if the compiler is running
|
||||
on a little endian architecture.
|
||||
- Print filename in the error message.
|
||||
- Merge declaration and definition nodes.
|
||||
|
||||
# Shell
|
||||
- Persist the history.
|
||||
|
@ -43,12 +43,19 @@ namespace elna::cli
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
auto ast = source::parser(std::move(lex_result.success())).parse();
|
||||
source::parser parser{ std::move(lex_result.success()) };
|
||||
auto ast = parser.parse();
|
||||
if (ast == nullptr)
|
||||
{
|
||||
for (const auto& compile_error : parser.errors())
|
||||
{
|
||||
std::cout << compile_error->line() << ':' << compile_error->column()
|
||||
<< ": " << compile_error->what() << std::endl;
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
source::name_analysis_visitor().visit(ast.get());
|
||||
source::type_analysis_visitor().visit(ast.get());
|
||||
source::allocator_visitor().visit(ast.get());
|
||||
riscv::riscv32_elf(ast.get(), out_file);
|
||||
|
||||
|
@ -107,6 +107,8 @@ namespace elna::source
|
||||
std::int32_t number() const;
|
||||
const elna::source::position& position() const noexcept;
|
||||
|
||||
std::string to_string() const;
|
||||
|
||||
private:
|
||||
type m_type;
|
||||
value m_value{};
|
||||
|
@ -279,6 +279,7 @@ namespace elna::source
|
||||
std::unique_ptr<expression> parse_term();
|
||||
std::unique_ptr<expression> parse_expression();
|
||||
std::unique_ptr<constant_definition> parse_constant_definition();
|
||||
std::unique_ptr<procedure_definition> parse_procedure_definition();
|
||||
std::unique_ptr<declaration> parse_declaration();
|
||||
std::unique_ptr<statement> parse_statement();
|
||||
std::unique_ptr<call_statement> parse_call_statement();
|
||||
@ -286,7 +287,8 @@ namespace elna::source
|
||||
std::unique_ptr<assign_statement> parse_assign_statement();
|
||||
std::unique_ptr<if_statement> parse_if_statement();
|
||||
std::unique_ptr<while_statement> parse_while_statement();
|
||||
std::vector<std::unique_ptr<definition>> parse_definitions();
|
||||
std::vector<std::unique_ptr<constant_definition>> parse_constant_definitions();
|
||||
std::vector<std::unique_ptr<procedure_definition>> parse_procedure_definitions();
|
||||
std::vector<std::unique_ptr<declaration>> parse_declarations();
|
||||
std::unique_ptr<block> parse_block();
|
||||
|
||||
|
@ -70,6 +70,11 @@ namespace elna::source
|
||||
errors().emplace_back(std::make_unique<U>(first));
|
||||
}
|
||||
|
||||
explicit result(E&& errors)
|
||||
: payload(std::move(errors))
|
||||
{
|
||||
}
|
||||
|
||||
bool has_errors() const noexcept
|
||||
{
|
||||
return std::holds_alternative<E>(payload);
|
||||
|
@ -22,4 +22,9 @@ namespace elna::source
|
||||
void visit(declaration *declaration) override;
|
||||
void visit(block *block) override;
|
||||
};
|
||||
|
||||
class type_analysis_visitor final : public empty_visitor
|
||||
{
|
||||
public:
|
||||
};
|
||||
}
|
||||
|
@ -6,6 +6,10 @@
|
||||
|
||||
#define BOOST_PROCESS_USE_STD_FS
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/process.hpp>
|
||||
#include <boost/process/v2.hpp>
|
||||
|
||||
namespace elna
|
||||
{
|
||||
enum class test_status
|
||||
@ -14,6 +18,7 @@ namespace elna
|
||||
compile_failed,
|
||||
build_failed,
|
||||
expectation_failed,
|
||||
expectation_not_found
|
||||
};
|
||||
|
||||
struct test_result final
|
||||
@ -39,5 +44,9 @@ namespace elna
|
||||
void add_exit_code(const test_status result) noexcept;
|
||||
};
|
||||
|
||||
test_result run_for_output(boost::asio::io_context& context, const std::filesystem::path& binary,
|
||||
std::initializer_list<boost::string_view> arguments);
|
||||
test_result build_test(boost::asio::io_context& context, const std::filesystem::directory_entry& test_entry);
|
||||
test_result run_test(boost::asio::io_context& context, const std::filesystem::directory_entry& test_entry);
|
||||
void print_result(const std::filesystem::directory_entry& test_entry, const test_result& result);
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
#include "elna/source/lexer.hpp"
|
||||
#include <cassert>
|
||||
#include <variant>
|
||||
#include <sstream>
|
||||
|
||||
@ -212,6 +213,60 @@ namespace elna::source
|
||||
|| of() == type::boolean;
|
||||
}
|
||||
|
||||
std::string token::to_string() const
|
||||
{
|
||||
switch (this->m_type)
|
||||
{
|
||||
case type::number:
|
||||
return "«number»";
|
||||
case type::boolean:
|
||||
return "«boolean»";
|
||||
case type::term_operator:
|
||||
return "«term_operator»";
|
||||
case type::let:
|
||||
return "«const»";
|
||||
case type::identifier:
|
||||
return "«identifier»";
|
||||
case type::equals:
|
||||
return "«=»";
|
||||
case type::var:
|
||||
return "«var»";
|
||||
case type::semicolon:
|
||||
return "«;»";
|
||||
case type::left_paren:
|
||||
return "«(»";
|
||||
case type::right_paren:
|
||||
return "«)»";
|
||||
case type::dot:
|
||||
return "«)»";
|
||||
case type::comma:
|
||||
return "«,»";
|
||||
case type::factor_operator:
|
||||
return "«*»";
|
||||
case type::eof:
|
||||
return "«EOF»";
|
||||
case type::begin:
|
||||
return "«begin»";
|
||||
case type::end:
|
||||
return "«end»";
|
||||
case type::assignment:
|
||||
return "«:=»";
|
||||
case type::colon:
|
||||
return "«:»";
|
||||
case type::when:
|
||||
return "«if»";
|
||||
case type::then:
|
||||
return "«then»";
|
||||
case type::_while:
|
||||
return "«while»";
|
||||
case type::_do:
|
||||
return "«do»";
|
||||
case type::procedure:
|
||||
return "«proc»";
|
||||
};
|
||||
assert(false);
|
||||
}
|
||||
|
||||
unexpected_character::unexpected_character(const std::string& character, const source::position position)
|
||||
: error(position), character(character)
|
||||
{
|
||||
@ -233,7 +288,7 @@ namespace elna::source
|
||||
|
||||
std::string unexpected_token::what() const
|
||||
{
|
||||
return "Unexpected token";
|
||||
return "Unexpected token " + m_token.to_string();
|
||||
}
|
||||
|
||||
lexer::lexer(std::vector<token>&& tokens, const position last_position)
|
||||
|
@ -49,9 +49,9 @@ namespace elna::source
|
||||
|
||||
void empty_visitor::visit(block *block)
|
||||
{
|
||||
for (const auto& block_definition : block->definitions())
|
||||
for (const auto& constant : block->definitions())
|
||||
{
|
||||
block_definition->accept(this);
|
||||
constant->accept(this);
|
||||
}
|
||||
for (const auto& block_declaration : block->declarations())
|
||||
{
|
||||
@ -83,9 +83,6 @@ namespace elna::source
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* AST node.
|
||||
*/
|
||||
void node::accept(parser_visitor *)
|
||||
{
|
||||
}
|
||||
@ -381,14 +378,26 @@ namespace elna::source
|
||||
|
||||
std::unique_ptr<program> parser::parse()
|
||||
{
|
||||
auto definitions = parse_definitions();
|
||||
auto constants = parse_constant_definitions();
|
||||
auto declarations = parse_declarations();
|
||||
auto procedures = parse_procedure_definitions();
|
||||
auto parsed_statement = parse_statement();
|
||||
|
||||
if (parsed_statement == nullptr)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
std::vector<std::unique_ptr<definition>> definitions(constants.size() + procedures.size());
|
||||
std::vector<std::unique_ptr<definition>>::iterator definition = definitions.begin();
|
||||
|
||||
for (auto& constant : constants)
|
||||
{
|
||||
*definition++ = std::move(constant);
|
||||
}
|
||||
for (auto& procedure : procedures)
|
||||
{
|
||||
*definition++ = std::move(procedure);
|
||||
}
|
||||
return std::make_unique<program>(std::move(definitions),
|
||||
std::move(declarations), std::move(parsed_statement));
|
||||
}
|
||||
@ -492,6 +501,28 @@ namespace elna::source
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<procedure_definition> parser::parse_procedure_definition()
|
||||
{
|
||||
if (!iterator.skip(token::type::procedure))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
auto definition_identifier = iterator.advance(token::type::identifier);
|
||||
|
||||
if (!definition_identifier.has_value() || !iterator.skip(token::type::semicolon))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
auto definition_body = parse_block();
|
||||
|
||||
if (definition_body == nullptr || !iterator.skip(token::type::semicolon))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
return std::make_unique<procedure_definition>(definition_identifier->get().identifier(),
|
||||
std::move(definition_body));
|
||||
}
|
||||
|
||||
std::unique_ptr<declaration> parser::parse_declaration()
|
||||
{
|
||||
auto declaration_identifier = iterator.advance(token::type::identifier);
|
||||
@ -641,9 +672,9 @@ namespace elna::source
|
||||
return std::make_unique<while_statement>(std::move(condition), std::move(body));
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<definition>> parser::parse_definitions()
|
||||
std::vector<std::unique_ptr<constant_definition>> parser::parse_constant_definitions()
|
||||
{
|
||||
std::vector<std::unique_ptr<definition>> definitions;
|
||||
std::vector<std::unique_ptr<constant_definition>> definitions;
|
||||
|
||||
if (iterator->of() != token::type::let)
|
||||
{
|
||||
@ -651,7 +682,7 @@ namespace elna::source
|
||||
}
|
||||
++iterator; // Skip const.
|
||||
|
||||
std::unique_ptr<definition> parsed_definition;
|
||||
std::unique_ptr<constant_definition> parsed_definition;
|
||||
while ((parsed_definition = parse_constant_definition()) != nullptr)
|
||||
{
|
||||
definitions.push_back(std::move(parsed_definition));
|
||||
@ -674,6 +705,23 @@ namespace elna::source
|
||||
return definitions;
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<procedure_definition>> parser::parse_procedure_definitions()
|
||||
{
|
||||
std::vector<std::unique_ptr<procedure_definition>> definitions;
|
||||
|
||||
while (iterator.current(token::type::procedure))
|
||||
{
|
||||
auto parsed_definition = parse_procedure_definition();
|
||||
|
||||
if (parsed_definition == nullptr)
|
||||
{
|
||||
break;
|
||||
}
|
||||
definitions.push_back(std::move(parsed_definition));
|
||||
}
|
||||
return definitions;
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<declaration>> parser::parse_declarations()
|
||||
{
|
||||
std::vector<std::unique_ptr<declaration>> declarations;
|
||||
@ -709,7 +757,7 @@ namespace elna::source
|
||||
|
||||
std::unique_ptr<block> parser::parse_block()
|
||||
{
|
||||
auto definitions = parse_definitions();
|
||||
auto constants = parse_constant_definitions();
|
||||
auto declarations = parse_declarations();
|
||||
auto parsed_statement = parse_statement();
|
||||
|
||||
@ -717,6 +765,13 @@ namespace elna::source
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
std::vector<std::unique_ptr<definition>> definitions(constants.size());
|
||||
std::vector<std::unique_ptr<definition>>::iterator definition = definitions.begin();
|
||||
|
||||
for (auto& constant : constants)
|
||||
{
|
||||
*definition++ = std::move(constant);
|
||||
}
|
||||
return std::make_unique<block>(std::move(definitions),
|
||||
std::move(declarations), std::move(parsed_statement));
|
||||
}
|
||||
|
1
tests/expectations/procedure_definition.txt
Normal file
1
tests/expectations/procedure_definition.txt
Normal file
@ -0,0 +1 @@
|
||||
2
|
1
tests/failures/single_word_error.txt
Normal file
1
tests/failures/single_word_error.txt
Normal file
@ -0,0 +1 @@
|
||||
1:1: Unexpected token «identifier»
|
6
tests/procedure_definition.eln
Normal file
6
tests/procedure_definition.eln
Normal file
@ -0,0 +1,6 @@
|
||||
proc f;
|
||||
writei(5);
|
||||
|
||||
begin
|
||||
writei(2)
|
||||
end.
|
1
tests/single_word_error.eln
Normal file
1
tests/single_word_error.eln
Normal file
@ -0,0 +1 @@
|
||||
asdf
|
117
tests/tester.cpp
117
tests/tester.cpp
@ -1,11 +1,7 @@
|
||||
#include "elna/tester.hpp"
|
||||
|
||||
#include <future>
|
||||
#include <iostream>
|
||||
|
||||
#include <boost/process.hpp>
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
namespace elna
|
||||
{
|
||||
std::uint32_t test_results::total() const noexcept
|
||||
@ -47,7 +43,36 @@ namespace elna
|
||||
return in_build_directory() / path;
|
||||
}
|
||||
|
||||
static test_result build_test(const std::filesystem::directory_entry& test_entry)
|
||||
test_result run_for_output(boost::asio::io_context& context, const std::filesystem::path& binary,
|
||||
std::initializer_list<boost::string_view> arguments)
|
||||
{
|
||||
boost::asio::readable_pipe read_pipe{ context };
|
||||
test_result result{};
|
||||
std::string output;
|
||||
boost::asio::dynamic_string_buffer buffer = boost::asio::dynamic_buffer(output);
|
||||
boost::system::error_code ec;
|
||||
boost::process::v2::process elna_child(context,
|
||||
binary, arguments,
|
||||
boost::process::v2::process_stdio{ nullptr, read_pipe, read_pipe });
|
||||
do
|
||||
{
|
||||
std::size_t transferred = read_pipe.read_some(buffer.prepare(512), ec);
|
||||
|
||||
if (transferred == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
buffer.commit(transferred);
|
||||
result.output.append(boost::asio::buffer_cast<const char *>(buffer.data()), buffer.size());
|
||||
buffer.consume(transferred);
|
||||
}
|
||||
while (ec == boost::system::errc::success);
|
||||
result.code = elna_child.wait();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
test_result build_test(boost::asio::io_context& context, const std::filesystem::directory_entry& test_entry)
|
||||
{
|
||||
const std::filesystem::path test_filename = test_entry.path().filename();
|
||||
|
||||
@ -59,37 +84,57 @@ namespace elna
|
||||
std::filesystem::remove(test_binary);
|
||||
std::filesystem::remove(test_object);
|
||||
|
||||
auto status = boost::process::system("./build/bin/elna",
|
||||
"-o", test_object.string(), test_entry.path().string());
|
||||
if (status != 0)
|
||||
test_result result = run_for_output(context, "./build/bin/elna",
|
||||
{ "-o", test_object.string(), test_entry.path().string() });
|
||||
|
||||
if (result.code != 0)
|
||||
{
|
||||
return test_result{ test_status::compile_failed, status };
|
||||
result.status = test_status::compile_failed;
|
||||
return result;
|
||||
}
|
||||
status = boost::process::system(
|
||||
"/opt/riscv/bin/riscv32-unknown-elf-ld",
|
||||
"-o", test_binary.string(),
|
||||
"-L/opt/riscv/lib/gcc/riscv32-unknown-elf/13.2.0/",
|
||||
"-L/opt/riscv/riscv32-unknown-elf/lib",
|
||||
"/opt/riscv/riscv32-unknown-elf/lib/crt0.o",
|
||||
"/opt/riscv/lib/gcc/riscv32-unknown-elf/13.2.0/crtbegin.o",
|
||||
test_object.string(),
|
||||
"--start-group", "-lgcc", "-lc", "-lgloss", "--end-group",
|
||||
"/opt/riscv/lib/gcc/riscv32-unknown-elf/13.2.0/crtend.o"
|
||||
);
|
||||
boost::process::v2::execute(boost::process::v2::process(
|
||||
context, "/opt/riscv/bin/riscv32-unknown-elf-ld",
|
||||
{
|
||||
"-o", test_binary.string(),
|
||||
"-L/opt/riscv/lib/gcc/riscv32-unknown-elf/13.2.0/",
|
||||
"-L/opt/riscv/riscv32-unknown-elf/lib",
|
||||
"/opt/riscv/riscv32-unknown-elf/lib/crt0.o",
|
||||
"/opt/riscv/lib/gcc/riscv32-unknown-elf/13.2.0/crtbegin.o",
|
||||
test_object.string(),
|
||||
"--start-group", "-lgcc", "-lc", "-lgloss", "--end-group",
|
||||
"/opt/riscv/lib/gcc/riscv32-unknown-elf/13.2.0/crtend.o"
|
||||
}
|
||||
));
|
||||
return test_result{};
|
||||
}
|
||||
|
||||
static test_result check_expectation(const std::filesystem::directory_entry& test_entry, const test_result& run)
|
||||
{
|
||||
const std::filesystem::path test_filename = test_entry.path().filename();
|
||||
std::filesystem::path test_filename = test_entry.path().filename();
|
||||
test_filename.replace_extension(".txt");
|
||||
|
||||
std::filesystem::path expectation_path = test_entry.path().parent_path() / "expectations" / test_filename;
|
||||
expectation_path.replace_extension(".txt");
|
||||
const std::filesystem::path expectation_path =
|
||||
test_entry.path().parent_path() / "expectations" / test_filename;
|
||||
const std::filesystem::path failures_path =
|
||||
test_entry.path().parent_path() / "failures" / test_filename;
|
||||
|
||||
std::string expected_result_path;
|
||||
if (std::filesystem::exists(expectation_path))
|
||||
{
|
||||
expected_result_path = expectation_path.string();
|
||||
}
|
||||
else if (std::filesystem::exists(failures_path))
|
||||
{
|
||||
expected_result_path = failures_path.string();
|
||||
}
|
||||
else
|
||||
{
|
||||
return test_result{ test_status::expectation_not_found, run.code, run.output };
|
||||
}
|
||||
boost::process::opstream pipe_stream;
|
||||
boost::process::child diff(
|
||||
boost::process::search_path("diff"), "-Nur", "--color",
|
||||
expectation_path.string(), "-",
|
||||
expected_result_path, "-",
|
||||
boost::process::std_in < pipe_stream
|
||||
);
|
||||
|
||||
@ -100,35 +145,27 @@ namespace elna
|
||||
|
||||
if (diff.exit_code() == 0)
|
||||
{
|
||||
return run;
|
||||
return test_result{ test_status::successful, run.code, run.output };
|
||||
}
|
||||
return test_result{ test_status::expectation_failed, run.code, run.output };
|
||||
}
|
||||
|
||||
static test_result run_test(const std::filesystem::directory_entry& test_entry)
|
||||
test_result run_test(boost::asio::io_context& context, const std::filesystem::directory_entry& test_entry)
|
||||
{
|
||||
const std::filesystem::path test_filename = test_entry.path().filename();
|
||||
|
||||
std::filesystem::path test_binary = in_build_directory(test_filename);
|
||||
test_binary.replace_extension();
|
||||
|
||||
boost::asio::io_service io_service;
|
||||
std::future<std::string> buffer;
|
||||
boost::process::child qemu(
|
||||
boost::process::search_path("qemu-riscv32"),
|
||||
test_binary.string(),
|
||||
boost::process::std_out > buffer,
|
||||
boost::process::std_err > buffer,
|
||||
io_service
|
||||
);
|
||||
io_service.run();
|
||||
|
||||
return test_result{ test_status::successful, qemu.exit_code(), buffer.get() };
|
||||
return run_for_output(context,
|
||||
boost::process::search_path("qemu-riscv32"),
|
||||
{ test_binary.string() });
|
||||
}
|
||||
|
||||
static test_results run_in_path(const std::filesystem::path test_directory)
|
||||
{
|
||||
test_results results;
|
||||
boost::asio::io_context context;
|
||||
|
||||
for (const auto& test_entry : std::filesystem::directory_iterator(test_directory))
|
||||
{
|
||||
@ -141,10 +178,10 @@ namespace elna
|
||||
std::cout << "Running " << test_entry << std::endl;
|
||||
try
|
||||
{
|
||||
result = build_test(test_entry);
|
||||
result = build_test(context, test_entry);
|
||||
if (result.status == test_status::successful)
|
||||
{
|
||||
result = run_test(test_entry);
|
||||
result = run_test(context, test_entry);
|
||||
}
|
||||
result = check_expectation(test_entry, result);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user