diff options
| author | Leitwolf | 2026-11-12 22:36:11 +0300 |
|---|---|---|
| committer | GitHub | 2026-11-12 22:36:11 +0300 |
| commit | 86214c96285f73d352f934b0da33d231150898f0 (patch) | |
| tree | affc0bfef110459bc8fcf3e8f197ed95cc46f6bb /sources/test | |
| parent | 95cdafcf1aa01b5d32171c56e6a94137402643b9 (diff) | |
| parent | 7cb809c6560fc4ed29f41e4e4199cd91af6cbed1 (diff) | |
| download | tazel-main.tar.gz | |
Build and run C, C++, Objective-C, Objective-C++ and Zig targets
Diffstat (limited to 'sources/test')
| -rw-r--r-- | sources/test/kotlin/com/progmasoft/tazel/cli/BuildCommandTest.kt | 165 | ||||
| -rw-r--r-- | sources/test/kotlin/com/progmasoft/tazel/driver/DriverTest.kt | 113 |
2 files changed, 278 insertions, 0 deletions
diff --git a/sources/test/kotlin/com/progmasoft/tazel/cli/BuildCommandTest.kt b/sources/test/kotlin/com/progmasoft/tazel/cli/BuildCommandTest.kt new file mode 100644 index 0000000..36e1323 --- /dev/null +++ b/sources/test/kotlin/com/progmasoft/tazel/cli/BuildCommandTest.kt @@ -0,0 +1,165 @@ +/* + * SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com> + * SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-2.0 + */ + +package com.progmasoft.tazel.cli + +import com.progmasoft.tazel.driver.GatheredRun +import com.progmasoft.tazel.driver.Processes +import com.progmasoft.tazel.driver.Programs +import com.progmasoft.tazel.driver.SystemProcesses +import com.progmasoft.tazel.dsl.TargetTriple +import java.nio.file.Files +import java.nio.file.Path +import kotlin.test.Test +import kotlin.test.assertContains +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +/** + * Builds and runs a small module with the C compiler of this machine. Without Clang or GCC on PATH + * there is nothing to build with, and the tests return early. + */ +class BuildCommandTest { + private val os = TargetTriple.host().os + private val programs = Programs(os) + private val compiler = programs.find("clang") ?: programs.find("gcc") + + /** Real processes; what a program `tazel run` starts prints is kept instead of shown. */ + private class Recording : Processes by SystemProcesses { + var printed = "" + + override fun runWith( + command: List<String>, + directory: Path, + environment: Map<String, String>, + ): Int { + val result: GatheredRun = SystemProcesses.runGatheredWith(command, directory, environment) + printed += result.output + return result.exitCode + } + } + + private fun module(): Path { + val root = Files.createTempDirectory("tazel-build-").toRealPath() + fun write(path: String, text: String) { + val file = root.resolve(path) + Files.createDirectories(file.parent) + Files.writeString(file, text.trimIndent() + "\n") + } + write("MODULE.kts", """module { name = "sample" }""") + write( + "core/BUILD.kts", + """ + isPublic = true + c.staticlib("core") { + sources(glob("src/*.c")) + headers("include") + stripIncludePrefix = "include" + includePrefix = "sample" + defines("SAMPLE_OFFSET=1") + } + """, + ) + write("core/include/core.h", "int sample_add(int a, int b);") + write( + "core/src/core.c", + "#include <sample/core.h>\nint sample_add(int a, int b) { return a + b + SAMPLE_OFFSET - 1; }", + ) + write( + "app/BUILD.kts", + """ + c.bin("app") { + sources("main.c") + depends("//core/:core") + args("--given") + env("SAMPLE_GREETING" to "hi") + } + """, + ) + write( + "app/main.c", + """ + #include <stdio.h> + #include <stdlib.h> + #include <sample/core.h> + int main(int argc, char **argv) { + printf("%s %d", getenv("SAMPLE_GREETING"), sample_add(2, 3)); + for (int i = 1; i < argc; ++i) printf(" %s", argv[i]); + printf("\n"); + return 0; + } + """, + ) + return root + } + + private fun tazel( + root: Path, + directory: String, + processes: Processes, + vararg arguments: String, + ): Pair<Int, Recorder> { + val recorder = Recorder() + val code = + run( + arguments.toList(), + recorder.out, + recorder.err, + Environment(root.resolve(directory), os, programs, processes), + ) + return code to recorder + } + + @Test + fun buildsRunsAndRebuildsOnlyWhatChanged() { + if (compiler == null) return + val root = module() + val processes = Recording() + + val (first, firstOutput) = tazel(root, ".", processes, "build", "//...") + assertEquals(ExitCode.SUCCESS, first, firstOutput.stderr() + firstOutput.stdout()) + assertContains(firstOutput.stdout(), "4 command(s) run") + + val (again, againOutput) = tazel(root, ".", processes, "build", "//...") + assertEquals(ExitCode.SUCCESS, again, againOutput.stderr()) + assertContains(againOutput.stdout(), "0 command(s) run") + + // A header change recompiles what includes it, and relinks. + val header = root.resolve("core/include/core.h") + Files.writeString(header, "int sample_add(int a, int b); /* changed */\n") + Files.setLastModifiedTime( + header, + java.nio.file.attribute.FileTime.fromMillis(System.currentTimeMillis() + 5_000), + ) + val (changed, changedOutput) = tazel(root, ".", processes, "build", "//...") + assertEquals(ExitCode.SUCCESS, changed, changedOutput.stderr()) + assertContains(changedOutput.stdout(), "4 command(s) run") + + val (ran, ranOutput) = tazel(root, "app", processes, "run", "--", "extra") + assertEquals(ExitCode.SUCCESS, ran, ranOutput.stderr()) + assertEquals("hi 5 --given extra", processes.printed.trim()) + } + + @Test + fun aCompilerErrorFailsTheBuildWithItsMessage() { + if (compiler == null) return + val root = module() + Files.writeString(root.resolve("app/main.c"), "int main(void) { return undeclared; }\n") + val (code, output) = tazel(root, ".", Recording(), "build", "//app/:app") + assertEquals(ExitCode.FAILURE, code) + assertContains(output.stdout(), "undeclared") + assertContains(output.stderr(), "//app/:app") + } + + @Test + fun commandLineMistakesAreUsageErrors() { + val root = module() + assertEquals(ExitCode.USAGE, tazel(root, ".", Recording(), "build", "--fast").first) + assertEquals(ExitCode.USAGE, tazel(root, ".", Recording(), "build", "//core").first) + val (code, output) = tazel(root, ".", Recording(), "run", "//core/:core") + assertEquals(ExitCode.FAILURE, code) + assertTrue("no bin target" in output.stderr(), output.stderr()) + } +} diff --git a/sources/test/kotlin/com/progmasoft/tazel/driver/DriverTest.kt b/sources/test/kotlin/com/progmasoft/tazel/driver/DriverTest.kt new file mode 100644 index 0000000..5f7d831 --- /dev/null +++ b/sources/test/kotlin/com/progmasoft/tazel/driver/DriverTest.kt @@ -0,0 +1,113 @@ +/* + * SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com> + * SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-2.0 + */ + +package com.progmasoft.tazel.driver + +import com.progmasoft.tazel.dsl.FilePath +import com.progmasoft.tazel.dsl.Glob +import com.progmasoft.tazel.dsl.ProjectException +import com.progmasoft.tazel.dsl.TargetTriple +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.attribute.FileTime +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class DriverTest { + @Test + fun depfilesAreReadWithTheirEscapes() { + val text = "obj/a.c.o: src/a.c include/a\\ b.h \\\n include/c.h \\\r\n /usr/include/stdio.h\n" + assertEquals( + listOf("src/a.c", "include/a b.h", "include/c.h", "/usr/include/stdio.h"), + depfileInputs(text), + ) + assertEquals(listOf("C:\\src\\a.c"), depfileInputs("C:\\out\\a.obj: C:\\src\\a.c\n")) + } + + @Test + fun anOutputIsCurrentUntilItsCommandOrAnInputChanges() { + val directory = Files.createTempDirectory("tazel-up-to-date") + val input = Files.writeString(directory.resolve("a.c"), "int a;") + val output = Files.writeString(directory.resolve("a.o"), "object") + Files.setLastModifiedTime(input, FileTime.fromMillis(1_000_000)) + Files.setLastModifiedTime(output, FileTime.fromMillis(2_000_000)) + val command = listOf("cc", "-c", "a.c") + assertFalse(UpToDate.check(output, command, listOf(input)), "never recorded") + UpToDate.record(output, command) + assertTrue(UpToDate.check(output, command, listOf(input))) + assertFalse(UpToDate.check(output, command + "-O2", listOf(input)), "another command") + Files.setLastModifiedTime(input, FileTime.fromMillis(3_000_000)) + assertFalse(UpToDate.check(output, command, listOf(input)), "a newer input") + Files.setLastModifiedTime(input, FileTime.fromMillis(1_000_000)) + assertFalse( + UpToDate.check(output, command, listOf(directory.resolve("gone.h"))), + "a missing input", + ) + UpToDate.forget(output) + assertFalse(UpToDate.check(output, command, listOf(input)), "forgotten") + } + + @Test + fun globsTakeFilesBelowThePackageAndExcludesRemoveThem() { + val root = Files.createTempDirectory("tazel-globs") + for (file in + listOf("src/a.c", "src/deep/b.c", "src/experimental/x.c", "src/a.h", "other/c.c", "top.c")) { + Files.createDirectories(root.resolve(file).parent) + Files.writeString(root.resolve(file), "") + } + val sets = FileSets(ignoreCase = false) + fun names(files: List<Path>) = files.map { root.relativize(it).toString().replace('\\', '/') } + assertEquals( + listOf("src/a.c", "src/deep/b.c", "src/experimental/x.c"), + names(sets.expand(root, listOf(Glob(listOf("src/**/*.c"))))), + ) + assertEquals( + listOf("src/a.c", "src/deep/b.c"), + names( + sets.expand( + root, + listOf(Glob(listOf("src/**/*.c"))), + listOf(Glob(listOf("src/experimental"))), + ) + ), + ) + assertEquals(listOf("src/a.c"), names(sets.expand(root, listOf(Glob(listOf("src/*.c")))))) + assertEquals( + listOf("top.c", "other/c.c"), + names(sets.expand(root, listOf(FilePath("top.c"), Glob(listOf("other/?.c"))))), + ) + val sub = root.resolve("src/deep") + assertEquals(listOf("other/c.c"), names(sets.expand(sub, listOf(FilePath("../../other/c.c"))))) + assertFailsWith<ProjectException> { sets.expand(root, listOf(FilePath("missing.c"))) } + assertEquals(emptyList(), sets.expand(root, listOf(Glob(listOf("nowhere/*.c"))))) + } + + @Test + fun zigSpellsTriplesItsOwnWay() { + fun zig(text: String) = zigTriple(TargetTriple.parse(text)) + assertEquals("x86_64-linux-gnu", zig("x86_64-unknown-linux-gnu")) + assertEquals("aarch64-macos", zig("aarch64-apple-darwin")) + assertEquals("aarch64-macos", zig("arm64-apple-macosx14.0")) + assertEquals("x86_64-windows-msvc", zig("x86_64-pc-windows-msvc")) + assertEquals("x86_64-freebsd", zig("x86_64-unknown-freebsd14")) + } + + @Test + fun outputNamesFollowTheSystemOfTheTriple() { + val linux = TargetTriple.parse("x86_64-unknown-linux-gnu").os + val windows = TargetTriple.parse("x86_64-pc-windows-msvc").os + val mac = TargetTriple.parse("aarch64-apple-darwin").os + assertEquals("libcore.a", OutputNames.staticLibrary("core", linux)) + assertEquals("core.lib", OutputNames.staticLibrary("core", windows)) + assertEquals("libcore.so.1", OutputNames.sharedLibrary("core", linux, "1")) + assertEquals("libcore.dylib", OutputNames.sharedLibrary("core", mac, null)) + assertEquals("core.dll", OutputNames.sharedLibrary("core", windows, "1")) + assertEquals("app.exe", OutputNames.executable("app", windows)) + assertEquals("app", OutputNames.executable("app", mac)) + } +} |