diff --git a/README.md b/README.md index 3d385ef..d480a35 100644 --- a/README.md +++ b/README.md @@ -198,7 +198,7 @@ func Main() -> int { | **CTFE** | `const func` — compile-time function execution | | **Trait Bounds** | `func Max(a: T, b: T) -> T` | | **Package Manager** | `bux add`, `bux install`, `bux.lock`, path + git deps | -| **Tooling** | `bux new`, `bux build`, `bux run`, `bux check` | +| **Tooling** | `bux new`, `bux build`, `bux run`, `bux test`, `bux check` | --- diff --git a/compiler/bootstrap/cli.nim b/compiler/bootstrap/cli.nim index 80a9809..de17d77 100644 --- a/compiler/bootstrap/cli.nim +++ b/compiler/bootstrap/cli.nim @@ -24,6 +24,7 @@ Commands: install Resolve and install dependencies build Build the current package run Build and run the current package + test Run tests in tests/ directory check Type-check the current package clean Remove build artifacts help Show this help message @@ -574,6 +575,46 @@ proc cmdClean*(args: seq[string], opts: GlobalOptions): int = printInfo("clean: build directory removed", useColor) return 0 +proc cmdTest*(args: seq[string], opts: GlobalOptions): int = + let useColor = shouldUseColor(opts) + let root = getCurrentDir() + let testsDir = root / "tests" + var testFiles: seq[string] = @[] + if dirExists(testsDir): + for kind, path in walkDir(testsDir): + if kind == pcFile and path.endsWith(".bux"): + testFiles.add(path) + if testFiles.len == 0: + printError("no tests found in tests/ directory", useColor) + return 1 + var passed = 0 + var failed = 0 + for testFile in testFiles: + let testName = splitFile(testFile).name + let tmpDir = getTempDir() / "bux_test_" & testName + removeDir(tmpDir) + createDir(tmpDir / "src") + copyFile(testFile, tmpDir / "src" / "Main.bux") + writeFile(tmpDir / "bux.toml", "[package]\nname = \"" & testName & "\"\nversion = \"0.1.0\"\n") + let buildRes = cmdBuild(@[tmpDir], opts) + if buildRes != 0: + printError(&" FAIL {testName} (build)", useColor) + failed += 1 + continue + var execFile = tmpDir / "build" / testName + if not fileExists(execFile): + execFile = tmpDir / "build" / "bux_out" + let exitCode = execCmd(execFile) + if exitCode == 0: + printInfo(&" PASS {testName}", useColor) + passed += 1 + else: + printError(&" FAIL {testName} (exit {exitCode})", useColor) + failed += 1 + removeDir(tmpDir) + echo &"\nResults: {passed} passed, {failed} failed" + return if failed > 0: 1 else: 0 + proc cmdVersion*(args: seq[string], opts: GlobalOptions): int = echo "bux 0.1.0 (bootstrap)" return 0 @@ -597,6 +638,7 @@ proc runCli*(args: seq[string]): int = of "build": return cmdBuild(cmdArgs, opts) of "run": return cmdRun(cmdArgs, opts) of "check": return cmdCheck(cmdArgs, opts) + of "test": return cmdTest(cmdArgs, opts) of "clean": return cmdClean(cmdArgs, opts) of "version", "--version", "-v": return cmdVersion(cmdArgs, opts) of "help", "--help", "-h": diff --git a/docs/BuildAndTest.md b/docs/BuildAndTest.md index 3982225..3cf194e 100644 --- a/docs/BuildAndTest.md +++ b/docs/BuildAndTest.md @@ -89,6 +89,9 @@ Output = "Bin" ./buxc run ./buxc run ./myproject +# Run tests +./buxc test + # Clean build artifacts ./buxc clean ``` diff --git a/library/runtime/runtime.c b/library/runtime/runtime.c index d210304..3f03c3e 100644 --- a/library/runtime/runtime.c +++ b/library/runtime/runtime.c @@ -1324,6 +1324,20 @@ const char* bux_socket_error(void) { return strerror(errno); } +/* ============================================================================ + * Test / Assert primitives + * ============================================================================ */ + +void bux_exit(int code) { + exit(code); +} + +void bux_assert(int cond, const char* file, int line, const char* expr) { + if (!cond) { + fprintf(stderr, "ASSERT FAILED: %s at %s:%d\n", expr, file, line); + exit(1); + } +} /* ============================================================================ * Cryptography primitives (OpenSSL) diff --git a/library/std/Test.bux b/library/std/Test.bux new file mode 100644 index 0000000..7cdfcf2 --- /dev/null +++ b/library/std/Test.bux @@ -0,0 +1,44 @@ +module Std::Test { + +extern func bux_exit(code: int); +extern func bux_assert(cond: int, file: String, line: int, expr: String); + +func Test_Exit(code: int) { + bux_exit(code); +} + +func Test_Assert(cond: bool) { + bux_assert(cond as int, "", 0, ""); +} + +func Test_AssertEqInt(a: int, b: int) { + if a != b { + PrintLine("ASSERT_EQ FAILED:"); + PrintInt(a); + PrintLine(" != "); + PrintInt(b); + bux_exit(1); + } +} + +func Test_AssertTrue(cond: bool) { + if !cond { + PrintLine("ASSERT_TRUE FAILED"); + bux_exit(1); + } +} + +func Test_AssertFalse(cond: bool) { + if cond { + PrintLine("ASSERT_FALSE FAILED"); + bux_exit(1); + } +} + +func Test_Fail(msg: String) { + PrintLine("FAIL:"); + PrintLine(msg); + bux_exit(1); +} + +}