Remove the per-array type category so arrays can mix types like structs. Add 13 passing and 125 expected-failure duskscript test fixtures with a table-driven runner. Also includes the in-progress entity/physics and archive moves from the working tree. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
315 lines
11 KiB
C
315 lines
11 KiB
C
/**
|
|
* Copyright (c) 2026 Dominic Masters
|
|
*
|
|
* This software is released under the MIT License.
|
|
* https://opensource.org/licenses/MIT
|
|
*/
|
|
|
|
#include "dusktest.h"
|
|
#include "script/script.h"
|
|
#include "console/console.h"
|
|
#include "asset/asset.h"
|
|
#include "asset/assetdsk.h"
|
|
#include "util/memory.h"
|
|
|
|
static void test_scriptRunsAndPrintsToConsole(void **state) {
|
|
scripterror_t error;
|
|
scriptresult_t result;
|
|
|
|
consoleInit();
|
|
assert_int_equal(errorIsOk(scriptInit()), true);
|
|
|
|
result = scriptRuntimeRunFile(SCRIPT.runtime, DUSKSCRIPT_TEST_FIXTURE, true, &error);
|
|
assert_int_equal(result, SCRIPT_OK);
|
|
|
|
// print() reaches the console through scriptPrintHandler.
|
|
assert_string_equal(CONSOLE.line[CONSOLE_HISTORY_MAX - 2], "print hello");
|
|
// consolePrint(text), the native, writes directly.
|
|
assert_string_equal(CONSOLE.line[CONSOLE_HISTORY_MAX - 1], "consolePrint hello");
|
|
|
|
assert_int_equal(errorIsOk(scriptDispose()), true);
|
|
consoleDispose();
|
|
}
|
|
|
|
static void test_scriptNumbersAreAllFloats(void **state) {
|
|
scripterror_t error;
|
|
scriptresult_t result;
|
|
// In the order fixture_numbers.ds prints them.
|
|
const char_t *expected[] = {
|
|
"5", "3.75", "2.5", "false", "10", "20", "1", "16", "3", "3.5"
|
|
};
|
|
const int32_t count = (int32_t)(sizeof(expected) / sizeof(expected[0]));
|
|
int32_t i;
|
|
|
|
consoleInit();
|
|
assert_int_equal(errorIsOk(scriptInit()), true);
|
|
|
|
result = scriptRuntimeRunFile(SCRIPT.runtime, DUSKSCRIPT_TEST_NUMBERS, true, &error);
|
|
assert_int_equal(result, SCRIPT_OK);
|
|
|
|
for(i = 0; i < count; i++) {
|
|
assert_string_equal(CONSOLE.line[CONSOLE_HISTORY_MAX - count + i], expected[i]);
|
|
}
|
|
|
|
assert_int_equal(errorIsOk(scriptDispose()), true);
|
|
consoleDispose();
|
|
}
|
|
|
|
// Runs a packed fixture and describes where it stopped, for a failure message
|
|
static scriptresult_t duskscript_test_run(const char_t *path, scripterror_t *error) {
|
|
scriptresult_t result;
|
|
|
|
consoleInit();
|
|
assert_int_equal(errorIsOk(scriptInit()), true);
|
|
result = scriptRuntimeRunFile(SCRIPT.runtime, path, true, error);
|
|
return result;
|
|
}
|
|
|
|
static void duskscript_test_finish(void) {
|
|
assert_int_equal(errorIsOk(scriptDispose()), true);
|
|
consoleDispose();
|
|
}
|
|
|
|
// A pass fixture is a script of assert() calls, it has to run to the end
|
|
static void test_scriptPasses(void **state) {
|
|
const char_t *path = (const char_t *)*state;
|
|
scripterror_t error;
|
|
scriptresult_t result;
|
|
uint8_t i;
|
|
|
|
memoryZero(&error, sizeof(error));
|
|
result = duskscript_test_run(path, &error);
|
|
duskscript_test_finish();
|
|
if(result != SCRIPT_OK) {
|
|
print_error("%s stopped with code %d: %s\n", path, (int)result, error.message);
|
|
for(i = 0; i < error.frameCount; i++) {
|
|
print_error(" at %s %s:%d:%d\n", error.frames[i].name, error.frames[i].file, (int)error.frames[i].line, (int)error.frames[i].column);
|
|
}
|
|
}
|
|
assert_int_equal(result, SCRIPT_OK);
|
|
}
|
|
|
|
// A fail fixture does one thing the engine has to refuse, as a runtime error
|
|
// with a message, and not a crash, a hang, or going on as if nothing happened
|
|
static void test_scriptFailsWithRuntimeError(void **state) {
|
|
const char_t *path = (const char_t *)*state;
|
|
scripterror_t error;
|
|
scriptresult_t result;
|
|
|
|
memoryZero(&error, sizeof(error));
|
|
result = duskscript_test_run(path, &error);
|
|
duskscript_test_finish();
|
|
if(result != SCRIPT_ERROR_RUNTIME) print_error("%s was expected to fail but gave code %d\n", path, (int)result);
|
|
assert_int_equal(result, SCRIPT_ERROR_RUNTIME);
|
|
assert_true(error.message[0] != '\0');
|
|
}
|
|
|
|
// The engine is still usable after a script failed: a run after an error
|
|
static void test_scriptRunsAfterAnError(void **state) {
|
|
scripterror_t error;
|
|
|
|
memoryZero(&error, sizeof(error));
|
|
consoleInit();
|
|
assert_int_equal(errorIsOk(scriptInit()), true);
|
|
assert_int_equal(scriptRuntimeRunFile(SCRIPT.runtime, "fail_assert_false.dsb", true, &error), SCRIPT_ERROR_RUNTIME);
|
|
assert_int_equal(scriptRuntimeRunFile(SCRIPT.runtime, "fail_div_zero.dsb", true, &error), SCRIPT_ERROR_RUNTIME);
|
|
assert_int_equal(scriptRuntimeRunFile(SCRIPT.runtime, "pass_arrays.dsb", true, &error), SCRIPT_OK);
|
|
assert_int_equal(scriptRuntimeRunFile(SCRIPT.runtime, "pass_structs.dsb", true, &error), SCRIPT_OK);
|
|
duskscript_test_finish();
|
|
}
|
|
|
|
// The same script can run again and again, nothing of it is left behind
|
|
static void test_scriptRunsRepeatedly(void **state) {
|
|
scripterror_t error;
|
|
int32_t i;
|
|
|
|
memoryZero(&error, sizeof(error));
|
|
consoleInit();
|
|
assert_int_equal(errorIsOk(scriptInit()), true);
|
|
for(i = 0; i < 20; i++) {
|
|
assert_int_equal(scriptRuntimeRunFile(SCRIPT.runtime, "pass_mixed.dsb", true, &error), SCRIPT_OK);
|
|
assert_int_equal(scriptRuntimeRunFile(SCRIPT.runtime, "pass_strings.dsb", true, &error), SCRIPT_OK);
|
|
}
|
|
duskscript_test_finish();
|
|
}
|
|
|
|
// A file that does not exist is an error, not a crash
|
|
static void test_scriptMissingFile(void **state) {
|
|
scripterror_t error;
|
|
|
|
memoryZero(&error, sizeof(error));
|
|
consoleInit();
|
|
assert_int_equal(errorIsOk(scriptInit()), true);
|
|
assert_int_not_equal(scriptRuntimeRunFile(SCRIPT.runtime, "no_such_script.dsb", true, &error), SCRIPT_OK);
|
|
duskscript_test_finish();
|
|
}
|
|
|
|
#define SCRIPT_PASS(name) { "pass_" #name, test_scriptPasses, NULL, NULL, (void *)("pass_" #name ".dsb") }
|
|
#define SCRIPT_FAIL(name) { "fail_" #name, test_scriptFailsWithRuntimeError, NULL, NULL, (void *)("fail_" #name ".dsb") }
|
|
|
|
// Fixtures load through the asset system now, so ASSET needs a real zip
|
|
// behind it - open the packed test.dsk directly rather than the full
|
|
// assetInit()/assetInitPlatform(), which expects a real launched engine.
|
|
static int duskscript_test_setup(void **state) {
|
|
memoryZero(&ASSET, sizeof(ASSET));
|
|
threadMutexInit(&ASSET.zipLock);
|
|
assert_int_equal(errorIsOk(assetDskOpenFromPath(DUSKSCRIPT_TEST_DSK, &ASSET.zip)), true);
|
|
return 0;
|
|
}
|
|
|
|
static int duskscript_test_teardown(void **state) {
|
|
zip_close(ASSET.zip);
|
|
threadMutexDispose(&ASSET.zipLock);
|
|
memoryZero(&ASSET, sizeof(ASSET));
|
|
return 0;
|
|
}
|
|
|
|
int main(void) {
|
|
const struct CMUnitTest tests[] = {
|
|
cmocka_unit_test(test_scriptRunsAndPrintsToConsole),
|
|
cmocka_unit_test(test_scriptNumbersAreAllFloats),
|
|
cmocka_unit_test(test_scriptRunsAfterAnError),
|
|
cmocka_unit_test(test_scriptRunsRepeatedly),
|
|
cmocka_unit_test(test_scriptMissingFile),
|
|
SCRIPT_PASS(arrays),
|
|
SCRIPT_PASS(async),
|
|
SCRIPT_PASS(constants),
|
|
SCRIPT_PASS(control),
|
|
SCRIPT_PASS(conversion),
|
|
SCRIPT_PASS(functions),
|
|
SCRIPT_PASS(logic),
|
|
SCRIPT_PASS(math),
|
|
SCRIPT_PASS(mixed),
|
|
SCRIPT_PASS(numbers),
|
|
SCRIPT_PASS(strings),
|
|
SCRIPT_PASS(structs),
|
|
SCRIPT_PASS(types),
|
|
SCRIPT_FAIL(abs_string),
|
|
SCRIPT_FAIL(add_array),
|
|
SCRIPT_FAIL(add_bool),
|
|
SCRIPT_FAIL(add_null),
|
|
SCRIPT_FAIL(array_too_long),
|
|
SCRIPT_FAIL(array_too_long_merge),
|
|
SCRIPT_FAIL(assert_empty_string),
|
|
SCRIPT_FAIL(assert_false),
|
|
SCRIPT_FAIL(assert_no_arguments),
|
|
SCRIPT_FAIL(assert_null),
|
|
SCRIPT_FAIL(assert_zero),
|
|
SCRIPT_FAIL(async_error),
|
|
SCRIPT_FAIL(async_error_nested),
|
|
SCRIPT_FAIL(bitwise_string),
|
|
SCRIPT_FAIL(call_missing_member),
|
|
SCRIPT_FAIL(call_null),
|
|
SCRIPT_FAIL(call_number),
|
|
SCRIPT_FAIL(callback_raises),
|
|
SCRIPT_FAIL(chr_fraction),
|
|
SCRIPT_FAIL(chr_too_big),
|
|
SCRIPT_FAIL(chr_zero),
|
|
SCRIPT_FAIL(compare_mixed),
|
|
SCRIPT_FAIL(compare_null),
|
|
SCRIPT_FAIL(compare_strings),
|
|
SCRIPT_FAIL(const_array_alias_assign),
|
|
SCRIPT_FAIL(const_array_pop),
|
|
SCRIPT_FAIL(const_array_push),
|
|
SCRIPT_FAIL(const_array_sort),
|
|
SCRIPT_FAIL(const_struct_alias_assign),
|
|
SCRIPT_FAIL(const_struct_alias_new_key),
|
|
SCRIPT_FAIL(contains_number),
|
|
SCRIPT_FAIL(deepclone_cycle),
|
|
SCRIPT_FAIL(deepclone_struct_cycle),
|
|
SCRIPT_FAIL(div_zero),
|
|
SCRIPT_FAIL(error_after_output),
|
|
SCRIPT_FAIL(error_in_loop),
|
|
SCRIPT_FAIL(error_inside_function),
|
|
SCRIPT_FAIL(error_inside_nested_calls),
|
|
SCRIPT_FAIL(fill_negative),
|
|
SCRIPT_FAIL(fill_too_many),
|
|
SCRIPT_FAIL(filter_non_function),
|
|
SCRIPT_FAIL(flatten_non_array_items),
|
|
SCRIPT_FAIL(foreach_null),
|
|
SCRIPT_FAIL(foreach_number),
|
|
SCRIPT_FAIL(foreach_struct),
|
|
SCRIPT_FAIL(include_missing),
|
|
SCRIPT_FAIL(index_assign_negative),
|
|
SCRIPT_FAIL(index_assign_oob),
|
|
SCRIPT_FAIL(index_fraction),
|
|
SCRIPT_FAIL(index_negative),
|
|
SCRIPT_FAIL(index_number),
|
|
SCRIPT_FAIL(index_read_oob),
|
|
SCRIPT_FAIL(index_string_key),
|
|
SCRIPT_FAIL(indexof_non_array),
|
|
SCRIPT_FAIL(indexof_string_long_needle),
|
|
SCRIPT_FAIL(insert_negative),
|
|
SCRIPT_FAIL(insert_out_of_range),
|
|
SCRIPT_FAIL(insertarr_out_of_range),
|
|
SCRIPT_FAIL(invert_string),
|
|
SCRIPT_FAIL(join_non_array),
|
|
SCRIPT_FAIL(join_non_string_separator),
|
|
SCRIPT_FAIL(len_null),
|
|
SCRIPT_FAIL(len_number),
|
|
SCRIPT_FAIL(map_non_array),
|
|
SCRIPT_FAIL(map_non_function),
|
|
SCRIPT_FAIL(member_of_null),
|
|
SCRIPT_FAIL(member_of_number),
|
|
SCRIPT_FAIL(min_string),
|
|
SCRIPT_FAIL(missing_parameter),
|
|
SCRIPT_FAIL(missing_second_parameter),
|
|
SCRIPT_FAIL(mod_zero),
|
|
SCRIPT_FAIL(mul_string),
|
|
SCRIPT_FAIL(mutual_recursion_too_deep),
|
|
SCRIPT_FAIL(negate_string),
|
|
SCRIPT_FAIL(pop_string),
|
|
SCRIPT_FAIL(pow_fraction),
|
|
SCRIPT_FAIL(push_string),
|
|
SCRIPT_FAIL(rand_backwards),
|
|
SCRIPT_FAIL(rand_empty_range),
|
|
SCRIPT_FAIL(rand_fraction),
|
|
SCRIPT_FAIL(randf_empty_range),
|
|
SCRIPT_FAIL(recursion_too_deep),
|
|
SCRIPT_FAIL(reduce_non_function),
|
|
SCRIPT_FAIL(reverse_string),
|
|
SCRIPT_FAIL(sort_bad_comparer_result),
|
|
SCRIPT_FAIL(sort_mixed),
|
|
SCRIPT_FAIL(sort_non_array),
|
|
SCRIPT_FAIL(sort_structs),
|
|
SCRIPT_FAIL(splice_negative_count),
|
|
SCRIPT_FAIL(splice_out_of_range),
|
|
SCRIPT_FAIL(split_non_string),
|
|
SCRIPT_FAIL(sqrt_negative),
|
|
SCRIPT_FAIL(string_assign),
|
|
SCRIPT_FAIL(string_index_oob),
|
|
SCRIPT_FAIL(string_too_long),
|
|
SCRIPT_FAIL(struct_number_key),
|
|
SCRIPT_FAIL(struct_number_key_assign),
|
|
SCRIPT_FAIL(struct_too_many_keys),
|
|
SCRIPT_FAIL(sub_string),
|
|
SCRIPT_FAIL(subarr_backwards),
|
|
SCRIPT_FAIL(subarr_negative),
|
|
SCRIPT_FAIL(subarr_past_end),
|
|
SCRIPT_FAIL(subarr_string_past_end),
|
|
SCRIPT_FAIL(tobase_bad_base),
|
|
SCRIPT_FAIL(tobase_big_base),
|
|
SCRIPT_FAIL(tobase_fraction),
|
|
SCRIPT_FAIL(tobase_string),
|
|
SCRIPT_FAIL(tobool_invalid),
|
|
SCRIPT_FAIL(tobool_null),
|
|
SCRIPT_FAIL(tonum_bad_digit),
|
|
SCRIPT_FAIL(tonum_bool),
|
|
SCRIPT_FAIL(tonum_empty),
|
|
SCRIPT_FAIL(tonum_invalid),
|
|
SCRIPT_FAIL(tonum_too_big),
|
|
SCRIPT_FAIL(tonum_trailing),
|
|
SCRIPT_FAIL(too_few_arguments),
|
|
SCRIPT_FAIL(too_many_arguments),
|
|
SCRIPT_FAIL(tostr_decimals_on_string),
|
|
SCRIPT_FAIL(tostr_negative_decimals),
|
|
SCRIPT_FAIL(tostr_too_many_decimals),
|
|
SCRIPT_FAIL(trim_null),
|
|
SCRIPT_FAIL(undefined_function_call),
|
|
SCRIPT_FAIL(undefined_variable_read),
|
|
SCRIPT_FAIL(undefined_variable_write),
|
|
SCRIPT_FAIL(upcase_number),
|
|
};
|
|
return cmocka_run_group_tests(tests, duskscript_test_setup, duskscript_test_teardown);
|
|
}
|