feat(build): make build.zig run unittests

This commit is contained in:
bfredl
2025-05-16 12:59:59 +02:00
parent c4501e98f2
commit e25b99c5b6
4 changed files with 60 additions and 18 deletions

View File

@ -213,6 +213,7 @@ jobs:
- run: sudo apt-get install -y inotify-tools - run: sudo apt-get install -y inotify-tools
- run: zig build test_nlua0 - run: zig build test_nlua0
- run: zig build nvim_bin && ./zig-out/bin/nvim --version - run: zig build nvim_bin && ./zig-out/bin/nvim --version
- run: zig build unittest
- run: zig build functionaltest - run: zig build functionaltest
windows: windows:

View File

@ -149,6 +149,8 @@ pub fn build(b: *std.Build) !void {
} }
} }
const support_unittests = use_luajit;
const gen_config = b.addWriteFiles(); const gen_config = b.addWriteFiles();
const version_lua = gen_config.add("nvim_version.lua", lua_version_info(b)); const version_lua = gen_config.add("nvim_version.lua", lua_version_info(b));
@ -232,7 +234,7 @@ pub fn build(b: *std.Build) !void {
// TODO(zig): using getEmittedIncludeTree() is ugly af. we want run_preprocessor() // TODO(zig): using getEmittedIncludeTree() is ugly af. we want run_preprocessor()
// to use the std.build.Module include_path thing // to use the std.build.Module include_path thing
const include_path = &.{ const include_path = [_]LazyPath{
b.path("src/"), b.path("src/"),
gen_config.getDirectory(), gen_config.getDirectory(),
lua.getEmittedIncludeTree(), lua.getEmittedIncludeTree(),
@ -243,10 +245,10 @@ pub fn build(b: *std.Build) !void {
treesitter.artifact("tree-sitter").getEmittedIncludeTree(), treesitter.artifact("tree-sitter").getEmittedIncludeTree(),
}; };
const gen_headers, const funcs_data = try gen.nvim_gen_sources(b, nlua0, &nvim_sources, &nvim_headers, &api_headers, include_path, target, versiondef_git, version_lua); const gen_headers, const funcs_data = try gen.nvim_gen_sources(b, nlua0, &nvim_sources, &nvim_headers, &api_headers, &include_path, target, versiondef_git, version_lua);
const test_config_step = b.addWriteFiles(); const test_config_step = b.addWriteFiles();
_ = test_config_step.add("test/cmakeconfig/paths.lua", try test_config(b, gen_headers.getDirectory())); _ = test_config_step.add("test/cmakeconfig/paths.lua", try test_config(b));
const test_gen_step = b.step("gen_headers", "debug: output generated headers"); const test_gen_step = b.step("gen_headers", "debug: output generated headers");
const config_install = b.addInstallDirectory(.{ .source_dir = gen_config.getDirectory(), .install_dir = .prefix, .install_subdir = "config/" }); const config_install = b.addInstallDirectory(.{ .source_dir = gen_config.getDirectory(), .install_dir = .prefix, .install_subdir = "config/" });
@ -258,6 +260,7 @@ pub fn build(b: *std.Build) !void {
.target = target, .target = target,
.optimize = optimize, .optimize = optimize,
}); });
nvim_exe.rdynamic = true; // -E
nvim_exe.linkLibrary(lua); nvim_exe.linkLibrary(lua);
nvim_exe.linkLibrary(libuv); nvim_exe.linkLibrary(libuv);
@ -273,16 +276,31 @@ pub fn build(b: *std.Build) !void {
nvim_exe.addIncludePath(gen_headers.getDirectory()); nvim_exe.addIncludePath(gen_headers.getDirectory());
build_lua.add_lua_modules(nvim_exe.root_module, lpeg, use_luajit, false); build_lua.add_lua_modules(nvim_exe.root_module, lpeg, use_luajit, false);
const src_paths = try b.allocator.alloc([]u8, nvim_sources.items.len); var unit_test_sources = try std.ArrayList([]u8).initCapacity(b.allocator, 10);
if (support_unittests) {
var unit_test_fixtures = try src_dir.openDir("test/unit/fixtures/", .{ .iterate = true });
defer unit_test_fixtures.close();
var it = unit_test_fixtures.iterateAssumeFirstIteration();
while (try it.next()) |entry| {
if (entry.name.len < 3) continue;
if (std.mem.eql(u8, ".c", entry.name[entry.name.len - 2 ..])) {
try unit_test_sources.append(b.fmt("test/unit/fixtures/{s}", .{entry.name}));
}
}
}
const src_paths = try b.allocator.alloc([]u8, nvim_sources.items.len + unit_test_sources.items.len);
for (nvim_sources.items, 0..) |s, i| { for (nvim_sources.items, 0..) |s, i| {
src_paths[i] = b.fmt("src/nvim/{s}", .{s.name}); src_paths[i] = b.fmt("src/nvim/{s}", .{s.name});
} }
@memcpy(src_paths[nvim_sources.items.len..], unit_test_sources.items);
const flags = [_][]const u8{ const flags = [_][]const u8{
"-std=gnu99", "-std=gnu99",
"-DINCLUDE_GENERATED_DECLARATIONS", "-DINCLUDE_GENERATED_DECLARATIONS",
"-DZIG_BUILD", "-DZIG_BUILD",
"-D_GNU_SOURCE", "-D_GNU_SOURCE",
if (support_unittests) "-DUNIT_TESTING" else "",
if (use_luajit) "" else "-DNVIM_VENDOR_BIT", if (use_luajit) "" else "-DNVIM_VENDOR_BIT",
}; };
nvim_exe.addCSourceFiles(.{ .files = src_paths, .flags = &flags }); nvim_exe.addCSourceFiles(.{ .files = src_paths, .flags = &flags });
@ -339,7 +357,9 @@ pub fn build(b: *std.Build) !void {
const parser_query = b.dependency("treesitter_query", .{ .target = target, .optimize = optimize }); const parser_query = b.dependency("treesitter_query", .{ .target = target, .optimize = optimize });
test_deps.dependOn(add_ts_parser(b, "query", parser_query.path("."), false, target, optimize)); test_deps.dependOn(add_ts_parser(b, "query", parser_query.path("."), false, target, optimize));
try tests.test_steps(b, nvim_exe, test_deps, lua_dev_deps.path("."), test_config_step.getDirectory()); const unit_headers: ?[]const LazyPath = if (support_unittests) &(include_path ++ .{gen_headers.getDirectory()}) else null;
try tests.test_steps(b, nvim_exe, test_deps, lua_dev_deps.path("."), test_config_step.getDirectory(), unit_headers);
} }
pub fn test_fixture( pub fn test_fixture(
@ -401,7 +421,7 @@ pub fn lua_version_info(b: *std.Build) []u8 {
, .{ v.major, v.minor, v.patch, v.prerelease.len > 0, v.api_level, v.api_level_compat, v.api_prerelease }); , .{ v.major, v.minor, v.patch, v.prerelease.len > 0, v.api_level, v.api_level_compat, v.api_prerelease });
} }
pub fn test_config(b: *std.Build, gen_dir: LazyPath) ![]u8 { pub fn test_config(b: *std.Build) ![]u8 {
var buf: [std.fs.max_path_bytes]u8 = undefined; var buf: [std.fs.max_path_bytes]u8 = undefined;
const src_path = try b.build_root.handle.realpath(".", &buf); const src_path = try b.build_root.handle.realpath(".", &buf);
@ -409,7 +429,6 @@ pub fn test_config(b: *std.Build, gen_dir: LazyPath) ![]u8 {
return b.fmt( return b.fmt(
\\local M = {{}} \\local M = {{}}
\\ \\
\\M.include_paths = {{}}
\\M.apple_sysroot = "" \\M.apple_sysroot = ""
\\M.translations_enabled = "$ENABLE_TRANSLATIONS" == "ON" \\M.translations_enabled = "$ENABLE_TRANSLATIONS" == "ON"
\\M.is_asan = "$ENABLE_ASAN_UBSAN" == "ON" \\M.is_asan = "$ENABLE_ASAN_UBSAN" == "ON"
@ -419,9 +438,9 @@ pub fn test_config(b: *std.Build, gen_dir: LazyPath) ![]u8 {
\\M.test_source_path = "{[src_path]s}" \\M.test_source_path = "{[src_path]s}"
\\M.test_lua_prg = "" \\M.test_lua_prg = ""
\\M.test_luajit_prg = "" \\M.test_luajit_prg = ""
\\table.insert(M.include_paths, "{[gen_dir]}/include") \\ -- include path passed on the cmdline, see test/lua_runner.lua
\\table.insert(M.include_paths, "{[gen_dir]}/src/nvim/auto") \\M.include_paths = _G.c_include_path or {{}}
\\ \\
\\return M \\return M
, .{ .bin_dir = b.install_path, .src_path = src_path, .gen_dir = gen_dir }); , .{ .bin_dir = b.install_path, .src_path = src_path });
} }

View File

@ -1,5 +1,9 @@
local platform = vim.uv.os_uname() local platform = vim.uv.os_uname()
local deps_install_dir = table.remove(_G.arg, 1) local deps_install_dir = table.remove(_G.arg, 1)
_G.c_include_path = {}
while vim.startswith(_G.arg[1], '-I') do
table.insert(_G.c_include_path, string.sub(table.remove(_G.arg, 1), 3))
end
local subcommand = table.remove(_G.arg, 1) local subcommand = table.remove(_G.arg, 1)
local suffix = (platform and platform.sysname:lower():find 'windows') and '.dll' or '.so' local suffix = (platform and platform.sysname:lower():find 'windows') and '.dll' or '.so'
package.path = (deps_install_dir .. '/?.lua;') package.path = (deps_install_dir .. '/?.lua;')

View File

@ -1,14 +1,19 @@
const std = @import("std"); const std = @import("std");
const LazyPath = std.Build.LazyPath; const LazyPath = std.Build.LazyPath;
pub fn test_steps(b: *std.Build, nvim_bin: *std.Build.Step.Compile, depend_on: *std.Build.Step, lua_deps: LazyPath, config_dir: LazyPath) !void { pub fn testStep(b: *std.Build, kind: []const u8, nvim_bin: *std.Build.Step.Compile, lua_deps: LazyPath, config_dir: LazyPath, include_path: ?[]const LazyPath) !*std.Build.Step.Run {
const test_step = b.addRunArtifact(nvim_bin); const test_step = b.addRunArtifact(nvim_bin);
test_step.addArg("-ll"); test_step.addArg("-ll");
test_step.addFileArg(b.path("./test/lua_runner.lua")); test_step.addFileArg(b.path("./test/lua_runner.lua"));
test_step.addDirectoryArg(lua_deps); test_step.addDirectoryArg(lua_deps);
if (include_path) |paths| {
for (paths) |path| {
test_step.addPrefixedDirectoryArg("-I", path);
}
}
test_step.addArgs(&.{ "busted", "-v", "-o", "test.busted.outputHandlers.nvim", "--lazy" }); test_step.addArgs(&.{ "busted", "-v", "-o", "test.busted.outputHandlers.nvim", "--lazy" });
// TODO(bfredl): a bit funky with paths, should work even if we run "zig build" in a nested dir // TODO(bfredl): a bit funky with paths, should work even if we run "zig build" in a nested dir
test_step.addArg("./test/functional/preload.lua"); // TEST_TYPE!! test_step.addArg(b.fmt("./test/{s}/preload.lua", .{kind}));
test_step.addArg("--lpath=./src/?.lua"); test_step.addArg("--lpath=./src/?.lua");
test_step.addArg("--lpath=./runtime/lua/?.lua"); test_step.addArg("--lpath=./runtime/lua/?.lua");
test_step.addArg("--lpath=./?.lua"); test_step.addArg("--lpath=./?.lua");
@ -17,11 +22,9 @@ pub fn test_steps(b: *std.Build, nvim_bin: *std.Build.Step.Compile, depend_on: *
if (b.args) |args| { if (b.args) |args| {
test_step.addArgs(args); // accept TEST_FILE as a positional argument test_step.addArgs(args); // accept TEST_FILE as a positional argument
} else { } else {
test_step.addArg("./test/functional/"); test_step.addArg(b.fmt("./test/{s}/", .{kind}));
} }
test_step.step.dependOn(depend_on);
const env = test_step.getEnvMap(); const env = test_step.getEnvMap();
try env.put("VIMRUNTIME", "runtime"); try env.put("VIMRUNTIME", "runtime");
try env.put("NVIM_RPLUGIN_MANIFEST", "Xtest_xdg/Xtest_rplugin_manifest"); try env.put("NVIM_RPLUGIN_MANIFEST", "Xtest_xdg/Xtest_rplugin_manifest");
@ -33,12 +36,27 @@ pub fn test_steps(b: *std.Build, nvim_bin: *std.Build.Step.Compile, depend_on: *
env.remove("NVIM"); env.remove("NVIM");
env.remove("XDG_DATA_DIRS"); env.remove("XDG_DATA_DIRS");
return test_step;
}
pub fn test_steps(b: *std.Build, nvim_bin: *std.Build.Step.Compile, depend_on: *std.Build.Step, lua_deps: LazyPath, config_dir: LazyPath, unit_paths: ?[]const LazyPath) !void {
const empty_dir = b.addWriteFiles(); const empty_dir = b.addWriteFiles();
_ = empty_dir.add(".touch", ""); _ = empty_dir.add(".touch", "");
const tmpdir_create = b.addInstallDirectory(.{ .source_dir = empty_dir.getDirectory(), .install_dir = .prefix, .install_subdir = "Xtest_tmpdir/" }); const tmpdir_create = b.addInstallDirectory(.{ .source_dir = empty_dir.getDirectory(), .install_dir = .prefix, .install_subdir = "Xtest_tmpdir/" });
test_step.step.dependOn(&tmpdir_create.step);
const functional_tests = try testStep(b, "functional", nvim_bin, lua_deps, config_dir, null);
functional_tests.step.dependOn(depend_on);
functional_tests.step.dependOn(&tmpdir_create.step);
const functionaltest_step = b.step("functionaltest", "run functional tests"); const functionaltest_step = b.step("functionaltest", "run functional tests");
functionaltest_step.dependOn(&test_step.step); functionaltest_step.dependOn(&functional_tests.step);
if (unit_paths) |paths| {
const unit_tests = try testStep(b, "unit", nvim_bin, lua_deps, config_dir, paths);
unit_tests.step.dependOn(depend_on);
unit_tests.step.dependOn(&tmpdir_create.step);
const unittest_step = b.step("unittest", "run unit tests");
unittest_step.dependOn(&unit_tests.step);
}
} }