#include #include #include "../include/compiler.h" #include "../include/std/hash_map.h" #include "../include/std/hash.h" #include "../include/std/log.h" #include #include #include static int num_threads = 0; static int num_successful_tests = 0; static int num_tests_run = 0; #define REQUIRE_EQ_INT(expected, actual) do{\ if((expected) != (actual)) {\ fprintf(stderr, "%s:%d: Assert failed (%s vs %s).\nExpected %d, got %d.\n", __FILE__, __LINE__, #expected, #actual, (expected), (actual));\ exit(1);\ }\ }while(0) static CHECK_RESULT int test_hash_map() { ScopedAllocator scoped_allocator; HashMap hash_map; int value; bool has_key; unsigned char i; return_if_error(scoped_allocator_init(&scoped_allocator)); cleanup_if_error(hash_map_init(&hash_map, &scoped_allocator, sizeof(int), hash_map_compare_string, amal_hash_string)); value = 34; return_if_error(hash_map_insert(&hash_map, create_buffer_view("hello", 5), &value)); value = 50; for(i = 0; i < 128; ++i) return_if_error(hash_map_insert(&hash_map, create_buffer_view((const char*)&i, 1), &value)); return_if_error(hash_map_insert(&hash_map, create_buffer_view("hellp", 5), &value)); has_key = hash_map_get(&hash_map, create_buffer_view("hello", 5), &value); if(!has_key) { fprintf(stderr, "Missing value for key \"hello\" in hash map\n"); exit(1); } REQUIRE_EQ_INT(34, value); cleanup: scoped_allocator_deinit(&scoped_allocator); return 0; } #define FAIL_TEST(name) do { fprintf(stderr, "Test failed: %s\n", (name)); return; } while(0) typedef struct { char *filepath; char *expected_error; bool got_expected_error; } ErrorExpectedData; typedef struct { char *filepath; } ErrorUnexpectedData; static void error_callback_assert(const char *err_msg, int err_msg_len, void *userdata) { ErrorExpectedData *expected_data; int expected_err_msg_len; expected_data = userdata; expected_err_msg_len = strlen(expected_data->expected_error); if(expected_data->got_expected_error) { fprintf(stderr, "We got the error we expected but also got additional error:\n%.*s\n", err_msg_len, err_msg); FAIL_TEST(expected_data->filepath); } if(err_msg_len != expected_err_msg_len || strncmp(err_msg, expected_data->expected_error, expected_err_msg_len) != 0) { fprintf(stderr, "Expected error message:\n%.*s\n", expected_err_msg_len, expected_data->expected_error); fprintf(stderr, "Actual error message:\n%.*s\n", err_msg_len, err_msg); fprintf(stderr, "a: %d, b: %d\n", expected_err_msg_len, err_msg_len); FAIL_TEST(expected_data->filepath); } expected_data->got_expected_error = bool_true; } #if 0 static void error_callback(const char *err_msg, int err_msg_len, void *userdata) { ErrorUnexpectedData *data; data = userdata; fprintf(stderr, "Test failed: %s with error: %.*s\n", data->filepath, err_msg_len, err_msg); exit(1); } #endif static char* get_full_path(const char *filepath) { #define PATH_LEN 4096 char *buf; int len; int filepath_len; buf = malloc(PATH_LEN); buf[PATH_LEN - 1] = '\0'; getcwd(buf, PATH_LEN); len = strlen(buf); filepath_len = strlen(filepath); buf[len++] = '/'; memcpy(buf + len, filepath, filepath_len); buf[len + filepath_len] = '\0'; return buf; } static char* join_str(const char *str1, const char *str2, char delimiter) { char *buf; int len1; int len2; len1 = strlen(str1); len2 = strlen(str2); buf = malloc(len1 + 1 + len2 + 1); memcpy(buf, str1, len1); buf[len1] = delimiter; memcpy(buf + len1 + 1, str2, len2); buf[len1 + 1 + len2] = '\0'; return buf; } static CHECK_RESULT int get_thread_count_env_var(int *thread_count) { char *threads; threads = getenv("THREADS"); if(!threads) return -1; *thread_count = atoi(threads); return 0; } static void test_load(const char *filepath) { amal_compiler_options options; amal_program program; char *full_path; int result; amal_compiler_options_init(&options); options.num_threads = num_threads; ++num_tests_run; full_path = get_full_path(filepath); amal_program_init(&program); result = amal_compiler_load_file(&options, &program, filepath); if(result != AMAL_COMPILER_OK) { fprintf(stderr, "Failed to load file %s, result: %d\n", full_path, result); FAIL_TEST(full_path); } result = amal_program_run(&program); if(result != 0) { fprintf(stderr, "Failed to run the program %s, result: %d\n", full_path, result); FAIL_TEST(full_path); } amal_program_deinit(&program); fprintf(stderr, "Test succeeded as expected: %s\n", full_path); ++num_successful_tests; free(full_path); } static void test_load_error(const char *filepath, const char *expected_error) { amal_compiler_options options; amal_program program; ErrorExpectedData expected_data; int result; amal_compiler_options_init(&options); options.num_threads = num_threads; ++num_tests_run; options.error_callback = error_callback_assert; expected_data.filepath = get_full_path(filepath); expected_data.expected_error = join_str(expected_data.filepath, expected_error, ':'); expected_data.got_expected_error = bool_false; options.error_callback_userdata = &expected_data; amal_program_init(&program); result = amal_compiler_load_file(&options, &program, filepath); if(result == AMAL_COMPILER_OK) { fprintf(stderr, "Expected to fail loading file\n"); FAIL_TEST(expected_data.filepath); } amal_program_deinit(&program); if(!expected_data.got_expected_error) { fprintf(stderr, "Didn't get expected error message:\n%s\n", expected_error); FAIL_TEST(expected_data.filepath); } fprintf(stderr, "Test failed as expected: %s\n", expected_data.filepath); ++num_successful_tests; free(expected_data.filepath); free(expected_data.expected_error); } /* TODO: Restrict variables in global scope to const */ int main(int argc, char **argv) { int result; result = get_thread_count_env_var(&num_threads); if(result != 0) num_threads = 0; if(num_threads < 0) { amal_log_error("Environment variable THREADS contains invalid number for threads. THREADS has to be at least 0 (0 = use the number of available threads on the system)"); exit(1); } return_if_error(test_hash_map()); /* Run all tests */ if(argc == 1) { test_load("tests/main.amal"); test_load("tests/utf8bom.amal"); test_load("tests/bytecode.amal"); test_load_error("tests/errors/duplicate_declaration.amal", "2:7: error: Variable with the name main was declared twice in the same scope\n" "const main = fn {}\n" " ^\n"); test_load_error("tests/errors/pub_in_closure.amal", "2:5: error: Only declarations in global structs can be public\n" " pub const num = 45;\n" " ^\n"); test_load_error("tests/errors/closure_no_lhs.amal", "1:1: error: Expected string, variable, closure, struct, function call or import\n" "fn {}\n" "^\n"); test_load_error("tests/errors/const_assign.amal", "3:5: error: Can't assign to a const value\n" " value = 34;\n" " ^\n"); fprintf(stderr, "##### %d/%d tests succeeded #####\n", num_successful_tests, num_tests_run); } else if(argc == 2) { test_load(argv[1]); fprintf(stderr, "##### %d/%d tests succeeded #####\n", num_successful_tests, num_tests_run); } else { fprintf(stderr, "usage: test [test-file-path]\n"); fprintf(stderr, "If you run test without any files then all tests will run.\n"); fprintf(stderr, "examples:\n"); fprintf(stderr, " test\n"); fprintf(stderr, " test tests/main.amal\n"); exit(1); } return 0; }