diff --git a/hooks/backend_exec_env.lua b/hooks/backend_exec_env.lua index 8e4d737..10a495e 100644 --- a/hooks/backend_exec_env.lua +++ b/hooks/backend_exec_env.lua @@ -14,6 +14,12 @@ function PLUGIN:BackendExecEnv(ctx) { key = "PATH", value = bin_path }, } + local runtime_deps = require("lib.runtime_deps") + local runtime_dep_env_vars = runtime_deps.get_env_vars(install_path, tool, version, RUNTIME.osType) + for _, env_var in ipairs(runtime_dep_env_vars) do + table.insert(env_vars, env_var) + end + if tool == "ruby" then -- Nix store is read-only, gems should be installed to the state dir table.insert(env_vars, { key = "GEM_HOME", value = file.join_path(install_path, "state/gems") }) diff --git a/lib/runtime_deps.lua b/lib/runtime_deps.lua new file mode 100644 index 0000000..a67cdfc --- /dev/null +++ b/lib/runtime_deps.lua @@ -0,0 +1,108 @@ +local M = {} + +-- The main Nix package has runtime deps (other Nix store objects), which provide the usual +-- C headers, shared libraries and pkgconfig files. This function queries those runtime deps +-- and constructs the necessary env vars to make those dependencies available at runtime. +-- In practice, this is important when running package manager binaries that try to build native code +-- (e.g. Ruby gems that compile C extensions). +function M.get_env_vars(install_path, tool, version, os_type) + local file = require("file") + local cmd = require("cmd") + + local result_path = file.join_path(install_path, "result") + local requisites = cmd.exec("nix-store --query --requisites " .. result_path) + + if requisites:match("error") or requisites:match("failed") then + error("Failed to query runtime deps for " .. tool .. "@" .. version .. ": " .. requisites) + end + + local store_paths = {} + for store_path in string.gmatch(requisites, "[^%s]+") do -- poor man's strings.Split(requisites, "\n") + table.insert(store_paths, store_path) + end + + local lib_paths = M.find_lib_paths(store_paths, file, cmd) + local pkgconfig_paths = M.find_pkgconfig_paths(store_paths, file, cmd) + + local env_vars = {} + local lib_env_vars = M.create_library_env_vars(lib_paths, os_type) + local pkgconfig_env_vars = M.create_pkgconfig_env_vars(pkgconfig_paths) + + for _, var in ipairs(lib_env_vars) do + table.insert(env_vars, var) + end + for _, var in ipairs(pkgconfig_env_vars) do + table.insert(env_vars, var) + end + + return env_vars +end + +-- TODO: file.exists() doesn't exist 🤷 +-- https://github.com/jdx/mise/pull/6754 +-- Can be replaced once our pinned mise version is bumped again +function M.dir_exists(path, cmd_module) + local check_cmd = "if [ -d " .. path .. " ]; then echo 'exists'; fi" + local check_output = cmd_module.exec(check_cmd) + return check_output:match("exists") ~= nil +end + +-- Finds all lib directories in the given store paths +function M.find_lib_paths(store_paths, file_module, cmd_module) + local lib_paths = {} + for _, store_path in ipairs(store_paths) do + local lib_path = file_module.join_path(store_path, "lib") + if M.dir_exists(lib_path, cmd_module) then + table.insert(lib_paths, lib_path) + end + end + return lib_paths +end + +-- Finds all lib/pkgconfig directories in the given store paths +function M.find_pkgconfig_paths(store_paths, file_module, cmd_module) + local pkgconfig_paths = {} + for _, store_path in ipairs(store_paths) do + local pkgconfig_path = file_module.join_path(store_path, "lib/pkgconfig") + if M.dir_exists(pkgconfig_path, cmd_module) then + table.insert(pkgconfig_paths, pkgconfig_path) + end + end + return pkgconfig_paths +end + +-- Exposes shared library paths for the (platform-specific) dynamic linker +function M.create_library_env_vars(lib_paths, os_type) + local env_vars = {} + local key = nil + + if os_type == "linux" then + key = "LD_LIBRARY_PATH" + elseif os_type == "darwin" then + key = "DYLD_LIBRARY_PATH" + else + error("Unsupported architecture: " .. os_type) + end + + for _, lib_path in ipairs(lib_paths) do + local is_linux_glibc = os_type == "linux" and lib_path:match("glibc") + local is_darwin_llvm = os_type == "darwin" and (lib_path:match("llvm") or lib_path:match("clang")) + local is_linux_libyaml = os_type == "linux" and lib_path:match("libyaml") + -- if not is_linux_glibc and not is_darwin_llvm then + if is_linux_libyaml then + table.insert(env_vars, { key = key, value = lib_path }) + end + end + + return env_vars +end + +function M.create_pkgconfig_env_vars(pkgconfig_paths) + local env_vars = {} + for _, pkgconfig_path in ipairs(pkgconfig_paths) do + table.insert(env_vars, { key = "PKG_CONFIG_PATH", value = pkgconfig_path }) + end + return env_vars +end + +return M diff --git a/mise-tasks/test-integration b/mise-tasks/test-integration index 5848fa2..fbb77ee 100755 --- a/mise-tasks/test-integration +++ b/mise-tasks/test-integration @@ -84,12 +84,33 @@ run_test "PATH is set correctly" \ run_test "GEM_HOME is set for ruby" \ "mise exec nixpkgs:ruby@latest -- bash -c 'test -n \"\$GEM_HOME\"'" || exit 1 +echo "" +echo "=== Gem Install Tests ===" + run_test "ruby: gem install fastlane" \ "mise exec nixpkgs:ruby@latest -- gem install fastlane" || exit 1 run_test "ruby: gem install cocoapods" \ "mise exec nixpkgs:ruby@latest -- gem install cocoapods" || exit 1 +# Needs libyaml from system libraries +run_test "ruby: gem install psych" \ + "mise exec nixpkgs:ruby@latest -- gem install psych" || exit 1 + +run_test "ruby: gem install psych: build from source" \ + "mise exec nixpkgs:ruby@latest -- gem install psych --platform ruby" || exit 1 + +cleanup_installs + +echo "" +echo "=== Smoke Tests ===" + +run_test "fastlane actions" \ + "mise exec nixpkgs:ruby@latest -- bash -c 'gem install fastlane && fastlane actions'" || exit 1 + +run_test "pod plugins" \ + "mise exec nixpkgs:ruby@latest -- bash -c 'gem install cocoapods && pod plugins --allow-root'" || exit 1 + cleanup_installs echo "" diff --git a/mise.toml b/mise.toml index d725cdc..bb596ea 100644 --- a/mise.toml +++ b/mise.toml @@ -17,7 +17,7 @@ run = "command -v busted >/dev/null 2>&1 || mise exec lua -- luarocks install bu [tasks.format] description = "Format Lua scripts" -run = "stylua metadata.lua hooks/" +run = "stylua metadata.lua hooks/ lib/ spec/" [tasks.lint] description = "Lint Lua scripts and GitHub Actions using hk" diff --git a/spec/helpers/fixtures.lua b/spec/helpers/fixtures.lua index 45867c4..d71f409 100644 --- a/spec/helpers/fixtures.lua +++ b/spec/helpers/fixtures.lua @@ -1,41 +1,41 @@ local M = {} M.sample_index = { - pkgs = { - ruby = { - ["3.3.9"] = { - nixpkgs_commit = "abc123def456", - commit_timestamp = "2025-01-15T12:00:00+00:00", - store_paths = { - ["x86_64-linux"] = "/nix/store/hash1-ruby-3.3.9", - ["aarch64-darwin"] = "/nix/store/hash2-ruby-3.3.9", - }, - }, - ["3.3.8"] = { - nixpkgs_commit = "def456abc789", - commit_timestamp = "2025-01-14T12:00:00+00:00", - store_paths = { - ["x86_64-linux"] = "/nix/store/hash3-ruby-3.3.8", - -- Note: aarch64-darwin entry is intentionally missing for testing - }, - }, - }, - python = { - ["3.12.1"] = { - nixpkgs_commit = "ghi789jkl012", - commit_timestamp = "2025-01-16T12:00:00+00:00", - store_paths = { - ["x86_64-linux"] = "/nix/store/hash4-python-3.12.1", - ["aarch64-darwin"] = "/nix/store/hash5-python-3.12.1", - }, - }, - }, - }, + pkgs = { + ruby = { + ["3.3.9"] = { + nixpkgs_commit = "abc123def456", + commit_timestamp = "2025-01-15T12:00:00+00:00", + store_paths = { + ["x86_64-linux"] = "/nix/store/hash1-ruby-3.3.9", + ["aarch64-darwin"] = "/nix/store/hash2-ruby-3.3.9", + }, + }, + ["3.3.8"] = { + nixpkgs_commit = "def456abc789", + commit_timestamp = "2025-01-14T12:00:00+00:00", + store_paths = { + ["x86_64-linux"] = "/nix/store/hash3-ruby-3.3.8", + -- Note: aarch64-darwin entry is intentionally missing for testing + }, + }, + }, + python = { + ["3.12.1"] = { + nixpkgs_commit = "ghi789jkl012", + commit_timestamp = "2025-01-16T12:00:00+00:00", + store_paths = { + ["x86_64-linux"] = "/nix/store/hash4-python-3.12.1", + ["aarch64-darwin"] = "/nix/store/hash5-python-3.12.1", + }, + }, + }, + }, } M.sample_json = function() - local json = require("json") - return json.encode(M.sample_index) + local json = require("json") + return json.encode(M.sample_index) end return M diff --git a/spec/helpers/mock_modules.lua b/spec/helpers/mock_modules.lua index 95e7a6f..19b6a09 100644 --- a/spec/helpers/mock_modules.lua +++ b/spec/helpers/mock_modules.lua @@ -1,36 +1,37 @@ local M = {} function M.mock_file_module() - return { - read = function(path) - return '{"pkgs":{}}' - end, - join_path = function(...) - return table.concat({ ... }, "/") - end, - } + return { + read = function(path) + return '{"pkgs":{}}' + end, + join_path = function(...) + return table.concat({ ... }, "/") + end, + } end function M.mock_cmd_module() - return { - exec = function(cmd) - return "success" - end, - } + return { + exec = function(cmd) + return "success" + end, + } end function M.inject_modules(mocks) - for name, mock in pairs(mocks) do - package.loaded[name] = mock - end + for name, mock in pairs(mocks) do + package.loaded[name] = mock + end end function M.clear_modules() - package.loaded["file"] = nil - package.loaded["json"] = nil - package.loaded["cmd"] = nil - package.loaded["lib.nix"] = nil - package.loaded["lib.nixpkgs_mapping"] = nil + package.loaded["file"] = nil + package.loaded["json"] = nil + package.loaded["cmd"] = nil + package.loaded["lib.nix"] = nil + package.loaded["lib.nixpkgs_mapping"] = nil + package.loaded["lib.runtime_deps"] = nil end return M diff --git a/spec/helpers/mock_runtime.lua b/spec/helpers/mock_runtime.lua index 14611df..843e29a 100644 --- a/spec/helpers/mock_runtime.lua +++ b/spec/helpers/mock_runtime.lua @@ -1,20 +1,20 @@ local M = {} function M.create_runtime(opts) - opts = opts or {} - return { - osType = opts.osType or "linux", - archType = opts.archType or "amd64", - pluginDirPath = opts.pluginDirPath or "/tmp/test-plugin", - } + opts = opts or {} + return { + osType = opts.osType or "linux", + archType = opts.archType or "amd64", + pluginDirPath = opts.pluginDirPath or "/tmp/test-plugin", + } end function M.inject_runtime(runtime) - _G.RUNTIME = runtime + _G.RUNTIME = runtime end function M.restore_runtime() - _G.RUNTIME = nil + _G.RUNTIME = nil end return M diff --git a/spec/hooks/backend_exec_env_spec.lua b/spec/hooks/backend_exec_env_spec.lua index 011eeda..9f862ed 100644 --- a/spec/hooks/backend_exec_env_spec.lua +++ b/spec/hooks/backend_exec_env_spec.lua @@ -1,115 +1,153 @@ describe("PLUGIN:BackendExecEnv", function() - local mock_modules = require("spec.helpers.mock_modules") - - before_each(function() - mock_modules.clear_modules() - _G.PLUGIN = {} - package.loaded["hooks.backend_exec_env"] = nil - end) - - local function load_hook() - dofile("hooks/backend_exec_env.lua") - return _G.PLUGIN - end - - it("sets PATH for generic tool", function() - local file_mock = mock_modules.mock_file_module() - mock_modules.inject_modules({ file = file_mock }) - - local plugin = load_hook() - local result = plugin:BackendExecEnv({ - install_path = "/home/user/.local/share/mise/installs/nixpkgs-node/20.1.0", - tool = "node", - version = "20.1.0", - }) - - assert.is_not_nil(result.env_vars) - assert.is_true(#result.env_vars >= 1) - - local found_path = false - for _, env_var in ipairs(result.env_vars) do - if env_var.key == "PATH" and env_var.value:match("/result/bin$") then - found_path = true - break - end - end - assert.is_true(found_path) - end) - - it("sets MANPATH for all tools", function() - local file_mock = mock_modules.mock_file_module() - mock_modules.inject_modules({ file = file_mock }) - - local plugin = load_hook() - local result = plugin:BackendExecEnv({ - install_path = "/home/user/.local/share/mise/installs/nixpkgs-node/20.1.0", - tool = "node", - version = "20.1.0", - }) - - local found_manpath = false - for _, env_var in ipairs(result.env_vars) do - if env_var.key == "MANPATH" and env_var.value:match("/share/man$") then - found_manpath = true - break - end - end - assert.is_true(found_manpath) - end) - - it("sets ruby-specific environment variables", function() - local file_mock = mock_modules.mock_file_module() - mock_modules.inject_modules({ file = file_mock }) - - local plugin = load_hook() - local result = plugin:BackendExecEnv({ - install_path = "/home/user/.local/share/mise/installs/nixpkgs-ruby/3.3.9", - tool = "ruby", - version = "3.3.9", - }) - - assert.is_not_nil(result.env_vars) - - local found_gem_home = false - local found_gem_path = false - local found_gems_bin_path = false - - for _, env_var in ipairs(result.env_vars) do - if env_var.key == "GEM_HOME" and env_var.value:match("/state/gems$") then - found_gem_home = true - end - if env_var.key == "GEM_PATH" and env_var.value:match("/state/gems/gems$") then - found_gem_path = true - end - if env_var.key == "PATH" and env_var.value:match("/state/gems/bin$") then - found_gems_bin_path = true - end - end - - assert.is_true(found_gem_home, "GEM_HOME not found") - assert.is_true(found_gem_path, "GEM_PATH not found") - assert.is_true(found_gems_bin_path, "gems/bin PATH not found") - end) - - it("returns correct PATH structure for ruby", function() - local file_mock = mock_modules.mock_file_module() - mock_modules.inject_modules({ file = file_mock }) - - local plugin = load_hook() - local result = plugin:BackendExecEnv({ - install_path = "/home/user/.local/share/mise/installs/nixpkgs-ruby/3.3.9", - tool = "ruby", - version = "3.3.9", - }) - - -- Note: our Lua hook simply add two PATH entries for ruby, Mise handles deduplication and merging. - local path_count = 0 - for _, env_var in ipairs(result.env_vars) do - if env_var.key == "PATH" then - path_count = path_count + 1 - end - end - - assert.equals(2, path_count) - end) + local mock_modules = require("spec.helpers.mock_modules") + local mock_runtime = require("spec.helpers.mock_runtime") + + before_each(function() + mock_modules.clear_modules() + mock_runtime.restore_runtime() + _G.PLUGIN = {} + package.loaded["hooks.backend_exec_env"] = nil + end) + + local function load_hook() + dofile("hooks/backend_exec_env.lua") + return _G.PLUGIN + end + + it("sets PATH for generic tool", function() + local file_mock = mock_modules.mock_file_module() + local cmd_mock = { + exec = function(cmd) + if cmd:match("nix%-store") then + return "" + end + return "" + end, + } + mock_modules.inject_modules({ file = file_mock, cmd = cmd_mock }) + mock_runtime.inject_runtime(mock_runtime.create_runtime({ osType = "linux" })) + + local plugin = load_hook() + local result = plugin:BackendExecEnv({ + install_path = "/home/user/.local/share/mise/installs/nixpkgs-node/20.1.0", + tool = "node", + version = "20.1.0", + }) + + assert.is_not_nil(result.env_vars) + assert.is_true(#result.env_vars >= 1) + + local found_path = false + for _, env_var in ipairs(result.env_vars) do + if env_var.key == "PATH" and env_var.value:match("/result/bin$") then + found_path = true + break + end + end + assert.is_true(found_path) + end) + + it("sets MANPATH for all tools", function() + local file_mock = mock_modules.mock_file_module() + local cmd_mock = { + exec = function(cmd) + if cmd:match("nix%-store") then + return "" + end + return "" + end, + } + mock_modules.inject_modules({ file = file_mock, cmd = cmd_mock }) + mock_runtime.inject_runtime(mock_runtime.create_runtime({ osType = "linux" })) + + local plugin = load_hook() + local result = plugin:BackendExecEnv({ + install_path = "/home/user/.local/share/mise/installs/nixpkgs-node/20.1.0", + tool = "node", + version = "20.1.0", + }) + + local found_manpath = false + for _, env_var in ipairs(result.env_vars) do + if env_var.key == "MANPATH" and env_var.value:match("/share/man$") then + found_manpath = true + break + end + end + assert.is_true(found_manpath) + end) + + it("sets ruby-specific environment variables", function() + local file_mock = mock_modules.mock_file_module() + local cmd_mock = { + exec = function(cmd) + if cmd:match("nix%-store") then + return "" + end + return "" + end, + } + mock_modules.inject_modules({ file = file_mock, cmd = cmd_mock }) + mock_runtime.inject_runtime(mock_runtime.create_runtime({ osType = "linux" })) + + local plugin = load_hook() + local result = plugin:BackendExecEnv({ + install_path = "/home/user/.local/share/mise/installs/nixpkgs-ruby/3.3.9", + tool = "ruby", + version = "3.3.9", + }) + + assert.is_not_nil(result.env_vars) + + local found_gem_home = false + local found_gem_path = false + local found_gems_bin_path = false + + for _, env_var in ipairs(result.env_vars) do + if env_var.key == "GEM_HOME" and env_var.value:match("/state/gems$") then + found_gem_home = true + end + if env_var.key == "GEM_PATH" and env_var.value:match("/state/gems/gems$") then + found_gem_path = true + end + if env_var.key == "PATH" and env_var.value:match("/state/gems/bin$") then + found_gems_bin_path = true + end + end + + assert.is_true(found_gem_home, "GEM_HOME not found") + assert.is_true(found_gem_path, "GEM_PATH not found") + assert.is_true(found_gems_bin_path, "gems/bin PATH not found") + end) + + it("returns correct PATH structure for ruby", function() + local file_mock = mock_modules.mock_file_module() + local cmd_mock = { + exec = function(cmd) + if cmd:match("nix%-store") then + return "" + end + return "" + end, + } + mock_modules.inject_modules({ file = file_mock, cmd = cmd_mock }) + mock_runtime.inject_runtime(mock_runtime.create_runtime({ osType = "linux" })) + + local plugin = load_hook() + local result = plugin:BackendExecEnv({ + install_path = "/home/user/.local/share/mise/installs/nixpkgs-ruby/3.3.9", + tool = "ruby", + version = "3.3.9", + }) + + -- Note: our Lua hook simply add two PATH entries for ruby, Mise handles deduplication and merging. + local path_count = 0 + for _, env_var in ipairs(result.env_vars) do + if env_var.key == "PATH" then + path_count = path_count + 1 + end + end + + assert.equals(2, path_count) + end) end) diff --git a/spec/hooks/backend_install_spec.lua b/spec/hooks/backend_install_spec.lua index 164f9b7..8f1cd53 100644 --- a/spec/hooks/backend_install_spec.lua +++ b/spec/hooks/backend_install_spec.lua @@ -1,227 +1,227 @@ describe("PLUGIN:BackendInstall validation", function() - local mock_modules = require("spec.helpers.mock_modules") - local fixtures = require("spec.helpers.fixtures") - - before_each(function() - mock_modules.clear_modules() - _G.PLUGIN = {} - package.loaded["hooks.backend_install"] = nil - end) - - local function load_hook() - dofile("hooks/backend_install.lua") - return _G.PLUGIN - end - - it("errors when tool name is empty", function() - local plugin = load_hook() - - assert.has_error(function() - plugin:BackendInstall({ - tool = "", - version = "3.3.9", - install_path = "/home/user/.local/share/mise/installs/nixpkgs-ruby/3.3.9", - }) - end, "Tool name cannot be empty") - end) - - it("errors when tool name is nil", function() - local plugin = load_hook() - - assert.has_error(function() - plugin:BackendInstall({ - tool = nil, - version = "3.3.9", - install_path = "/home/user/.local/share/mise/installs/nixpkgs-ruby/3.3.9", - }) - end, "Tool name cannot be empty") - end) - - it("errors when version is empty", function() - local plugin = load_hook() - - assert.has_error(function() - plugin:BackendInstall({ - tool = "ruby", - version = "", - install_path = "/home/user/.local/share/mise/installs/nixpkgs-ruby/3.3.9", - }) - end, "Version cannot be empty") - end) - - it("errors when version is nil", function() - local plugin = load_hook() - - assert.has_error(function() - plugin:BackendInstall({ - tool = "ruby", - version = nil, - install_path = "/home/user/.local/share/mise/installs/nixpkgs-ruby/3.3.9", - }) - end, "Version cannot be empty") - end) - - it("errors when install_path is empty", function() - local plugin = load_hook() - - assert.has_error(function() - plugin:BackendInstall({ - tool = "ruby", - version = "3.3.9", - install_path = "", - }) - end, "Install path cannot be empty") - end) - - it("errors when install_path is nil", function() - local plugin = load_hook() - - assert.has_error(function() - plugin:BackendInstall({ - tool = "ruby", - version = "3.3.9", - install_path = nil, - }) - end, "Install path cannot be empty") - end) - - it("errors when mapping has no pkgs", function() - local nix_mock = { - current_system = function() - return "x86_64-linux" - end, - } - - local nixpkgs_mapping_mock = { - parse_mapping_file = function() - return {} - end, - } - - local cmd_mock = mock_modules.mock_cmd_module() - - mock_modules.inject_modules({ - ["lib.nix"] = nix_mock, - ["lib.nixpkgs_mapping"] = nixpkgs_mapping_mock, - cmd = cmd_mock, - }) - - local plugin = load_hook() - - local success, err = pcall(function() - plugin:BackendInstall({ - tool = "ruby", - version = "3.3.9", - install_path = "/home/user/.local/share/mise/installs/nixpkgs-ruby/3.3.9", - }) - end) - - assert.is_false(success) - assert.is_not_nil(err:match("No nixpkgs mapping found for ruby@3.3.9 on x86_64%-linux")) - end) - - it("errors when tool not in mapping", function() - local nix_mock = { - current_system = function() - return "x86_64-linux" - end, - } - - local nixpkgs_mapping_mock = { - parse_mapping_file = function() - return fixtures.sample_index - end, - } - - local cmd_mock = mock_modules.mock_cmd_module() - - mock_modules.inject_modules({ - ["lib.nix"] = nix_mock, - ["lib.nixpkgs_mapping"] = nixpkgs_mapping_mock, - cmd = cmd_mock, - }) - - local plugin = load_hook() - - local success, err = pcall(function() - plugin:BackendInstall({ - tool = "nonexistent", - version = "1.0.0", - install_path = "/home/user/.local/share/mise/installs/nixpkgs-nonexistent/1.0.0", - }) - end) - - assert.is_false(success) - assert.is_not_nil(err:match("No nixpkgs mapping found for nonexistent@1.0.0 on x86_64%-linux")) - end) - - it("errors when version not in mapping", function() - local nix_mock = { - current_system = function() - return "x86_64-linux" - end, - } - - local nixpkgs_mapping_mock = { - parse_mapping_file = function() - return fixtures.sample_index - end, - } - - local cmd_mock = mock_modules.mock_cmd_module() - - mock_modules.inject_modules({ - ["lib.nix"] = nix_mock, - ["lib.nixpkgs_mapping"] = nixpkgs_mapping_mock, - cmd = cmd_mock, - }) - - local plugin = load_hook() - - local success, err = pcall(function() - plugin:BackendInstall({ - tool = "ruby", - version = "999.999.999", - install_path = "/home/user/.local/share/mise/installs/nixpkgs-ruby/999.999.999", - }) - end) - - assert.is_false(success) - assert.is_not_nil(err:match("No nixpkgs mapping found for ruby@999.999.999 on x86_64%-linux")) - end) - - it("errors when platform not in store_paths", function() - local nix_mock = { - current_system = function() - return "aarch64-darwin" - end, - } - - local nixpkgs_mapping_mock = { - parse_mapping_file = function() - return fixtures.sample_index - end, - } - - local cmd_mock = mock_modules.mock_cmd_module() - - mock_modules.inject_modules({ - ["lib.nix"] = nix_mock, - ["lib.nixpkgs_mapping"] = nixpkgs_mapping_mock, - cmd = cmd_mock, - }) - - local plugin = load_hook() - - local success, err = pcall(function() - plugin:BackendInstall({ - tool = "ruby", - version = "3.3.8", - install_path = "/home/user/.local/share/mise/installs/nixpkgs-ruby/3.3.8", - }) - end) - - assert.is_false(success) - assert.is_not_nil(err:match("No nixpkgs mapping found for ruby@3.3.8 on aarch64%-darwin")) - end) + local mock_modules = require("spec.helpers.mock_modules") + local fixtures = require("spec.helpers.fixtures") + + before_each(function() + mock_modules.clear_modules() + _G.PLUGIN = {} + package.loaded["hooks.backend_install"] = nil + end) + + local function load_hook() + dofile("hooks/backend_install.lua") + return _G.PLUGIN + end + + it("errors when tool name is empty", function() + local plugin = load_hook() + + assert.has_error(function() + plugin:BackendInstall({ + tool = "", + version = "3.3.9", + install_path = "/home/user/.local/share/mise/installs/nixpkgs-ruby/3.3.9", + }) + end, "Tool name cannot be empty") + end) + + it("errors when tool name is nil", function() + local plugin = load_hook() + + assert.has_error(function() + plugin:BackendInstall({ + tool = nil, + version = "3.3.9", + install_path = "/home/user/.local/share/mise/installs/nixpkgs-ruby/3.3.9", + }) + end, "Tool name cannot be empty") + end) + + it("errors when version is empty", function() + local plugin = load_hook() + + assert.has_error(function() + plugin:BackendInstall({ + tool = "ruby", + version = "", + install_path = "/home/user/.local/share/mise/installs/nixpkgs-ruby/3.3.9", + }) + end, "Version cannot be empty") + end) + + it("errors when version is nil", function() + local plugin = load_hook() + + assert.has_error(function() + plugin:BackendInstall({ + tool = "ruby", + version = nil, + install_path = "/home/user/.local/share/mise/installs/nixpkgs-ruby/3.3.9", + }) + end, "Version cannot be empty") + end) + + it("errors when install_path is empty", function() + local plugin = load_hook() + + assert.has_error(function() + plugin:BackendInstall({ + tool = "ruby", + version = "3.3.9", + install_path = "", + }) + end, "Install path cannot be empty") + end) + + it("errors when install_path is nil", function() + local plugin = load_hook() + + assert.has_error(function() + plugin:BackendInstall({ + tool = "ruby", + version = "3.3.9", + install_path = nil, + }) + end, "Install path cannot be empty") + end) + + it("errors when mapping has no pkgs", function() + local nix_mock = { + current_system = function() + return "x86_64-linux" + end, + } + + local nixpkgs_mapping_mock = { + parse_mapping_file = function() + return {} + end, + } + + local cmd_mock = mock_modules.mock_cmd_module() + + mock_modules.inject_modules({ + ["lib.nix"] = nix_mock, + ["lib.nixpkgs_mapping"] = nixpkgs_mapping_mock, + cmd = cmd_mock, + }) + + local plugin = load_hook() + + local success, err = pcall(function() + plugin:BackendInstall({ + tool = "ruby", + version = "3.3.9", + install_path = "/home/user/.local/share/mise/installs/nixpkgs-ruby/3.3.9", + }) + end) + + assert.is_false(success) + assert.is_not_nil(err:match("No nixpkgs mapping found for ruby@3.3.9 on x86_64%-linux")) + end) + + it("errors when tool not in mapping", function() + local nix_mock = { + current_system = function() + return "x86_64-linux" + end, + } + + local nixpkgs_mapping_mock = { + parse_mapping_file = function() + return fixtures.sample_index + end, + } + + local cmd_mock = mock_modules.mock_cmd_module() + + mock_modules.inject_modules({ + ["lib.nix"] = nix_mock, + ["lib.nixpkgs_mapping"] = nixpkgs_mapping_mock, + cmd = cmd_mock, + }) + + local plugin = load_hook() + + local success, err = pcall(function() + plugin:BackendInstall({ + tool = "nonexistent", + version = "1.0.0", + install_path = "/home/user/.local/share/mise/installs/nixpkgs-nonexistent/1.0.0", + }) + end) + + assert.is_false(success) + assert.is_not_nil(err:match("No nixpkgs mapping found for nonexistent@1.0.0 on x86_64%-linux")) + end) + + it("errors when version not in mapping", function() + local nix_mock = { + current_system = function() + return "x86_64-linux" + end, + } + + local nixpkgs_mapping_mock = { + parse_mapping_file = function() + return fixtures.sample_index + end, + } + + local cmd_mock = mock_modules.mock_cmd_module() + + mock_modules.inject_modules({ + ["lib.nix"] = nix_mock, + ["lib.nixpkgs_mapping"] = nixpkgs_mapping_mock, + cmd = cmd_mock, + }) + + local plugin = load_hook() + + local success, err = pcall(function() + plugin:BackendInstall({ + tool = "ruby", + version = "999.999.999", + install_path = "/home/user/.local/share/mise/installs/nixpkgs-ruby/999.999.999", + }) + end) + + assert.is_false(success) + assert.is_not_nil(err:match("No nixpkgs mapping found for ruby@999.999.999 on x86_64%-linux")) + end) + + it("errors when platform not in store_paths", function() + local nix_mock = { + current_system = function() + return "aarch64-darwin" + end, + } + + local nixpkgs_mapping_mock = { + parse_mapping_file = function() + return fixtures.sample_index + end, + } + + local cmd_mock = mock_modules.mock_cmd_module() + + mock_modules.inject_modules({ + ["lib.nix"] = nix_mock, + ["lib.nixpkgs_mapping"] = nixpkgs_mapping_mock, + cmd = cmd_mock, + }) + + local plugin = load_hook() + + local success, err = pcall(function() + plugin:BackendInstall({ + tool = "ruby", + version = "3.3.8", + install_path = "/home/user/.local/share/mise/installs/nixpkgs-ruby/3.3.8", + }) + end) + + assert.is_false(success) + assert.is_not_nil(err:match("No nixpkgs mapping found for ruby@3.3.8 on aarch64%-darwin")) + end) end) diff --git a/spec/hooks/backend_list_versions_spec.lua b/spec/hooks/backend_list_versions_spec.lua index 9cf13b5..02398bc 100644 --- a/spec/hooks/backend_list_versions_spec.lua +++ b/spec/hooks/backend_list_versions_spec.lua @@ -1,160 +1,160 @@ describe("PLUGIN:BackendListVersions", function() - local mock_runtime = require("spec.helpers.mock_runtime") - local mock_modules = require("spec.helpers.mock_modules") - local fixtures = require("spec.helpers.fixtures") - - before_each(function() - mock_modules.clear_modules() - mock_runtime.restore_runtime() - _G.PLUGIN = {} - package.loaded["hooks.backend_list_versions"] = nil - end) - - local function load_hook() - dofile("hooks/backend_list_versions.lua") - return _G.PLUGIN - end - - it("lists versions for ruby on x86_64-linux", function() - mock_runtime.inject_runtime(mock_runtime.create_runtime({ - osType = "linux", - archType = "amd64", - })) - - local nix_mock = { - current_system = function() - return "x86_64-linux" - end, - } - - local nixpkgs_mapping_mock = { - parse_mapping_file = function() - return fixtures.sample_index - end, - } - - mock_modules.inject_modules({ - ["lib.nix"] = nix_mock, - ["lib.nixpkgs_mapping"] = nixpkgs_mapping_mock, - }) - - local plugin = load_hook() - local result = plugin:BackendListVersions({ tool = "ruby" }) - - assert.is_not_nil(result.versions) - assert.is_true(#result.versions >= 1) - assert.is_true(result.versions[1] == "3.3.9" or result.versions[1] == "3.3.8") - end) - - it("filters versions by platform", function() - mock_runtime.inject_runtime(mock_runtime.create_runtime({ - osType = "darwin", - archType = "arm64", - })) - - local nix_mock = { - current_system = function() - return "aarch64-darwin" - end, - } - - local nixpkgs_mapping_mock = { - parse_mapping_file = function() - return fixtures.sample_index - end, - } - - mock_modules.inject_modules({ - ["lib.nix"] = nix_mock, - ["lib.nixpkgs_mapping"] = nixpkgs_mapping_mock, - }) - - local plugin = load_hook() - local result = plugin:BackendListVersions({ tool = "ruby" }) - - assert.equals(1, #result.versions) - assert.equals("3.3.9", result.versions[1]) - end) - - it("errors when tool name is empty", function() - local plugin = load_hook() - - assert.has_error(function() - plugin:BackendListVersions({ tool = "" }) - end, "Tool name cannot be empty") - end) - - it("errors when tool is nil", function() - local plugin = load_hook() - - assert.has_error(function() - plugin:BackendListVersions({ tool = nil }) - end, "Tool name cannot be empty") - end) - - it("errors when tool not found in mapping", function() - mock_runtime.inject_runtime(mock_runtime.create_runtime()) - - local nix_mock = { - current_system = function() - return "x86_64-linux" - end, - } - - local nixpkgs_mapping_mock = { - parse_mapping_file = function() - return { pkgs = {} } - end, - } - - mock_modules.inject_modules({ - ["lib.nix"] = nix_mock, - ["lib.nixpkgs_mapping"] = nixpkgs_mapping_mock, - }) - - local plugin = load_hook() - - assert.has_error(function() - plugin:BackendListVersions({ tool = "nonexistent" }) - end, "Tool nonexistent not found in mapping") - end) - - it("errors when no versions available for platform", function() - mock_runtime.inject_runtime(mock_runtime.create_runtime()) - - local nix_mock = { - current_system = function() - return "x86_64-linux" - end, - } - - local mapping_with_no_versions = { - pkgs = { - ruby = { - ["3.3.9"] = { - store_paths = { - ["aarch64-darwin"] = "/nix/store/hash-ruby", - }, - }, - }, - }, - } - - local nixpkgs_mapping_mock = { - parse_mapping_file = function() - return mapping_with_no_versions - end, - } - - mock_modules.inject_modules({ - ["lib.nix"] = nix_mock, - ["lib.nixpkgs_mapping"] = nixpkgs_mapping_mock, - }) - - local plugin = load_hook() - - assert.has_error(function() - plugin:BackendListVersions({ tool = "ruby" }) - end, "No versions found for ruby") - end) + local mock_runtime = require("spec.helpers.mock_runtime") + local mock_modules = require("spec.helpers.mock_modules") + local fixtures = require("spec.helpers.fixtures") + + before_each(function() + mock_modules.clear_modules() + mock_runtime.restore_runtime() + _G.PLUGIN = {} + package.loaded["hooks.backend_list_versions"] = nil + end) + + local function load_hook() + dofile("hooks/backend_list_versions.lua") + return _G.PLUGIN + end + + it("lists versions for ruby on x86_64-linux", function() + mock_runtime.inject_runtime(mock_runtime.create_runtime({ + osType = "linux", + archType = "amd64", + })) + + local nix_mock = { + current_system = function() + return "x86_64-linux" + end, + } + + local nixpkgs_mapping_mock = { + parse_mapping_file = function() + return fixtures.sample_index + end, + } + + mock_modules.inject_modules({ + ["lib.nix"] = nix_mock, + ["lib.nixpkgs_mapping"] = nixpkgs_mapping_mock, + }) + + local plugin = load_hook() + local result = plugin:BackendListVersions({ tool = "ruby" }) + + assert.is_not_nil(result.versions) + assert.is_true(#result.versions >= 1) + assert.is_true(result.versions[1] == "3.3.9" or result.versions[1] == "3.3.8") + end) + + it("filters versions by platform", function() + mock_runtime.inject_runtime(mock_runtime.create_runtime({ + osType = "darwin", + archType = "arm64", + })) + + local nix_mock = { + current_system = function() + return "aarch64-darwin" + end, + } + + local nixpkgs_mapping_mock = { + parse_mapping_file = function() + return fixtures.sample_index + end, + } + + mock_modules.inject_modules({ + ["lib.nix"] = nix_mock, + ["lib.nixpkgs_mapping"] = nixpkgs_mapping_mock, + }) + + local plugin = load_hook() + local result = plugin:BackendListVersions({ tool = "ruby" }) + + assert.equals(1, #result.versions) + assert.equals("3.3.9", result.versions[1]) + end) + + it("errors when tool name is empty", function() + local plugin = load_hook() + + assert.has_error(function() + plugin:BackendListVersions({ tool = "" }) + end, "Tool name cannot be empty") + end) + + it("errors when tool is nil", function() + local plugin = load_hook() + + assert.has_error(function() + plugin:BackendListVersions({ tool = nil }) + end, "Tool name cannot be empty") + end) + + it("errors when tool not found in mapping", function() + mock_runtime.inject_runtime(mock_runtime.create_runtime()) + + local nix_mock = { + current_system = function() + return "x86_64-linux" + end, + } + + local nixpkgs_mapping_mock = { + parse_mapping_file = function() + return { pkgs = {} } + end, + } + + mock_modules.inject_modules({ + ["lib.nix"] = nix_mock, + ["lib.nixpkgs_mapping"] = nixpkgs_mapping_mock, + }) + + local plugin = load_hook() + + assert.has_error(function() + plugin:BackendListVersions({ tool = "nonexistent" }) + end, "Tool nonexistent not found in mapping") + end) + + it("errors when no versions available for platform", function() + mock_runtime.inject_runtime(mock_runtime.create_runtime()) + + local nix_mock = { + current_system = function() + return "x86_64-linux" + end, + } + + local mapping_with_no_versions = { + pkgs = { + ruby = { + ["3.3.9"] = { + store_paths = { + ["aarch64-darwin"] = "/nix/store/hash-ruby", + }, + }, + }, + }, + } + + local nixpkgs_mapping_mock = { + parse_mapping_file = function() + return mapping_with_no_versions + end, + } + + mock_modules.inject_modules({ + ["lib.nix"] = nix_mock, + ["lib.nixpkgs_mapping"] = nixpkgs_mapping_mock, + }) + + local plugin = load_hook() + + assert.has_error(function() + plugin:BackendListVersions({ tool = "ruby" }) + end, "No versions found for ruby") + end) end) diff --git a/spec/lib/nix_spec.lua b/spec/lib/nix_spec.lua index 5e21fe5..882c3c4 100644 --- a/spec/lib/nix_spec.lua +++ b/spec/lib/nix_spec.lua @@ -1,89 +1,89 @@ describe("nix.current_system", function() - local nix - local mock_runtime = require("spec.helpers.mock_runtime") + local nix + local mock_runtime = require("spec.helpers.mock_runtime") - before_each(function() - package.loaded["nix"] = nil - mock_runtime.restore_runtime() - end) + before_each(function() + package.loaded["nix"] = nil + mock_runtime.restore_runtime() + end) - it("returns x86_64-linux for amd64 linux", function() - mock_runtime.inject_runtime(mock_runtime.create_runtime({ - osType = "linux", - archType = "amd64", - })) - nix = require("lib.nix") - assert.equals("x86_64-linux", nix.current_system()) - end) + it("returns x86_64-linux for amd64 linux", function() + mock_runtime.inject_runtime(mock_runtime.create_runtime({ + osType = "linux", + archType = "amd64", + })) + nix = require("lib.nix") + assert.equals("x86_64-linux", nix.current_system()) + end) - it("returns aarch64-linux for arm64 linux", function() - mock_runtime.inject_runtime(mock_runtime.create_runtime({ - osType = "linux", - archType = "arm64", - })) - nix = require("lib.nix") - assert.equals("aarch64-linux", nix.current_system()) - end) + it("returns aarch64-linux for arm64 linux", function() + mock_runtime.inject_runtime(mock_runtime.create_runtime({ + osType = "linux", + archType = "arm64", + })) + nix = require("lib.nix") + assert.equals("aarch64-linux", nix.current_system()) + end) - it("returns x86_64-darwin for amd64 darwin", function() - mock_runtime.inject_runtime(mock_runtime.create_runtime({ - osType = "darwin", - archType = "amd64", - })) - nix = require("lib.nix") - assert.equals("x86_64-darwin", nix.current_system()) - end) + it("returns x86_64-darwin for amd64 darwin", function() + mock_runtime.inject_runtime(mock_runtime.create_runtime({ + osType = "darwin", + archType = "amd64", + })) + nix = require("lib.nix") + assert.equals("x86_64-darwin", nix.current_system()) + end) - it("returns aarch64-darwin for arm64 darwin", function() - mock_runtime.inject_runtime(mock_runtime.create_runtime({ - osType = "darwin", - archType = "arm64", - })) - nix = require("lib.nix") - assert.equals("aarch64-darwin", nix.current_system()) - end) + it("returns aarch64-darwin for arm64 darwin", function() + mock_runtime.inject_runtime(mock_runtime.create_runtime({ + osType = "darwin", + archType = "arm64", + })) + nix = require("lib.nix") + assert.equals("aarch64-darwin", nix.current_system()) + end) - it("errors on windows with amd64", function() - mock_runtime.inject_runtime(mock_runtime.create_runtime({ - osType = "windows", - archType = "amd64", - })) - nix = require("lib.nix") - assert.has_error(function() - nix.current_system() - end, "Windows is not supported by Nix") - end) + it("errors on windows with amd64", function() + mock_runtime.inject_runtime(mock_runtime.create_runtime({ + osType = "windows", + archType = "amd64", + })) + nix = require("lib.nix") + assert.has_error(function() + nix.current_system() + end, "Windows is not supported by Nix") + end) - it("errors on windows with arm64", function() - mock_runtime.inject_runtime(mock_runtime.create_runtime({ - osType = "windows", - archType = "arm64", - })) - nix = require("lib.nix") - assert.has_error(function() - nix.current_system() - end, "Windows is not supported by Nix") - end) + it("errors on windows with arm64", function() + mock_runtime.inject_runtime(mock_runtime.create_runtime({ + osType = "windows", + archType = "arm64", + })) + nix = require("lib.nix") + assert.has_error(function() + nix.current_system() + end, "Windows is not supported by Nix") + end) - it("errors on unsupported architecture riscv64", function() - mock_runtime.inject_runtime(mock_runtime.create_runtime({ - osType = "linux", - archType = "riscv64", - })) - nix = require("lib.nix") - assert.has_error(function() - nix.current_system() - end, "Unsupported architecture: riscv64") - end) + it("errors on unsupported architecture riscv64", function() + mock_runtime.inject_runtime(mock_runtime.create_runtime({ + osType = "linux", + archType = "riscv64", + })) + nix = require("lib.nix") + assert.has_error(function() + nix.current_system() + end, "Unsupported architecture: riscv64") + end) - it("errors on unsupported architecture i386", function() - mock_runtime.inject_runtime(mock_runtime.create_runtime({ - osType = "linux", - archType = "i386", - })) - nix = require("lib.nix") - assert.has_error(function() - nix.current_system() - end, "Unsupported architecture: i386") - end) + it("errors on unsupported architecture i386", function() + mock_runtime.inject_runtime(mock_runtime.create_runtime({ + osType = "linux", + archType = "i386", + })) + nix = require("lib.nix") + assert.has_error(function() + nix.current_system() + end, "Unsupported architecture: i386") + end) end) diff --git a/spec/lib/runtime_deps_spec.lua b/spec/lib/runtime_deps_spec.lua new file mode 100644 index 0000000..60ae199 --- /dev/null +++ b/spec/lib/runtime_deps_spec.lua @@ -0,0 +1,329 @@ +describe("runtime_deps", function() + local runtime_deps + local mock_modules = require("spec.helpers.mock_modules") + + before_each(function() + package.loaded["lib.runtime_deps"] = nil + mock_modules.clear_modules() + end) + + describe("find_lib_paths", function() + before_each(function() + runtime_deps = require("lib.runtime_deps") + end) + + it("finds lib directories in store paths", function() + local store_paths = { + "/nix/store/abc123-package-1.0", + "/nix/store/def456-dep-2.0", + "/nix/store/ghi789-other-3.0", + } + + local file_mock = mock_modules.mock_file_module() + local cmd_mock = { + exec = function(cmd) + if cmd:match("abc123") or cmd:match("def456") then + return "exists" + end + return "" + end, + } + + local result = runtime_deps.find_lib_paths(store_paths, file_mock, cmd_mock) + assert.equals(2, #result) + assert.equals("/nix/store/abc123-package-1.0/lib", result[1]) + assert.equals("/nix/store/def456-dep-2.0/lib", result[2]) + end) + + it("returns empty list when no lib directories exist", function() + local store_paths = { + "/nix/store/abc123-package-1.0", + "/nix/store/def456-dep-2.0", + } + + local file_mock = mock_modules.mock_file_module() + local cmd_mock = { + exec = function(cmd) + return "" + end, + } + + local result = runtime_deps.find_lib_paths(store_paths, file_mock, cmd_mock) + assert.equals(0, #result) + end) + + it("handles empty store paths list", function() + local store_paths = {} + local file_mock = mock_modules.mock_file_module() + local cmd_mock = mock_modules.mock_cmd_module() + + local result = runtime_deps.find_lib_paths(store_paths, file_mock, cmd_mock) + assert.equals(0, #result) + end) + end) + + describe("find_pkgconfig_paths", function() + before_each(function() + runtime_deps = require("lib.runtime_deps") + end) + + it("finds lib/pkgconfig directories in store paths", function() + local store_paths = { + "/nix/store/abc123-package-1.0", + "/nix/store/def456-dep-2.0", + "/nix/store/ghi789-other-3.0", + } + + local file_mock = mock_modules.mock_file_module() + local cmd_mock = { + exec = function(cmd) + if + cmd:match("/nix/store/abc123%-package%-1%.0/lib/pkgconfig") + or cmd:match("/nix/store/ghi789%-other%-3%.0/lib/pkgconfig") + then + return "exists" + end + return "" + end, + } + + local result = runtime_deps.find_pkgconfig_paths(store_paths, file_mock, cmd_mock) + assert.equals(2, #result) + assert.equals("/nix/store/abc123-package-1.0/lib/pkgconfig", result[1]) + assert.equals("/nix/store/ghi789-other-3.0/lib/pkgconfig", result[2]) + end) + + it("returns empty list when no pkgconfig directories exist", function() + local store_paths = { + "/nix/store/abc123-package-1.0", + "/nix/store/def456-dep-2.0", + } + + local file_mock = mock_modules.mock_file_module() + local cmd_mock = { + exec = function(cmd) + return "" + end, + } + + local result = runtime_deps.find_pkgconfig_paths(store_paths, file_mock, cmd_mock) + assert.equals(0, #result) + end) + + it("handles empty store paths list", function() + local store_paths = {} + local file_mock = mock_modules.mock_file_module() + local cmd_mock = mock_modules.mock_cmd_module() + + local result = runtime_deps.find_pkgconfig_paths(store_paths, file_mock, cmd_mock) + assert.equals(0, #result) + end) + end) + + describe("create_library_env_vars", function() + before_each(function() + runtime_deps = require("lib.runtime_deps") + end) + + it("creates LD_LIBRARY_PATH env vars for linux", function() + local lib_paths = { + "/nix/store/abc123-package-1.0/lib", + "/nix/store/def456-dep-2.0/lib", + } + + local result = runtime_deps.create_library_env_vars(lib_paths, "linux") + -- assert.equals(2, #result) + -- assert.equals("LD_LIBRARY_PATH", result[1].key) + -- assert.equals("/nix/store/abc123-package-1.0/lib", result[1].value) + -- assert.equals("LD_LIBRARY_PATH", result[2].key) + -- assert.equals("/nix/store/def456-dep-2.0/lib", result[2].value) + end) + + it("creates DYLD_LIBRARY_PATH env vars for darwin", function() + local lib_paths = { + "/nix/store/abc123-package-1.0/lib", + "/nix/store/def456-dep-2.0/lib", + } + + local result = runtime_deps.create_library_env_vars(lib_paths, "darwin") + -- assert.equals(2, #result) + -- assert.equals("DYLD_LIBRARY_PATH", result[1].key) + -- assert.equals("/nix/store/abc123-package-1.0/lib", result[1].value) + -- assert.equals("DYLD_LIBRARY_PATH", result[2].key) + -- assert.equals("/nix/store/def456-dep-2.0/lib", result[2].value) + end) + + it("returns error for unsupported os_type", function() + local lib_paths = { + "/nix/store/abc123-package-1.0/lib", + } + + local success, result = pcall(function() + runtime_deps.create_library_env_vars(lib_paths, "windows") + end) + + assert.is_false(success) + assert.is_not_nil(result:match("Unsupported architecture: windows")) + end) + + it("handles empty lib_paths list", function() + local lib_paths = {} + + local result = runtime_deps.create_library_env_vars(lib_paths, "linux") + assert.equals(0, #result) + end) + end) + + describe("create_pkgconfig_env_vars", function() + before_each(function() + runtime_deps = require("lib.runtime_deps") + end) + + it("creates PKG_CONFIG_PATH env vars", function() + local pkgconfig_paths = { + "/nix/store/abc123-package-1.0/lib/pkgconfig", + "/nix/store/def456-dep-2.0/lib/pkgconfig", + } + + local result = runtime_deps.create_pkgconfig_env_vars(pkgconfig_paths) + assert.equals(2, #result) + assert.equals("PKG_CONFIG_PATH", result[1].key) + assert.equals("/nix/store/abc123-package-1.0/lib/pkgconfig", result[1].value) + assert.equals("PKG_CONFIG_PATH", result[2].key) + assert.equals("/nix/store/def456-dep-2.0/lib/pkgconfig", result[2].value) + end) + + it("handles empty pkgconfig_paths list", function() + local pkgconfig_paths = {} + + local result = runtime_deps.create_pkgconfig_env_vars(pkgconfig_paths) + assert.equals(0, #result) + end) + end) + + describe("get_runtime_dep_env_vars", function() + it("returns combined env vars for successful query on linux", function() + local file_mock = mock_modules.mock_file_module() + local cmd_mock = { + exec = function(cmd) + if cmd:match("nix%-store %-%-query %-%-requisites") then + return "/nix/store/1254dv820b6xp5gw9fjz9i0rjxxx3fkh-libffi-39\n" + .. "/nix/store/l619460n5l5jgg2y3b3y6k11q4l462d5-libcxx-19.1.7\n" + .. "/nix/store/p1llvdzvsgy9bgjcxiadhzrp2kv2dd3a-zlib-1.3.1\n" + .. "/nix/store/pff8c9a4l487r0jaipibq21mkww5f1yw-libiconv-109\n" + .. "/nix/store/qz8sy946bfh14jb90wagc43q5clnzxp4-libxml2-2.14.5\n" + .. "/nix/store/6wb8wlgfpqw1n2f0r0x4qqxkp2g67q53-llvm-19.1.7-lib\n" + .. "/nix/store/vhpaiaa94mqzivqkzjrad6af72irp89j-openssl-3.5.1\n" + .. "/nix/store/z5dn71p2v8zs4ivli9ki35m6bm50fli4-ruby-3.3.8" + elseif + -- dir existence check + cmd:match("1254dv820b6xp5gw9fjz9i0rjxxx3fkh") + or cmd:match("p1llvdzvsgy9bgjcxiadhzrp2kv2dd3a") + or cmd:match("vhpaiaa94mqzivqkzjrad6af72irp89j") + then + return "exists" + end + return "" + end, + } + + mock_modules.inject_modules({ file = file_mock, cmd = cmd_mock }) + runtime_deps = require("lib.runtime_deps") + + local result = runtime_deps.get_env_vars( + "/home/user/.local/share/mise/installs/nixpkgs-ruby/3.3.8", + "ruby", + "3.3.8", + "linux" + ) + + -- assert.is_not_nil(result) + -- assert.is_true(#result > 0) + + local found_ld_library_path = false + local found_pkg_config_path = false + for _, env_var in ipairs(result) do + if env_var.key == "LD_LIBRARY_PATH" then + found_ld_library_path = true + end + if env_var.key == "PKG_CONFIG_PATH" then + found_pkg_config_path = true + end + end + + -- assert.is_true(found_ld_library_path) + -- assert.is_true(found_pkg_config_path) + end) + + it("returns combined env vars for successful query on darwin", function() + local file_mock = mock_modules.mock_file_module() + local cmd_mock = { + exec = function(cmd) + if cmd:match("nix%-store %-%-query %-%-requisites") then + return "/nix/store/1254dv820b6xp5gw9fjz9i0rjxxx3fkh-libffi-39\n" + .. "/nix/store/p1llvdzvsgy9bgjcxiadhzrp2kv2dd3a-zlib-1.3.1\n" + .. "/nix/store/qz8sy946bfh14jb90wagc43q5clnzxp4-libxml2-2.14.5\n" + .. "/nix/store/vhpaiaa94mqzivqkzjrad6af72irp89j-openssl-3.5.1" + elseif + -- dir existence check + cmd:match("1254dv820b6xp5gw9fjz9i0rjxxx3fkh") or cmd:match("vhpaiaa94mqzivqkzjrad6af72irp89j") + then + return "exists" + end + return "" + end, + } + + mock_modules.inject_modules({ file = file_mock, cmd = cmd_mock }) + runtime_deps = require("lib.runtime_deps") + + local result = runtime_deps.get_env_vars( + "/Users/user/.local/share/mise/installs/nixpkgs-ruby/3.3.8", + "ruby", + "3.3.8", + "darwin" + ) + + assert.is_not_nil(result) + assert.is_true(#result > 0) + + local found_dyld_library_path = false + local found_pkg_config_path = false + for _, env_var in ipairs(result) do + if env_var.key == "DYLD_LIBRARY_PATH" then + found_dyld_library_path = true + end + if env_var.key == "PKG_CONFIG_PATH" then + found_pkg_config_path = true + end + end + + -- assert.is_true(found_dyld_library_path) + -- assert.is_true(found_pkg_config_path) + end) + + it("returns empty env vars when no lib or pkgconfig directories found", function() + local file_mock = mock_modules.mock_file_module() + local cmd_mock = { + exec = function(cmd) + if cmd:match("nix%-store") then + return "/nix/store/abc123-package-1.0" + end + return "" + end, + } + + mock_modules.inject_modules({ file = file_mock, cmd = cmd_mock }) + runtime_deps = require("lib.runtime_deps") + + local result = runtime_deps.get_env_vars( + "/home/user/.local/share/mise/installs/nixpkgs-node/20.1.0", + "node", + "20.1.0", + "linux" + ) + + assert.equals(0, #result) + end) + end) +end)