gs_test/module.jai

280 lines
8.1 KiB
Plaintext

// See License Information at the bottom of this file
#import "Basic";
#import "Compiler";
#import "File";
#import "File_Utilities";
#import "String";
Test_Category :: struct
{
file_extension: string = ".test.jai";
// The extension all test files will end in.
// You can override this, but it is recommended that you never set it to .jai as
// that will match all your normal jai files as well.
exe_extension: string = "";
// The extension of the output test executable.
// If not set, this will be overridden
// with .test.exe on windows and .test everywhere else.
category_name: string = "default";
// If set to anything other than defualt, will put these tests inside an if block inside
// run_all_tests that checks for this name in the arguments to run_all_tests.sh
// ie. if category_name is "unit", then calling "run_all_tests.sh unit" will run just the
// tests in this category
exclude_from_run_all_tests: bool = false;
// This is useful if you have a category of tests that need to be run manually, or don't
// make sense to be run in CI
}
Config :: struct
{
root_paths: []string;
// REQUIRED: The root directory in which tests are found
test_categories: []Test_Category;
// REQUIRED: You must supply at least one test category. However, it is enough to simply
// do:
// config.test_categories = .[.{}];
tests_output_dir: string = "";
// Where test executables will be output.
// If not set, this will be set to the value of {build_options.root_path}/tests_out
}
build_all_tests :: (config: Config, build_options: Build_Options, loc := #caller_location)
{
set_optimization(*build_options, .DEBUG);
configure(config, build_options, loc);
output();
}
configure :: (config: Config, build_options: Build_Options, loc := #caller_location)
{
stored_config = config;
if stored_config.test_categories.count == 0
{
print("gs_test: Error - No test_categories provided, so no tests will be build. Aborting.\n");
exit(1);
}
for * stored_config.test_categories
{
if (it.file_extension == "") it.file_extension = ".test.jai";
if (it.exe_extension == "")
{
it.exe_extension = ".test";
}
}
if (stored_config.root_paths.count == 0)
{
compiler_report(loc.fully_pathed_filename, loc.line_number, loc.character_number, "You must supply a root_path in gs_test's config object");
exit(1);
}
if (stored_config.tests_output_dir == "")
{
path := parse_path(stored_config.root_paths[0]);
array_add(*path.words, "tests_out");
stored_config.tests_output_dir = path_to_string(path);
print("Fallback tests_output_dir: %\n", stored_config.tests_output_dir);
}
else
{
for i: 0..stored_config.tests_output_dir.count - 1
{
if stored_config.tests_output_dir[i] == #char "\\"
{
stored_config.tests_output_dir[i] = #char "/";
}
}
path := parse_path(stored_config.tests_output_dir);
stored_config.tests_output_dir = path_to_string(path);
}
stored_build_options = build_options;
stored_build_options.output_path = stored_config.tests_output_dir;
make_directory_if_it_does_not_exist(stored_build_options.output_path, true);
for root_path: config.root_paths
{
visit_files(root_path, true, *test_files, record_test_file);
}
}
output :: ()
{
run_all_tests_builder: String_Builder;
init_string_builder(*run_all_tests_builder);
append(*run_all_tests_builder, "#!/bin/bash\n");
append(*run_all_tests_builder, "SUCCESS=0\n");
append(*run_all_tests_builder, "ARGS=$@\n");
category_builders: [..]String_Builder;
for stored_config.test_categories
{
b := array_add(*category_builders);
init_string_builder(b);
}
for test_files
{
category := stored_config.test_categories[it.category];
exe_path := build_test_file(it.file_path, category);
if (exe_path != "" && !category.exclude_from_run_all_tests)
{
print_to_builder(*category_builders[it.category], "% $@\n%", exe_path, UPDATE_SUCCESS_CODE);
}
}
for category, i: stored_config.test_categories
{
if category.exclude_from_run_all_tests continue;
category_builder := *category_builders[i];
print_to_builder(*run_all_tests_builder, "\n# Category: %\n", category.category_name);
if category.category_name != "default"
{
RUN_CONDITION :: "if [[ \" ${ARGS[*]} \" =~ \" % \" ]] || [[ \" ${ARGS[*]} \" =~ \" all \" ]]; then\n";
print_to_builder(*run_all_tests_builder, RUN_CONDITION, category.category_name);
}
category_tests_string := builder_to_string(category_builder);
if category_tests_string.count > 0 {
print_to_builder(*run_all_tests_builder, "%\n", category_tests_string);
} else {
print_to_builder(
*run_all_tests_builder,
"echo No tests in this category: %\n",
category.category_name
);
}
if category.category_name != "default"
{
print_to_builder(*run_all_tests_builder, "fi\n");
}
}
append(*run_all_tests_builder, "exit $SUCCESS");
run_all_tests_sh := builder_to_string(*run_all_tests_builder);
write_entire_file(string_append(stored_config.tests_output_dir, "/run_all_tests.sh"), run_all_tests_sh);
}
#scope_file
stored_config: Config;
stored_build_options: Build_Options;
Test_File :: struct
{
file_path: string;
category: int;
}
test_files: [..]Test_File;
UPDATE_SUCCESS_CODE :: #string DONE
if [ $? != 0 ];
then
SUCCESS=1
fi
DONE
string_append :: (args: .. string) -> string
{
sb: String_Builder;
init_string_builder(*sb);
for args {
append(*sb, it);
}
return builder_to_string(*sb);
}
record_test_file :: (info: *File_Visit_Info, test_files: *[..]Test_File)
{
if (info.is_directory) return;
longest_match := 0;
longest_match_index := -1;
for category, category_i: stored_config.test_categories
{
if (ends_with_nocase(info.full_name, category.file_extension))
{
if longest_match < category.file_extension.count
{
longest_match = category.file_extension.count;
longest_match_index = category_i;
}
}
}
if longest_match_index >= 0
{
array_add(test_files, .{
file_path = copy_string(info.full_name),
category = longest_match_index
});
print(" Test File: % - Category: %\n", info.full_name, stored_config.test_categories[longest_match_index].category_name);
}
}
build_test_file :: (path_string: string, category: Test_Category) -> string
{
workspace := compiler_create_workspace(tprint("Test: %", path_string));
if !workspace
{
compiler_report(path_string, 0, 0, "Unable to create workspace for test file");
return "";
}
path := parse_path(path_string);
filename := path.words[path.words.count - 1];
filename_without_ext := slice(filename, 0, filename.count - category.file_extension.count);
exe_output_path := string_append(
filename_without_ext,
category.exe_extension
);
build_options := stored_build_options;
build_options.output_executable_name = exe_output_path;
set_build_options(build_options, workspace);
add_build_file(path_string, workspace);
add_build_string(tprint("#load \"%harness.jai\";\n", #filepath), workspace);
output_path := parse_path(stored_build_options.output_path);
array_add(*output_path.words, exe_output_path);
output_path.trailing_slash = false;
return path_to_string(output_path);
}
/*
zlib License
(C) Copyright 2023 Peter Slattery
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/