aboutsummaryrefslogtreecommitdiff
path: root/boot
diff options
context:
space:
mode:
Diffstat (limited to 'boot')
-rw-r--r--boot/common.c72
-rw-r--r--boot/tests/parse/loops.cskt31
-rw-r--r--boot/tests/test.c128
3 files changed, 216 insertions, 15 deletions
diff --git a/boot/common.c b/boot/common.c
index 7416696..6a95bb6 100644
--- a/boot/common.c
+++ b/boot/common.c
@@ -254,6 +254,78 @@ string_format(struct String s, const ascii* format, ...)
va_end(args);
}
+enum String_Concat_Arg
+{
+ ARG_END,
+ ARG_STRING,
+ ARG_ASCII,
+};
+
+#define MAX_STRING_CONCAT_LENGTH 2048
+
+struct String
+string_concatenate(enum String_Concat_Arg type1, ...)
+{
+ va_list args;
+ va_start(args, type1);
+ uint total_length = 0;
+ enum String_Concat_Arg type = type1;
+ while (type != ARG_END) {
+ switch (type) {
+ case ARG_STRING: {
+ struct String s = va_arg(args, struct String);
+ if (!string_is_empty(s)) total_length += s.length;
+ break;
+ }
+ case ARG_ASCII: {
+ ascii* str = va_arg(args, ascii*);
+ if (str) total_length += strlen(str);
+ break;
+ }
+ default:
+ break;
+ }
+ type = va_arg(args, enum String_Concat_Arg);
+ }
+ va_end(args);
+
+ if (total_length == 0) return string_empty();
+
+ check(total_length < MAX_STRING_CONCAT_LENGTH - 1, "string concatenation too long");
+ ascii buffer[MAX_STRING_CONCAT_LENGTH];
+
+ ascii* cursor = buffer;
+ va_start(args, type1);
+ type = type1;
+ while (type != ARG_END) {
+ switch (type) {
+ case ARG_STRING: {
+ struct String s = va_arg(args, struct String);
+ if (!string_is_empty(s)) {
+ memcpy(cursor, s.data, s.length);
+ cursor += s.length;
+ }
+ break;
+ }
+ case ARG_ASCII: {
+ ascii* str = va_arg(args, ascii*);
+ if (str) {
+ uint length = strlen(str);
+ memcpy(cursor, str, length);
+ cursor += length;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ type = va_arg(args, enum String_Concat_Arg);
+ }
+ va_end(args);
+
+ return string_new(buffer, total_length);
+}
+
struct String_View
string_substring(struct String s, uint start, uint end)
{
diff --git a/boot/tests/parse/loops.cskt b/boot/tests/parse/loops.cskt
new file mode 100644
index 0000000..45b1040
--- /dev/null
+++ b/boot/tests/parse/loops.cskt
@@ -0,0 +1,31 @@
+looping structures!
+
+<<<
+
+for i uint = 0..10 {
+ print(i)
+}
+
+for x uint = 0, x < 10, x++ {
+ print(r)
+}
+
+var y uint = 0
+while y < 10 {
+ print(y)
+ y++
+}
+
+>>>
+
+(loop for-each (declaration i (type name uint) (initializer (expr (binary .. (expr 0) (expr 10))))) (block
+ (expr (call (expr (name print)) (arg (expr (name i)))))
+))
+(loop c-style (declaration x (type name uint) (initializer (expr 0))) (condition (expr (binary < (expr (name x)) (expr 10)))) (iteration (expr (increment/decrement ++ postfix (expr (name x))))) (block
+ (expr (call (expr (name print)) (arg (expr (name r)))))
+))
+(variable (declaration y (type name uint) (initializer (expr 0))))
+(loop while (condition (expr (binary < (expr (name y)) (expr 10)))) (block
+ (expr (call (expr (name print)) (arg (expr (name y)))))
+ (expr (increment/decrement ++ postfix (expr (name y))))
+)) \ No newline at end of file
diff --git a/boot/tests/test.c b/boot/tests/test.c
index 56d1a77..b8a535d 100644
--- a/boot/tests/test.c
+++ b/boot/tests/test.c
@@ -83,11 +83,47 @@ usage(void)
{
fprintf(
stderr,
- "usage: test [<target> ...]\n"
+ "usage: test [options] [<target> ...]\n"
"targets:\n");
for (uint i = 0; i < ARRAY_SIZE(known_targets); ++i) {
fprintf(stderr, " %s\n", known_targets[i].name);
}
+
+ fprintf(
+ stderr,
+ "\noptions:\n"
+ " -a, --adjust\t\t\t overwrite expected output with actual output for failed tests\n"
+ " -?, --help\t\t\t print this help message\n");
+}
+
+enum Option
+{
+ OPTION_NONE = 0,
+ OPTION_ADJUST = 1 << 0, // overwrite expected output with actual output for failed tests
+ OPTION_HELP = 1 << 1, // print help message
+};
+
+enum Option
+options_from_arguments(int argc, const ascii** argv)
+{
+ enum Option options = OPTION_NONE;
+ for (uint ai = 1; ai < argc; ++ai) {
+ const ascii* arg = argv[ai];
+
+ if (arg[0] != '-') continue; // skip non-option arguments
+
+ if (strcmp(arg, "-a") == 0 || strcmp(arg, "--adjust") == 0) {
+ options |= OPTION_ADJUST;
+ } else if (strcmp(arg, "-?") == 0 || strcmp(arg, "--help") == 0) {
+ options |= OPTION_HELP;
+ } else {
+ fprintf(stderr, "unknown option '%s'\n", arg);
+ usage();
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ return options;
}
enum Test_Target
@@ -97,6 +133,8 @@ targets_from_arguments(int argc, const ascii** argv)
for (uint ai = 1; ai < argc; ++ai) {
const ascii* arg = argv[ai];
+ if (arg[0] == '-') continue; // skip option arguments
+
bool found = false;
for (uint ti = 0; ti < ARRAY_SIZE(known_targets); ++ti) {
struct Known_Target kt = known_targets[ti];
@@ -192,6 +230,29 @@ string_from_file(const ascii* path)
return string_from_static_c_string(file_data);
}
+void
+string_to_file(struct String str, const ascii* path)
+{
+ check(!string_is_empty(str), "string to write to file is empty");
+ check(path, "path to write string to is nil");
+
+ FILE* file = fopen(path, "w");
+ if (!file) {
+ fprintf(stderr, "i couldn't open file '%s' for writing: %s\n", path, strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ size_t written = fwrite(str.data, 1, str.length, file);
+ if (written < str.length) {
+ fprintf(
+ stderr, "i couldn't write complete string to file '%s': %s\n", path, strerror(errno));
+ fclose(file);
+ exit(EXIT_FAILURE);
+ }
+
+ fclose(file);
+}
+
#define MAX_INPUT_LENGTH 8192
#define MAX_PATH_LENGTH 256
@@ -343,8 +404,32 @@ run_command(const ascii* command)
};
}
+void
+write_failed_output_artifact(const ascii* test_definition_path, struct String output)
+{
+ struct Temporary_File wrong_output_file = temporary_file_from_string(output);
+ fprintf(stderr, "'%s': completed with incorrect output. written to %s.\n", test_definition_path,
+ wrong_output_file.path);
+ free(wrong_output_file.path); // free but don't delete.
+}
+
+void
+adjust_failed_definition_output(const ascii* test_definition_path, struct String output)
+{
+ struct String test_file = string_from_file(test_definition_path);
+ struct String test_without_output = string_strip_from_pattern(test_file, OUTPUT_MARK);
+
+ struct String new_test_content = string_concatenate(
+ ARG_STRING, test_without_output, ARG_ASCII, OUTPUT_MARK "\n\n", ARG_STRING, output,
+ ARG_END);
+
+ string_to_file(new_test_content, test_definition_path);
+ fprintf(stderr, "'%s': adjusted test definition output to match actual output.\n",
+ test_definition_path);
+}
+
bool
-run_test(const ascii* test_definition_path, const ascii* base_command)
+run_test(const ascii* test_definition_path, const ascii* base_command, bool adjust)
{
struct String test_file = string_from_file(test_definition_path);
@@ -371,10 +456,11 @@ run_test(const ascii* test_definition_path, const ascii* base_command)
struct String output = string_trim(result.output);
if (!string_equal(output, expected_output)) {
- struct Temporary_File wrong_output_file = temporary_file_from_string(output);
- fprintf(stderr, "'%s': completed with incorrect output. written to %s.\n",
- test_definition_path, wrong_output_file.path);
- free(wrong_output_file.path); // free but don't delete.
+ if (adjust)
+ adjust_failed_definition_output(test_definition_path, output);
+ else
+ write_failed_output_artifact(test_definition_path, output);
+
success = false;
goto end;
}
@@ -387,7 +473,7 @@ end:
}
struct Result_Summary
-tests(const ascii* base_path, const ascii* base_command)
+tests(const ascii* base_path, const ascii* base_command, bool adjust)
{
struct Result_Summary summary = { 0 };
@@ -412,7 +498,7 @@ tests(const ascii* base_path, const ascii* base_command)
if (!is_viable_test_definition_file(test_definition_path)) continue;
- bool succeeded = run_test(test_definition_path, base_command);
+ bool succeeded = run_test(test_definition_path, base_command, adjust);
summary.total++;
if (succeeded) {
@@ -426,26 +512,38 @@ tests(const ascii* base_path, const ascii* base_command)
}
struct Result_Summary
-lex_tests(void)
+lex_tests(bool adjust)
{
const ascii* base_path = "./boot/tests/lex/";
const ascii* base_command = "./build/catboot --test-lex";
- return tests(base_path, base_command);
+ return tests(base_path, base_command, adjust);
}
struct Result_Summary
-parse_tests(void)
+parse_tests(bool adjust)
{
const ascii* base_path = "./boot/tests/parse/";
const ascii* base_command = "./build/catboot --test-parse";
- return tests(base_path, base_command);
+ return tests(base_path, base_command, adjust);
}
int
main(int argc, const ascii** argv)
{
+ enum Option options = options_from_arguments(argc, argv);
+ if (options & OPTION_HELP) {
+ usage();
+ return EXIT_SUCCESS;
+ }
+
+ bool adjust = false;
+ if (options & OPTION_ADJUST) {
+ fprintf(stderr, "--adjust option will overwrite failed test outputs\n");
+ adjust = true;
+ }
+
enum Test_Target targets = targets_from_arguments(argc, argv);
if (targets == TARGET_NONE) {
usage();
@@ -466,11 +564,11 @@ main(int argc, const ascii** argv)
struct Result_Summary summary = { 0 };
switch (target.target) {
case TARGET_LEX:
- summary = lex_tests();
+ summary = lex_tests(adjust);
break;
case TARGET_PARSE:
- summary = parse_tests();
+ summary = parse_tests(adjust);
break;
default:
@@ -508,4 +606,4 @@ main(int argc, const ascii** argv)
result_summary_print(full_summary);
return EXIT_SUCCESS;
-} \ No newline at end of file
+}