Initial commit
This commit is contained in:
2
tests/7_member_sum.eln
Normal file
2
tests/7_member_sum.eln
Normal file
@ -0,0 +1,2 @@
|
||||
writei(3 + 4 + 5 + 1 + 2 + 4 + 3)
|
||||
.
|
3
tests/const_list.eln
Normal file
3
tests/const_list.eln
Normal file
@ -0,0 +1,3 @@
|
||||
const a = 1, b = 2;
|
||||
writei(a + b)
|
||||
.
|
5
tests/declare_variable.eln
Normal file
5
tests/declare_variable.eln
Normal file
@ -0,0 +1,5 @@
|
||||
var x: Int;
|
||||
begin
|
||||
x := 5;
|
||||
writei(x)
|
||||
end.
|
1
tests/divide.eln
Normal file
1
tests/divide.eln
Normal file
@ -0,0 +1 @@
|
||||
writei(6 / 3)
|
1
tests/expectations/7_member_sum.txt
Normal file
1
tests/expectations/7_member_sum.txt
Normal file
@ -0,0 +1 @@
|
||||
22
|
1
tests/expectations/const_list.txt
Normal file
1
tests/expectations/const_list.txt
Normal file
@ -0,0 +1 @@
|
||||
3
|
1
tests/expectations/declare_variable.txt
Normal file
1
tests/expectations/declare_variable.txt
Normal file
@ -0,0 +1 @@
|
||||
5
|
1
tests/expectations/divide.txt
Normal file
1
tests/expectations/divide.txt
Normal file
@ -0,0 +1 @@
|
||||
2
|
2
tests/expectations/if_condition.txt
Normal file
2
tests/expectations/if_condition.txt
Normal file
@ -0,0 +1,2 @@
|
||||
8
|
||||
9
|
1
tests/expectations/left_nested_sum.txt
Normal file
1
tests/expectations/left_nested_sum.txt
Normal file
@ -0,0 +1 @@
|
||||
8
|
2
tests/expectations/multiline_output.txt
Normal file
2
tests/expectations/multiline_output.txt
Normal file
@ -0,0 +1,2 @@
|
||||
5
|
||||
7
|
1
tests/expectations/multiply.txt
Normal file
1
tests/expectations/multiply.txt
Normal file
@ -0,0 +1 @@
|
||||
10
|
1
tests/expectations/multiply_3.txt
Normal file
1
tests/expectations/multiply_3.txt
Normal file
@ -0,0 +1 @@
|
||||
24
|
1
tests/expectations/plus_minus.txt
Normal file
1
tests/expectations/plus_minus.txt
Normal file
@ -0,0 +1 @@
|
||||
4
|
1
tests/expectations/pointer_in_expression.txt
Normal file
1
tests/expectations/pointer_in_expression.txt
Normal file
@ -0,0 +1 @@
|
||||
5
|
2
tests/expectations/print_boolean.txt
Normal file
2
tests/expectations/print_boolean.txt
Normal file
@ -0,0 +1,2 @@
|
||||
t
|
||||
f
|
12
tests/expectations/print_equals.txt
Normal file
12
tests/expectations/print_equals.txt
Normal file
@ -0,0 +1,12 @@
|
||||
t
|
||||
f
|
||||
t
|
||||
f
|
||||
f
|
||||
t
|
||||
t
|
||||
f
|
||||
t
|
||||
f
|
||||
f
|
||||
t
|
5
tests/expectations/print_in_loop.txt
Normal file
5
tests/expectations/print_in_loop.txt
Normal file
@ -0,0 +1,5 @@
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
1
tests/expectations/print_number.txt
Normal file
1
tests/expectations/print_number.txt
Normal file
@ -0,0 +1 @@
|
||||
3
|
2
tests/expectations/procedure_2_statements.txt
Normal file
2
tests/expectations/procedure_2_statements.txt
Normal file
@ -0,0 +1,2 @@
|
||||
t
|
||||
5
|
1
tests/expectations/procedure_definition.txt
Normal file
1
tests/expectations/procedure_definition.txt
Normal file
@ -0,0 +1 @@
|
||||
5
|
1
tests/expectations/procedure_print_argument.txt
Normal file
1
tests/expectations/procedure_print_argument.txt
Normal file
@ -0,0 +1 @@
|
||||
5
|
1
tests/expectations/subtraction.txt
Normal file
1
tests/expectations/subtraction.txt
Normal file
@ -0,0 +1 @@
|
||||
1
|
1
tests/expectations/sum.txt
Normal file
1
tests/expectations/sum.txt
Normal file
@ -0,0 +1 @@
|
||||
8
|
1
tests/expectations/sums.txt
Normal file
1
tests/expectations/sums.txt
Normal file
@ -0,0 +1 @@
|
||||
8
|
1
tests/failures/missing_semicolon.txt
Normal file
1
tests/failures/missing_semicolon.txt
Normal file
@ -0,0 +1 @@
|
||||
tests/missing_semicolon.eln:3:3: Unexpected token «identifier»
|
1
tests/failures/single_word_error.txt
Normal file
1
tests/failures/single_word_error.txt
Normal file
@ -0,0 +1 @@
|
||||
tests/single_word_error.eln:1:1: Unexpected token «identifier»
|
11
tests/if_condition.eln
Normal file
11
tests/if_condition.eln
Normal file
@ -0,0 +1,11 @@
|
||||
begin
|
||||
if True then writei(8);
|
||||
if False then
|
||||
begin
|
||||
writei(5);
|
||||
writei(5);
|
||||
writei(5)
|
||||
end;
|
||||
if 1 < 2 then writei(9);
|
||||
if 1 > 2 then writei(10)
|
||||
end.
|
2
tests/left_nested_sum.eln
Normal file
2
tests/left_nested_sum.eln
Normal file
@ -0,0 +1,2 @@
|
||||
writei((3 + 4) + 1)
|
||||
.
|
4
tests/missing_semicolon.eln
Normal file
4
tests/missing_semicolon.eln
Normal file
@ -0,0 +1,4 @@
|
||||
begin
|
||||
writei(1)
|
||||
writei(2)
|
||||
end.
|
4
tests/multiline_output.eln
Normal file
4
tests/multiline_output.eln
Normal file
@ -0,0 +1,4 @@
|
||||
begin
|
||||
writei(5);
|
||||
writei(7)
|
||||
end.
|
2
tests/multiply.eln
Normal file
2
tests/multiply.eln
Normal file
@ -0,0 +1,2 @@
|
||||
writei(5 * 2)
|
||||
.
|
2
tests/multiply_3.eln
Normal file
2
tests/multiply_3.eln
Normal file
@ -0,0 +1,2 @@
|
||||
writei(3 * 4 * 2)
|
||||
.
|
2
tests/plus_minus.eln
Normal file
2
tests/plus_minus.eln
Normal file
@ -0,0 +1,2 @@
|
||||
writei(2 + 3 - 1)
|
||||
.
|
6
tests/pointer_in_expression.eln
Normal file
6
tests/pointer_in_expression.eln
Normal file
@ -0,0 +1,6 @@
|
||||
var a: Int, b: ^Int;
|
||||
begin
|
||||
a := 5;
|
||||
b := @a;
|
||||
writei(b^)
|
||||
end.
|
4
tests/print_boolean.eln
Normal file
4
tests/print_boolean.eln
Normal file
@ -0,0 +1,4 @@
|
||||
begin
|
||||
writeb(True);
|
||||
writeb(False)
|
||||
end.
|
14
tests/print_equals.eln
Normal file
14
tests/print_equals.eln
Normal file
@ -0,0 +1,14 @@
|
||||
begin
|
||||
writeb(5 = 5);
|
||||
writeb(5 = 4);
|
||||
writeb(5 /= 4);
|
||||
writeb(5 /= 5);
|
||||
writeb(5 < 4);
|
||||
writeb(4 < 5);
|
||||
writeb(5 >= 4);
|
||||
writeb(4 >= 5);
|
||||
writeb(5 > 4);
|
||||
writeb(4 > 5);
|
||||
writeb(5 <= 4);
|
||||
writeb(4 <= 5)
|
||||
end.
|
10
tests/print_in_loop.eln
Normal file
10
tests/print_in_loop.eln
Normal file
@ -0,0 +1,10 @@
|
||||
var i: Int;
|
||||
begin
|
||||
i := 1;
|
||||
|
||||
while i < 6 do
|
||||
begin
|
||||
writei(i);
|
||||
i := i + 1
|
||||
end;
|
||||
end.
|
2
tests/print_number.eln
Normal file
2
tests/print_number.eln
Normal file
@ -0,0 +1,2 @@
|
||||
writei(3)
|
||||
.
|
9
tests/procedure_2_statements.eln
Normal file
9
tests/procedure_2_statements.eln
Normal file
@ -0,0 +1,9 @@
|
||||
proc g(a: Boolean, b: Int)
|
||||
begin
|
||||
writeb(a);
|
||||
writei(b)
|
||||
end;
|
||||
|
||||
begin
|
||||
g(True, 5)
|
||||
end.
|
5
tests/procedure_definition.eln
Normal file
5
tests/procedure_definition.eln
Normal file
@ -0,0 +1,5 @@
|
||||
proc f() writei(5);
|
||||
|
||||
begin
|
||||
f()
|
||||
end.
|
5
tests/procedure_print_argument.eln
Normal file
5
tests/procedure_print_argument.eln
Normal file
@ -0,0 +1,5 @@
|
||||
proc f(a: Int) writei(a);
|
||||
|
||||
begin
|
||||
f(5)
|
||||
end.
|
1
tests/single_word_error.eln
Normal file
1
tests/single_word_error.eln
Normal file
@ -0,0 +1 @@
|
||||
asdf
|
2
tests/subtraction.eln
Normal file
2
tests/subtraction.eln
Normal file
@ -0,0 +1,2 @@
|
||||
writei(5 - 4)
|
||||
.
|
2
tests/sum.eln
Normal file
2
tests/sum.eln
Normal file
@ -0,0 +1,2 @@
|
||||
writei(1 + 7)
|
||||
.
|
2
tests/sums.eln
Normal file
2
tests/sums.eln
Normal file
@ -0,0 +1,2 @@
|
||||
writei(1 + (3 + 4))
|
||||
.
|
263
tests/tester.cpp
Executable file
263
tests/tester.cpp
Executable file
@ -0,0 +1,263 @@
|
||||
#include "elna/tester.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
#include <boost/process/env.hpp>
|
||||
|
||||
namespace elna
|
||||
{
|
||||
std::uint32_t test_results::total() const noexcept
|
||||
{
|
||||
return m_total;
|
||||
}
|
||||
|
||||
std::uint32_t test_results::passed() const noexcept
|
||||
{
|
||||
return m_passed;
|
||||
}
|
||||
|
||||
std::uint32_t test_results::failed() const noexcept
|
||||
{
|
||||
return m_total - m_passed;
|
||||
}
|
||||
|
||||
int test_results::exit_code() const noexcept
|
||||
{
|
||||
return m_total == m_passed ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
||||
|
||||
void test_results::add_exit_code(const test_status status) noexcept
|
||||
{
|
||||
++m_total;
|
||||
if (status == test_status::successful)
|
||||
{
|
||||
++m_passed;
|
||||
}
|
||||
}
|
||||
|
||||
static std::filesystem::path in_build_directory()
|
||||
{
|
||||
return "build/riscv";
|
||||
}
|
||||
|
||||
static std::string in_build_directory(const std::filesystem::path& path)
|
||||
{
|
||||
return in_build_directory() / path;
|
||||
}
|
||||
|
||||
boost::process::v2::process_stdio get_output_streams(const std::uint8_t stream_number,
|
||||
boost::asio::readable_pipe& read_pipe)
|
||||
{
|
||||
if (stream_number == 1)
|
||||
{
|
||||
return boost::process::v2::process_stdio{ nullptr, read_pipe };
|
||||
}
|
||||
if (stream_number == 2)
|
||||
{
|
||||
return boost::process::v2::process_stdio{ nullptr, {}, read_pipe };
|
||||
}
|
||||
return boost::process::v2::process_stdio{ nullptr, read_pipe, read_pipe };
|
||||
}
|
||||
|
||||
test_result run_for_output(boost::asio::io_context& context, const std::uint8_t stream_number,
|
||||
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_stdio process_stdio;
|
||||
boost::process::v2::process elna_child(context,
|
||||
binary, arguments,
|
||||
get_output_streams(stream_number, 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;
|
||||
}
|
||||
|
||||
std::string find_object(const std::vector<std::filesystem::path>& environment, const std::string& object)
|
||||
{
|
||||
auto variable = std::find(environment.cbegin(), environment.cend(), object);
|
||||
|
||||
for (const auto& variable : environment)
|
||||
{
|
||||
auto full_path = variable / object;
|
||||
|
||||
if (std::filesystem::exists(full_path))
|
||||
{
|
||||
return full_path.native();
|
||||
}
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
std::filesystem::path test_binary = in_build_directory(test_filename);
|
||||
std::filesystem::path test_object = test_binary;
|
||||
test_binary.replace_extension();
|
||||
test_object.replace_extension(".o");
|
||||
|
||||
std::filesystem::remove(test_binary);
|
||||
std::filesystem::remove(test_object);
|
||||
|
||||
test_result result = run_for_output(context, 2, "./build/bin/elna",
|
||||
{ "-o", test_object.string(), test_entry.path().string() });
|
||||
|
||||
if (result.code != 0)
|
||||
{
|
||||
result.status = test_status::compile_failed;
|
||||
return result;
|
||||
}
|
||||
std::vector<std::filesystem::path> environment;
|
||||
std::vector<std::string> linker_arguments = { "-o", test_binary.string() };
|
||||
|
||||
for (const auto segment : boost::this_process::environment()["LIBRARY_PATH"].to_vector())
|
||||
{
|
||||
linker_arguments.push_back("-L" + segment);
|
||||
environment.push_back(std::filesystem::path(segment));
|
||||
}
|
||||
linker_arguments.push_back(find_object(environment, "crt0.o"));
|
||||
linker_arguments.push_back(find_object(environment, "crtbegin.o"));
|
||||
linker_arguments.push_back(test_object.string());
|
||||
linker_arguments.insert(linker_arguments.cend(),
|
||||
{ "--start-group", "-lgcc", "-lc", "-lgloss", "--end-group" });
|
||||
linker_arguments.push_back(find_object(environment, "crtend.o"));
|
||||
|
||||
boost::process::v2::execute(boost::process::v2::process(context,
|
||||
boost::process::search_path("ld"),
|
||||
linker_arguments
|
||||
));
|
||||
return test_result{};
|
||||
}
|
||||
|
||||
static test_result check_expectation(const std::filesystem::directory_entry& test_entry, const test_result& run)
|
||||
{
|
||||
std::filesystem::path test_filename = test_entry.path().filename();
|
||||
test_filename.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",
|
||||
expected_result_path, "-",
|
||||
boost::process::std_in < pipe_stream
|
||||
);
|
||||
|
||||
pipe_stream << run.output;
|
||||
pipe_stream.flush();
|
||||
pipe_stream.pipe().close();
|
||||
diff.wait();
|
||||
|
||||
if (diff.exit_code() == 0)
|
||||
{
|
||||
return test_result{ test_status::successful, run.code, run.output };
|
||||
}
|
||||
return test_result{ test_status::expectation_failed, run.code, run.output };
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
return run_for_output(context, 1,
|
||||
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))
|
||||
{
|
||||
if (test_entry.path().extension() != ".eln" || !test_entry.is_regular_file())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
test_result result;
|
||||
|
||||
std::cout << "Running " << test_entry << std::endl;
|
||||
try
|
||||
{
|
||||
result = build_test(context, test_entry);
|
||||
if (result.status == test_status::successful)
|
||||
{
|
||||
result = run_test(context, test_entry);
|
||||
}
|
||||
result = check_expectation(test_entry, result);
|
||||
}
|
||||
catch (const boost::process::process_error& exception)
|
||||
{
|
||||
result.status = test_status::build_failed;
|
||||
result.output = exception.what();
|
||||
std::cout << result.output << std::endl;
|
||||
}
|
||||
print_result(test_entry, result);
|
||||
results.add_exit_code(result.status);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
void print_result(const std::filesystem::directory_entry& test_entry, const test_result& result)
|
||||
{
|
||||
if (result.status != test_status::successful)
|
||||
{
|
||||
std::cout << test_entry << " failed." << std::endl;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
int main()
|
||||
{
|
||||
std::filesystem::create_directory(elna::in_build_directory());
|
||||
|
||||
std::cout << "Run all tests and check the results" << std::endl;
|
||||
std::filesystem::path test_directory{ "tests" };
|
||||
const auto results = elna::run_in_path(test_directory);
|
||||
|
||||
std::cout << std::endl;
|
||||
std::cout << results.total() << " tests run, "
|
||||
<< results.passed() << " passed, "
|
||||
<< results.failed() << " failed." << std::endl;
|
||||
|
||||
return results.exit_code();
|
||||
}
|
Reference in New Issue
Block a user