From 49dc3d9424353e6fb3ae022ab1261c83a8f1c0e3 Mon Sep 17 00:00:00 2001 From: bfredl Date: Thu, 2 Apr 2026 11:53:48 +0200 Subject: [PATCH] build(zig): run oldtests in zig builds with new lua runner The new lua based runner replaces Makefile, runnvim.sh and runnvim.vim As it happens, we run a `--headless` nvim inside a `:terminal` layer, this is pointless. Also there is still a lot remnants for oldesttests, but we don't have any except for test1.in which just checks the environment for following, nonexistant oldesttests. so just skip that. For now, the actual vimscript code which runs in vim-under-test is completely unchanged. On macos, luajit is finally working with the latest ziglua master. Also fix some minor bugs regarding locales, such as incorrect HAVE_WORKING_LIBINTL checks --- .github/workflows/test.yml | 13 +- build.zig.zon | 4 +- runtime/lua/vim/treesitter.lua | 10 +- src/nvim/eval.c | 2 - src/nvim/os/lang.c | 10 +- src/nvim/spellfile.c | 2 +- src/nvim/usercmd.c | 2 - test/functional/treesitter/language_spec.lua | 10 +- test/old/runner.lua | 160 +++++++++++++++++++ test/run_tests.zig | 13 ++ 10 files changed, 208 insertions(+), 18 deletions(-) create mode 100644 test/old/runner.lua diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1f85c98bec..7adcf2579e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -258,10 +258,12 @@ jobs: version: 0.16.0 - run: sudo apt-get install -y inotify-tools + - run: mkdir -p build/ # build/ is used for shared log paths, see global ENV defs - run: zig build $OPTS test_nlua0 - run: zig build $OPTS nvim_bin && ./zig-out/bin/nvim --version - run: zig build $OPTS unittest - run: zig build $OPTS functionaltest + - run: zig build $OPTS oldtest # `zig build` uses a lua script for doctags in order to support cross-compiling # compare with the builtin generator that they match - run: cd runtime; ../zig-out/bin/nvim -u NONE -i NONE -e --headless -c "helptags ++t doc" -c quit @@ -271,6 +273,8 @@ jobs: runs-on: macos-15 timeout-minutes: 45 name: build using zig build (macos 15) + env: + OPTS: # empty steps: - uses: actions/checkout@v6 with: @@ -280,9 +284,11 @@ jobs: with: version: 0.16.0 - - run: zig build test_nlua0 -Dluajit=false - - run: zig build nvim_bin -Dluajit=false && ./zig-out/bin/nvim --version - - run: zig build functionaltest -Dluajit=false + - run: mkdir -p build/ # build/ is used for shared log paths, see global ENV defs + - run: zig build $OPTS test_nlua0 + - run: zig build $OPTS nvim_bin && ./zig-out/bin/nvim --version + - run: zig build $OPTS functionaltest + - run: zig build $OPTS oldtest zig-build-windows: runs-on: windows-2025 @@ -297,6 +303,7 @@ jobs: with: version: 0.16.0 + - run: mkdir -p build/ # build/ is used for shared log paths, see global ENV defs - run: zig build test_nlua0 - run: zig build nvim_bin - run: ./zig-out/bin/nvim --version diff --git a/build.zig.zon b/build.zig.zon index fd5326dac6..938546f673 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -6,8 +6,8 @@ .dependencies = .{ .zlua = .{ - .url = "git+https://github.com/natecraddock/ziglua#8f271c82baa5fc43aa02a72f6da020c2025d9436", - .hash = "zlua-0.1.0-hGRpC2aABQD4D9PBVH3wAW8k32-I4969MRQ0CpOwoley", + .url = "git+https://github.com/natecraddock/ziglua#1dcb9f2b7b466a1609cc11e746c2efc4f17685da", + .hash = "zlua-0.1.0-hGRpC22ABQA4y12Uz7QaHuCkKrpaa_66xidApjtFuKFg", }, .lpeg = .{ .url = "https://github.com/neovim/deps/raw/d495ee6f79e7962a53ad79670cb92488abe0b9b4/opt/lpeg-1.1.0.tar.gz", diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 5a982deb0d..8e63750dd2 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -97,12 +97,12 @@ function M.get_parser(buf, lang, opts) if not api.nvim_buf_is_loaded(buf) then return nil, string.format('Buffer %s must be loaded to create parser', buf) end - local parser = vim.npcall(M._create_parser, buf, lang, opts) - if not parser then - return nil, - string.format('Parser could not be created for buffer %s and language "%s"', buf, lang) + local status, res = pcall(M._create_parser, buf, lang, opts) + if not status then + local msg = 'Parser could not be created for buffer %s and language "%s": %s' + return nil, string.format(msg, buf, lang, res) end - parsers[buf] = parser + parsers[buf] = res end parsers[buf]:register_cbs(opts.buf_attach_cbs) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index bc1342d4f5..c0339e9e1d 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -212,9 +212,7 @@ void eval_clear(void) { evalvars_clear(); free_scriptnames(); // must come after evalvars_clear(). -# ifdef HAVE_WORKING_LIBINTL free_locales(); -# endif // autoloaded script names free_autoload_scriptnames(); diff --git a/src/nvim/os/lang.c b/src/nvim/os/lang.c index 81efaa7f1a..39a369babc 100644 --- a/src/nvim/os/lang.c +++ b/src/nvim/os/lang.c @@ -338,8 +338,15 @@ char *get_locales(expand_T *xp, int idx) void lang_init(void) { -#if defined(__APPLE__) && !defined(ZIG_BUILD) +#if defined(__APPLE__) if (!os_env_exists("LANG", true)) { +# if defined(ZIG_BUILD) + // fuck it just use the objectively correct values: + os_setenv("LANG", "C.UTF-8", true); + setlocale(LC_ALL, "C.UTF-8"); + // Make sure strtod() uses a decimal point, not a comma. + setlocale(LC_NUMERIC, "C"); +# else char buf[50] = { 0 }; // $LANG is not set, either because it was unset or Nvim was started @@ -358,6 +365,7 @@ void lang_init(void) } else { ELOG("$LANG is empty and the macOS primary language cannot be inferred."); } +# endif } #endif } diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c index aa7565fd75..c3774cf293 100644 --- a/src/nvim/spellfile.c +++ b/src/nvim/spellfile.c @@ -1789,7 +1789,7 @@ static idx_T read_tree_node(FILE *fd, uint8_t *byts, idx_T *idxs, int maxidx, id c = (getc(fd) << 16) + c; // } if (c & WF_AFX) { - c = (getc(fd) << 24) + c; // + c = (int)((unsigned)getc(fd) << 24) + c; // } } diff --git a/src/nvim/usercmd.c b/src/nvim/usercmd.c index 4c7305e2a9..0a9a6ce9c0 100644 --- a/src/nvim/usercmd.c +++ b/src/nvim/usercmd.c @@ -77,9 +77,7 @@ static const char *command_complete[] = { [EXPAND_HIGHLIGHT] = "highlight", [EXPAND_HISTORY] = "history", [EXPAND_KEYMAP] = "keymap", -#ifdef HAVE_WORKING_LIBINTL [EXPAND_LOCALES] = "locale", -#endif [EXPAND_LUA] = "lua", [EXPAND_MAPCLEAR] = "mapclear", [EXPAND_MAPPINGS] = "mapping", diff --git a/test/functional/treesitter/language_spec.lua b/test/functional/treesitter/language_spec.lua index f01530a24b..96305a611a 100644 --- a/test/functional/treesitter/language_spec.lua +++ b/test/functional/treesitter/language_spec.lua @@ -19,7 +19,10 @@ describe('treesitter language API', function() return vim.treesitter.get_parser(0, 'borklang') end) eq(NIL, parser) - eq('Parser could not be created for buffer 1 and language "borklang"', error) + matches( + '^Parser could not be created for buffer 1 and language "borklang": .+: No parser for language "borklang"$', + error + ) -- actual message depends on platform matches( @@ -110,7 +113,10 @@ describe('treesitter language API', function() return vim.treesitter.get_parser(0, 'borklang') end) eq(NIL, parser) - eq('Parser could not be created for buffer 1 and language "borklang"', error) + matches( + '^Parser could not be created for buffer 1 and language "borklang": .+: No parser for language "borklang"$', + error + ) end ) diff --git a/test/old/runner.lua b/test/old/runner.lua new file mode 100644 index 0000000000..e8558cc39a --- /dev/null +++ b/test/old/runner.lua @@ -0,0 +1,160 @@ +-- execute old style tests + +-- when compared to the testdir/Makefile based approach, this replaces +-- all wrapper process logic: +-- Makefile +-- runnvim.sh and its library test.sh +-- runnvim.vim (wrapper which runs a nested vim inside a terminal) + +-- it DOES NOT not replace the logic running inside the nvim-under-test +-- runtest.vim and its libraries setup.vim, shared.vim, check.vim + +local nvim_bin = vim.v.progpath -- must be absolute + +vim.api.nvim_set_current_dir('test/old/testdir/') + +local function readable(fn) + return vim.fn.filereadable(fn) ~= 0 +end + +local errlog = 'test.log' +local messages = 'messages' +local starttime = 'starttime' +local gen_opt_log = 'gen_opt_test.log' + +function oksystem(args) + local res = vim.system(args, {}):wait() + if res.code ~= 0 then + error(vim.inspect(args) .. ' failed:\n' .. res.stdout .. res.stderr) + end + return res +end + +function clean_output() + for _, f in ipairs { errlog, messages, starttime, gen_opt_log, 'opt_test.vim' } do + vim.uv.fs_unlink(f) + end + -- check status, some tests mess with perms so this could fail: + oksystem { 'bash', '-c', 'rm -rf X* viminfo test.out test.ok' } +end + +function if_nonempty(rawtext, name) + text = vim.trim(rawtext) + if #text > 0 then + if name then + print('== ' .. name) + end + print(text) + print(name and '== END ' .. name or '\n') + end +end + +function if_exists(fn, name) + local fil = io.open(fn) + if fil then + if_nonempty(fil:read '*a', name) + fil:close() + return true + end +end + +local verbose = vim.env.VERBOSE + +function run_nvim(...) + local cmd = vim.system( + { nvim_bin, '-u', 'NONE', '-i', 'NONE', '--headless', ... }, + { env = { VIMRUNTIME = '../../../runtime' } } + ) + return cmd:wait() +end + +function run_test(testfile) + if not vim.endswith(testfile, '.vim') then + error 'not an oldtest' + end + + clean_output() + + if testfile == 'test_options_all.vim' then + local res = run_nvim( + '-S', + 'gen_opt_test.vim', + '../../../src/nvim/options.lua', + '../../../runtime/doc/options.txt' + ) + + if if_exists(gen_opt_log, 'GEN opt_test.vim') or res.code > 0 then + return false + end + end + + local resfile = string.sub(testfile, 1, -4) .. 'res' + vim.uv.fs_unlink(resfile) + assert(not readable(resfile)) + + local opts = 'set shortmess-=F backupdir=. undodir=. viewdir=.' + local res = run_nvim('--cmd', opts, '-S', 'runtest.vim', testfile) + local passed = (readable(resfile) and res.code == 0) + + if_exists(messages) + + if verbose or not passed then + if_nonempty(res.stdout, 'STDOUT') + if_nonempty(res.stderr, 'STDERR') + -- if_exists(starttime, "starttime") + if_exists(errlog, 'test.log') + end + return passed +end + +local function all_tests() + local nested_tests = {} + local alot = io.open('test_alot.vim') + local sourcepat = '^source (test_.*%.vim)$' + for line in alot:lines() do + local m = string.match(line, sourcepat) + if m then + nested_tests[m] = true + end + end + alot:close() + + local files = {} + for name, type in vim.fs.dir('.') do + match = string.match(name, '^test_.*%.vim$') + if match then + if nested_tests[match] then + nested_tests[match] = nil + elseif match == 'test_largefile.vim' then + -- ignored: uses too much resources to run on CI + else + table.insert(files, match) + end + end + end + return files +end + +local files +if #arg > 0 then + files = {} + for _, a in ipairs(arg) do + table.insert(files, string.match(a, '[^/]*$')) + end +else + files = all_tests() +end + +local count = #files +local failures = {} +for i, fil in ipairs(files) do + print('run: ', fil) + if not run_test(fil) then + table.insert(failures, fil) + end +end + +if #failures > 0 then + print('failed:', vim.inspect(failures)) + vim.cmd 'cquit' +end diff --git a/test/run_tests.zig b/test/run_tests.zig index 39238b4473..1c864e8b79 100644 --- a/test/run_tests.zig +++ b/test/run_tests.zig @@ -50,6 +50,19 @@ pub fn test_steps(b: *std.Build, nvim_bin: *std.Build.Step.Compile, depend_on: * const functionaltest_step = b.step("functionaltest", "run functional tests"); functionaltest_step.dependOn(&functional_tests.step); + const old_tests = b.addRunArtifact(nvim_bin); + old_tests.addArg("-l"); + old_tests.addFileArg(b.path("./test/old/runner.lua")); + if (b.args) |args| { + old_tests.addArgs(args); // accept TEST_FILE as a positional argument + } + const env = old_tests.getEnvMap(); + try env.put("BUILD_DIR", b.install_path); + + const oldtest_step = b.step("oldtest", "run old tests"); + oldtest_step.dependOn(&old_tests.step); + oldtest_step.dependOn(depend_on); + if (unit_paths) |paths| { const unit_tests = try testStep(b, "unit", nvim_bin, config_dir, paths); unit_tests.step.dependOn(depend_on);