about summary refs log tree commit diff
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
+}