#include "../include/compiler.h" #include "../include/std/hash_map.h" #include "../include/std/hash.h" #include "../include/std/log.h" #include #include #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(void) { ArenaAllocator arena_allocator; HashMapType(BufferView, int) hash_map; int value; bool has_key; unsigned char i; return_if_error(arena_allocator_init(&arena_allocator)); cleanup_if_error(hash_map_init(&hash_map, &arena_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: arena_allocator_deinit(&arena_allocator); return 0; } #define FAIL_TEST(name) do { fprintf(stderr, "Test failed: %s\n", (name)); return; } while(0) #define FAIL_TEST_CLEANUP(name) do { fprintf(stderr, "Test failed: %s\n", (name)); goto cleanup; } 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 int print_extern(void) { printf("hello from amalgam extern func, print_extern!\n"); return 0; } static int print_extern_num(i64 num) { printf("hello from amalgam extern func, print_extern_num, value: %ld!\n", num); return 0; } static void test_load_gl(void) { #if 0 GLFWwindow* window; /* Initialize the library */ if (!glfwInit()) FAIL_TEST(full_path); /* Create a windowed mode window and its OpenGL context */ window = glfwCreateWindow(640, 480, "Hello World", NULL, NULL); if (!window) { glfwTerminate(); FAIL_TEST(full_path); } /* Make the window's context current */ glfwMakeContextCurrent(window); /* Loop until the user closes the window */ while (!glfwWindowShouldClose(window)) { /* Render here */ glClear(GL_COLOR_BUFFER_BIT); glClearColor(1.0f, 0.0f, 0.0f, 1.0f); /* Swap front and back buffers */ glfwSwapBuffers(window); /* Poll for and process events */ glfwPollEvents(); } glfwTerminate(); #endif char *full_path = get_full_path("tests/glfw.amal"); amal_compiler_options options; amal_program program; int result; amal_compiler_options_init(&options); options.num_threads = num_threads; ++num_tests_run; if(amal_program_init(&program) != 0) { fprintf(stderr, "Failed to initialize amal program\n"); FAIL_TEST_CLEANUP(full_path); } if(amal_program_register_extern_func(&program, create_buffer_view_auto("glfwInit"), glfwInit, 0) != 0) { fprintf(stderr, "Unexpected error (alloc failure)\n"); FAIL_TEST_CLEANUP(full_path); } if(amal_program_register_extern_func(&program, create_buffer_view_auto("glfwTerminate"), glfwTerminate, 0) != 0) { fprintf(stderr, "Unexpected error (alloc failure)\n"); FAIL_TEST_CLEANUP(full_path); } if(amal_program_register_extern_func(&program, create_buffer_view_auto("glfwCreateWindow"), glfwCreateWindow, sizeof(int) + sizeof(int) + sizeof(const char*) + sizeof(void*) + sizeof(void*)) != 0) { fprintf(stderr, "Unexpected error (alloc failure)\n"); FAIL_TEST_CLEANUP(full_path); } if(amal_program_register_extern_func(&program, create_buffer_view_auto("glfwWindowShouldClose"), glfwWindowShouldClose, sizeof(void*)) != 0) { fprintf(stderr, "Unexpected error (alloc failure)\n"); FAIL_TEST_CLEANUP(full_path); } result = amal_compiler_load_file(&options, &program, full_path); if(result != AMAL_COMPILER_OK) { fprintf(stderr, "Failed to load file %s, result: %d\n", full_path, result); FAIL_TEST_CLEANUP(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_CLEANUP(full_path); } fprintf(stderr, "Test succeeded as expected: %s\n", full_path); ++num_successful_tests; cleanup: amal_program_deinit(&program); free(full_path); } 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); if(amal_program_init(&program) != 0) { fprintf(stderr, "Failed to initialize amal program\n"); FAIL_TEST_CLEANUP(full_path); } if(amal_program_register_extern_func(&program, create_buffer_view_auto("print_extern"), print_extern, 0) != 0) { fprintf(stderr, "Unexpected error (alloc failure)\n"); FAIL_TEST_CLEANUP(full_path); } if(amal_program_register_extern_func(&program, create_buffer_view_auto("print_extern_num"), print_extern_num, sizeof(i64)) != 0) { fprintf(stderr, "Unexpected error (alloc failure)\n"); FAIL_TEST_CLEANUP(full_path); } if(amal_program_register_extern_func(&program, create_buffer_view_auto("printf"), printf, sizeof(const char*)) != 0) { fprintf(stderr, "Unexpected error (alloc failure)\n"); FAIL_TEST_CLEANUP(full_path); } 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_CLEANUP(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_CLEANUP(full_path); } fprintf(stderr, "Test succeeded as expected: %s\n", full_path); ++num_successful_tests; cleanup: amal_program_deinit(&program); 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; 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); if(expected_error) { expected_data.expected_error = join_str(expected_data.filepath, expected_error, ':'); expected_data.got_expected_error = bool_false; } options.error_callback_userdata = &expected_data; if(amal_program_init(&program) != 0) { fprintf(stderr, "Failed to initialize amal program\n"); FAIL_TEST_CLEANUP(expected_data.filepath); } if(amal_compiler_load_file(&options, &program, filepath) == AMAL_COMPILER_OK) { fprintf(stderr, "Successfully loaded file when it was expected to fail\n"); FAIL_TEST_CLEANUP(expected_data.filepath); } if(expected_error && !expected_data.got_expected_error) { fprintf(stderr, "Didn't get expected error message:\n%s\n", expected_error); FAIL_TEST_CLEANUP(expected_data.filepath); } fprintf(stderr, "Test failed as expected: %s\n", expected_data.filepath); ++num_successful_tests; cleanup: amal_program_deinit(&program); free(expected_data.filepath); if(expected_error) free(expected_data.expected_error); } static void run_all_tests(void) { test_load("tests/main.amal"); test_load("tests/utf8bom.amal"); test_load("tests/bytecode.amal"); test_load("tests/binop.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"); test_load_error("tests/errors/arithmetic_incompatible_types.amal", "4:28: error: Can't cast type \"str\" to type \"i64\"\n" " const value3 = value + value2;\n" " ^\n" "4:20: error: Left-hand side is of type i64\n" " const value3 = value + value2;\n" " ^\n" "4:28: error: Right-hand side is of type str\n" " const value3 = value + value2;\n" " ^\n"); test_load_error("tests/errors/non_arithmetic_type_arithmetic.amal", "3:20: error: Arithmetic operation can only be performed with the types i8, u8, i16, u16, i32, u32, i64, u64, isize and usize\n" " const value2 = value + value;\n" " ^\n"); test_load_error("tests/errors/empty_body_regular_func.amal", "1:15: error: Expected function declaration. Only extern functions can have empty declarations.\n" " const func: fn;\n" " ^\n"); test_load_error("tests/errors/declaration_no_type.amal", "1:8: error: A variable can't be declared without a type or assignment\n" " const a;\n" " ^\n"); test_load_error("tests/errors/declaration_no_type_extern.amal", "1:15: error: A variable can't be declared without a type or assignment\n" " extern const a;\n" " ^\n"); test_load_error("tests/errors/no_main_func.amal", NULL); test_load_error("tests/errors/closure_duplicate_param_name.amal", "TODO: Add expected error here"); test_load_error("tests/errors/extern_closure_one_return_value.amal", "TODO: Add expected error here"); test_load_error("tests/errors/too_long_var_name.amal", "TODO: Add expected error here"); test_load_error("tests/errors/incorrect_main.amal", "TODO: Add expected error here"); } /* 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); } /* Sanity check */ return_if_error(test_hash_map()); /* Run all tests */ if(argc == 1) { run_all_tests(); } else if(argc == 2) { if(strcmp(argv[1], "opengl") == 0) test_load_gl(); else test_load(argv[1]); } 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); } fprintf(stderr, "##### %d/%d tests succeeded #####\n", num_successful_tests, num_tests_run); return 0; }