lcm :: (a: $T, b: T) -> T {
return (a * b) / gcd(a, b);
}
+
+
+
+//
+// Tests
+//
+
+#if #defined(core.Running_Tests) {
+
+use core {test}
+
+@test.test.{"GCD works"}
+(t: ^test.T) {
+ t->assert(gcd(35, 18) == 1, "gcd(35, 18) == 1");
+ t->assert(gcd(35, 15) == 5, "gcd(35, 15) == 5");
+ t->assert(gcd(35, 21) == 7, "gcd(35, 21) == 7");
+ t->assert(gcd(35, 70) == 35, "gcd(35, 70) == 35");
+}
+
+}
--- /dev/null
+package core.test
+
+use core {array, string, printf}
+
+#doc """
+ Test tag. Use this to mark a function as a test.
+
+ You can either use just the type name:
+
+ @core.test.test
+ (t: ^core.test.T) {
+ }
+
+ Or you can specify a name using the full struct literal:
+
+ @core.test.test.{"Important test name"}
+ (t: ^core.test.T) {
+ }
+"""
+test :: struct {
+ name: str;
+}
+
+
+#doc "Testing context"
+T :: struct {
+ current_test_case: ^Test_Case;
+}
+
+#inject
+T.assert :: (t: ^T, cond: bool, name := "", site := #callsite) {
+ t.current_test_case.assertions << .{
+ name, cond, site
+ };
+
+ if !cond {
+ t.current_test_case.passed = false;
+ }
+}
+
+
+
+#doc """
+ Runs all test cases in the provide packages.
+ If no packages are provided, ALL package tests are run.
+"""
+run_tests :: (packages: [] package_id = .[], log := true) -> bool {
+ ctx: T;
+
+ cases := gather_test_cases(packages);
+ defer delete(^cases);
+
+ failed_cases := make([..] ^Test_Case);
+
+ if log do printf("Running {} test cases...\n", cases.count);
+ for ^ cases {
+ ctx.current_test_case = it;
+
+ // Assume the test case passed until it didn't.
+ it.passed = true;
+
+ it.runner(^ctx);
+
+ if !it.passed {
+ failed_cases << it;
+ }
+ }
+
+ if log {
+ printf("Results: {} / {} test cases passed.\n",
+ cases.count - failed_cases.length, cases.count);
+
+ if failed_cases.count > 0 do printf("Failing test cases:\n");
+
+ for failed_cases {
+ printf("[{}]\n", it.name if !string.is_empty(it.name) else "Unnamed test");
+
+ for ^ it.assertions {
+ if it.passed do continue;
+
+ printf(" {} at {}:{}\n",
+ it.name if !string.is_empty(it.name) else "Unnamed assertion",
+ it.loc.file, it.loc.line);
+ }
+ }
+ }
+
+ return failed_cases.count == 0;
+}
+
+
+
+
+//
+// Private interface
+//
+
+#local
+runner_proc :: #type (^T) -> void;
+
+#local
+Test_Case :: struct {
+ name: str;
+ runner: (^T) -> void;
+
+ passed := false;
+ assertions: [..] Assertion = make([..] Assertion);
+
+ Assertion :: struct {
+ name: str;
+ passed: bool;
+ loc: CallSite;
+ }
+}
+
+#local
+gather_test_cases :: (packages: [] package_id) -> [] Test_Case {
+ result := make([..] Test_Case);
+
+ procs1 := runtime.info.get_procedures_with_tag(test);
+ defer delete(^procs1);
+
+ for procs1 {
+ if packages.count == 0 || array.contains(packages, it.pack) {
+ result << .{it.tag.name, *cast(^runner_proc) ^it.func};
+ }
+ }
+
+
+ procs2 := runtime.info.get_procedures_with_tag(type_expr);
+ defer delete(^procs2);
+
+ for procs2 {
+ if packages.count != 0 && !array.contains(packages, it.pack) do continue;
+
+ if *it.tag == test {
+ tag := cast(^test) it.tag;
+ result << .{"", *cast(^runner_proc) ^it.func};
+ }
+ }
+
+ return result;
+}