diff options
87 files changed, 2482 insertions, 2177 deletions
diff --git a/doc/reference.md b/doc/reference.md index 2f23e793..ef00425c 100644 --- a/doc/reference.md +++ b/doc/reference.md @@ -34,7 +34,8 @@ RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as de - [`Package.License`](#packagelicense) - [`Package.new({spec})`](#packagenewspec) - [`Package.spec`](#packagespec) - - [`Package:install({opts?})`](#packageinstallopts) + - [`Package:is_installing()`](#packageis_installing) + - [`Package:install({opts?}, {callback?})`](#packageinstallopts-callback) - [`Package:uninstall()`](#packageuninstall) - [`Package:is_installed()`](#packageis_installed) - [`Package:get_install_path()`](#packageget_install_path) @@ -82,12 +83,12 @@ RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as de The `mason-registry` Lua module extends the [EventEmitter](#eventemitter) interface and emits the following events: -| Event | Handler signature | -| --------------------------- | ------------------------------------------ | -| `package:handle` | `fun(pkg: Package, handle: InstallHandle)` | -| `package:install:success` | `fun(pkg: Package, handle: InstallHandle)` | -| `package:install:failed` | `fun(pkg: Package, handle: InstallHandle)` | -| `package:uninstall:success` | `fun(pkg: Package)` | +| Event | Handler signature | +| --------------------------- | ------------------------------------------------------ | +| `package:handle` | `fun(pkg: Package, handle: InstallHandle)` | +| `package:install:success` | `fun(pkg: Package, handle: InstallHandle)` | +| `package:install:failed` | `fun(pkg: Package, handle: InstallHandle, error: any)` | +| `package:uninstall:success` | `fun(pkg: Package)` | The following is an example for how to register handlers for events: @@ -135,11 +136,11 @@ The `Package` class encapsulates the installation instructions and metadata abou This class extends the [EventEmitter](#eventemitter) interface and emits the following events: -| Event | Handler signature | -| ------------------- | ---------------------------- | -| `install:success` | `fun(handle: InstallHandle)` | -| `install:failed` | `fun(handle: InstallHandle)` | -| `uninstall:success` | `fun()` | +| Event | Handler signature | +| ------------------- | ------------------------------------------------------ | +| `install:success` | `fun(handle: InstallHandle)` | +| `install:failed` | `fun(pkg: Package, handle: InstallHandle, error: any)` | +| `uninstall:success` | `fun()` | ### `Package.Parse({package_identifier})` @@ -193,22 +194,28 @@ Similar as [`Package.Lang`](#packagelang) but for SPDX license identifiers. **Type**: [`RegistryPackageSpec`](#registrypackagespec) -### `Package:install({opts?})` +### `Package:is_installing()` + +**Returns:** `boolean` + +### `Package:install({opts?}, {callback?})` **Parameters:** - `opts?`: [`PackageInstallOpts`](#packageinstallopts-1) (optional) +- `callback?`: `fun(success: boolean, result: any)` (optional) - Callback to be called when package installation completes. _Note: this is called before events (["package:install:success"](#registry-events), ["install:success"](#package)) are emitted._ **Returns:** [`InstallHandle`](#installhandle) -Installs the package instance this method is being called on. Accepts an -optional `{opts}` argument, which can be used to specify a desired version to -install. +Installs the package instance this method is being called on. Accepts an optional `{opts}` argument which can be used to +for example specify which version to install (see [`PackageInstallOpts`](#packageinstallopts-1)), and an optional +`{callback}` argument which is called when the installation finishes. The returned [`InstallHandle`](#installhandle) can be used to observe progress and control the installation process (e.g., cancelling). -_Note that if the package already have an active handle registered, that handler is returned instead of a new one._ +_Note that if the package is already being installed this method will error. See +[`Package:is_installing()`](#packageis_installing)._ ### `Package:uninstall()` diff --git a/lua/mason-core/installer/registry/providers/cargo.lua b/lua/mason-core/installer/compiler/compilers/cargo.lua index f4904b73..e0f281c5 100644 --- a/lua/mason-core/installer/registry/providers/cargo.lua +++ b/lua/mason-core/installer/compiler/compilers/cargo.lua @@ -1,7 +1,7 @@ local Result = require "mason-core.result" local _ = require "mason-core.functional" local providers = require "mason-core.providers" -local util = require "mason-core.installer.registry.util" +local util = require "mason-core.installer.compiler.util" local M = {} diff --git a/lua/mason-core/installer/registry/providers/composer.lua b/lua/mason-core/installer/compiler/compilers/composer.lua index d85dd2ba..259512a2 100644 --- a/lua/mason-core/installer/registry/providers/composer.lua +++ b/lua/mason-core/installer/compiler/compilers/composer.lua @@ -1,6 +1,6 @@ local Result = require "mason-core.result" local providers = require "mason-core.providers" -local util = require "mason-core.installer.registry.util" +local util = require "mason-core.installer.compiler.util" local M = {} diff --git a/lua/mason-core/installer/registry/providers/gem.lua b/lua/mason-core/installer/compiler/compilers/gem.lua index 9653f116..7a343eec 100644 --- a/lua/mason-core/installer/registry/providers/gem.lua +++ b/lua/mason-core/installer/compiler/compilers/gem.lua @@ -1,7 +1,7 @@ local Result = require "mason-core.result" local _ = require "mason-core.functional" local providers = require "mason-core.providers" -local util = require "mason-core.installer.registry.util" +local util = require "mason-core.installer.compiler.util" local M = {} diff --git a/lua/mason-core/installer/registry/providers/generic/build.lua b/lua/mason-core/installer/compiler/compilers/generic/build.lua index a0d517d8..df97a118 100644 --- a/lua/mason-core/installer/registry/providers/generic/build.lua +++ b/lua/mason-core/installer/compiler/compilers/generic/build.lua @@ -1,8 +1,8 @@ local Result = require "mason-core.result" local _ = require "mason-core.functional" local common = require "mason-core.installer.managers.common" -local expr = require "mason-core.installer.registry.expr" -local util = require "mason-core.installer.registry.util" +local expr = require "mason-core.installer.compiler.expr" +local util = require "mason-core.installer.compiler.util" local M = {} diff --git a/lua/mason-core/installer/registry/providers/generic/download.lua b/lua/mason-core/installer/compiler/compilers/generic/download.lua index 4622a844..37e54d96 100644 --- a/lua/mason-core/installer/registry/providers/generic/download.lua +++ b/lua/mason-core/installer/compiler/compilers/generic/download.lua @@ -1,8 +1,8 @@ local Result = require "mason-core.result" local _ = require "mason-core.functional" local common = require "mason-core.installer.managers.common" -local expr = require "mason-core.installer.registry.expr" -local util = require "mason-core.installer.registry.util" +local expr = require "mason-core.installer.compiler.expr" +local util = require "mason-core.installer.compiler.util" local M = {} diff --git a/lua/mason-core/installer/registry/providers/generic/init.lua b/lua/mason-core/installer/compiler/compilers/generic/init.lua index 1bf79e94..8206883f 100644 --- a/lua/mason-core/installer/registry/providers/generic/init.lua +++ b/lua/mason-core/installer/compiler/compilers/generic/init.lua @@ -9,10 +9,10 @@ local M = {} function M.parse(source, purl, opts) if source.download then source = source --[[@as GenericDownloadSource]] - return require("mason-core.installer.registry.providers.generic.download").parse(source, purl, opts) + return require("mason-core.installer.compiler.compilers.generic.download").parse(source, purl, opts) elseif source.build then source = source --[[@as GenericBuildSource]] - return require("mason-core.installer.registry.providers.generic.build").parse(source, purl, opts) + return require("mason-core.installer.compiler.compilers.generic.build").parse(source, purl, opts) else return Result.failure "Unknown source type." end @@ -24,10 +24,10 @@ end function M.install(ctx, source) if source.download then source = source --[[@as ParsedGenericDownloadSource]] - return require("mason-core.installer.registry.providers.generic.download").install(ctx, source) + return require("mason-core.installer.compiler.compilers.generic.download").install(ctx, source) elseif source.build then source = source --[[@as ParsedGenericBuildSource]] - return require("mason-core.installer.registry.providers.generic.build").install(ctx, source) + return require("mason-core.installer.compiler.compilers.generic.build").install(ctx, source) else return Result.failure "Unknown source type." end diff --git a/lua/mason-core/installer/registry/providers/github/build.lua b/lua/mason-core/installer/compiler/compilers/github/build.lua index 1c17bb1a..22f3e3cc 100644 --- a/lua/mason-core/installer/registry/providers/github/build.lua +++ b/lua/mason-core/installer/compiler/compilers/github/build.lua @@ -1,8 +1,8 @@ local Result = require "mason-core.result" local _ = require "mason-core.functional" local common = require "mason-core.installer.managers.common" -local expr = require "mason-core.installer.registry.expr" -local util = require "mason-core.installer.registry.util" +local expr = require "mason-core.installer.compiler.expr" +local util = require "mason-core.installer.compiler.util" local M = {} diff --git a/lua/mason-core/installer/registry/providers/github/init.lua b/lua/mason-core/installer/compiler/compilers/github/init.lua index 0d68f3a5..d8646975 100644 --- a/lua/mason-core/installer/registry/providers/github/init.lua +++ b/lua/mason-core/installer/compiler/compilers/github/init.lua @@ -8,10 +8,10 @@ local M = {} function M.parse(source, purl, opts) if source.asset then source = source --[[@as GitHubReleaseSource]] - return require("mason-core.installer.registry.providers.github.release").parse(source, purl, opts) + return require("mason-core.installer.compiler.compilers.github.release").parse(source, purl, opts) elseif source.build then source = source --[[@as GitHubBuildSource]] - return require("mason-core.installer.registry.providers.github.build").parse(source, purl, opts) + return require("mason-core.installer.compiler.compilers.github.build").parse(source, purl, opts) else return Result.failure "Unknown source type." end @@ -23,10 +23,10 @@ end function M.install(ctx, source, purl) if source.asset then source = source--[[@as ParsedGitHubReleaseSource]] - return require("mason-core.installer.registry.providers.github.release").install(ctx, source) + return require("mason-core.installer.compiler.compilers.github.release").install(ctx, source) elseif source.build then source = source--[[@as ParsedGitHubBuildSource]] - return require("mason-core.installer.registry.providers.github.build").install(ctx, source) + return require("mason-core.installer.compiler.compilers.github.build").install(ctx, source) else return Result.failure "Unknown source type." end @@ -37,7 +37,7 @@ end ---@param source GitHubReleaseSource | GitHubBuildSource function M.get_versions(purl, source) if source.asset then - return require("mason-core.installer.registry.providers.github.release").get_versions(purl) + return require("mason-core.installer.compiler.compilers.github.release").get_versions(purl) elseif source.build then -- We can't yet reliably determine the true source (release, tag, commit, etc.) for "build" sources. return Result.failure "Unimplemented." diff --git a/lua/mason-core/installer/registry/providers/github/release.lua b/lua/mason-core/installer/compiler/compilers/github/release.lua index 8c8a8a8f..39f7d862 100644 --- a/lua/mason-core/installer/registry/providers/github/release.lua +++ b/lua/mason-core/installer/compiler/compilers/github/release.lua @@ -1,13 +1,16 @@ local Result = require "mason-core.result" local _ = require "mason-core.functional" local common = require "mason-core.installer.managers.common" -local expr = require "mason-core.installer.registry.expr" +local expr = require "mason-core.installer.compiler.expr" local providers = require "mason-core.providers" local settings = require "mason.settings" -local util = require "mason-core.installer.registry.util" +local util = require "mason-core.installer.compiler.util" + +---@class GitHubReleaseSourceAsset : FileDownloadSpec +---@field target? Platform | Platform[] ---@class GitHubReleaseSource : RegistryPackageSource ----@field asset FileDownloadSpec | FileDownloadSpec[] +---@field asset GitHubReleaseSourceAsset | GitHubReleaseSourceAsset[] local M = {} @@ -17,7 +20,7 @@ local M = {} function M.parse(source, purl, opts) return Result.try(function(try) local expr_ctx = { version = purl.version } - ---@type FileDownloadSpec + ---@type GitHubReleaseSourceAsset local asset = try(util.coalesce_by_target(try(expr.tbl_interpolate(source.asset, expr_ctx)), opts)) local downloads = common.parse_downloads(asset, function(file) diff --git a/lua/mason-core/installer/registry/providers/golang.lua b/lua/mason-core/installer/compiler/compilers/golang.lua index 896d9bf9..01807088 100644 --- a/lua/mason-core/installer/registry/providers/golang.lua +++ b/lua/mason-core/installer/compiler/compilers/golang.lua @@ -1,7 +1,7 @@ local Result = require "mason-core.result" local _ = require "mason-core.functional" local providers = require "mason-core.providers" -local util = require "mason-core.installer.registry.util" +local util = require "mason-core.installer.compiler.util" local M = {} diff --git a/lua/mason-core/installer/registry/providers/luarocks.lua b/lua/mason-core/installer/compiler/compilers/luarocks.lua index 356857c0..356857c0 100644 --- a/lua/mason-core/installer/registry/providers/luarocks.lua +++ b/lua/mason-core/installer/compiler/compilers/luarocks.lua diff --git a/lua/mason-core/installer/registry/providers/mason.lua b/lua/mason-core/installer/compiler/compilers/mason.lua index 3490ebaa..3490ebaa 100644 --- a/lua/mason-core/installer/registry/providers/mason.lua +++ b/lua/mason-core/installer/compiler/compilers/mason.lua diff --git a/lua/mason-core/installer/registry/providers/npm.lua b/lua/mason-core/installer/compiler/compilers/npm.lua index e8489fe8..e8489fe8 100644 --- a/lua/mason-core/installer/registry/providers/npm.lua +++ b/lua/mason-core/installer/compiler/compilers/npm.lua diff --git a/lua/mason-core/installer/registry/providers/nuget.lua b/lua/mason-core/installer/compiler/compilers/nuget.lua index 370c7b95..370c7b95 100644 --- a/lua/mason-core/installer/registry/providers/nuget.lua +++ b/lua/mason-core/installer/compiler/compilers/nuget.lua diff --git a/lua/mason-core/installer/registry/providers/opam.lua b/lua/mason-core/installer/compiler/compilers/opam.lua index 276686ae..276686ae 100644 --- a/lua/mason-core/installer/registry/providers/opam.lua +++ b/lua/mason-core/installer/compiler/compilers/opam.lua diff --git a/lua/mason-core/installer/registry/providers/openvsx.lua b/lua/mason-core/installer/compiler/compilers/openvsx.lua index df52807a..bf31e2f9 100644 --- a/lua/mason-core/installer/registry/providers/openvsx.lua +++ b/lua/mason-core/installer/compiler/compilers/openvsx.lua @@ -1,16 +1,17 @@ local Result = require "mason-core.result" local common = require "mason-core.installer.managers.common" -local expr = require "mason-core.installer.registry.expr" +local expr = require "mason-core.installer.compiler.expr" local providers = require "mason-core.providers" -local util = require "mason-core.installer.registry.util" +local util = require "mason-core.installer.compiler.util" local M = {} ---@class OpenVSXSourceDownload : FileDownloadSpec +---@field target? Platform | Platform[] ---@field target_platform? string ---@class OpenVSXSource : RegistryPackageSource ----@field download FileDownloadSpec | FileDownloadSpec[] +---@field download OpenVSXSourceDownload | OpenVSXSourceDownload[] ---@param source OpenVSXSource ---@param purl Purl diff --git a/lua/mason-core/installer/registry/providers/pypi.lua b/lua/mason-core/installer/compiler/compilers/pypi.lua index 3fe6f89e..c44fcfe1 100644 --- a/lua/mason-core/installer/registry/providers/pypi.lua +++ b/lua/mason-core/installer/compiler/compilers/pypi.lua @@ -2,7 +2,7 @@ local Result = require "mason-core.result" local _ = require "mason-core.functional" local providers = require "mason-core.providers" local settings = require "mason.settings" -local util = require "mason-core.installer.registry.util" +local util = require "mason-core.installer.compiler.util" local M = {} diff --git a/lua/mason-core/installer/registry/expr.lua b/lua/mason-core/installer/compiler/expr.lua index a07fc00d..a07fc00d 100644 --- a/lua/mason-core/installer/registry/expr.lua +++ b/lua/mason-core/installer/compiler/expr.lua diff --git a/lua/mason-core/installer/registry/init.lua b/lua/mason-core/installer/compiler/init.lua index 7376db86..e1df6784 100644 --- a/lua/mason-core/installer/registry/init.lua +++ b/lua/mason-core/installer/compiler/init.lua @@ -3,10 +3,10 @@ local Purl = require "mason-core.purl" local Result = require "mason-core.result" local _ = require "mason-core.functional" local a = require "mason-core.async" -local link = require "mason-core.installer.registry.link" +local link = require "mason-core.installer.compiler.link" local log = require "mason-core.log" -local schemas = require "mason-core.installer.registry.schemas" -local util = require "mason-core.installer.registry.util" +local schemas = require "mason-core.installer.compiler.schemas" +local util = require "mason-core.installer.compiler.util" local M = {} @@ -15,35 +15,37 @@ M.SCHEMA_CAP = _.set_of { "registry+v1", } ----@type table<string, InstallerProvider> -local PROVIDERS = {} +---@type table<string, InstallerCompiler> +local COMPILERS = {} ---@param id string ----@param provider InstallerProvider -function M.register_provider(id, provider) - PROVIDERS[id] = provider +---@param compiler InstallerCompiler +function M.register_compiler(id, compiler) + COMPILERS[id] = compiler end -M.register_provider("cargo", _.lazy_require "mason-core.installer.registry.providers.cargo") -M.register_provider("composer", _.lazy_require "mason-core.installer.registry.providers.composer") -M.register_provider("gem", _.lazy_require "mason-core.installer.registry.providers.gem") -M.register_provider("generic", _.lazy_require "mason-core.installer.registry.providers.generic") -M.register_provider("github", _.lazy_require "mason-core.installer.registry.providers.github") -M.register_provider("golang", _.lazy_require "mason-core.installer.registry.providers.golang") -M.register_provider("luarocks", _.lazy_require "mason-core.installer.registry.providers.luarocks") -M.register_provider("npm", _.lazy_require "mason-core.installer.registry.providers.npm") -M.register_provider("nuget", _.lazy_require "mason-core.installer.registry.providers.nuget") -M.register_provider("opam", _.lazy_require "mason-core.installer.registry.providers.opam") -M.register_provider("openvsx", _.lazy_require "mason-core.installer.registry.providers.openvsx") -M.register_provider("pypi", _.lazy_require "mason-core.installer.registry.providers.pypi") -M.register_provider("mason", _.lazy_require "mason-core.installer.registry.providers.mason") +M.register_compiler("cargo", _.lazy_require "mason-core.installer.compiler.compilers.cargo") +M.register_compiler("composer", _.lazy_require "mason-core.installer.compiler.compilers.composer") +M.register_compiler("gem", _.lazy_require "mason-core.installer.compiler.compilers.gem") +M.register_compiler("generic", _.lazy_require "mason-core.installer.compiler.compilers.generic") +M.register_compiler("github", _.lazy_require "mason-core.installer.compiler.compilers.github") +M.register_compiler("golang", _.lazy_require "mason-core.installer.compiler.compilers.golang") +M.register_compiler("luarocks", _.lazy_require "mason-core.installer.compiler.compilers.luarocks") +M.register_compiler("mason", _.lazy_require "mason-core.installer.compiler.compilers.mason") +M.register_compiler("npm", _.lazy_require "mason-core.installer.compiler.compilers.npm") +M.register_compiler("nuget", _.lazy_require "mason-core.installer.compiler.compilers.nuget") +M.register_compiler("opam", _.lazy_require "mason-core.installer.compiler.compilers.opam") +M.register_compiler("openvsx", _.lazy_require "mason-core.installer.compiler.compilers.openvsx") +M.register_compiler("pypi", _.lazy_require "mason-core.installer.compiler.compilers.pypi") ---@param purl Purl -function M.get_provider(purl) - return Optional.of_nilable(PROVIDERS[purl.type]):ok_or(("Unknown purl type: %s"):format(purl.type)) +---@return Result # Result<InstallerCompiler> +function M.get_compiler(purl) + return Optional.of_nilable(COMPILERS[purl.type]) + :ok_or(("Current version of mason.nvim is not capable of parsing package type %q."):format(purl.type)) end ----@class InstallerProvider +---@class InstallerCompiler ---@field parse fun(source: RegistryPackageSource, purl: Purl, opts: PackageInstallOpts): Result ---@field install async fun(ctx: InstallContext, source: ParsedPackageSource, purl: Purl): Result ---@field get_versions async fun(purl: Purl, source: RegistryPackageSource): Result # Result<string[]> @@ -128,13 +130,13 @@ function M.parse(spec, opts) purl.version = opts.version end - ---@type InstallerProvider - local provider = try(M.get_provider(purl)) - log.trace("Found provider for purl.", source.id) - local parsed_source = try(provider.parse(source, purl, opts)) + ---@type InstallerCompiler + local compiler = try(M.get_compiler(purl)) + log.trace("Found compiler for purl.", source.id) + local parsed_source = try(compiler.parse(source, purl, opts)) log.trace("Parsed source for purl.", source.id, parsed_source) return { - provider = provider, + compiler = compiler, source = vim.tbl_extend("keep", parsed_source, source), raw_source = source, purl = purl, @@ -167,7 +169,7 @@ function M.compile(spec, opts) { _.T, _.identity }, } - ---@type { purl: Purl, provider: InstallerProvider, source: ParsedPackageSource, raw_source: RegistryPackageSource } + ---@type { purl: Purl, compiler: InstallerCompiler, source: ParsedPackageSource, raw_source: RegistryPackageSource } local parsed = try(M.parse(spec, opts):map_err(map_parse_err)) ---@async @@ -176,13 +178,13 @@ function M.compile(spec, opts) return Result.try(function(try) if ctx.opts.version then try(util.ensure_valid_version(function() - return parsed.provider.get_versions(parsed.purl, parsed.raw_source) + return parsed.compiler.get_versions(parsed.purl, parsed.raw_source) end)) end -- Run installer a.scheduler() - try(parsed.provider.install(ctx, parsed.source, parsed.purl)) + try(parsed.compiler.install(ctx, parsed.source, parsed.purl)) if spec.schemas then local result = schemas.download(ctx, spec, parsed.purl, parsed.source):on_failure(function(err) diff --git a/lua/mason-core/installer/registry/link.lua b/lua/mason-core/installer/compiler/link.lua index 85e751b7..5d136322 100644 --- a/lua/mason-core/installer/registry/link.lua +++ b/lua/mason-core/installer/compiler/link.lua @@ -2,7 +2,7 @@ local Optional = require "mason-core.optional" local Result = require "mason-core.result" local _ = require "mason-core.functional" local a = require "mason-core.async" -local expr = require "mason-core.installer.registry.expr" +local expr = require "mason-core.installer.compiler.expr" local fs = require "mason-core.fs" local log = require "mason-core.log" local path = require "mason-core.path" diff --git a/lua/mason-core/installer/registry/schemas.lua b/lua/mason-core/installer/compiler/schemas.lua index f9d044af..5e578dbd 100644 --- a/lua/mason-core/installer/registry/schemas.lua +++ b/lua/mason-core/installer/compiler/schemas.lua @@ -1,7 +1,7 @@ local Result = require "mason-core.result" local _ = require "mason-core.functional" local a = require "mason-core.async" -local expr = require "mason-core.installer.registry.expr" +local expr = require "mason-core.installer.compiler.expr" local fetch = require "mason-core.fetch" local log = require "mason-core.log" local path = require "mason-core.path" diff --git a/lua/mason-core/installer/registry/util.lua b/lua/mason-core/installer/compiler/util.lua index b3735c9c..b3735c9c 100644 --- a/lua/mason-core/installer/registry/util.lua +++ b/lua/mason-core/installer/compiler/util.lua diff --git a/lua/mason-core/installer/context/cwd.lua b/lua/mason-core/installer/context/cwd.lua new file mode 100644 index 00000000..4f645fbb --- /dev/null +++ b/lua/mason-core/installer/context/cwd.lua @@ -0,0 +1,48 @@ +local Result = require "mason-core.result" +local fs = require "mason-core.fs" +local path = require "mason-core.path" + +---@class InstallContextCwd +---@field private location InstallLocation Defines the upper boundary for which paths are allowed as cwd. +---@field private cwd string? +local InstallContextCwd = {} +InstallContextCwd.__index = InstallContextCwd + +---@param location InstallLocation +function InstallContextCwd.new(location) + assert(location, "location not provided") + return setmetatable({ + location = location, + cwd = nil, + }, InstallContextCwd) +end + +---@param handle InstallHandle +function InstallContextCwd:initialize(handle) + return Result.try(function(try) + local staging_dir = self.location:staging(handle.package.name) + if fs.async.dir_exists(staging_dir) then + try(Result.pcall(fs.async.rmrf, staging_dir)) + end + try(Result.pcall(fs.async.mkdirp, staging_dir)) + self:set(staging_dir) + end) +end + +function InstallContextCwd:get() + assert(self.cwd ~= nil, "Tried to access cwd before it was set.") + return self.cwd +end + +---@param new_abs_cwd string +function InstallContextCwd:set(new_abs_cwd) + assert(type(new_abs_cwd) == "string", "new_cwd is not a string") + assert( + path.is_subdirectory(self.location:get_dir(), new_abs_cwd), + ("%q is not a subdirectory of %q"):format(new_abs_cwd, self.location) + ) + self.cwd = new_abs_cwd + return self +end + +return InstallContextCwd diff --git a/lua/mason-core/installer/context/fs.lua b/lua/mason-core/installer/context/fs.lua new file mode 100644 index 00000000..5c51fb56 --- /dev/null +++ b/lua/mason-core/installer/context/fs.lua @@ -0,0 +1,108 @@ +local fs = require "mason-core.fs" +local log = require "mason-core.log" +local path = require "mason-core.path" + +---@class InstallContextFs +---@field private cwd InstallContextCwd +local InstallContextFs = {} +InstallContextFs.__index = InstallContextFs + +---@param cwd InstallContextCwd +function InstallContextFs.new(cwd) + return setmetatable({ cwd = cwd }, InstallContextFs) +end + +---@async +---@param rel_path string The relative path from the current working directory to the file to append. +---@param contents string +function InstallContextFs:append_file(rel_path, contents) + return fs.async.append_file(path.concat { self.cwd:get(), rel_path }, contents) +end + +---@async +---@param rel_path string The relative path from the current working directory to the file to write. +---@param contents string +function InstallContextFs:write_file(rel_path, contents) + return fs.async.write_file(path.concat { self.cwd:get(), rel_path }, contents) +end + +---@async +---@param rel_path string The relative path from the current working directory to the file to read. +function InstallContextFs:read_file(rel_path) + return fs.async.read_file(path.concat { self.cwd:get(), rel_path }) +end + +---@async +---@param rel_path string The relative path from the current working directory. +function InstallContextFs:file_exists(rel_path) + return fs.async.file_exists(path.concat { self.cwd:get(), rel_path }) +end + +---@async +---@param rel_path string The relative path from the current working directory. +function InstallContextFs:dir_exists(rel_path) + return fs.async.dir_exists(path.concat { self.cwd:get(), rel_path }) +end + +---@async +---@param rel_path string The relative path from the current working directory. +function InstallContextFs:rmrf(rel_path) + return fs.async.rmrf(path.concat { self.cwd:get(), rel_path }) +end + +---@async +---@param rel_path string The relative path from the current working directory. +function InstallContextFs:unlink(rel_path) + return fs.async.unlink(path.concat { self.cwd:get(), rel_path }) +end + +---@async +---@param old_path string +---@param new_path string +function InstallContextFs:rename(old_path, new_path) + return fs.async.rename(path.concat { self.cwd:get(), old_path }, path.concat { self.cwd:get(), new_path }) +end + +---@async +---@param dir_path string +function InstallContextFs:mkdir(dir_path) + return fs.async.mkdir(path.concat { self.cwd:get(), dir_path }) +end + +---@async +---@param dir_path string +function InstallContextFs:mkdirp(dir_path) + return fs.async.mkdirp(path.concat { self.cwd:get(), dir_path }) +end + +---@async +---@param file_path string +function InstallContextFs:chmod_exec(file_path) + local bit = require "bit" + -- see chmod(2) + local USR_EXEC = 0x40 + local GRP_EXEC = 0x8 + local ALL_EXEC = 0x1 + local EXEC = bit.bor(USR_EXEC, GRP_EXEC, ALL_EXEC) + local fstat = self:fstat(file_path) + if bit.band(fstat.mode, EXEC) ~= EXEC then + local plus_exec = bit.bor(fstat.mode, EXEC) + log.fmt_debug("Setting exec flags on file %s %o -> %o", file_path, fstat.mode, plus_exec) + self:chmod(file_path, plus_exec) -- chmod +x + end +end + +---@async +---@param file_path string +---@param mode integer +function InstallContextFs:chmod(file_path, mode) + return fs.async.chmod(path.concat { self.cwd:get(), file_path }, mode) +end + +---@async +---@param file_path string +function InstallContextFs:fstat(file_path) + return fs.async.fstat(path.concat { self.cwd:get(), file_path }) +end + +return InstallContextFs diff --git a/lua/mason-core/installer/context.lua b/lua/mason-core/installer/context/init.lua index a991cd9f..0d178c4e 100644 --- a/lua/mason-core/installer/context.lua +++ b/lua/mason-core/installer/context/init.lua @@ -1,213 +1,37 @@ -local Optional = require "mason-core.optional" +local Result = require "mason-core.result" local _ = require "mason-core.functional" local fs = require "mason-core.fs" local log = require "mason-core.log" local path = require "mason-core.path" local platform = require "mason-core.platform" local receipt = require "mason-core.receipt" -local spawn = require "mason-core.spawn" - ----@class ContextualSpawn ----@field strict_mode boolean Whether spawn failures should raise an exception rather then return a Result. ----@field cwd CwdManager ----@field handle InstallHandle ----@field [string] async fun(opts: SpawnArgs): Result -local ContextualSpawn = {} - ----@param cwd CwdManager ----@param handle InstallHandle ----@param strict_mode boolean -function ContextualSpawn.new(cwd, handle, strict_mode) - return setmetatable({ cwd = cwd, handle = handle, strict_mode = strict_mode }, ContextualSpawn) -end - ----@param cmd string -function ContextualSpawn:__index(cmd) - ---@param args JobSpawnOpts - return function(args) - args.cwd = args.cwd or self.cwd:get() - args.stdio_sink = args.stdio_sink or self.handle.stdio.sink - local on_spawn = args.on_spawn - local captured_handle - args.on_spawn = function(handle, stdio, pid, ...) - captured_handle = handle - self.handle:register_spawn_handle(handle, pid, cmd, spawn._flatten_cmd_args(args)) - if on_spawn then - on_spawn(handle, stdio, pid, ...) - end - end - local function pop_spawn_stack() - if captured_handle then - self.handle:deregister_spawn_handle(captured_handle) - end - end - local result = spawn[cmd](args):on_success(pop_spawn_stack):on_failure(pop_spawn_stack) - if self.strict_mode then - return result:get_or_throw() - else - return result - end - end -end - ----@class ContextualFs ----@field private cwd CwdManager -local ContextualFs = {} -ContextualFs.__index = ContextualFs - ----@param cwd CwdManager -function ContextualFs.new(cwd) - return setmetatable({ cwd = cwd }, ContextualFs) -end - ----@async ----@param rel_path string The relative path from the current working directory to the file to append. ----@param contents string -function ContextualFs:append_file(rel_path, contents) - return fs.async.append_file(path.concat { self.cwd:get(), rel_path }, contents) -end - ----@async ----@param rel_path string The relative path from the current working directory to the file to write. ----@param contents string -function ContextualFs:write_file(rel_path, contents) - return fs.async.write_file(path.concat { self.cwd:get(), rel_path }, contents) -end - ----@async ----@param rel_path string The relative path from the current working directory to the file to read. -function ContextualFs:read_file(rel_path) - return fs.async.read_file(path.concat { self.cwd:get(), rel_path }) -end - ----@async ----@param rel_path string The relative path from the current working directory. -function ContextualFs:file_exists(rel_path) - return fs.async.file_exists(path.concat { self.cwd:get(), rel_path }) -end - ----@async ----@param rel_path string The relative path from the current working directory. -function ContextualFs:dir_exists(rel_path) - return fs.async.dir_exists(path.concat { self.cwd:get(), rel_path }) -end - ----@async ----@param rel_path string The relative path from the current working directory. -function ContextualFs:rmrf(rel_path) - return fs.async.rmrf(path.concat { self.cwd:get(), rel_path }) -end - ----@async ----@param rel_path string The relative path from the current working directory. -function ContextualFs:unlink(rel_path) - return fs.async.unlink(path.concat { self.cwd:get(), rel_path }) -end - ----@async ----@param old_path string ----@param new_path string -function ContextualFs:rename(old_path, new_path) - return fs.async.rename(path.concat { self.cwd:get(), old_path }, path.concat { self.cwd:get(), new_path }) -end - ----@async ----@param dir_path string -function ContextualFs:mkdir(dir_path) - return fs.async.mkdir(path.concat { self.cwd:get(), dir_path }) -end - ----@async ----@param dir_path string -function ContextualFs:mkdirp(dir_path) - return fs.async.mkdirp(path.concat { self.cwd:get(), dir_path }) -end - ----@async ----@param file_path string -function ContextualFs:chmod_exec(file_path) - local bit = require "bit" - -- see chmod(2) - local USR_EXEC = 0x40 - local GRP_EXEC = 0x8 - local ALL_EXEC = 0x1 - local EXEC = bit.bor(USR_EXEC, GRP_EXEC, ALL_EXEC) - local fstat = self:fstat(file_path) - if bit.band(fstat.mode, EXEC) ~= EXEC then - local plus_exec = bit.bor(fstat.mode, EXEC) - log.fmt_debug("Setting exec flags on file %s %o -> %o", file_path, fstat.mode, plus_exec) - self:chmod(file_path, plus_exec) -- chmod +x - end -end - ----@async ----@param file_path string ----@param mode integer -function ContextualFs:chmod(file_path, mode) - return fs.async.chmod(path.concat { self.cwd:get(), file_path }, mode) -end - ----@async ----@param file_path string -function ContextualFs:fstat(file_path) - return fs.async.fstat(path.concat { self.cwd:get(), file_path }) -end - ----@class CwdManager ----@field private install_prefix string Defines the upper boundary for which paths are allowed as cwd. ----@field private cwd string -local CwdManager = {} -CwdManager.__index = CwdManager - -function CwdManager.new(install_prefix) - assert(type(install_prefix) == "string", "install_prefix not provided") - return setmetatable({ - install_prefix = install_prefix, - cwd = nil, - }, CwdManager) -end - -function CwdManager:get() - assert(self.cwd ~= nil, "Tried to access cwd before it was set.") - return self.cwd -end - ----@param new_cwd string -function CwdManager:set(new_cwd) - assert(type(new_cwd) == "string", "new_cwd is not a string") - assert( - path.is_subdirectory(self.install_prefix, new_cwd), - ("%q is not a subdirectory of %q"):format(new_cwd, self.install_prefix) - ) - self.cwd = new_cwd -end ---@class InstallContext ----@field public receipt InstallReceiptBuilder ----@field public requested_version Optional ----@field public fs ContextualFs ----@field public spawn ContextualSpawn ----@field public handle InstallHandle ----@field public package Package ----@field public cwd CwdManager ----@field public opts PackageInstallOpts ----@field public stdio_sink StdioSink +---@field receipt InstallReceiptBuilder +---@field fs InstallContextFs +---@field spawn InstallContextSpawn +---@field handle InstallHandle +---@field package Package +---@field cwd InstallContextCwd +---@field opts PackageInstallOpts +---@field stdio_sink StdioSink ---@field links { bin: table<string, string>, share: table<string, string>, opt: table<string, string> } local InstallContext = {} InstallContext.__index = InstallContext ---@param handle InstallHandle +---@param cwd InstallContextCwd +---@param spawn InstallContextSpawn +---@param fs InstallContextFs ---@param opts PackageInstallOpts -function InstallContext.new(handle, opts) - local cwd_manager = CwdManager.new(path.install_prefix()) +function InstallContext.new(handle, cwd, spawn, fs, opts) return setmetatable({ - cwd = cwd_manager, - spawn = ContextualSpawn.new(cwd_manager, handle, false), + cwd = cwd, + spawn = spawn, handle = handle, package = handle.package, -- for convenience - fs = ContextualFs.new(cwd_manager), + fs = fs, receipt = receipt.InstallReceiptBuilder.new(), - requested_version = Optional.of_nilable(opts.version), stdio_sink = handle.stdio.sink, links = { bin = {}, @@ -227,8 +51,8 @@ function InstallContext:promote_cwd() return end log.fmt_debug("Promoting cwd %s to %s", cwd, install_path) - -- 1. Unlink any existing installation - self.handle.package:unlink() + -- 1. Uninstall any existing installation + self.handle.package:uninstall() -- 2. Prepare for renaming cwd to destination if platform.is.unix then -- Some Unix systems will raise an error when renaming a directory to a destination that does not already exist. @@ -396,4 +220,42 @@ function InstallContext:link_bin(executable, rel_path) return self end +InstallContext.CONTEXT_REQUEST = {} + +---@generic T +---@param fn fun(context: InstallContext): T +---@return T +function InstallContext:execute(fn) + local thread = coroutine.create(function(...) + -- We wrap the function to allow it to be a spy instance (in which case it's not actually a function, but a + -- callable metatable - coroutine.create strictly expects functions only) + return fn(...) + end) + local step + local ret_val + step = function(...) + local ok, result = coroutine.resume(thread, ...) + if not ok then + error(result, 0) + elseif result == InstallContext.CONTEXT_REQUEST then + step(self) + elseif coroutine.status(thread) == "suspended" then + -- yield to parent coroutine + step(coroutine.yield(result)) + else + ret_val = result + end + end + step(self) + return ret_val +end + +---@async +function InstallContext:build_receipt() + log.fmt_debug("Building receipt for %s", self.package) + return Result.pcall(function() + return self.receipt:with_name(self.package.name):with_completion_time(vim.loop.gettimeofday()):build() + end) +end + return InstallContext diff --git a/lua/mason-core/installer/context/spawn.lua b/lua/mason-core/installer/context/spawn.lua new file mode 100644 index 00000000..6528c4b3 --- /dev/null +++ b/lua/mason-core/installer/context/spawn.lua @@ -0,0 +1,46 @@ +local spawn = require "mason-core.spawn" + +---@class InstallContextSpawn +---@field strict_mode boolean Whether spawn failures should raise an exception rather then return a Result. +---@field private cwd InstallContextCwd +---@field private handle InstallHandle +---@field [string] async fun(opts: SpawnArgs): Result +local InstallContextSpawn = {} + +---@param cwd InstallContextCwd +---@param handle InstallHandle +---@param strict_mode boolean +function InstallContextSpawn.new(cwd, handle, strict_mode) + return setmetatable({ cwd = cwd, handle = handle, strict_mode = strict_mode }, InstallContextSpawn) +end + +---@param cmd string +function InstallContextSpawn:__index(cmd) + ---@param args JobSpawnOpts + return function(args) + args.cwd = args.cwd or self.cwd:get() + args.stdio_sink = args.stdio_sink or self.handle.stdio.sink + local on_spawn = args.on_spawn + local captured_handle + args.on_spawn = function(handle, stdio, pid, ...) + captured_handle = handle + self.handle:register_spawn_handle(handle, pid, cmd, spawn._flatten_cmd_args(args)) + if on_spawn then + on_spawn(handle, stdio, pid, ...) + end + end + local function pop_spawn_stack() + if captured_handle then + self.handle:deregister_spawn_handle(captured_handle) + end + end + local result = spawn[cmd](args):on_success(pop_spawn_stack):on_failure(pop_spawn_stack) + if self.strict_mode then + return result:get_or_throw() + else + return result + end + end +end + +return InstallContextSpawn diff --git a/lua/mason-core/installer/handle.lua b/lua/mason-core/installer/handle.lua index f9b03557..96acbdd1 100644 --- a/lua/mason-core/installer/handle.lua +++ b/lua/mason-core/installer/handle.lua @@ -120,6 +120,10 @@ function InstallHandle:is_closed() return self.state == "CLOSED" end +function InstallHandle:is_closing() + return self:is_closed() or self.is_terminated +end + ---@param new_state InstallHandleState function InstallHandle:set_state(new_state) local old_state = self.state diff --git a/lua/mason-core/installer/init.lua b/lua/mason-core/installer/init.lua index 45bba46b..37c74fcb 100644 --- a/lua/mason-core/installer/init.lua +++ b/lua/mason-core/installer/init.lua @@ -1,263 +1,10 @@ local InstallContext = require "mason-core.installer.context" -local Result = require "mason-core.result" -local _ = require "mason-core.functional" -local a = require "mason-core.async" -local control = require "mason-core.async.control" -local fs = require "mason-core.fs" -local linker = require "mason-core.installer.linker" -local log = require "mason-core.log" -local path = require "mason-core.path" -local settings = require "mason.settings" - -local Semaphore = control.Semaphore - -local sem = Semaphore.new(settings.current.max_concurrent_installers) local M = {} ----@async -function M.create_prefix_dirs() - return Result.try(function(try) - for _, p in ipairs { - path.install_prefix(), - path.bin_prefix(), - path.share_prefix(), - path.package_prefix(), - path.package_build_prefix(), - } do - if not fs.async.dir_exists(p) then - try(Result.pcall(fs.async.mkdirp, p)) - end - end - end) -end - ----@async ----@param context InstallContext -local function build_receipt(context) - return Result.pcall(function() - log.fmt_debug("Building receipt for %s", context.package) - return context.receipt:with_name(context.package.name):with_completion_time(vim.loop.gettimeofday()):build() - end) -end - -local CONTEXT_REQUEST = {} - ---@return InstallContext function M.context() - return coroutine.yield(CONTEXT_REQUEST) -end - ----@async ----@param ctx InstallContext -local function lock_package(ctx) - log.debug("Attempting to lock package", ctx.package) - local lockfile = path.package_lock(ctx.package.name) - if not ctx.opts.force and fs.async.file_exists(lockfile) then - log.error("Lockfile already exists.", ctx.package) - return Result.failure( - ("Lockfile exists, installation is already running in another process (pid: %s). Run with :MasonInstall --force to bypass."):format( - fs.sync.read_file(lockfile) - ) - ) - end - a.scheduler() - fs.async.write_file(lockfile, vim.fn.getpid()) - log.debug("Wrote lockfile", ctx.package) - return Result.success(lockfile) -end - ----@async ----@param context InstallContext -function M.prepare_installer(context) - local installer = require "mason-core.installer.registry" - return Result.try(function(try) - local package_build_prefix = path.package_build_prefix(context.package.name) - if fs.async.dir_exists(package_build_prefix) then - try(Result.pcall(fs.async.rmrf, package_build_prefix)) - end - try(Result.pcall(fs.async.mkdirp, package_build_prefix)) - context.cwd:set(package_build_prefix) - - return try(installer.compile(context.handle.package.spec, context.opts)) - end) -end - ----@generic T ----@param context InstallContext ----@param fn fun(context: InstallContext): T ----@return T -function M.exec_in_context(context, fn) - local thread = coroutine.create(function(...) - -- We wrap the function to allow it to be a spy instance (in which case it's not actually a function, but a - -- callable metatable - coroutine.create strictly expects functions only) - return fn(...) - end) - local step - local ret_val - step = function(...) - local ok, result = coroutine.resume(thread, ...) - if not ok then - error(result, 0) - elseif result == CONTEXT_REQUEST then - step(context) - elseif coroutine.status(thread) == "suspended" then - -- yield to parent coroutine - step(coroutine.yield(result)) - else - ret_val = result - end - end - context.receipt:with_start_time(vim.loop.gettimeofday()) - step(context) - return ret_val -end - ----@async ----@param context InstallContext ----@param installer async fun(ctx: InstallContext) -local function run_installer(context, installer) - local handle = context.handle - return Result.pcall(function() - return a.wait(function(resolve, reject) - local cancel_thread = a.run(M.exec_in_context, function(success, result) - if success then - resolve(result) - else - reject(result) - end - end, context, installer) - - handle:once("terminate", function() - cancel_thread() - if handle:is_closed() then - reject "Installation was aborted." - else - handle:once("closed", function() - reject "Installation was aborted." - end) - end - end) - end) - end) -end - ----@async ----@param handle InstallHandle ----@param opts PackageInstallOpts -function M.execute(handle, opts) - if handle:is_active() or handle:is_closed() then - log.fmt_debug("Received active or closed handle %s", handle) - return Result.failure "Invalid handle state." - end - - handle:queued() - local permit = sem:acquire() - if handle:is_closed() then - permit:forget() - log.fmt_trace("Installation was aborted %s", handle) - return Result.failure "Installation was aborted." - end - log.fmt_trace("Activating handle %s", handle) - handle:active() - - local pkg = handle.package - local context = InstallContext.new(handle, opts) - local tailed_output = {} - - if opts.debug then - local function append_log(chunk) - tailed_output[#tailed_output + 1] = chunk - end - handle:on("stdout", append_log) - handle:on("stderr", append_log) - end - - log.fmt_info("Executing installer for %s %s", pkg, opts) - - return M.create_prefix_dirs() - :and_then(function() - return lock_package(context) - end) - :and_then(function(lockfile) - local release_lock = _.partial(pcall, fs.async.unlink, lockfile) - return Result.try(function(try) - -- 1. prepare directories and initialize cwd - local installer = try(M.prepare_installer(context)) - - -- 2. execute installer - try(run_installer(context, installer)) - - -- 3. promote temporary installation dir - try(Result.pcall(function() - context:promote_cwd() - end)) - - -- 4. link package - try(linker.link(context)) - - -- 5. build & write receipt - ---@type InstallReceipt - local receipt = try(build_receipt(context)) - try(Result.pcall(function() - receipt:write(context.cwd:get()) - end)) - end) - :on_success(function() - release_lock() - if opts.debug then - context.fs:write_file("mason-debug.log", table.concat(tailed_output, "")) - end - end) - :on_failure(function() - release_lock() - if not opts.debug then - -- clean up installation dir - pcall(function() - fs.async.rmrf(context.cwd:get()) - end) - else - context.fs:write_file("mason-debug.log", table.concat(tailed_output, "")) - context.stdio_sink.stdout( - ("[debug] Installation directory retained at %q.\n"):format(context.cwd:get()) - ) - end - - -- unlink linked executables (in the occasion an error occurs after linking) - build_receipt(context):on_success(function(receipt) - linker.unlink(context.package, receipt):on_failure(function(err) - log.error("Failed to unlink failed installation", err) - end) - end) - end) - end) - :on_success(function() - permit:forget() - handle:close() - log.fmt_info("Installation succeeded for %s", pkg) - end) - :on_failure(function(failure) - permit:forget() - log.fmt_error("Installation failed for %s error=%s", pkg, failure) - context.stdio_sink.stderr(tostring(failure)) - context.stdio_sink.stderr "\n" - - if not handle:is_closed() and not handle.is_terminated then - handle:close() - end - end) -end - ----Runs the provided async functions concurrently and returns their result, once all are resolved. ----This is really just a wrapper around a.wait_all() that makes sure to patch the coroutine context before creating the ----new async execution contexts. ----@async ----@param suspend_fns async fun(ctx: InstallContext)[] -function M.run_concurrently(suspend_fns) - local context = M.context() - return a.wait_all(_.map(function(suspend_fn) - return _.partial(M.exec_in_context, context, suspend_fn) - end, suspend_fns)) + return coroutine.yield(InstallContext.CONTEXT_REQUEST) end return M diff --git a/lua/mason-core/installer/location.lua b/lua/mason-core/installer/location.lua new file mode 100644 index 00000000..2cc038e4 --- /dev/null +++ b/lua/mason-core/installer/location.lua @@ -0,0 +1,63 @@ +local Path = require "mason-core.path" +local Result = require "mason-core.result" +local fs = require "mason-core.fs" + +---@class InstallLocation +---@field private dir string +local InstallLocation = {} +InstallLocation.__index = InstallLocation + +---@param dir string +function InstallLocation.new(dir) + return setmetatable({ + dir = dir, + }, InstallLocation) +end + +function InstallLocation:get_dir() + return self.dir +end + +---@async +function InstallLocation:initialize() + return Result.try(function(try) + for _, p in ipairs { + self.dir, + self:bin(), + self:share(), + self:package(), + self:staging(), + } do + if not fs.async.dir_exists(p) then + try(Result.pcall(fs.async.mkdirp, p)) + end + end + end) +end + +---@param path string? +function InstallLocation:bin(path) + return Path.concat { self.dir, "bin", path } +end + +---@param path string? +function InstallLocation:share(path) + return Path.concat { self.dir, "share", path } +end + +---@param path string? +function InstallLocation:package(path) + return Path.concat { self.dir, "packages", path } +end + +---@param path string? +function InstallLocation:staging(path) + return Path.concat { self.dir, "staging", path } +end + +---@param name string +function InstallLocation:lockfile(name) + return self:staging(("%s.lock"):format(name)) +end + +return InstallLocation diff --git a/lua/mason-core/installer/runner.lua b/lua/mason-core/installer/runner.lua new file mode 100644 index 00000000..175610d5 --- /dev/null +++ b/lua/mason-core/installer/runner.lua @@ -0,0 +1,218 @@ +local Result = require "mason-core.result" +local _ = require "mason-core.functional" +local a = require "mason-core.async" +local compiler = require "mason-core.installer.compiler" +local fs = require "mason-core.fs" +local linker = require "mason-core.installer.linker" +local log = require "mason-core.log" +local registry = require "mason-registry" + +local InstallContext = require "mason-core.installer.context" +local InstallContextCwd = require "mason-core.installer.context.cwd" +local InstallContextFs = require "mason-core.installer.context.fs" +local InstallContextSpawn = require "mason-core.installer.context.spawn" + +---@class InstallRunner +---@field location InstallLocation +---@field handle InstallHandle +---@field semaphore Semaphore +---@field permit Permit? +local InstallRunner = {} +InstallRunner.__index = InstallRunner + +---@param location InstallLocation +---@param handle InstallHandle +---@param semaphore Semaphore +function InstallRunner.new(location, handle, semaphore) + return setmetatable({ + location = location, + semaphore = semaphore, + handle = handle, + }, InstallRunner) +end + +---@param opts PackageInstallOpts +---@param callback? fun(success: boolean, result: any) +function InstallRunner:execute(opts, callback) + local handle = self.handle + log.fmt_info("Executing installer for %s %s", handle.package, opts) + + local context_cwd = InstallContextCwd.new(self.location) + local context_spawn = InstallContextSpawn.new(context_cwd, handle, false) + local context_fs = InstallContextFs.new(context_cwd) + local context = InstallContext.new(handle, context_cwd, context_spawn, context_fs, opts) + + local tailed_output = {} + + if opts.debug then + local function append_log(chunk) + tailed_output[#tailed_output + 1] = chunk + end + handle:on("stdout", append_log) + handle:on("stderr", append_log) + end + + ---@async + local function finalize_logs(success, result) + if not success then + context.stdio_sink.stderr(tostring(result)) + context.stdio_sink.stderr "\n" + end + + if opts.debug then + context.fs:write_file("mason-debug.log", table.concat(tailed_output, "")) + context.stdio_sink.stdout(("[debug] Installation directory retained at %q.\n"):format(context.cwd:get())) + end + end + + ---@async + local finalize = a.scope(function(success, result) + finalize_logs(success, result) + + if not opts.debug and not success then + -- clean up installation dir + pcall(function() + fs.async.rmrf(context.cwd:get()) + end) + end + + if not handle:is_closing() then + handle:close() + end + + self:release_lock() + self:release_permit() + + if callback then + callback(success, result) + end + + if success then + log.fmt_info("Installation succeeded for %s", handle.package) + handle.package:emit("install:success", handle) + registry:emit("package:install:success", handle.package, handle) + else + log.fmt_error("Installation failed for %s error=%s", handle.package, result) + handle.package:emit("install:failed", handle, result) + registry:emit("package:install:failed", handle.package, handle, result) + end + end) + + local cancel_execution = a.run(function() + return Result.try(function(try) + try(self:acquire_permit()) + try(self.location:initialize()) + try(self:acquire_lock(opts.force)) + + context.receipt:with_start_time(vim.loop.gettimeofday()) + + -- 1. initialize working directory + try(context_cwd:initialize(handle)) + + -- 2. run installer + ---@type async fun(ctx: InstallContext): Result + local installer = try(compiler.compile(handle.package.spec, opts)) + try(context:execute(installer)) + + -- 3. promote temporary installation dir + try(Result.pcall(function() + context:promote_cwd() + end)) + + -- 4. link package & write receipt + return linker + .link(context) + :and_then(function() + return context:build_receipt(context) + end) + :and_then( + ---@param receipt InstallReceipt + function(receipt) + return receipt:write(context.cwd:get()) + end + ) + :on_failure(function() + -- unlink any links that were made before failure + context:build_receipt():on_success( + ---@param receipt InstallReceipt + function(receipt) + linker.unlink(handle.package, receipt):on_failure(function(err) + log.error("Failed to unlink failed installation.", err) + end) + end + ) + end) + end):get_or_throw() + end, finalize) + + handle:once("terminate", function() + cancel_execution() + local function on_close() + finalize(false, "Installation was aborted.") + end + if handle:is_closed() then + on_close() + else + handle:once("closed", on_close) + end + end) +end + +---@async +---@private +function InstallRunner:release_lock() + pcall(fs.async.unlink, self.location:lockfile(self.handle.package.name)) +end + +---@async +---@param force boolean? +---@private +function InstallRunner:acquire_lock(force) + local pkg = self.handle.package + log.debug("Attempting to lock package", pkg) + local lockfile = self.location:lockfile(pkg.name) + if force ~= true and fs.async.file_exists(lockfile) then + log.error("Lockfile already exists.", pkg) + return Result.failure( + ("Lockfile exists, installation is already running in another process (pid: %s). Run with :MasonInstall --force to bypass."):format( + fs.async.read_file(lockfile) + ) + ) + end + a.scheduler() + fs.async.write_file(lockfile, vim.fn.getpid()) + log.debug("Wrote lockfile", pkg) + return Result.success(lockfile) +end + +---@async +---@private +function InstallRunner:acquire_permit() + local handle = self.handle + if handle:is_active() or handle:is_closed() then + log.fmt_debug("Received active or closed handle %s", handle) + return Result.failure "Invalid handle state." + end + + handle:queued() + local permit = self.semaphore:acquire() + if handle:is_closed() then + permit:forget() + log.fmt_trace("Installation was aborted %s", handle) + return Result.failure "Installation was aborted." + end + log.fmt_trace("Activating handle %s", handle) + handle:active() + self.permit = permit + return Result.success() +end + +---@private +function InstallRunner:release_permit() + if self.permit then + self.permit:forget() + self.permit = nil + end +end + +return InstallRunner diff --git a/lua/mason-core/package/init.lua b/lua/mason-core/package/init.lua index bc98a72a..b0da8a61 100644 --- a/lua/mason-core/package/init.lua +++ b/lua/mason-core/package/init.lua @@ -1,14 +1,17 @@ local EventEmitter = require "mason-core.EventEmitter" +local InstallLocation = require "mason-core.installer.location" +local InstallRunner = require "mason-core.installer.runner" local Optional = require "mason-core.optional" local Purl = require "mason-core.purl" local Result = require "mason-core.result" local _ = require "mason-core.functional" -local a = require "mason-core.async" local fs = require "mason-core.fs" local log = require "mason-core.log" local path = require "mason-core.path" local platform = require "mason-core.platform" local registry = require "mason-registry" +local settings = require "mason.settings" +local Semaphore = require("mason-core.async.control").Semaphore ---@class Package : EventEmitter ---@field name string @@ -135,81 +138,56 @@ end ---@alias PackageInstallOpts { version?: string, debug?: boolean, target?: string, force?: boolean, strict?: boolean } ----@param opts? PackageInstallOpts ----@return InstallHandle -function Package:install(opts) - opts = opts or {} +-- TODO this needs to be elsewhere +local semaphore = Semaphore.new(settings.current.max_concurrent_installers) + +function Package:is_installing() return self:get_handle() - :map(function(handle) - if not handle:is_closed() then - log.fmt_debug("Handle %s already exist for package %s", handle, self) - return handle + :map( + ---@param handle InstallHandle + function(handle) + return not handle:is_closed() end - end) - :or_else_get(function() - local handle = self:new_handle() - a.run( - require("mason-core.installer").execute, - ---@param success boolean - ---@param result Result - function(success, result) - if not success then - -- Installer failed abnormally (i.e. unexpected exception in the installer code itself). - log.error("Unexpected error", result) - handle.stdio.sink.stderr(tostring(result)) - handle.stdio.sink.stderr "\nInstallation failed abnormally. Please report this error." - self:emit("install:failed", handle) - registry:emit("package:install:failed", self, handle) + ) + :or_else(false) +end - -- We terminate _after_ emitting failure events because [termination -> failed] have different - -- meaning than [failed -> terminate] ([termination -> failed] is interpreted as a triggered - -- termination). - if not handle:is_closed() and not handle.is_terminated then - handle:terminate() - end - return - end - result - :on_success(function() - self:emit("install:success", handle) - registry:emit("package:install:success", self, handle) - end) - :on_failure(function() - self:emit("install:failed", handle) - registry:emit("package:install:failed", self, handle) - end) - end, - handle, - opts - ) - return handle - end) +---@param opts? PackageInstallOpts +---@param callback? fun(success: boolean, result: any) +---@return InstallHandle +function Package:install(opts, callback) + opts = opts or {} + assert(not self:is_installing(), "Package is already installing.") + local handle = self:new_handle() + local runner = InstallRunner.new(InstallLocation.new(settings.current.install_root_dir), handle, semaphore) + runner:execute(opts, callback) + return handle end +---@return boolean function Package:uninstall() - local was_unlinked = self:unlink() - if was_unlinked then - self:emit "uninstall:success" - registry:emit("package:uninstall:success", self) - end - return was_unlinked + return self:get_receipt() + :map(function(receipt) + self:unlink(receipt) + self:emit("uninstall:success", receipt) + registry:emit("package:uninstall:success", self, receipt) + return true + end) + :or_else(false) end -function Package:unlink() +---@private +---@param receipt InstallReceipt +function Package:unlink(receipt) log.fmt_trace("Unlinking %s", self) local install_path = self:get_install_path() + -- 1. Unlink - self:get_receipt():if_present(function(receipt) - local linker = require "mason-core.installer.linker" - linker.unlink(self, receipt):get_or_throw() - end) + local linker = require "mason-core.installer.linker" + linker.unlink(self, receipt):get_or_throw() -- 2. Remove installation artifacts - if fs.sync.dir_exists(install_path) then - fs.sync.rmrf(install_path) - return true - end - return false + fs.sync.rmrf(install_path) end function Package:is_installed() @@ -260,18 +238,18 @@ end ---@param opts? PackageInstallOpts function Package:is_installable(opts) - return require("mason-core.installer.registry").parse(self.spec, opts or {}):is_success() + return require("mason-core.installer.compiler").parse(self.spec, opts or {}):is_success() end ---@return Result # Result<string[]> function Package:get_all_versions() - local registry_installer = require "mason-core.installer.registry" + local compiler = require "mason-core.installer.compiler" return Result.try(function(try) ---@type Purl local purl = try(Purl.parse(self.spec.source.id)) - ---@type InstallerProvider - local provider = try(registry_installer.get_provider(purl)) - return provider.get_versions(purl, self.spec.source) + ---@type InstallerCompiler + local compiler = try(compiler.get_compiler(purl)) + return compiler.get_versions(purl, self.spec.source) end) end diff --git a/lua/mason-core/receipt.lua b/lua/mason-core/receipt.lua index d9fe9d88..748cab38 100644 --- a/lua/mason-core/receipt.lua +++ b/lua/mason-core/receipt.lua @@ -1,3 +1,7 @@ +local Result = require "mason-core.result" +local fs = require "mason-core.fs" +local path = require "mason-core.path" + local M = {} ---@alias InstallReceiptSchemaVersion @@ -56,11 +60,11 @@ function InstallReceipt:get_links() end ---@async ----@param cwd string -function InstallReceipt:write(cwd) - local path = require "mason-core.path" - local fs = require "mason-core.fs" - fs.async.write_file(path.concat { cwd, "mason-receipt.json" }, vim.json.encode(self)) +---@param dir string +function InstallReceipt:write(dir) + return Result.pcall(function() + fs.async.write_file(path.concat { dir, "mason-receipt.json" }, vim.json.encode(self)) + end) end ---@class InstallReceiptBuilder diff --git a/lua/mason-registry/sources/util.lua b/lua/mason-registry/sources/util.lua index 80d5f16f..04ab7845 100644 --- a/lua/mason-registry/sources/util.lua +++ b/lua/mason-registry/sources/util.lua @@ -1,8 +1,8 @@ local Optional = require "mason-core.optional" local Pkg = require "mason-core.package" local _ = require "mason-core.functional" +local compiler = require "mason-core.installer.compiler" local log = require "mason-core.log" -local registry_installer = require "mason-core.installer.registry" local M = {} @@ -10,7 +10,7 @@ local M = {} function M.map_registry_spec(spec) spec.schema = spec.schema or "registry+v1" - if not registry_installer.SCHEMA_CAP[spec.schema] then + if not compiler.SCHEMA_CAP[spec.schema] then log.fmt_debug("Excluding package=%s with unsupported schema_version=%s", spec.name, spec.schema) return Optional.empty() end diff --git a/lua/mason-test/helpers.lua b/lua/mason-test/helpers.lua new file mode 100644 index 00000000..57b486ea --- /dev/null +++ b/lua/mason-test/helpers.lua @@ -0,0 +1,33 @@ +local InstallContext = require "mason-core.installer.context" +local InstallContextCwd = require "mason-core.installer.context.cwd" +local InstallContextFs = require "mason-core.installer.context.fs" +local InstallContextSpawn = require "mason-core.installer.context.spawn" +local InstallHandle = require "mason-core.installer.handle" +local InstallLocation = require "mason-core.installer.location" +local Result = require "mason-core.result" +local registry = require "mason-registry" +local spy = require "luassert.spy" + +local M = {} + +---@param opts? { install_opts?: PackageInstallOpts, package?: string } +function M.create_context(opts) + local pkg = registry.get_package(opts and opts.package or "dummy") + local handle = InstallHandle.new(pkg) + local location = InstallLocation.new "/tmp/install-dir" + local context_cwd = InstallContextCwd.new(location):set(location.dir) + local context_spawn = InstallContextSpawn.new(context_cwd, handle, false) + local context_fs = InstallContextFs.new(context_cwd) + local context = InstallContext.new(handle, context_cwd, context_spawn, context_fs, opts and opts.install_opts or {}) + context.spawn = setmetatable({}, { + __index = function(s, cmd) + s[cmd] = spy.new(function() + return Result.success { stdout = nil, stderr = nil } + end) + return s[cmd] + end, + }) + return context +end + +return M diff --git a/tests/helpers/lua/luassertx.lua b/tests/helpers/lua/luassertx.lua index e9bc4e44..3de3bf15 100644 --- a/tests/helpers/lua/luassertx.lua +++ b/tests/helpers/lua/luassertx.lua @@ -2,6 +2,26 @@ local a = require "mason-core.async" local assert = require "luassert" local match = require "luassert.match" +local function wait(_, arguments) + ---@type (fun()) Function to execute until it does not error. + local assertions_fn = arguments[1] + ---@type number Timeout in milliseconds. Defaults to 5000. + local timeout = arguments[2] or 5000 + + local err + if + not vim.wait(timeout, function() + local ok, err_ = pcall(assertions_fn) + err = err_ + return ok + end, math.min(timeout, 100)) + then + error(err) + end + + return true +end + local function wait_for(_, arguments) ---@type (fun()) Function to execute until it does not error. local assertions_fn = arguments[1] @@ -76,3 +96,4 @@ assert:register("matcher", "list_containing", list_containing) assert:register("matcher", "instanceof", instanceof) assert:register("matcher", "capture", capture) assert:register("assertion", "wait_for", wait_for) +assert:register("assertion", "wait", wait) diff --git a/tests/helpers/lua/test_helpers.lua b/tests/helpers/lua/test_helpers.lua deleted file mode 100644 index c7d6f983..00000000 --- a/tests/helpers/lua/test_helpers.lua +++ /dev/null @@ -1,70 +0,0 @@ ----@diagnostic disable: lowercase-global -local spy = require "luassert.spy" -local util = require "luassert.util" - -local InstallContext = require "mason-core.installer.context" -local InstallHandle = require "mason-core.installer.handle" -local Result = require "mason-core.result" -local a = require "mason-core.async" -local path = require "mason-core.path" -local registry = require "mason-registry" - --- selene: allow(unused_variable) -function async_test(suspend_fn) - return function() - local ok, err = pcall(a.run_blocking, suspend_fn) - if not ok then - error(err, util.errorlevel()) - end - end -end - --- selene: allow(unscoped_variables, incorrect_standard_library_use) -mockx = { - just_runs = function() end, - returns = function(val) - return function() - return val - end - end, - throws = function(exception) - return function() - error(exception, 2) - end - end, -} - ----@param opts? PackageInstallOpts -function create_dummy_context(opts) - local ctx = InstallContextGenerator(InstallHandleGenerator "registry", opts) - ctx.cwd:set(path.package_build_prefix "registry") - ctx.spawn = setmetatable({}, { - __index = function(s, cmd) - s[cmd] = spy.new(function() - return Result.success { stdout = nil, stderr = nil } - end) - return s[cmd] - end, - }) - return ctx -end - --- selene: allow(unused_variable) ----@param package_name string -function InstallHandleGenerator(package_name) - return InstallHandle.new(registry.get_package(package_name)) -end - --- selene: allow(unused_variable) ----@param handle InstallHandle ----@param opts PackageInstallOpts? -function InstallContextGenerator(handle, opts) - local context = InstallContext.new(handle, opts or {}) - context.spawn = setmetatable({ strict_mode = true }, { - __index = function(self, cmd) - self[cmd] = spy.new(mockx.just_runs()) - return self[cmd] - end, - }) - return context -end diff --git a/tests/mason-core/EventEmitter_spec.lua b/tests/mason-core/EventEmitter_spec.lua index caec8a80..76a9964b 100644 --- a/tests/mason-core/EventEmitter_spec.lua +++ b/tests/mason-core/EventEmitter_spec.lua @@ -32,26 +32,23 @@ describe("EventEmitter", function() it("should remove registered event handlers", function() local emitter = EventEmitter.init(setmetatable({}, { __index = EventEmitter })) - local my_event_handler = spy.new() - emitter:on("my:event", my_event_handler --[[@as fun()]]) - emitter:once("my:event", my_event_handler --[[@as fun()]]) + local my_event_handler = spy.new() --[[@as fun()]] + emitter:on("my:event", my_event_handler) + emitter:once("my:event", my_event_handler) - emitter:off("my:event", my_event_handler --[[@as fun()]]) + emitter:off("my:event", my_event_handler) emitter:emit("my:event", { table = "value" }) assert.spy(my_event_handler).was_called(0) end) - it( - "should print errors in handlers", - async_test(function() - spy.on(vim.api, "nvim_err_writeln") - local emitter = EventEmitter.init(setmetatable({}, { __index = EventEmitter })) - emitter:on("event", mockx.throws "My error.") - emitter:emit "event" - a.wait(vim.schedule) - assert.spy(vim.api.nvim_err_writeln).was_called(1) - assert.spy(vim.api.nvim_err_writeln).was_called_with "My error." - end) - ) + it("should print errors in handlers", function() + spy.on(vim.api, "nvim_err_writeln") + local emitter = EventEmitter.init(setmetatable({}, { __index = EventEmitter })) + emitter:on("event", mockx.throws "My error.") + emitter:emit "event" + a.run_blocking(a.wait, vim.schedule) + assert.spy(vim.api.nvim_err_writeln).was_called(1) + assert.spy(vim.api.nvim_err_writeln).was_called_with "My error." + end) end) diff --git a/tests/mason-core/async/async_spec.lua b/tests/mason-core/async/async_spec.lua index 61eeeb1b..a4197b85 100644 --- a/tests/mason-core/async/async_spec.lua +++ b/tests/mason-core/async/async_spec.lua @@ -31,51 +31,39 @@ describe("async", function() assert.equals(8, value) end) - it( - "should pass arguments to .run", - async_test(function() - local callback = spy.new() - local start = timestamp() - a.run(a.sleep, callback, 100) - assert.wait_for(function() - assert.spy(callback).was_called(1) - local stop = timestamp() - local grace_ms = 25 - assert.is_true((stop - start) >= (100 - grace_ms)) - end, 150) - end) - ) + it("should pass arguments to .run", function() + local fn = spy.new() + a.run(function(...) + fn(...) + end, spy.new(), 100, 200) + assert.spy(fn).was_called(1) + assert.spy(fn).was_called_with(100, 200) + end) - it( - "should wrap callback-style async functions", - async_test(function() - local stdio = process.in_memory_sink() - local success, exit_code = a.promisify(process.spawn)("env", { - args = {}, - env = { "FOO=BAR", "BAR=BAZ" }, - stdio_sink = stdio.sink, - }) - assert.is_true(success) - assert.equals(0, exit_code) - assert.equals("FOO=BAR\nBAR=BAZ\n", table.concat(stdio.buffers.stdout, "")) - end) - ) + it("should wrap callback-style async functions via promisify", function() + local async_spawn = _.compose(_.table_pack, a.promisify(process.spawn)) + local stdio = process.in_memory_sink() + local success, exit_code = unpack(a.run_blocking(async_spawn, "env", { + args = {}, + env = { "FOO=BAR", "BAR=BAZ" }, + stdio_sink = stdio.sink, + })) + assert.is_true(success) + assert.equals(0, exit_code) + assert.equals("FOO=BAR\nBAR=BAZ\n", table.concat(stdio.buffers.stdout, "")) + end) - it( - "should reject callback-style functions", - async_test(function() - local err = assert.has_error(function() - a.promisify(function(arg1, cb) - cb(arg1, nil) - end, true) "påskmust" - end) - assert.equals(err, "påskmust") + it("should propagate errors in callback-style functions via promisify", function() + local err = assert.has_error(function() + a.run_blocking(a.promisify(function(cb) + cb "Error message." + end, true)) end) - ) + assert.equals(err, "Error message.") + end) - it( - "should return all values", - async_test(function() + it("should return all values from a.wait", function() + a.run_blocking(function() local val1, val2, val3 = a.wait(function(resolve) resolve(1, 2, 3) end) @@ -83,35 +71,32 @@ describe("async", function() assert.equals(2, val2) assert.equals(3, val3) end) - ) + end) - it( - "should cancel coroutine", - async_test(function() - local james_bond = spy.new() - local poutine = a.scope(function() - a.sleep(100) - james_bond() + it("should cancel coroutine", function() + local capture = spy.new() + a.run_blocking(function() + local cancel = a.scope(function() + a.sleep(10) + capture() end)() - poutine() - a.sleep(200) - assert.spy(james_bond).was_not.called() + cancel() + a.sleep(20) end) - ) + assert.spy(capture).was_not.called() + end) - it( - "should raise error if async function raises error", - async_test(function() + it("should raise error if async function raises error", function() + a.run_blocking(function() local err = assert.has.errors(a.promisify(function() error "something went wrong" end)) assert.is_true(match.has_match "something went wrong$"(err)) end) - ) + end) - it( - "should raise error if async function rejects", - async_test(function() + it("should raise error if async function rejects", function() + a.run_blocking(function() local err = assert.has.errors(function() a.wait(function(_, reject) reject "This is an error" @@ -119,18 +104,17 @@ describe("async", function() end) assert.equals("This is an error", err) end) - ) + end) - it( - "should pass nil arguments to promisified functions", - async_test(function() - local fn = spy.new(function(_, _, _, _, _, _, _, cb) - cb() - end) + it("should pass nil arguments to promisified functions", function() + local fn = spy.new(function(_, _, _, _, _, _, _, cb) + cb() + end) + a.run_blocking(function() a.promisify(fn)(nil, 2, nil, 4, nil, nil, 7) - assert.spy(fn).was_called_with(nil, 2, nil, 4, nil, nil, 7, match.is_function()) end) - ) + assert.spy(fn).was_called_with(nil, 2, nil, 4, nil, nil, 7, match.is_function()) + end) it("should accept yielding non-promise values to parent coroutine context", function() local thread = coroutine.create(function(val) @@ -143,60 +127,56 @@ describe("async", function() assert.equals(1337, value) end) - it( - "should run all suspending functions concurrently", - async_test(function() - local start = timestamp() - local function sleep(ms, ret_val) - return function() - a.sleep(ms) - return ret_val - end + it("should run all suspending functions concurrently", function() + local function sleep(ms, ret_val) + return function() + a.sleep(ms) + return ret_val end - local one, two, three, four, five = a.wait_all { + end + local start = timestamp() + local one, two, three, four, five = unpack(a.run_blocking(function() + return _.table_pack(a.wait_all { sleep(100, 1), sleep(100, "two"), sleep(100, "three"), sleep(100, 4), sleep(100, 5), - } - local grace = 50 - local delta = timestamp() - start - assert.is_true(delta <= (100 + grace)) - assert.is_true(delta >= (100 - grace)) - assert.equals(1, one) - assert.equals("two", two) - assert.equals("three", three) - assert.equals(4, four) - assert.equals(5, five) - end) - ) + }) + end)) + local grace = 50 + local delta = timestamp() - start + assert.is_true(delta <= (100 + grace)) + assert.is_true(delta >= (100 - grace)) + assert.equals(1, one) + assert.equals("two", two) + assert.equals("three", three) + assert.equals(4, four) + assert.equals(5, five) + end) - it( - "should run all suspending functions concurrently", - async_test(function() - local start = timestamp() - local called = spy.new() - local function sleep(ms, ret_val) - return function() - a.sleep(ms) - called() - return ret_val - end + it("should run all suspending functions concurrently", function() + local start = timestamp() + local called = spy.new() + local function sleep(ms, ret_val) + return function() + a.sleep(ms) + called() + return ret_val end - local first = a.wait_first { - sleep(150, 1), - sleep(50, "first"), - sleep(150, "three"), - sleep(150, 4), - sleep(150, 5), - } - local grace = 20 - local delta = timestamp() - start - assert.is_true(delta <= (50 + grace)) - assert.equals("first", first) - end) - ) + end + local first = a.run_blocking(a.wait_first, { + sleep(150, 1), + sleep(50, "first"), + sleep(150, "three"), + sleep(150, 4), + sleep(150, 5), + }) + local grace = 20 + local delta = timestamp() - start + assert.is_true(delta <= (50 + grace)) + assert.equals("first", first) + end) it("should yield back immediately when not providing any functions", function() assert.is_nil(a.wait_first {}) diff --git a/tests/mason-core/fetch_spec.lua b/tests/mason-core/fetch_spec.lua index 107b6417..5a890318 100644 --- a/tests/mason-core/fetch_spec.lua +++ b/tests/mason-core/fetch_spec.lua @@ -6,118 +6,115 @@ local stub = require "luassert.stub" local version = require "mason.version" describe("fetch", function() - it( - "should exhaust all candidates", - async_test(function() - stub(spawn, "wget") - stub(spawn, "curl") - spawn.wget.returns(Result.failure "wget failure") - spawn.curl.returns(Result.failure "curl failure") + local snapshot - local result = fetch("https://api.github.com", { - headers = { ["X-Custom-Header"] = "here" }, - }) - assert.is_true(result:is_failure()) - assert.spy(spawn.wget).was_called(1) - assert.spy(spawn.curl).was_called(1) - assert.spy(spawn.wget).was_called_with { - { - ("--header=User-Agent: mason.nvim %s (+https://github.com/williamboman/mason.nvim)"):format( - version.VERSION - ), - "--header=X-Custom-Header: here", - }, - "-nv", - "-o", - "/dev/null", - "-O", - "-", - "--timeout=30", - "--method=GET", - vim.NIL, -- body-data - "https://api.github.com", - } + before_each(function() + snapshot = assert.snapshot() + end) - assert.spy(spawn.curl).was_called_with(match.tbl_containing { - match.same { - { - "-H", - ("User-Agent: mason.nvim %s (+https://github.com/williamboman/mason.nvim)"):format( - version.VERSION - ), - }, - { - "-H", - "X-Custom-Header: here", - }, - }, - "-fsSL", - match.same { "-X", "GET" }, - vim.NIL, -- data - vim.NIL, -- out file - match.same { "--connect-timeout", 30 }, - "https://api.github.com", - on_spawn = match.is_function(), - }) - end) - ) + after_each(function() + snapshot:revert() + end) - it( - "should return stdout", - async_test(function() - stub(spawn, "wget") - spawn.wget.returns(Result.success { - stdout = [[{"data": "here"}]], - }) - local result = fetch "https://api.github.com/data" - assert.is_true(result:is_success()) - assert.equals([[{"data": "here"}]], result:get_or_throw()) - end) - ) + it("should exhaust all candidates", function() + stub(spawn, "wget") + stub(spawn, "curl") + spawn.wget.returns(Result.failure "wget failure") + spawn.curl.returns(Result.failure "curl failure") - it( - "should respect out_file opt", - async_test(function() - stub(spawn, "wget") - stub(spawn, "curl") - spawn.wget.returns(Result.failure "wget failure") - spawn.curl.returns(Result.failure "curl failure") - fetch("https://api.github.com/data", { out_file = "/test.json" }) + local result = fetch("https://api.github.com", { + headers = { ["X-Custom-Header"] = "here" }, + }) + assert.is_true(result:is_failure()) + assert.spy(spawn.wget).was_called(1) + assert.spy(spawn.curl).was_called(1) + assert.spy(spawn.wget).was_called_with { + { + ("--header=User-Agent: mason.nvim %s (+https://github.com/williamboman/mason.nvim)"):format( + version.VERSION + ), + "--header=X-Custom-Header: here", + }, + "-nv", + "-o", + "/dev/null", + "-O", + "-", + "--timeout=30", + "--method=GET", + vim.NIL, -- body-data + "https://api.github.com", + } - assert.spy(spawn.wget).was_called_with { + assert.spy(spawn.curl).was_called_with(match.tbl_containing { + match.same { + { + "-H", + ("User-Agent: mason.nvim %s (+https://github.com/williamboman/mason.nvim)"):format(version.VERSION), + }, { - ("--header=User-Agent: mason.nvim %s (+https://github.com/williamboman/mason.nvim)"):format( - version.VERSION - ), + "-H", + "X-Custom-Header: here", }, - "-nv", - "-o", - "/dev/null", - "-O", - "/test.json", - "--timeout=30", - "--method=GET", - vim.NIL, -- body-data - "https://api.github.com/data", - } + }, + "-fsSL", + match.same { "-X", "GET" }, + vim.NIL, -- data + vim.NIL, -- out file + match.same { "--connect-timeout", 30 }, + "https://api.github.com", + on_spawn = match.is_function(), + }) + end) - assert.spy(spawn.curl).was_called_with(match.tbl_containing { - match.same { - { - "-H", - ("User-Agent: mason.nvim %s (+https://github.com/williamboman/mason.nvim)"):format( - version.VERSION - ), - }, + it("should return stdout", function() + stub(spawn, "curl") + spawn.curl.returns(Result.success { + stdout = [[{"data": "here"}]], + }) + local result = fetch "https://api.github.com/data" + assert.is_true(result:is_success()) + assert.equals([[{"data": "here"}]], result:get_or_throw()) + end) + + it("should respect out_file opt", function() + stub(spawn, "wget") + stub(spawn, "curl") + spawn.wget.returns(Result.failure "wget failure") + spawn.curl.returns(Result.failure "curl failure") + fetch("https://api.github.com/data", { out_file = "/test.json" }) + + assert.spy(spawn.wget).was_called_with { + { + ("--header=User-Agent: mason.nvim %s (+https://github.com/williamboman/mason.nvim)"):format( + version.VERSION + ), + }, + "-nv", + "-o", + "/dev/null", + "-O", + "/test.json", + "--timeout=30", + "--method=GET", + vim.NIL, -- body-data + "https://api.github.com/data", + } + + assert.spy(spawn.curl).was_called_with(match.tbl_containing { + match.same { + { + "-H", + ("User-Agent: mason.nvim %s (+https://github.com/williamboman/mason.nvim)"):format(version.VERSION), }, - "-fsSL", - match.same { "-X", "GET" }, - vim.NIL, -- data - match.same { "-o", "/test.json" }, - match.same { "--connect-timeout", 30 }, - "https://api.github.com/data", - on_spawn = match.is_function(), - }) - end) - ) + }, + "-fsSL", + match.same { "-X", "GET" }, + vim.NIL, -- data + match.same { "-o", "/test.json" }, + match.same { "--connect-timeout", 30 }, + "https://api.github.com/data", + on_spawn = match.is_function(), + }) + end) end) diff --git a/tests/mason-core/fs_spec.lua b/tests/mason-core/fs_spec.lua index 38f97eeb..bd3696da 100644 --- a/tests/mason-core/fs_spec.lua +++ b/tests/mason-core/fs_spec.lua @@ -8,17 +8,14 @@ describe("fs", function() } end) - it( - "refuses to rmrf paths outside of boundary", - async_test(function() - local e = assert.has_error(function() - fs.async.rmrf "/thisisa/path" - end) - - assert.equals( - [[Refusing to rmrf "/thisisa/path" which is outside of the allowed boundary "/foo". Please report this error at https://github.com/williamboman/mason.nvim/issues/new]], - e - ) + it("refuses to rmrf paths outside of boundary", function() + local e = assert.has_error(function() + fs.sync.rmrf "/thisisa/path" end) - ) + + assert.equals( + [[Refusing to rmrf "/thisisa/path" which is outside of the allowed boundary "/foo". Please report this error at https://github.com/williamboman/mason.nvim/issues/new]], + e + ) + end) end) diff --git a/tests/mason-core/installer/context_spec.lua b/tests/mason-core/installer/context_spec.lua index 646f7e30..9c1805cb 100644 --- a/tests/mason-core/installer/context_spec.lua +++ b/tests/mason-core/installer/context_spec.lua @@ -3,10 +3,20 @@ local path = require "mason-core.path" local pypi = require "mason-core.installer.managers.pypi" local registry = require "mason-registry" local stub = require "luassert.stub" +local test_helpers = require "mason-test.helpers" describe("installer", function() ---@module "mason-core.platform" local platform + local snapshot + + before_each(function() + snapshot = assert.snapshot() + end) + + after_each(function() + snapshot:revert() + end) before_each(function() package.loaded["mason-core.installer.platform"] = nil @@ -15,8 +25,7 @@ describe("installer", function() end) it("should write shell exec wrapper on Unix", function() - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle) + local ctx = test_helpers.create_context() stub(ctx.fs, "write_file") stub(ctx.fs, "file_exists") stub(ctx.fs, "dir_exists") @@ -44,8 +53,7 @@ exec bash -c 'echo $GREETING' "$@"]] platform.is.unix = false platform.is.linux = false platform.is.win = true - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle) + local ctx = test_helpers.create_context() stub(ctx.fs, "write_file") stub(ctx.fs, "file_exists") stub(ctx.fs, "dir_exists") @@ -68,8 +76,7 @@ cmd.exe /C echo %GREETING% %*]] it("should not write shell exec wrapper if new executable path already exists", function() local exec_rel_path = path.concat { "obscure", "path", "to", "server" } - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle) + local ctx = test_helpers.create_context() stub(ctx.fs, "file_exists") stub(ctx.fs, "dir_exists") ctx.fs.file_exists.on_call_with(match.is_ref(ctx.fs), exec_rel_path).returns(true) @@ -86,8 +93,7 @@ cmd.exe /C echo %GREETING% %*]] it("should write Node exec wrapper", function() local js_rel_path = path.concat { "some", "obscure", "path", "server.js" } local dummy = registry.get_package "dummy" - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle) + local ctx = test_helpers.create_context() stub(ctx, "write_shell_exec_wrapper") stub(ctx.fs, "file_exists") ctx.fs.file_exists.on_call_with(match.is_ref(ctx.fs), js_rel_path).returns(true) @@ -105,8 +111,7 @@ cmd.exe /C echo %GREETING% %*]] it("should write Ruby exec wrapper", function() local js_rel_path = path.concat { "some", "obscure", "path", "server.js" } local dummy = registry.get_package "dummy" - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle) + local ctx = test_helpers.create_context() stub(ctx, "write_shell_exec_wrapper") stub(ctx.fs, "file_exists") ctx.fs.file_exists.on_call_with(match.is_ref(ctx.fs), js_rel_path).returns(true) @@ -123,8 +128,7 @@ cmd.exe /C echo %GREETING% %*]] it("should not write Node exec wrapper if the target script doesn't exist", function() local js_rel_path = path.concat { "some", "obscure", "path", "server.js" } - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle) + local ctx = test_helpers.create_context() stub(ctx, "write_shell_exec_wrapper") stub(ctx.fs, "file_exists") ctx.fs.file_exists.on_call_with(match.is_ref(ctx.fs), js_rel_path).returns(false) @@ -142,8 +146,7 @@ cmd.exe /C echo %GREETING% %*]] it("should write Python exec wrapper", function() local dummy = registry.get_package "dummy" - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle) + local ctx = test_helpers.create_context() stub(ctx.cwd, "get") ctx.cwd.get.returns "/tmp/placeholder" stub(ctx, "write_shell_exec_wrapper") @@ -159,8 +162,7 @@ cmd.exe /C echo %GREETING% %*]] end) it("should not write Python exec wrapper if module cannot be found", function() - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle) + local ctx = test_helpers.create_context() stub(ctx.cwd, "get") ctx.cwd.get.returns "/tmp/placeholder" stub(ctx, "write_shell_exec_wrapper") @@ -181,8 +183,7 @@ cmd.exe /C echo %GREETING% %*]] it("should write exec wrapper", function() local dummy = registry.get_package "dummy" local exec_rel_path = path.concat { "obscure", "path", "to", "server" } - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle) + local ctx = test_helpers.create_context() stub(ctx, "write_shell_exec_wrapper") stub(ctx.fs, "file_exists") ctx.fs.file_exists.on_call_with(match.is_ref(ctx.fs), exec_rel_path).returns(true) @@ -201,8 +202,7 @@ cmd.exe /C echo %GREETING% %*]] it("should not write exec wrapper if target executable doesn't exist", function() local exec_rel_path = path.concat { "obscure", "path", "to", "server" } - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle) + local ctx = test_helpers.create_context() stub(ctx, "write_shell_exec_wrapper") stub(ctx.fs, "file_exists") ctx.fs.file_exists.on_call_with(match.is_ref(ctx.fs), exec_rel_path).returns(false) @@ -218,8 +218,7 @@ cmd.exe /C echo %GREETING% %*]] it("should write PHP exec wrapper", function() local php_rel_path = path.concat { "some", "obscure", "path", "cli.php" } local dummy = registry.get_package "dummy" - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle) + local ctx = test_helpers.create_context() stub(ctx, "write_shell_exec_wrapper") stub(ctx.fs, "file_exists") ctx.fs.file_exists.on_call_with(match.is_ref(ctx.fs), php_rel_path).returns(true) @@ -236,8 +235,7 @@ cmd.exe /C echo %GREETING% %*]] it("should not write PHP exec wrapper if the target script doesn't exist", function() local php_rel_path = path.concat { "some", "obscure", "path", "cli.php" } - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle) + local ctx = test_helpers.create_context() stub(ctx, "write_shell_exec_wrapper") stub(ctx.fs, "file_exists") ctx.fs.file_exists.on_call_with(match.is_ref(ctx.fs), php_rel_path).returns(false) diff --git a/tests/mason-core/installer/handle_spec.lua b/tests/mason-core/installer/handle_spec.lua index c301b28b..66a9a5c4 100644 --- a/tests/mason-core/installer/handle_spec.lua +++ b/tests/mason-core/installer/handle_spec.lua @@ -4,6 +4,16 @@ local spy = require "luassert.spy" local stub = require "luassert.stub" describe("installer handle", function() + local snapshot + + before_each(function() + snapshot = assert.snapshot() + end) + + after_each(function() + snapshot:revert() + end) + it("should register spawn handle", function() local handle = InstallHandle.new(mock.new {}) local spawn_handle_change_handler = spy.new() @@ -72,36 +82,33 @@ describe("installer handle", function() assert.spy(kill_handler).was_called_with(9) end) - it( - "should terminate handle", - async_test(function() - local process = require "mason-core.process" - stub(process, "kill") - local uv_handle1 = {} - local uv_handle2 = {} - local handle = InstallHandle.new(mock.new {}) - local kill_handler = spy.new() - local terminate_handler = spy.new() - local closed_handler = spy.new() + it("should terminate handle", function() + local process = require "mason-core.process" + stub(process, "kill") + local uv_handle1 = {} + local uv_handle2 = {} + local handle = InstallHandle.new(mock.new {}) + local kill_handler = spy.new() + local terminate_handler = spy.new() + local closed_handler = spy.new() - handle:once("kill", kill_handler) - handle:once("terminate", terminate_handler) - handle:once("closed", closed_handler) - handle.state = "ACTIVE" - handle.spawn_handles = { { uv_handle = uv_handle2 }, { uv_handle = uv_handle2 } } - handle:terminate() + handle:once("kill", kill_handler) + handle:once("terminate", terminate_handler) + handle:once("closed", closed_handler) + handle.state = "ACTIVE" + handle.spawn_handles = { { uv_handle = uv_handle2 }, { uv_handle = uv_handle2 } } + handle:terminate() - assert.spy(process.kill).was_called(2) - assert.spy(process.kill).was_called_with(uv_handle1, 15) - assert.spy(process.kill).was_called_with(uv_handle2, 15) - assert.spy(kill_handler).was_called(1) - assert.spy(kill_handler).was_called_with(15) - assert.spy(terminate_handler).was_called(1) - assert.is_true(handle.is_terminated) - assert.wait_for(function() - assert.is_true(handle:is_closed()) - assert.spy(closed_handler).was_called(1) - end) + assert.spy(process.kill).was_called(2) + assert.spy(process.kill).was_called_with(uv_handle1, 15) + assert.spy(process.kill).was_called_with(uv_handle2, 15) + assert.spy(kill_handler).was_called(1) + assert.spy(kill_handler).was_called_with(15) + assert.spy(terminate_handler).was_called(1) + assert.is_true(handle.is_terminated) + assert.wait(function() + assert.is_true(handle:is_closed()) + assert.spy(closed_handler).was_called(1) end) - ) + end) end) diff --git a/tests/mason-core/installer/installer_spec.lua b/tests/mason-core/installer/installer_spec.lua deleted file mode 100644 index 3e291308..00000000 --- a/tests/mason-core/installer/installer_spec.lua +++ /dev/null @@ -1,217 +0,0 @@ -local InstallContext = require "mason-core.installer.context" -local Result = require "mason-core.result" -local a = require "mason-core.async" -local fs = require "mason-core.fs" -local installer = require "mason-core.installer" -local match = require "luassert.match" -local path = require "mason-core.path" -local spy = require "luassert.spy" -local stub = require "luassert.stub" - -local function timestamp() - local seconds, microseconds = vim.loop.gettimeofday() - return (seconds * 1000) + math.floor(microseconds / 1000) -end - -describe("installer", function() - before_each(function() - package.loaded["dummy_package"] = nil - end) - - it( - "should call installer", - async_test(function() - spy.on(fs.async, "mkdirp") - spy.on(fs.async, "rename") - - local handle = InstallHandleGenerator "dummy" - spy.on(handle.package.spec.source, "install") - local result = installer.execute(handle, {}) - - assert.is_nil(result:err_or_nil()) - assert.spy(handle.package.spec.source.install).was_called(1) - assert - .spy(handle.package.spec.source.install) - .was_called_with(match.instanceof(InstallContext), match.is_table()) - assert.spy(fs.async.mkdirp).was_called_with(path.package_build_prefix "dummy") - assert.spy(fs.async.rename).was_called_with(path.package_build_prefix "dummy", path.package_prefix "dummy") - end) - ) - - it( - "should return failure if installer errors", - async_test(function() - spy.on(fs.async, "rmrf") - spy.on(fs.async, "rename") - local installer_fn = spy.new(function() - error("something went wrong. don't try again.", 0) - end) - local handler = InstallHandleGenerator "dummy" - stub(handler.package.spec.source, "install", installer_fn) - local result = installer.execute(handler, {}) - assert.spy(installer_fn).was_called(1) - assert.is_true(result:is_failure()) - assert.equals("something went wrong. don't try again.", result:err_or_nil()) - assert.spy(fs.async.rmrf).was_called_with(path.package_build_prefix "dummy") - assert.spy(fs.async.rename).was_not_called() - end) - ) - - it( - "should write receipt", - async_test(function() - spy.on(fs.async, "write_file") - local handle = InstallHandleGenerator "dummy" - stub(handle.package.spec.source, "install", function(ctx) - ctx.fs:write_file("target", "") - ctx.fs:write_file("file.jar", "") - ctx.fs:write_file("opt-cmd", "") - end) - handle.package.spec.bin = { - ["executable"] = "target", - } - handle.package.spec.share = { - ["package/file.jar"] = "file.jar", - } - handle.package.spec.opt = { - ["package/bin/opt-cmd"] = "opt-cmd", - } - installer.execute(handle, {}) - handle.package.spec.bin = {} - handle.package.spec.share = {} - handle.package.spec.opt = {} - assert.spy(fs.async.write_file).was_called_with( - ("%s/mason-receipt.json"):format(handle.package:get_install_path()), - match.capture(function(arg) - ---@type InstallReceipt - local receipt = vim.json.decode(arg) - assert.is_true(match.tbl_containing { - name = "dummy", - source = match.same { - type = handle.package.spec.schema, - id = handle.package.spec.source.id, - }, - schema_version = "1.2", - metrics = match.is_table(), - links = match.same { - bin = { executable = "target" }, - share = { ["package/file.jar"] = "file.jar" }, - opt = { ["package/bin/opt-cmd"] = "opt-cmd" }, - }, - }(receipt)) - end) - ) - end) - ) - - it( - "should run async functions concurrently", - async_test(function() - spy.on(fs.async, "write_file") - local capture = spy.new() - local start = timestamp() - local handle = InstallHandleGenerator "dummy" - stub(handle.package.spec.source, "install", function(ctx) - capture(installer.run_concurrently { - function() - a.sleep(100) - return installer.context() - end, - function() - a.sleep(100) - return "two" - end, - function() - a.sleep(100) - return "three" - end, - }) - end) - installer.execute(handle, {}) - local stop = timestamp() - local grace_ms = 25 - assert.is_true((stop - start) >= (100 - grace_ms)) - assert.spy(capture).was_called_with(match.instanceof(InstallContext), "two", "three") - end) - ) - - it( - "should write log files if debug is true", - async_test(function() - spy.on(fs.async, "write_file") - local handle = InstallHandleGenerator "dummy" - stub(handle.package.spec.source, "install", function(ctx) - ctx.stdio_sink.stdout "Hello stdout!\n" - ctx.stdio_sink.stderr "Hello " - ctx.stdio_sink.stderr "stderr!" - end) - installer.execute(handle, { debug = true }) - assert - .spy(fs.async.write_file) - .was_called_with(path.package_prefix "dummy/mason-debug.log", "Hello stdout!\nHello stderr!") - end) - ) - - it( - "should raise spawn errors in strict mode", - async_test(function() - local handle = InstallHandleGenerator "dummy" - stub(handle.package.spec.source, "install", function(ctx) - ctx.spawn.bash { "-c", "exit 42" } - end) - local result = installer.execute(handle, { debug = true }) - assert.same( - Result.failure { - exit_code = 42, - signal = 0, - }, - result - ) - assert.equals("spawn: bash failed with exit code 42 and signal 0. ", tostring(result:err_or_nil())) - end) - ) - - it( - "should lock package", - async_test(function() - local handle = InstallHandleGenerator "dummy" - local callback = spy.new() - stub(handle.package.spec.source, "install", function() - a.sleep(3000) - end) - - a.run(function() - return installer.execute(handle, { debug = true }) - end, callback) - - assert.wait_for(function() - assert.is_true(fs.sync.file_exists(path.package_lock "dummy")) - end) - handle:terminate() - assert.wait_for(function() - assert.spy(callback).was_called(1) - end) - assert.is_false(fs.sync.file_exists(path.package_lock "dummy")) - end) - ) - - it( - "should not run installer if package lock exists", - async_test(function() - local handle = InstallHandleGenerator "dummy" - local install = spy.new() - stub(handle.package.spec.source, "install", install) - - fs.sync.write_file(path.package_lock "dummy", "dummypid") - local result = installer.execute(handle, { debug = true }) - assert.is_true(fs.sync.file_exists(path.package_lock "dummy")) - fs.sync.unlink(path.package_lock "dummy") - - assert.spy(install).was_not_called() - assert.equals( - "Lockfile exists, installation is already running in another process (pid: dummypid). Run with :MasonInstall --force to bypass.", - result:err_or_nil() - ) - end) - ) -end) diff --git a/tests/mason-core/installer/linker_spec.lua b/tests/mason-core/installer/linker_spec.lua index 8bcf2607..9684f57d 100644 --- a/tests/mason-core/installer/linker_spec.lua +++ b/tests/mason-core/installer/linker_spec.lua @@ -1,7 +1,9 @@ +local a = require "mason-core.async" local fs = require "mason-core.fs" local path = require "mason-core.path" local registry = require "mason-registry" local stub = require "luassert.stub" +local test_helpers = require "mason-test.helpers" local WIN_CMD_SCRIPT = [[@ECHO off GOTO start @@ -15,6 +17,16 @@ CALL :find_dp0 endLocal & goto #_undefined_# 2>NUL || title %%COMSPEC%% & "%s" %%*]] describe("linker", function() + local snapshot + + before_each(function() + snapshot = assert.snapshot() + end) + + after_each(function() + snapshot:revert() + end) + ---@module "mason-core.installer.linker" local linker ---@module "mason-core.platform" @@ -27,130 +39,119 @@ describe("linker", function() linker = require "mason-core.installer.linker" end) - it( - "should symlink executable on Unix", - async_test(function() - local dummy = registry.get_package "dummy" - stub(fs.async, "file_exists") - stub(fs.async, "symlink") - stub(fs.async, "write_file") + it("should symlink executable on Unix", function() + local dummy = registry.get_package "dummy" + stub(fs.async, "file_exists") + stub(fs.async, "symlink") + stub(fs.async, "write_file") - fs.async.file_exists.on_call_with(path.bin_prefix "my-executable").returns(false) - fs.async.file_exists.on_call_with(path.bin_prefix "another-executable").returns(false) - fs.async.file_exists - .on_call_with(path.concat { dummy:get_install_path(), "nested", "path", "my-executable" }) - .returns(true) - fs.async.file_exists - .on_call_with(path.concat { dummy:get_install_path(), "another-executable" }) - .returns(true) + fs.async.file_exists.on_call_with(path.bin_prefix "my-executable").returns(false) + fs.async.file_exists.on_call_with(path.bin_prefix "another-executable").returns(false) + fs.async.file_exists + .on_call_with(path.concat { dummy:get_install_path(), "nested", "path", "my-executable" }) + .returns(true) + fs.async.file_exists.on_call_with(path.concat { dummy:get_install_path(), "another-executable" }).returns(true) - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle) - ctx:link_bin("my-executable", path.concat { "nested", "path", "my-executable" }) - ctx:link_bin("another-executable", "another-executable") - assert.is_true(linker.link(ctx):is_success()) + local ctx = test_helpers.create_context() + ctx:link_bin("my-executable", path.concat { "nested", "path", "my-executable" }) + ctx:link_bin("another-executable", "another-executable") + local result = a.run_blocking(linker.link, ctx) + assert.is_true(result:is_success()) - assert.spy(fs.async.write_file).was_called(0) - assert.spy(fs.async.symlink).was_called(2) - assert - .spy(fs.async.symlink) - .was_called_with(path.concat { dummy:get_install_path(), "another-executable" }, path.bin_prefix "another-executable") - assert.spy(fs.async.symlink).was_called_with( + assert.spy(fs.async.write_file).was_called(0) + assert.spy(fs.async.symlink).was_called(2) + assert + .spy(fs.async.symlink) + .was_called_with(path.concat { dummy:get_install_path(), "another-executable" }, path.bin_prefix "another-executable") + assert + .spy(fs.async.symlink) + .was_called_with( path.concat { dummy:get_install_path(), "nested", "path", "my-executable" }, path.bin_prefix "my-executable" ) - end) - ) + end) - it( - "should write executable wrapper on Windows", - async_test(function() - platform.is.darwin = false - platform.is.mac = false - platform.is.linux = false - platform.is.unix = false - platform.is.win = true + it("should write executable wrapper on Windows", function() + platform.is.darwin = false + platform.is.mac = false + platform.is.linux = false + platform.is.unix = false + platform.is.win = true - local dummy = registry.get_package "dummy" - stub(fs.async, "file_exists") - stub(fs.async, "symlink") - stub(fs.async, "write_file") + local dummy = registry.get_package "dummy" + stub(fs.async, "file_exists") + stub(fs.async, "symlink") + stub(fs.async, "write_file") - fs.async.file_exists.on_call_with(path.bin_prefix "my-executable").returns(false) - fs.async.file_exists.on_call_with(path.bin_prefix "another-executable").returns(false) - fs.async.file_exists - .on_call_with(path.concat { dummy:get_install_path(), "nested", "path", "my-executable" }) - .returns(true) - fs.async.file_exists - .on_call_with(path.concat { dummy:get_install_path(), "another-executable" }) - .returns(true) + fs.async.file_exists.on_call_with(path.bin_prefix "my-executable").returns(false) + fs.async.file_exists.on_call_with(path.bin_prefix "another-executable").returns(false) + fs.async.file_exists + .on_call_with(path.concat { dummy:get_install_path(), "nested", "path", "my-executable" }) + .returns(true) + fs.async.file_exists.on_call_with(path.concat { dummy:get_install_path(), "another-executable" }).returns(true) - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle) - ctx:link_bin("my-executable", path.concat { "nested", "path", "my-executable" }) - ctx:link_bin("another-executable", "another-executable") - assert.is_true(linker.link(ctx):is_success()) + local ctx = test_helpers.create_context() + ctx:link_bin("my-executable", path.concat { "nested", "path", "my-executable" }) + ctx:link_bin("another-executable", "another-executable") - assert.spy(fs.async.symlink).was_called(0) - assert.spy(fs.async.write_file).was_called(2) - assert.spy(fs.async.write_file).was_called_with( - path.bin_prefix "another-executable.cmd", - WIN_CMD_SCRIPT:format(path.concat { dummy:get_install_path(), "another-executable" }) - ) - assert.spy(fs.async.write_file).was_called_with( - path.bin_prefix "my-executable.cmd", - WIN_CMD_SCRIPT:format(path.concat { dummy:get_install_path(), "nested", "path", "my-executable" }) - ) - end) - ) + local result = a.run_blocking(linker.link, ctx) + assert.is_true(result:is_success()) - it( - "should symlink share files", - async_test(function() - local dummy = registry.get_package "dummy" - stub(fs.async, "mkdirp") - stub(fs.async, "dir_exists") - stub(fs.async, "file_exists") - stub(fs.async, "symlink") - stub(fs.async, "write_file") + assert.spy(fs.async.symlink).was_called(0) + assert.spy(fs.async.write_file).was_called(2) + assert.spy(fs.async.write_file).was_called_with( + path.bin_prefix "another-executable.cmd", + WIN_CMD_SCRIPT:format(path.concat { dummy:get_install_path(), "another-executable" }) + ) + assert.spy(fs.async.write_file).was_called_with( + path.bin_prefix "my-executable.cmd", + WIN_CMD_SCRIPT:format(path.concat { dummy:get_install_path(), "nested", "path", "my-executable" }) + ) + end) - -- mock non-existent dest files - fs.async.file_exists.on_call_with(path.share_prefix "share-file").returns(false) - fs.async.file_exists.on_call_with(path.share_prefix(path.concat { "nested", "share-file" })).returns(false) + it("should symlink share files", function() + local dummy = registry.get_package "dummy" + stub(fs.async, "mkdirp") + stub(fs.async, "dir_exists") + stub(fs.async, "file_exists") + stub(fs.async, "symlink") + stub(fs.async, "write_file") - fs.async.dir_exists.on_call_with(path.share_prefix()).returns(false) - fs.async.dir_exists.on_call_with(path.share_prefix "nested/path").returns(false) + -- mock non-existent dest files + fs.async.file_exists.on_call_with(path.share_prefix "share-file").returns(false) + fs.async.file_exists.on_call_with(path.share_prefix(path.concat { "nested", "share-file" })).returns(false) - -- mock existent source files - fs.async.file_exists.on_call_with(path.concat { dummy:get_install_path(), "share-file" }).returns(true) - fs.async.file_exists - .on_call_with(path.concat { dummy:get_install_path(), "nested", "path", "to", "share-file" }) - .returns(true) + fs.async.dir_exists.on_call_with(path.share_prefix()).returns(false) + fs.async.dir_exists.on_call_with(path.share_prefix "nested/path").returns(false) - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle) - ctx.links.share["nested/path/share-file"] = path.concat { "nested", "path", "to", "share-file" } - ctx.links.share["share-file"] = "share-file" + -- mock existent source files + fs.async.file_exists.on_call_with(path.concat { dummy:get_install_path(), "share-file" }).returns(true) + fs.async.file_exists + .on_call_with(path.concat { dummy:get_install_path(), "nested", "path", "to", "share-file" }) + .returns(true) - local result = linker.link(ctx) + local ctx = test_helpers.create_context() + ctx.links.share["nested/path/share-file"] = path.concat { "nested", "path", "to", "share-file" } + ctx.links.share["share-file"] = "share-file" - assert.is_true(result:is_success()) + local result = a.run_blocking(linker.link, ctx) - assert.spy(fs.async.write_file).was_called(0) - assert.spy(fs.async.symlink).was_called(2) - assert - .spy(fs.async.symlink) - .was_called_with(path.concat { dummy:get_install_path(), "share-file" }, path.share_prefix "share-file") - assert.spy(fs.async.symlink).was_called_with( - path.concat { dummy:get_install_path(), "nested", "path", "to", "share-file" }, - path.share_prefix "nested/path/share-file" - ) + assert.is_true(result:is_success()) - assert.spy(fs.async.mkdirp).was_called(2) - assert.spy(fs.async.mkdirp).was_called_with(path.share_prefix()) - assert.spy(fs.async.mkdirp).was_called_with(path.share_prefix "nested/path") - end) - ) + assert.spy(fs.async.write_file).was_called(0) + assert.spy(fs.async.symlink).was_called(2) + assert + .spy(fs.async.symlink) + .was_called_with(path.concat { dummy:get_install_path(), "share-file" }, path.share_prefix "share-file") + assert.spy(fs.async.symlink).was_called_with( + path.concat { dummy:get_install_path(), "nested", "path", "to", "share-file" }, + path.share_prefix "nested/path/share-file" + ) + + assert.spy(fs.async.mkdirp).was_called(2) + assert.spy(fs.async.mkdirp).was_called_with(path.share_prefix()) + assert.spy(fs.async.mkdirp).was_called_with(path.share_prefix "nested/path") + end) it("should copy share files on Windows", function() platform.is.darwin = false @@ -178,8 +179,7 @@ describe("linker", function() .on_call_with(path.concat { dummy:get_install_path(), "nested", "path", "to", "share-file" }) .returns(true) - local handle = InstallHandleGenerator "dummy" - local ctx = InstallContextGenerator(handle) + local ctx = test_helpers.create_context() ctx.links.share["nested/path/share-file"] = path.concat { "nested", "path", "to", "share-file" } ctx.links.share["share-file"] = "share-file" diff --git a/tests/mason-core/installer/managers/cargo_spec.lua b/tests/mason-core/installer/managers/cargo_spec.lua index 475c2c86..bc5c5f21 100644 --- a/tests/mason-core/installer/managers/cargo_spec.lua +++ b/tests/mason-core/installer/managers/cargo_spec.lua @@ -1,12 +1,12 @@ local cargo = require "mason-core.installer.managers.cargo" -local installer = require "mason-core.installer" local spy = require "luassert.spy" +local test_helpers = require "mason-test.helpers" describe("cargo manager", function() it("should install", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() - installer.exec_in_context(ctx, function() + ctx:execute(function() cargo.install("my-crate", "1.0.0") end) @@ -23,10 +23,10 @@ describe("cargo manager", function() end) it("should write output", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() spy.on(ctx.stdio_sink, "stdout") - installer.exec_in_context(ctx, function() + ctx:execute(function() cargo.install("my-crate", "1.0.0") end) @@ -34,8 +34,8 @@ describe("cargo manager", function() end) it("should install locked", function() - local ctx = create_dummy_context() - installer.exec_in_context(ctx, function() + local ctx = test_helpers.create_context() + ctx:execute(function() cargo.install("my-crate", "1.0.0", { locked = true, }) @@ -54,8 +54,8 @@ describe("cargo manager", function() end) it("should install provided features", function() - local ctx = create_dummy_context() - installer.exec_in_context(ctx, function() + local ctx = test_helpers.create_context() + ctx:execute(function() cargo.install("my-crate", "1.0.0", { features = "lsp,cli", }) @@ -74,8 +74,8 @@ describe("cargo manager", function() end) it("should install git tag source", function() - local ctx = create_dummy_context() - installer.exec_in_context(ctx, function() + local ctx = test_helpers.create_context() + ctx:execute(function() cargo.install("my-crate", "1.0.0", { git = { url = "https://github.com/neovim/neovim", @@ -96,8 +96,8 @@ describe("cargo manager", function() end) it("should install git rev source", function() - local ctx = create_dummy_context() - installer.exec_in_context(ctx, function() + local ctx = test_helpers.create_context() + ctx:execute(function() cargo.install("my-crate", "16dfc89abd413c391e5b63ae5d132c22843ce9a7", { git = { url = "https://github.com/neovim/neovim", diff --git a/tests/mason-core/installer/managers/common_spec.lua b/tests/mason-core/installer/managers/common_spec.lua index e72d7697..16d3ba52 100644 --- a/tests/mason-core/installer/managers/common_spec.lua +++ b/tests/mason-core/installer/managers/common_spec.lua @@ -7,6 +7,7 @@ local mock = require "luassert.mock" local spy = require "luassert.spy" local std = require "mason-core.installer.managers.std" local stub = require "luassert.stub" +local test_helpers = require "mason-test.helpers" describe("common manager :: download", function() it("should parse download files from common structure", function() @@ -54,11 +55,11 @@ describe("common manager :: download", function() end) it("should download files", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() stub(std, "download_file", mockx.returns(Result.success())) stub(std, "unpack", mockx.returns(Result.success())) - local result = installer.exec_in_context(ctx, function() + local result = ctx:execute(function() return common.download_files(ctx, { { out_file = "file.jar", download_url = "https://example.com/file.jar" }, { out_file = "LICENSE.md", download_url = "https://example.com/LICENSE" }, @@ -75,12 +76,12 @@ describe("common manager :: download", function() end) it("should download files to specified directory", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() stub(std, "download_file", mockx.returns(Result.success())) stub(std, "unpack", mockx.returns(Result.success())) stub(ctx.fs, "mkdirp") - local result = installer.exec_in_context(ctx, function() + local result = ctx:execute(function() return common.download_files(ctx, { { out_file = "lib/file.jar", download_url = "https://example.com/file.jar" }, { out_file = "doc/LICENSE.md", download_url = "https://example.com/LICENSE" }, @@ -99,7 +100,7 @@ end) describe("common manager :: build", function() it("should run build instruction", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() local uv = require "mason-core.async.uv" spy.on(ctx, "promote_cwd") stub(uv, "write") @@ -115,7 +116,7 @@ describe("common manager :: build", function() end ) - local result = installer.exec_in_context(ctx, function() + local result = ctx:execute(function() return common.run_build_instruction { run = [[npm install && npm run compile]], env = { @@ -143,11 +144,11 @@ describe("common manager :: build", function() end) it("should promote cwd if not staged", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() stub(ctx, "promote_cwd") stub(ctx.spawn, "bash", mockx.returns(Result.success())) - local result = installer.exec_in_context(ctx, function() + local result = ctx:execute(function() return common.run_build_instruction { run = "make", staged = false, diff --git a/tests/mason-core/installer/managers/composer_spec.lua b/tests/mason-core/installer/managers/composer_spec.lua index a8ccaffb..f3887c68 100644 --- a/tests/mason-core/installer/managers/composer_spec.lua +++ b/tests/mason-core/installer/managers/composer_spec.lua @@ -1,11 +1,11 @@ local composer = require "mason-core.installer.managers.composer" -local installer = require "mason-core.installer" local spy = require "luassert.spy" +local test_helpers = require "mason-test.helpers" describe("composer manager", function() it("should install", function() - local ctx = create_dummy_context() - installer.exec_in_context(ctx, function() + local ctx = test_helpers.create_context() + ctx:execute(function() composer.install("my-package", "1.0.0") end) @@ -22,10 +22,10 @@ describe("composer manager", function() end) it("should write output", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() spy.on(ctx.stdio_sink, "stdout") - installer.exec_in_context(ctx, function() + ctx:execute(function() composer.install("my-package", "1.0.0") end) diff --git a/tests/mason-core/installer/managers/gem_spec.lua b/tests/mason-core/installer/managers/gem_spec.lua index 7ac8c33e..83b8d96a 100644 --- a/tests/mason-core/installer/managers/gem_spec.lua +++ b/tests/mason-core/installer/managers/gem_spec.lua @@ -1,13 +1,15 @@ local gem = require "mason-core.installer.managers.gem" -local installer = require "mason-core.installer" local spy = require "luassert.spy" +local test_helper = require "mason-test.helpers" describe("gem manager", function() it("should install", function() - local ctx = create_dummy_context() - installer.exec_in_context(ctx, function() - gem.install("my-gem", "1.0.0") + local ctx = test_helper.create_context() + + local result = ctx:execute(function() + return gem.install("my-gem", "1.0.0") end) + assert.is_true(result:is_success()) assert.spy(ctx.spawn.gem).was_called(1) assert.spy(ctx.spawn.gem).was_called_with { @@ -20,15 +22,15 @@ describe("gem manager", function() "my-gem:1.0.0", vim.NIL, -- extra_packages env = { - GEM_HOME = ctx.cwd:get(), + GEM_HOME = "/tmp/install-dir", }, } end) it("should write output", function() - local ctx = create_dummy_context() + local ctx = test_helper.create_context() spy.on(ctx.stdio_sink, "stdout") - installer.exec_in_context(ctx, function() + ctx:execute(function() gem.install("my-gem", "1.0.0") end) @@ -36,8 +38,8 @@ describe("gem manager", function() end) it("should install extra packages", function() - local ctx = create_dummy_context() - installer.exec_in_context(ctx, function() + local ctx = test_helper.create_context() + ctx:execute(function() gem.install("my-gem", "1.0.0", { extra_packages = { "extra-gem" }, }) diff --git a/tests/mason-core/installer/managers/golang_spec.lua b/tests/mason-core/installer/managers/golang_spec.lua index 58e4c4b8..e1a99cbd 100644 --- a/tests/mason-core/installer/managers/golang_spec.lua +++ b/tests/mason-core/installer/managers/golang_spec.lua @@ -1,12 +1,12 @@ local golang = require "mason-core.installer.managers.golang" -local installer = require "mason-core.installer" local spy = require "luassert.spy" +local test_helpers = require "mason-test.helpers" describe("golang manager", function() it("should install", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() - installer.exec_in_context(ctx, function() + ctx:execute(function() golang.install("my-golang", "1.0.0") end) @@ -22,10 +22,10 @@ describe("golang manager", function() end) it("should write output", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() spy.on(ctx.stdio_sink, "stdout") - installer.exec_in_context(ctx, function() + ctx:execute(function() golang.install("my-golang", "1.0.0") end) @@ -33,8 +33,8 @@ describe("golang manager", function() end) it("should install extra packages", function() - local ctx = create_dummy_context() - installer.exec_in_context(ctx, function() + local ctx = test_helpers.create_context() + ctx:execute(function() golang.install("my-golang", "1.0.0", { extra_packages = { "extra", "package" }, }) diff --git a/tests/mason-core/installer/managers/luarocks_spec.lua b/tests/mason-core/installer/managers/luarocks_spec.lua index 3be963a8..406c5c51 100644 --- a/tests/mason-core/installer/managers/luarocks_spec.lua +++ b/tests/mason-core/installer/managers/luarocks_spec.lua @@ -1,13 +1,23 @@ -local installer = require "mason-core.installer" local luarocks = require "mason-core.installer.managers.luarocks" local spy = require "luassert.spy" local stub = require "luassert.stub" +local test_helpers = require "mason-test.helpers" describe("luarocks manager", function() + local snapshot + + before_each(function() + snapshot = assert.snapshot() + end) + + after_each(function() + snapshot:revert() + end) + it("should install", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() stub(ctx, "promote_cwd") - installer.exec_in_context(ctx, function() + ctx:execute(function() luarocks.install("my-rock", "1.0.0") end) @@ -23,9 +33,9 @@ describe("luarocks manager", function() end) it("should install dev mode", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() stub(ctx, "promote_cwd") - installer.exec_in_context(ctx, function() + ctx:execute(function() luarocks.install("my-rock", "1.0.0", { dev = true, }) @@ -43,9 +53,9 @@ describe("luarocks manager", function() end) it("should install using provided server", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() stub(ctx, "promote_cwd") - installer.exec_in_context(ctx, function() + ctx:execute(function() luarocks.install("my-rock", "1.0.0", { server = "https://luarocks.org/dev", }) @@ -63,10 +73,10 @@ describe("luarocks manager", function() end) it("should write output", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() stub(ctx, "promote_cwd") spy.on(ctx.stdio_sink, "stdout") - installer.exec_in_context(ctx, function() + ctx:execute(function() luarocks.install("my-rock", "1.0.0") end) diff --git a/tests/mason-core/installer/managers/npm_spec.lua b/tests/mason-core/installer/managers/npm_spec.lua index 59a8c84f..b2fabc80 100644 --- a/tests/mason-core/installer/managers/npm_spec.lua +++ b/tests/mason-core/installer/managers/npm_spec.lua @@ -1,21 +1,31 @@ local Result = require "mason-core.result" -local installer = require "mason-core.installer" local match = require "luassert.match" local npm = require "mason-core.installer.managers.npm" local spawn = require "mason-core.spawn" local spy = require "luassert.spy" local stub = require "luassert.stub" +local test_helpers = require "mason-test.helpers" describe("npm manager", function() + local snapshot + + before_each(function() + snapshot = assert.snapshot() + end) + + after_each(function() + snapshot:revert() + end) + it("should init package.json", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() stub(ctx.fs, "append_file") stub(spawn, "npm") spawn.npm.returns(Result.success {}) spawn.npm.on_call_with({ "version", "--json" }).returns(Result.success { stdout = [[ { "npm": "8.1.0" } ]], }) - installer.exec_in_context(ctx, function() + ctx:execute(function() npm.init() end) @@ -30,14 +40,14 @@ describe("npm manager", function() end) it("should use install-strategy on npm >= 9", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() stub(ctx.fs, "append_file") stub(spawn, "npm") spawn.npm.returns(Result.success {}) spawn.npm.on_call_with({ "version", "--json" }).returns(Result.success { stdout = [[ { "npm": "9.1.0" } ]], }) - installer.exec_in_context(ctx, function() + ctx:execute(function() npm.init() end) @@ -51,8 +61,8 @@ describe("npm manager", function() end) it("should install", function() - local ctx = create_dummy_context() - installer.exec_in_context(ctx, function() + local ctx = test_helpers.create_context() + ctx:execute(function() npm.install("my-package", "1.0.0") end) @@ -65,8 +75,8 @@ describe("npm manager", function() end) it("should install extra packages", function() - local ctx = create_dummy_context() - installer.exec_in_context(ctx, function() + local ctx = test_helpers.create_context() + ctx:execute(function() npm.install("my-package", "1.0.0", { extra_packages = { "extra-package" }, }) @@ -81,10 +91,10 @@ describe("npm manager", function() end) it("should write output", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() spy.on(ctx.stdio_sink, "stdout") - installer.exec_in_context(ctx, function() + ctx:execute(function() npm.install("my-package", "1.0.0") end) diff --git a/tests/mason-core/installer/managers/nuget_spec.lua b/tests/mason-core/installer/managers/nuget_spec.lua index 8d4b0e87..fdfbdc82 100644 --- a/tests/mason-core/installer/managers/nuget_spec.lua +++ b/tests/mason-core/installer/managers/nuget_spec.lua @@ -1,11 +1,11 @@ -local installer = require "mason-core.installer" local nuget = require "mason-core.installer.managers.nuget" local spy = require "luassert.spy" +local test_helpers = require "mason-test.helpers" describe("nuget manager", function() it("should install", function() - local ctx = create_dummy_context() - installer.exec_in_context(ctx, function() + local ctx = test_helpers.create_context() + ctx:execute(function() nuget.install("nuget-package", "1.0.0") end) @@ -21,10 +21,10 @@ describe("nuget manager", function() end) it("should write output", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() spy.on(ctx.stdio_sink, "stdout") - installer.exec_in_context(ctx, function() + ctx:execute(function() nuget.install("nuget-package", "1.0.0") end) diff --git a/tests/mason-core/installer/managers/opam_spec.lua b/tests/mason-core/installer/managers/opam_spec.lua index cc552114..51f116e8 100644 --- a/tests/mason-core/installer/managers/opam_spec.lua +++ b/tests/mason-core/installer/managers/opam_spec.lua @@ -1,12 +1,12 @@ -local installer = require "mason-core.installer" local opam = require "mason-core.installer.managers.opam" local spy = require "luassert.spy" +local test_helpers = require "mason-test.helpers" describe("opam manager", function() it("should install", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() - installer.exec_in_context(ctx, function() + ctx:execute(function() opam.install("opam-package", "1.0.0") end) @@ -21,10 +21,10 @@ describe("opam manager", function() end) it("should write output", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() spy.on(ctx.stdio_sink, "stdout") - installer.exec_in_context(ctx, function() + ctx:execute(function() opam.install("opam-package", "1.0.0") end) diff --git a/tests/mason-core/installer/managers/powershell_spec.lua b/tests/mason-core/installer/managers/powershell_spec.lua index 86bbe1f9..14478305 100644 --- a/tests/mason-core/installer/managers/powershell_spec.lua +++ b/tests/mason-core/installer/managers/powershell_spec.lua @@ -1,3 +1,4 @@ +local a = require "mason-core.async" local match = require "luassert.match" local mock = require "luassert.mock" local spawn = require "mason-core.spawn" @@ -5,6 +6,16 @@ local spy = require "luassert.spy" local stub = require "luassert.stub" describe("powershell manager", function() + local snapshot + + before_each(function() + snapshot = assert.snapshot() + end) + + after_each(function() + snapshot:revert() + end) + local function powershell() package.loaded["mason-core.installer.managers.powershell"] = nil return require "mason-core.installer.managers.powershell" @@ -22,21 +33,18 @@ describe("powershell manager", function() assert.spy(spawn.powershell).was_called(0) end) - it( - "should use powershell if pwsh is not available", - async_test(function() - stub(spawn, "pwsh", function() end) - stub(spawn, "powershell", function() end) - stub(vim.fn, "executable") - vim.fn.executable.on_call_with("pwsh").returns(0) + it("should use powershell if pwsh is not available", function() + stub(spawn, "pwsh", function() end) + stub(spawn, "powershell", function() end) + stub(vim.fn, "executable") + vim.fn.executable.on_call_with("pwsh").returns(0) - local powershell = powershell() - powershell.command "echo 'Is this bash?'" + local powershell = powershell() + a.run_blocking(powershell.command, "echo 'Is this bash?'") - assert.spy(spawn.pwsh).was_called(0) - assert.spy(spawn.powershell).was_called(1) - end) - ) + assert.spy(spawn.pwsh).was_called(0) + assert.spy(spawn.powershell).was_called(1) + end) it("should use the provided spawner for commands", function() spy.on(spawn, "pwsh") diff --git a/tests/mason-core/installer/managers/pypi_spec.lua b/tests/mason-core/installer/managers/pypi_spec.lua index 6689e350..f3a7e429 100644 --- a/tests/mason-core/installer/managers/pypi_spec.lua +++ b/tests/mason-core/installer/managers/pypi_spec.lua @@ -1,5 +1,4 @@ local Result = require "mason-core.result" -local installer = require "mason-core.installer" local match = require "luassert.match" local path = require "mason-core.path" local providers = require "mason-core.providers" @@ -7,6 +6,7 @@ local pypi = require "mason-core.installer.managers.pypi" local spawn = require "mason-core.spawn" local spy = require "luassert.spy" local stub = require "luassert.stub" +local test_helpers = require "mason-test.helpers" ---@param ctx InstallContext local function venv_py(ctx) @@ -19,17 +19,24 @@ local function venv_py(ctx) end describe("pypi manager", function() + local snapshot + before_each(function() + snapshot = assert.snapshot() stub(spawn, "python3", mockx.returns(Result.success())) spawn.python3.on_call_with({ "--version" }).returns(Result.success { stdout = "Python 3.11.0" }) end) + after_each(function() + snapshot:revert() + end) + it("should init venv without upgrading pip", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() stub(ctx, "promote_cwd") stub(providers.pypi, "get_supported_python_versions", mockx.returns(Result.failure())) - installer.exec_in_context(ctx, function() + ctx:execute(function() pypi.init { package = { name = "cmake-language-server", version = "0.1.10" }, upgrade_pip = false } end) @@ -44,13 +51,13 @@ describe("pypi manager", function() end) it("should init venv and upgrade pip", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() stub(ctx, "promote_cwd") stub(ctx.fs, "file_exists") stub(providers.pypi, "get_supported_python_versions", mockx.returns(Result.failure())) ctx.fs.file_exists.on_call_with(match.ref(ctx.fs), "venv/bin/python").returns(true) - installer.exec_in_context(ctx, function() + ctx:execute(function() pypi.init { package = { name = "cmake-language-server", version = "0.1.10" }, upgrade_pip = true, @@ -80,7 +87,7 @@ describe("pypi manager", function() end) it("should find versioned candidates during init", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() stub(ctx, "promote_cwd") stub(ctx.fs, "file_exists") stub(providers.pypi, "get_supported_python_versions", mockx.returns(Result.success ">=3.12")) @@ -90,7 +97,7 @@ describe("pypi manager", function() spawn["python3.12"].on_call_with({ "--version" }).returns(Result.success { stdout = "Python 3.12.0" }) ctx.fs.file_exists.on_call_with(match.ref(ctx.fs), "venv/bin/python").returns(true) - installer.exec_in_context(ctx, function() + ctx:execute(function() pypi.init { package = { name = "cmake-language-server", version = "0.1.10" }, upgrade_pip = false, @@ -109,7 +116,7 @@ describe("pypi manager", function() end) it("should error if unable to find a suitable python3 version", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() spy.on(ctx.stdio_sink, "stderr") stub(ctx, "promote_cwd") stub(ctx.fs, "file_exists") @@ -123,7 +130,7 @@ describe("pypi manager", function() stub(spawn, "python3", mockx.returns(Result.success())) spawn.python3.on_call_with({ "--version" }).returns(Result.success { stdout = "Python 3.5.0" }) - local result = installer.exec_in_context(ctx, function() + local result = ctx:execute(function() return pypi.init { package = { name = "cmake-language-server", version = "0.1.10" }, upgrade_pip = false, @@ -143,7 +150,7 @@ describe("pypi manager", function() it( "should default to stock version if unable to find suitable versioned candidate during init and when force=true", function() - local ctx = create_dummy_context { force = true } + local ctx = test_helpers.create_context { install_opts = { force = true } } spy.on(ctx.stdio_sink, "stderr") stub(ctx, "promote_cwd") stub(ctx.fs, "file_exists") @@ -157,7 +164,7 @@ describe("pypi manager", function() stub(spawn, "python3", mockx.returns(Result.success())) spawn.python3.on_call_with({ "--version" }).returns(Result.success { stdout = "Python 3.5.0" }) - installer.exec_in_context(ctx, function() + ctx:execute(function() pypi.init { package = { name = "cmake-language-server", version = "0.1.10" }, upgrade_pip = true, @@ -180,7 +187,7 @@ describe("pypi manager", function() ) it("should prioritize stock python", function() - local ctx = create_dummy_context { force = true } + local ctx = test_helpers.create_context { install_opts = { force = true } } spy.on(ctx.stdio_sink, "stderr") stub(ctx, "promote_cwd") stub(ctx.fs, "file_exists") @@ -190,7 +197,7 @@ describe("pypi manager", function() stub(spawn, "python3", mockx.returns(Result.success())) spawn.python3.on_call_with({ "--version" }).returns(Result.success { stdout = "Python 3.8.0" }) - installer.exec_in_context(ctx, function() + ctx:execute(function() pypi.init { package = { name = "cmake-language-server", version = "0.1.10" }, upgrade_pip = true, @@ -210,10 +217,11 @@ describe("pypi manager", function() end) it("should install", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() stub(ctx.fs, "file_exists") ctx.fs.file_exists.on_call_with(match.ref(ctx.fs), "venv/bin/python").returns(true) - installer.exec_in_context(ctx, function() + + ctx:execute(function() pypi.install("pypi-package", "1.0.0") end) @@ -234,12 +242,12 @@ describe("pypi manager", function() end) it("should write output", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() stub(ctx.fs, "file_exists") ctx.fs.file_exists.on_call_with(match.ref(ctx.fs), "venv/bin/python").returns(true) spy.on(ctx.stdio_sink, "stdout") - installer.exec_in_context(ctx, function() + ctx:execute(function() pypi.install("pypi-package", "1.0.0") end) @@ -247,11 +255,11 @@ describe("pypi manager", function() end) it("should install extra specifier", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() stub(ctx.fs, "file_exists") ctx.fs.file_exists.on_call_with(match.ref(ctx.fs), "venv/bin/python").returns(true) - installer.exec_in_context(ctx, function() + ctx:execute(function() pypi.install("pypi-package", "1.0.0", { extra = "lsp", }) @@ -274,10 +282,10 @@ describe("pypi manager", function() end) it("should install extra packages", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() stub(ctx.fs, "file_exists") ctx.fs.file_exists.on_call_with(match.ref(ctx.fs), "venv/bin/python").returns(true) - installer.exec_in_context(ctx, function() + ctx:execute(function() pypi.install("pypi-package", "1.0.0", { extra_packages = { "extra-package" }, install_extra_args = { "--proxy", "http://localhost:9000" }, diff --git a/tests/mason-core/installer/managers/std_spec.lua b/tests/mason-core/installer/managers/std_spec.lua index dea342bc..20caac18 100644 --- a/tests/mason-core/installer/managers/std_spec.lua +++ b/tests/mason-core/installer/managers/std_spec.lua @@ -1,12 +1,22 @@ -local installer = require "mason-core.installer" local match = require "luassert.match" local std = require "mason-core.installer.managers.std" local stub = require "luassert.stub" +local test_helpers = require "mason-test.helpers" describe("std unpack [Unix]", function() + local snapshot + + before_each(function() + snapshot = assert.snapshot() + end) + + after_each(function() + snapshot:revert() + end) + it("should unpack .gz", function() - local ctx = create_dummy_context() - installer.exec_in_context(ctx, function() + local ctx = test_helpers.create_context() + ctx:execute(function() std.unpack "file.gz" end) @@ -21,12 +31,12 @@ describe("std unpack [Unix]", function() end) it("should use gtar if available", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() stub(ctx.fs, "unlink") stub(vim.fn, "executable") vim.fn.executable.on_call_with("gtar").returns(1) - installer.exec_in_context(ctx, function() + ctx:execute(function() std.unpack "file.tar.gz" end) @@ -35,9 +45,9 @@ describe("std unpack [Unix]", function() end) it("should unpack .tar", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() stub(ctx.fs, "unlink") - installer.exec_in_context(ctx, function() + ctx:execute(function() std.unpack "file.tar" end) @@ -48,9 +58,9 @@ describe("std unpack [Unix]", function() end) it("should unpack .tar.bz2", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() stub(ctx.fs, "unlink") - installer.exec_in_context(ctx, function() + ctx:execute(function() std.unpack "file.tar.bz2" end) @@ -61,9 +71,9 @@ describe("std unpack [Unix]", function() end) it("should unpack .tar.gz", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() stub(ctx.fs, "unlink") - installer.exec_in_context(ctx, function() + ctx:execute(function() std.unpack "file.tar.gz" end) @@ -74,9 +84,9 @@ describe("std unpack [Unix]", function() end) it("should unpack .tar.xz", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() stub(ctx.fs, "unlink") - installer.exec_in_context(ctx, function() + ctx:execute(function() std.unpack "file.tar.xz" end) @@ -87,9 +97,9 @@ describe("std unpack [Unix]", function() end) it("should unpack .tar.zst", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() stub(ctx.fs, "unlink") - installer.exec_in_context(ctx, function() + ctx:execute(function() std.unpack "file.tar.zst" end) @@ -101,9 +111,9 @@ describe("std unpack [Unix]", function() end) it("should unpack .vsix", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() stub(ctx.fs, "unlink") - installer.exec_in_context(ctx, function() + ctx:execute(function() std.unpack "file.vsix" end) @@ -114,9 +124,9 @@ describe("std unpack [Unix]", function() end) it("should unpack .zip", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() stub(ctx.fs, "unlink") - installer.exec_in_context(ctx, function() + ctx:execute(function() std.unpack "file.zip" end) @@ -129,8 +139,8 @@ end) describe("std clone", function() it("should clone", function() - local ctx = create_dummy_context() - installer.exec_in_context(ctx, function() + local ctx = test_helpers.create_context() + ctx:execute(function() std.clone "https://github.com/williamboman/mason.nvim" end) @@ -146,8 +156,8 @@ describe("std clone", function() end) it("should clone and checkout rev", function() - local ctx = create_dummy_context() - installer.exec_in_context(ctx, function() + local ctx = test_helpers.create_context() + ctx:execute(function() std.clone("https://github.com/williamboman/mason.nvim", { rev = "e1fd03b1856cb5ad8425f49e18353dc524b02f91", recursive = true, diff --git a/tests/mason-core/installer/registry/providers/cargo_spec.lua b/tests/mason-core/installer/registry/compilers/cargo_spec.lua index 1bdad5f4..69ac446d 100644 --- a/tests/mason-core/installer/registry/providers/cargo_spec.lua +++ b/tests/mason-core/installer/registry/compilers/cargo_spec.lua @@ -1,9 +1,9 @@ local Purl = require "mason-core.purl" local Result = require "mason-core.result" -local cargo = require "mason-core.installer.registry.providers.cargo" -local installer = require "mason-core.installer" +local cargo = require "mason-core.installer.compiler.compilers.cargo" local providers = require "mason-core.providers" local stub = require "luassert.stub" +local test_helpers = require "mason-test.helpers" ---@param overrides Purl local function purl(overrides) @@ -94,12 +94,22 @@ describe("cargo provider :: parsing", function() end) describe("cargo provider :: installing", function() + local snapshot + + before_each(function() + snapshot = assert.snapshot() + end) + + after_each(function() + snapshot:revert() + end) + it("should install cargo packages", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() local manager = require "mason-core.installer.managers.cargo" stub(manager, "install", mockx.returns(Result.success())) - local result = installer.exec_in_context(ctx, function() + local result = ctx:execute(function() return cargo.install(ctx, { crate = "crate-name", version = "1.2.0", @@ -120,6 +130,16 @@ describe("cargo provider :: installing", function() end) describe("cargo provider :: versions", function() + local snapshot + + before_each(function() + snapshot = assert.snapshot() + end) + + after_each(function() + snapshot:revert() + end) + it("should recognize github cargo source", function() stub(providers.github, "get_all_tags", function() return Result.success { "1.0.0", "2.0.0", "3.0.0" } diff --git a/tests/mason-core/installer/registry/providers/composer_spec.lua b/tests/mason-core/installer/registry/compilers/composer_spec.lua index 8b771ff9..c184adf5 100644 --- a/tests/mason-core/installer/registry/providers/composer_spec.lua +++ b/tests/mason-core/installer/registry/compilers/composer_spec.lua @@ -1,8 +1,8 @@ local Purl = require "mason-core.purl" local Result = require "mason-core.result" -local composer = require "mason-core.installer.registry.providers.composer" -local installer = require "mason-core.installer" +local composer = require "mason-core.installer.compiler.compilers.composer" local stub = require "luassert.stub" +local test_helpers = require "mason-test.helpers" ---@param overrides Purl local function purl(overrides) @@ -26,12 +26,22 @@ describe("composer provider :: parsing", function() end) describe("composer provider :: installing", function() + local snapshot + + before_each(function() + snapshot = assert.snapshot() + end) + + after_each(function() + snapshot:revert() + end) + it("should install composer packages", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() local manager = require "mason-core.installer.managers.composer" stub(manager, "install", mockx.returns(Result.success())) - local result = installer.exec_in_context(ctx, function() + local result = ctx:execute(function() return composer.install(ctx, { package = "vendor/package", version = "1.2.0", diff --git a/tests/mason-core/installer/registry/providers/gem_spec.lua b/tests/mason-core/installer/registry/compilers/gem_spec.lua index 965cdbe8..b38bba33 100644 --- a/tests/mason-core/installer/registry/providers/gem_spec.lua +++ b/tests/mason-core/installer/registry/compilers/gem_spec.lua @@ -1,8 +1,8 @@ local Purl = require "mason-core.purl" local Result = require "mason-core.result" -local gem = require "mason-core.installer.registry.providers.gem" -local installer = require "mason-core.installer" +local gem = require "mason-core.installer.compiler.compilers.gem" local stub = require "luassert.stub" +local test_helpers = require "mason-test.helpers" ---@param overrides Purl local function purl(overrides) @@ -31,12 +31,22 @@ describe("gem provider :: parsing", function() end) describe("gem provider :: installing", function() + local snapshot + + before_each(function() + snapshot = assert.snapshot() + end) + + after_each(function() + snapshot:revert() + end) + it("should install gem packages", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() local manager = require "mason-core.installer.managers.gem" stub(manager, "install", mockx.returns(Result.success())) - local result = installer.exec_in_context(ctx, function() + local result = ctx:execute(function() return gem.install(ctx, { package = "package", version = "5.2.0", diff --git a/tests/mason-core/installer/registry/providers/generic/build_spec.lua b/tests/mason-core/installer/registry/compilers/generic/build_spec.lua index 443cb99a..8b8baeab 100644 --- a/tests/mason-core/installer/registry/providers/generic/build_spec.lua +++ b/tests/mason-core/installer/registry/compilers/generic/build_spec.lua @@ -1,8 +1,8 @@ local Purl = require "mason-core.purl" local Result = require "mason-core.result" -local generic = require "mason-core.installer.registry.providers.generic" -local installer = require "mason-core.installer" +local generic = require "mason-core.installer.compiler.compilers.generic" local stub = require "luassert.stub" +local test_helpers = require "mason-test.helpers" ---@param overrides Purl local function purl(overrides) @@ -119,12 +119,22 @@ describe("generic provider :: build :: parsing", function() end) describe("generic provider :: build :: installing", function() + local snapshot + + before_each(function() + snapshot = assert.snapshot() + end) + + after_each(function() + snapshot:revert() + end) + it("should install", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() local common = require "mason-core.installer.managers.common" stub(common, "run_build_instruction", mockx.returns(Result.success())) - local result = installer.exec_in_context(ctx, function() + local result = ctx:execute(function() return generic.install(ctx, { build = { run = "make", diff --git a/tests/mason-core/installer/registry/providers/generic/download_spec.lua b/tests/mason-core/installer/registry/compilers/generic/download_spec.lua index 4bcb1976..4046d898 100644 --- a/tests/mason-core/installer/registry/providers/generic/download_spec.lua +++ b/tests/mason-core/installer/registry/compilers/generic/download_spec.lua @@ -1,9 +1,9 @@ local Purl = require "mason-core.purl" local Result = require "mason-core.result" -local generic = require "mason-core.installer.registry.providers.generic" -local installer = require "mason-core.installer" local match = require "luassert.match" +local generic = require "mason-core.installer.compiler.compilers.generic" local stub = require "luassert.stub" +local test_helpers = require "mason-test.helpers" ---@param overrides Purl local function purl(overrides) @@ -99,12 +99,22 @@ describe("generic provider :: download :: parsing", function() end) describe("generic provider :: download :: installing", function() + local snapshot + + before_each(function() + snapshot = assert.snapshot() + end) + + after_each(function() + snapshot:revert() + end) + it("should install generic packages", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() local common = require "mason-core.installer.managers.common" stub(common, "download_files", mockx.returns(Result.success())) - local result = installer.exec_in_context(ctx, function() + local result = ctx:execute(function() return generic.install(ctx, { downloads = { { diff --git a/tests/mason-core/installer/registry/providers/github/build_spec.lua b/tests/mason-core/installer/registry/compilers/github/build_spec.lua index 17667d2c..0adc00fe 100644 --- a/tests/mason-core/installer/registry/providers/github/build_spec.lua +++ b/tests/mason-core/installer/registry/compilers/github/build_spec.lua @@ -1,6 +1,6 @@ local Purl = require "mason-core.purl" local Result = require "mason-core.result" -local github = require "mason-core.installer.registry.providers.github" +local github = require "mason-core.installer.compiler.compilers.github" ---@param overrides Purl local function purl(overrides) diff --git a/tests/mason-core/installer/registry/providers/github/release_spec.lua b/tests/mason-core/installer/registry/compilers/github/release_spec.lua index a6648b33..3cfbabc5 100644 --- a/tests/mason-core/installer/registry/providers/github/release_spec.lua +++ b/tests/mason-core/installer/registry/compilers/github/release_spec.lua @@ -1,11 +1,11 @@ local Purl = require "mason-core.purl" local Result = require "mason-core.result" local common = require "mason-core.installer.managers.common" -local github = require "mason-core.installer.registry.providers.github" -local installer = require "mason-core.installer" +local compiler = require "mason-core.installer.compiler" +local github = require "mason-core.installer.compiler.compilers.github" local match = require "luassert.match" -local registry_installer = require "mason-core.installer.registry" local stub = require "luassert.stub" +local test_helpers = require "mason-test.helpers" ---@param overrides Purl local function purl(overrides) @@ -198,7 +198,7 @@ describe("github provider :: release :: parsing", function() end) it("should upsert version overrides", function() - local result = registry_installer.parse({ + local result = compiler.parse({ schema = "registry+v1", source = { id = "pkg:github/owner/repo@1.2.3", @@ -252,7 +252,7 @@ describe("github provider :: release :: parsing", function() end) it("should override source if version override provides its own purl id", function() - local result = registry_installer.parse({ + local result = compiler.parse({ schema = "registry+v1", source = { id = "pkg:github/owner/repo@1.2.3", @@ -280,14 +280,24 @@ describe("github provider :: release :: parsing", function() end) describe("github provider :: release :: installing", function() + local snapshot + + before_each(function() + snapshot = assert.snapshot() + end) + + after_each(function() + snapshot:revert() + end) + it("should install github release assets", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() local std = require "mason-core.installer.managers.std" stub(std, "download_file", mockx.returns(Result.success())) stub(std, "unpack", mockx.returns(Result.success())) stub(common, "download_files", mockx.returns(Result.success())) - local result = installer.exec_in_context(ctx, function() + local result = ctx:execute(function() return github.install(ctx, { repo = "namespace/name", asset = { diff --git a/tests/mason-core/installer/registry/providers/golang_spec.lua b/tests/mason-core/installer/registry/compilers/golang_spec.lua index 6ba57272..8a3abc8a 100644 --- a/tests/mason-core/installer/registry/providers/golang_spec.lua +++ b/tests/mason-core/installer/registry/compilers/golang_spec.lua @@ -1,8 +1,8 @@ local Purl = require "mason-core.purl" local Result = require "mason-core.result" -local golang = require "mason-core.installer.registry.providers.golang" -local installer = require "mason-core.installer" +local golang = require "mason-core.installer.compiler.compilers.golang" local stub = require "luassert.stub" +local test_helpers = require "mason-test.helpers" ---@param overrides Purl local function purl(overrides) @@ -27,12 +27,22 @@ describe("golang provider :: parsing", function() end) describe("golang provider :: installing", function() + local snapshot + + before_each(function() + snapshot = assert.snapshot() + end) + + after_each(function() + snapshot:revert() + end) + it("should install golang packages", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() local manager = require "mason-core.installer.managers.golang" stub(manager, "install", mockx.returns(Result.success())) - local result = installer.exec_in_context(ctx, function() + local result = ctx:execute(function() return golang.install(ctx, { package = "namespace/package", version = "v1.5.0", diff --git a/tests/mason-core/installer/registry/providers/luarocks_spec.lua b/tests/mason-core/installer/registry/compilers/luarocks_spec.lua index 0a4ea9ad..b8642fcf 100644 --- a/tests/mason-core/installer/registry/providers/luarocks_spec.lua +++ b/tests/mason-core/installer/registry/compilers/luarocks_spec.lua @@ -1,9 +1,9 @@ local Purl = require "mason-core.purl" local Result = require "mason-core.result" -local installer = require "mason-core.installer" -local luarocks = require "mason-core.installer.registry.providers.luarocks" +local luarocks = require "mason-core.installer.compiler.compilers.luarocks" local match = require "luassert.match" local stub = require "luassert.stub" +local test_helpers = require "mason-test.helpers" ---@param overrides Purl local function purl(overrides) @@ -53,13 +53,23 @@ describe("luarocks provider :: parsing", function() end) describe("luarocks provider :: installing", function() + local snapshot + + before_each(function() + snapshot = assert.snapshot() + end) + + after_each(function() + snapshot:revert() + end) + it("should install luarocks packages", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() local manager = require "mason-core.installer.managers.luarocks" local ret_val = Result.success() stub(manager, "install", mockx.returns(ret_val)) - local result = installer.exec_in_context(ctx, function() + local result = ctx:execute(function() return luarocks.install(ctx, { package = "namespace/name", version = "1.0.0", diff --git a/tests/mason-core/installer/registry/providers/npm_spec.lua b/tests/mason-core/installer/registry/compilers/npm_spec.lua index b39d092a..680df5bc 100644 --- a/tests/mason-core/installer/registry/providers/npm_spec.lua +++ b/tests/mason-core/installer/registry/compilers/npm_spec.lua @@ -1,8 +1,8 @@ local Purl = require "mason-core.purl" local Result = require "mason-core.result" -local installer = require "mason-core.installer" -local npm = require "mason-core.installer.registry.providers.npm" +local npm = require "mason-core.installer.compiler.compilers.npm" local stub = require "luassert.stub" +local test_helpers = require "mason-test.helpers" ---@param overrides Purl local function purl(overrides) @@ -27,13 +27,23 @@ describe("npm provider :: parsing", function() end) describe("npm provider :: installing", function() + local snapshot + + before_each(function() + snapshot = assert.snapshot() + end) + + after_each(function() + snapshot:revert() + end) + it("should install npm packages", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() local manager = require "mason-core.installer.managers.npm" stub(manager, "init", mockx.returns(Result.success())) stub(manager, "install", mockx.returns(Result.success())) - local result = installer.exec_in_context(ctx, function() + local result = ctx:execute(function() return npm.install(ctx, { package = "@namespace/package", version = "v1.5.0", diff --git a/tests/mason-core/installer/registry/providers/nuget_spec.lua b/tests/mason-core/installer/registry/compilers/nuget_spec.lua index 2437d8de..f514e666 100644 --- a/tests/mason-core/installer/registry/providers/nuget_spec.lua +++ b/tests/mason-core/installer/registry/compilers/nuget_spec.lua @@ -1,8 +1,8 @@ local Purl = require "mason-core.purl" local Result = require "mason-core.result" -local installer = require "mason-core.installer" -local nuget = require "mason-core.installer.registry.providers.nuget" +local nuget = require "mason-core.installer.compiler.compilers.nuget" local stub = require "luassert.stub" +local test_helpers = require "mason-test.helpers" ---@param overrides Purl local function purl(overrides) @@ -26,12 +26,22 @@ describe("nuget provider :: parsing", function() end) describe("nuget provider :: installing", function() + local snapshot + + before_each(function() + snapshot = assert.snapshot() + end) + + after_each(function() + snapshot:revert() + end) + it("should install nuget packages", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() local manager = require "mason-core.installer.managers.nuget" stub(manager, "install", mockx.returns(Result.success())) - local result = installer.exec_in_context(ctx, function() + local result = ctx:execute(function() return nuget.install(ctx, { package = "package", version = "1.5.0", diff --git a/tests/mason-core/installer/registry/providers/opam_spec.lua b/tests/mason-core/installer/registry/compilers/opam_spec.lua index c0f73b02..c2c7638e 100644 --- a/tests/mason-core/installer/registry/providers/opam_spec.lua +++ b/tests/mason-core/installer/registry/compilers/opam_spec.lua @@ -1,8 +1,8 @@ local Purl = require "mason-core.purl" local Result = require "mason-core.result" -local installer = require "mason-core.installer" -local opam = require "mason-core.installer.registry.providers.opam" +local opam = require "mason-core.installer.compiler.compilers.opam" local stub = require "luassert.stub" +local test_helpers = require "mason-test.helpers" ---@param overrides Purl local function purl(overrides) @@ -26,12 +26,22 @@ describe("opam provider :: parsing", function() end) describe("opam provider :: installing", function() + local snapshot + + before_each(function() + snapshot = assert.snapshot() + end) + + after_each(function() + snapshot:revert() + end) + it("should install opam packages", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() local manager = require "mason-core.installer.managers.opam" stub(manager, "install", mockx.returns(Result.success())) - local result = installer.exec_in_context(ctx, function() + local result = ctx:execute(function() return opam.install(ctx, { package = "package", version = "1.5.0", diff --git a/tests/mason-core/installer/registry/providers/openvsx_spec.lua b/tests/mason-core/installer/registry/compilers/openvsx_spec.lua index 1452ea0f..d3868a69 100644 --- a/tests/mason-core/installer/registry/providers/openvsx_spec.lua +++ b/tests/mason-core/installer/registry/compilers/openvsx_spec.lua @@ -1,10 +1,10 @@ local Purl = require "mason-core.purl" local Result = require "mason-core.result" local common = require "mason-core.installer.managers.common" -local installer = require "mason-core.installer" local match = require "luassert.match" -local openvsx = require "mason-core.installer.registry.providers.openvsx" +local openvsx = require "mason-core.installer.compiler.compilers.openvsx" local stub = require "luassert.stub" +local test_helpers = require "mason-test.helpers" ---@param overrides Purl local function purl(overrides) @@ -117,13 +117,10 @@ end) describe("openvsx provider :: download :: installing", function() it("should install openvsx assets", function() - local ctx = create_dummy_context() - local std = require "mason-core.installer.managers.std" - stub(std, "download_file", mockx.returns(Result.success())) - stub(std, "unpack", mockx.returns(Result.success())) + local ctx = test_helpers.create_context() stub(common, "download_files", mockx.returns(Result.success())) - local result = installer.exec_in_context(ctx, function() + local result = ctx:execute(function() return openvsx.install(ctx, { download = { file = "file-1.10.1.jar", diff --git a/tests/mason-core/installer/registry/providers/pypi_spec.lua b/tests/mason-core/installer/registry/compilers/pypi_spec.lua index 539ba53b..61742b4e 100644 --- a/tests/mason-core/installer/registry/providers/pypi_spec.lua +++ b/tests/mason-core/installer/registry/compilers/pypi_spec.lua @@ -1,9 +1,9 @@ local Purl = require "mason-core.purl" local Result = require "mason-core.result" -local installer = require "mason-core.installer" -local pypi = require "mason-core.installer.registry.providers.pypi" +local pypi = require "mason-core.installer.compiler.compilers.pypi" local settings = require "mason.settings" local stub = require "luassert.stub" +local test_helpers = require "mason-test.helpers" ---@param overrides Purl local function purl(overrides) @@ -44,8 +44,18 @@ describe("pypi provider :: parsing", function() end) describe("pypi provider :: installing", function() + local snapshot + + before_each(function() + snapshot = assert.snapshot() + end) + + after_each(function() + snapshot:revert() + end) + it("should install pypi packages", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() local manager = require "mason-core.installer.managers.pypi" stub(manager, "init", mockx.returns(Result.success())) stub(manager, "install", mockx.returns(Result.success())) @@ -56,7 +66,7 @@ describe("pypi provider :: installing", function() }, } - local result = installer.exec_in_context(ctx, function() + local result = ctx:execute(function() return pypi.install(ctx, { package = "package", extra = "lsp", diff --git a/tests/mason-core/installer/registry/expr_spec.lua b/tests/mason-core/installer/registry/expr_spec.lua index 65994dfa..944a5983 100644 --- a/tests/mason-core/installer/registry/expr_spec.lua +++ b/tests/mason-core/installer/registry/expr_spec.lua @@ -1,6 +1,6 @@ local Result = require "mason-core.result" local _ = require "mason-core.functional" -local expr = require "mason-core.installer.registry.expr" +local expr = require "mason-core.installer.compiler.expr" local match = require "luassert.match" describe("registry expressions", function() diff --git a/tests/mason-core/installer/registry/installer_spec.lua b/tests/mason-core/installer/registry/installer_spec.lua index 51d9035e..93c91444 100644 --- a/tests/mason-core/installer/registry/installer_spec.lua +++ b/tests/mason-core/installer/registry/installer_spec.lua @@ -1,12 +1,13 @@ local Result = require "mason-core.result" -local installer = require "mason-core.installer.registry" +local compiler = require "mason-core.installer.compiler" local match = require "luassert.match" local spy = require "luassert.spy" local stub = require "luassert.stub" -local util = require "mason-core.installer.registry.util" +local test_helpers = require "mason-test.helpers" +local util = require "mason-core.installer.compiler.util" ----@type InstallerProvider -local dummy_provider = { +---@type InstallerCompiler +local dummy_compiler = { ---@param source RegistryPackageSource ---@param purl Purl ---@param opts PackageInstallOpts @@ -36,9 +37,9 @@ local dummy_provider = { describe("registry installer :: parsing", function() it("should parse valid package specs", function() - installer.register_provider("dummy", dummy_provider) + compiler.register_compiler("dummy", dummy_compiler) - local result = installer.parse({ + local result = compiler.parse({ schema = "registry+v1", source = { id = "pkg:dummy/package-name@v1.2.3", @@ -48,7 +49,7 @@ describe("registry installer :: parsing", function() local parsed = result:get_or_nil() assert.is_true(result:is_success()) - assert.is_true(match.is_ref(dummy_provider)(parsed.provider)) + assert.is_true(match.is_ref(dummy_compiler)(parsed.compiler)) assert.same({ name = "package-name", scheme = "pkg", @@ -63,9 +64,9 @@ describe("registry installer :: parsing", function() end) it("should keep unmapped fields", function() - installer.register_provider("dummy", dummy_provider) + compiler.register_compiler("dummy", dummy_compiler) - local result = installer.parse({ + local result = compiler.parse({ schema = "registry+v1", source = { id = "pkg:dummy/package-name@v1.2.3", @@ -83,9 +84,9 @@ describe("registry installer :: parsing", function() end) it("should reject incompatible schema versions", function() - installer.register_provider("dummy", dummy_provider) + compiler.register_compiler("dummy", dummy_compiler) - local result = installer.parse({ + local result = compiler.parse({ schema = "registry+v1337", source = { id = "pkg:dummy/package-name@v1.2.3", @@ -98,9 +99,9 @@ describe("registry installer :: parsing", function() end) it("should use requested version", function() - installer.register_provider("dummy", dummy_provider) + compiler.register_compiler("dummy", dummy_compiler) - local result = installer.parse({ + local result = compiler.parse({ schema = "registry+v1", source = { id = "pkg:dummy/package-name@v1.2.3", @@ -119,9 +120,9 @@ describe("registry installer :: parsing", function() end) it("should handle PLATFORM_UNSUPPORTED", function() - installer.register_provider("dummy", dummy_provider) + compiler.register_compiler("dummy", dummy_compiler) - local result = installer.compile({ + local result = compiler.compile({ schema = "registry+v1", source = { id = "pkg:dummy/package-name@v1.2.3", @@ -133,9 +134,9 @@ describe("registry installer :: parsing", function() end) it("should error upon parsing failures", function() - installer.register_provider("dummy", dummy_provider) + compiler.register_compiler("dummy", dummy_compiler) - local result = installer.compile({ + local result = compiler.compile({ schema = "registry+v1", source = { id = "pkg:dummy/package-name@v1.2.3", @@ -148,14 +149,24 @@ describe("registry installer :: parsing", function() end) describe("registry installer :: compiling", function() + local snapshot + + before_each(function() + snapshot = assert.snapshot() + end) + + after_each(function() + snapshot:revert() + end) + it("should run compiled installer function successfully", function() - installer.register_provider("dummy", dummy_provider) - spy.on(dummy_provider, "get_versions") + compiler.register_compiler("dummy", dummy_compiler) + spy.on(dummy_compiler, "get_versions") ---@type PackageInstallOpts local opts = {} - local result = installer.compile({ + local result = compiler.compile({ schema = "registry+v1", source = { id = "pkg:dummy/package-name@v1.2.3", @@ -165,21 +176,21 @@ describe("registry installer :: compiling", function() assert.is_true(result:is_success()) local installer_fn = result:get_or_throw() - local ctx = create_dummy_context(opts) - local installer_result = require("mason-core.installer").exec_in_context(ctx, installer_fn) + local ctx = test_helpers.create_context() + local installer_result = ctx:execute(installer_fn) assert.same(Result.success(), installer_result) - assert.spy(dummy_provider.get_versions).was_not_called() + assert.spy(dummy_compiler.get_versions).was_not_called() end) it("should ensure valid version", function() - installer.register_provider("dummy", dummy_provider) - spy.on(dummy_provider, "get_versions") + compiler.register_compiler("dummy", dummy_compiler) + spy.on(dummy_compiler, "get_versions") ---@type PackageInstallOpts local opts = { version = "v2.0.0" } - local result = installer.compile({ + local result = compiler.compile({ schema = "registry+v1", source = { id = "pkg:dummy/package-name@v1.2.3", @@ -189,12 +200,12 @@ describe("registry installer :: compiling", function() assert.is_true(result:is_success()) local installer_fn = result:get_or_throw() - local ctx = create_dummy_context(opts) - local installer_result = require("mason-core.installer").exec_in_context(ctx, installer_fn) + local ctx = test_helpers.create_context { install_opts = opts } + local installer_result = ctx:execute(installer_fn) assert.same(Result.success(), installer_result) - assert.spy(dummy_provider.get_versions).was_called(1) - assert.spy(dummy_provider.get_versions).was_called_with({ + assert.spy(dummy_compiler.get_versions).was_called(1) + assert.spy(dummy_compiler.get_versions).was_called_with({ name = "package-name", scheme = "pkg", type = "dummy", @@ -205,13 +216,13 @@ describe("registry installer :: compiling", function() end) it("should reject invalid version", function() - installer.register_provider("dummy", dummy_provider) - spy.on(dummy_provider, "get_versions") + compiler.register_compiler("dummy", dummy_compiler) + spy.on(dummy_compiler, "get_versions") ---@type PackageInstallOpts local opts = { version = "v13.3.7" } - local result = installer.compile({ + local result = compiler.compile({ schema = "registry+v1", source = { id = "pkg:dummy/package-name@v1.2.3", @@ -221,14 +232,14 @@ describe("registry installer :: compiling", function() assert.is_true(result:is_success()) local installer_fn = result:get_or_throw() - local ctx = create_dummy_context(opts) + local ctx = test_helpers.create_context { install_opts = opts } local err = assert.has_error(function() - require("mason-core.installer").exec_in_context(ctx, installer_fn) + ctx:execute(installer_fn) end) assert.equals([[Version "v13.3.7" is not available.]], err) - assert.spy(dummy_provider.get_versions).was_called(1) - assert.spy(dummy_provider.get_versions).was_called_with({ + assert.spy(dummy_compiler.get_versions).was_called(1) + assert.spy(dummy_compiler.get_versions).was_called_with({ name = "package-name", scheme = "pkg", type = "dummy", @@ -239,12 +250,12 @@ describe("registry installer :: compiling", function() end) it("should raise errors upon installer failures", function() - installer.register_provider("dummy", dummy_provider) + compiler.register_compiler("dummy", dummy_compiler) ---@type PackageInstallOpts local opts = {} - local result = installer.compile({ + local result = compiler.compile({ schema = "registry+v1", source = { id = "pkg:dummy/package-name@v1.2.3", @@ -255,16 +266,16 @@ describe("registry installer :: compiling", function() assert.is_true(result:is_success()) local installer_fn = result:get_or_nil() - local ctx = create_dummy_context(opts) + local ctx = test_helpers.create_context() local err = assert.has_error(function() - require("mason-core.installer").exec_in_context(ctx, installer_fn) + ctx:execute(installer_fn) end) assert.equals("This is a failure.", err) end) it("should register links", function() - installer.register_provider("dummy", dummy_provider) - local link = require "mason-core.installer.registry.link" + compiler.register_compiler("dummy", dummy_compiler) + local link = require "mason-core.installer.compiler.link" stub(link, "bin", mockx.returns(Result.success())) stub(link, "share", mockx.returns(Result.success())) stub(link, "opt", mockx.returns(Result.success())) @@ -281,13 +292,13 @@ describe("registry installer :: compiling", function() ---@type PackageInstallOpts local opts = {} - local result = installer.compile(spec, opts) + local result = compiler.compile(spec, opts) assert.is_true(result:is_success()) local installer_fn = result:get_or_nil() - local ctx = create_dummy_context(opts) - local installer_result = require("mason-core.installer").exec_in_context(ctx, installer_fn) + local ctx = test_helpers.create_context() + local installer_result = ctx:execute(installer_fn) assert.is_true(installer_result:is_success()) for _, spy in ipairs { link.bin, link.share, link.opt } do diff --git a/tests/mason-core/installer/registry/link_spec.lua b/tests/mason-core/installer/registry/link_spec.lua index eb6af1cc..62777bc9 100644 --- a/tests/mason-core/installer/registry/link_spec.lua +++ b/tests/mason-core/installer/registry/link_spec.lua @@ -1,14 +1,25 @@ local Purl = require "mason-core.purl" local Result = require "mason-core.result" local fs = require "mason-core.fs" -local link = require "mason-core.installer.registry.link" +local link = require "mason-core.installer.compiler.link" local match = require "luassert.match" local path = require "mason-core.path" local stub = require "luassert.stub" +local test_helpers = require "mason-test.helpers" describe("registry linker", function() + local snapshot + + before_each(function() + snapshot = assert.snapshot() + end) + + after_each(function() + snapshot:revert() + end) + it("should expand bin table", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() stub(ctx.fs, "file_exists") stub(ctx.fs, "chmod") stub(ctx.fs, "fstat") @@ -45,7 +56,7 @@ describe("registry linker", function() end) it("should chmod executable if necessary", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() stub(ctx.fs, "file_exists") stub(ctx.fs, "chmod") stub(ctx.fs, "fstat") @@ -74,7 +85,7 @@ describe("registry linker", function() end) it("should interpolate bin table", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() stub(ctx.fs, "file_exists") stub(ctx.fs, "chmod") stub(ctx.fs, "fstat") @@ -106,7 +117,7 @@ describe("registry linker", function() end) it("should delegate bin paths", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() stub(ctx.fs, "file_exists") stub(ctx.fs, "chmod") stub(ctx.fs, "fstat") @@ -144,7 +155,7 @@ describe("registry linker", function() end) it("should register share links", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() stub(ctx.fs, "file_exists") stub(fs.sync, "file_exists") stub(vim.fn, "glob") @@ -192,7 +203,7 @@ describe("registry linker", function() end) it("should register opt links", function() - local ctx = create_dummy_context() + local ctx = test_helpers.create_context() stub(ctx.fs, "file_exists") stub(fs.sync, "file_exists") stub(vim.fn, "glob") diff --git a/tests/mason-core/installer/registry/util_spec.lua b/tests/mason-core/installer/registry/util_spec.lua index 851164d0..be687f36 100644 --- a/tests/mason-core/installer/registry/util_spec.lua +++ b/tests/mason-core/installer/registry/util_spec.lua @@ -1,8 +1,8 @@ local Result = require "mason-core.result" -local installer = require "mason-core.installer" local match = require "luassert.match" local platform = require "mason-core.platform" -local util = require "mason-core.installer.registry.util" +local test_helpers = require "mason-test.helpers" +local util = require "mason-core.installer.compiler.util" describe("registry installer util", function() it("should coalesce single target", function() @@ -40,8 +40,8 @@ describe("registry installer util", function() end) it("should accept valid version", function() - local ctx = create_dummy_context { version = "1.0.0" } - local result = installer.exec_in_context(ctx, function() + local ctx = test_helpers.create_context { install_opts = { version = "1.0.0" } } + local result = ctx:execute(function() return util.ensure_valid_version(function() return Result.success { "1.0.0", "2.0.0", "3.0.0" } end) @@ -50,8 +50,8 @@ describe("registry installer util", function() end) it("should reject invalid version", function() - local ctx = create_dummy_context { version = "13.3.7" } - local result = installer.exec_in_context(ctx, function() + local ctx = test_helpers.create_context { install_opts = { version = "13.3.7" } } + local result = ctx:execute(function() return util.ensure_valid_version(function() return Result.success { "1.0.0", "2.0.0", "3.0.0" } end) @@ -60,8 +60,8 @@ describe("registry installer util", function() end) it("should gracefully accept version if unable to resolve available versions", function() - local ctx = create_dummy_context { version = "13.3.7" } - local result = installer.exec_in_context(ctx, function() + local ctx = test_helpers.create_context { install_opts = { version = "13.3.7" } } + local result = ctx:execute(function() return util.ensure_valid_version(function() return Result.failure() end) @@ -70,8 +70,8 @@ describe("registry installer util", function() end) it("should accept version if in force mode", function() - local ctx = create_dummy_context { version = "13.3.7", force = true } - local result = installer.exec_in_context(ctx, function() + local ctx = test_helpers.create_context { install_opts = { version = "13.3.7", force = true } } + local result = ctx:execute(function() return util.ensure_valid_version(function() return Result.success { "1.0.0" } end) diff --git a/tests/mason-core/installer/runner_spec.lua b/tests/mason-core/installer/runner_spec.lua new file mode 100644 index 00000000..b39a75ac --- /dev/null +++ b/tests/mason-core/installer/runner_spec.lua @@ -0,0 +1,300 @@ +local InstallHandle = require "mason-core.installer.handle" +local InstallLocation = require "mason-core.installer.location" +local InstallRunner = require "mason-core.installer.runner" +local fs = require "mason-core.fs" +local match = require "luassert.match" +local spy = require "luassert.spy" +local stub = require "luassert.stub" +local Semaphore = require("mason-core.async.control").Semaphore +local a = require "mason-core.async" +local registry = require "mason-registry" +local settings = require "mason.settings" + +describe("install runner ::", function() + local dummy = registry.get_package "dummy" + local dummy2 = registry.get_package "dummy2" + + local snapshot + + before_each(function() + snapshot = assert.snapshot() + end) + + after_each(function() + snapshot:revert() + end) + + before_each(function() + dummy:uninstall() + dummy2:uninstall() + end) + + describe("locking ::", function() + it("should respect semaphore locks", function() + local semaphore = Semaphore.new(1) + local location = InstallLocation.new(settings.current.install_root_dir) + local dummy_handle = InstallHandle.new(dummy) + local runner_1 = InstallRunner.new(location, dummy_handle, semaphore) + local runner_2 = InstallRunner.new(location, InstallHandle.new(dummy2), semaphore) + + stub(dummy.spec.source, "install", function() + a.sleep(10000) + end) + spy.on(dummy2.spec.source, "install") + + runner_1:execute {} + runner_2:execute {} + + assert.wait(function() + assert.spy(dummy.spec.source.install).was_called(1) + assert.spy(dummy2.spec.source.install).was_not_called() + end) + + dummy_handle:terminate() + + assert.wait(function() + assert.spy(dummy2.spec.source.install).was_called(1) + end) + end) + + it("should write lockfile", function() + local semaphore = Semaphore.new(1) + local location = InstallLocation.new(settings.current.install_root_dir) + local dummy_handle = InstallHandle.new(dummy) + local runner = InstallRunner.new(location, dummy_handle, semaphore) + + spy.on(fs.async, "write_file") + + runner:execute {} + + assert.wait(function() + assert.spy(fs.async.write_file).was_called_with(location:lockfile(dummy.name), vim.fn.getpid()) + end) + end) + + it("should abort installation if installation lock exists", function() + local semaphore = Semaphore.new(1) + local location = InstallLocation.new(settings.current.install_root_dir) + local dummy_handle = InstallHandle.new(dummy) + local runner = InstallRunner.new(location, dummy_handle, semaphore) + + stub(fs.async, "file_exists") + stub(fs.async, "read_file") + fs.async.file_exists.on_call_with(location:lockfile(dummy.name)).returns(true) + fs.async.read_file.on_call_with(location:lockfile(dummy.name)).returns "1337" + + local callback = spy.new() + runner:execute({}, callback) + + assert.wait(function() + assert.spy(callback).was_called() + assert.spy(callback).was_called_with( + false, + "Lockfile exists, installation is already running in another process (pid: 1337). Run with :MasonInstall --force to bypass." + ) + end) + end) + + it("should not abort installation if installation lock exists with force=true", function() + local semaphore = Semaphore.new(1) + local location = InstallLocation.new(settings.current.install_root_dir) + local dummy_handle = InstallHandle.new(dummy) + local runner = InstallRunner.new(location, dummy_handle, semaphore) + + stub(fs.async, "file_exists") + stub(fs.async, "read_file") + fs.async.file_exists.on_call_with(location:lockfile(dummy.name)).returns(true) + fs.async.read_file.on_call_with(location:lockfile(dummy.name)).returns "1337" + + local callback = spy.new() + runner:execute({ force = true }, callback) + + assert.wait(function() + assert.spy(callback).was_called() + assert.spy(callback).was_called_with(true, nil) + end) + end) + + it("should release lock after successful installation", function() + local semaphore = Semaphore.new(1) + local location = InstallLocation.new(settings.current.install_root_dir) + local dummy_handle = InstallHandle.new(dummy) + local runner = InstallRunner.new(location, dummy_handle, semaphore) + + local callback = spy.new() + runner:execute({}, callback) + + assert.wait(function() + assert.is_true(fs.sync.file_exists(location:lockfile(dummy.name))) + end) + assert.wait(function() + assert.spy(callback).was_called() + end) + assert.is_false(fs.sync.file_exists(location:lockfile(dummy.name))) + end) + end) + + it("should initialize install location", function() + local location = InstallLocation.new(settings.current.install_root_dir) + local runner = InstallRunner.new(location, InstallHandle.new(registry.get_package "dummy"), Semaphore.new(1)) + + spy.on(location, "initialize") + + runner:execute {} + + assert.wait(function() + assert.spy(location.initialize).was_called(1) + end) + end) + + describe("receipt ::", function() + it("should write receipt", function() + local location = InstallLocation.new(settings.current.install_root_dir) + local runner = + InstallRunner.new(location, InstallHandle.new(registry.get_package "dummy"), Semaphore.new(1)) + + runner:execute {} + + assert.wait(function() + local receipt_file = location:package "dummy/mason-receipt.json" + assert.is_true(fs.sync.file_exists(receipt_file)) + assert.is_true(match.tbl_containing { + name = "dummy", + schema_version = "1.2", + metrics = match.tbl_containing { + completion_time = match.is_number(), + start_time = match.is_number(), + }, + source = match.same { + id = "pkg:mason/dummy@1.0.0", + type = "registry+v1", + }, + links = match.same { + bin = {}, + opt = {}, + share = {}, + }, + }(vim.json.decode(fs.sync.read_file(receipt_file)))) + end) + end) + end) + + it("should emit failures", function() + local registry_spy = spy.new() + local package_spy = spy.new() + registry:once("package:install:failed", registry_spy) + dummy:once("install:failed", package_spy) + + local location = InstallLocation.new(settings.current.install_root_dir) + local handle = InstallHandle.new(registry.get_package "dummy") + local runner = InstallRunner.new(location, handle, Semaphore.new(1)) + + stub(dummy.spec.source, "install", function() + error("I've made a mistake.", 0) + end) + + local callback = spy.new() + runner:execute({}, callback) + + assert.wait(function() + assert.spy(registry_spy).was_called(1) + assert.spy(registry_spy).was_called_with(match.is_ref(dummy), match.is_ref(handle), "I've made a mistake.") + assert.spy(package_spy).was_called(1) + assert.spy(package_spy).was_called_with(match.is_ref(handle), "I've made a mistake.") + + assert.spy(callback).was_called(1) + assert.spy(callback).was_called_with(false, "I've made a mistake.") + end, 10) + end) + + it("should terminate installation", function() + local location = InstallLocation.new(settings.current.install_root_dir) + local handle = InstallHandle.new(registry.get_package "dummy") + local runner = InstallRunner.new(location, handle, Semaphore.new(1)) + + local capture = spy.new() + stub(dummy.spec.source, "install", function() + capture() + handle:terminate() + a.sleep(0) + capture() + end) + + local callback = spy.new() + + runner:execute({}, callback) + + assert.wait(function() + assert.spy(callback).was_called(1) + assert.spy(callback).was_called_with(false, "Installation was aborted.") + + assert.spy(capture).was_called(1) + end) + end) + + it("should write debug logs when debug=true", function() + local location = InstallLocation.new(settings.current.install_root_dir) + local handle = InstallHandle.new(registry.get_package "dummy") + local runner = InstallRunner.new(location, handle, Semaphore.new(1)) + + stub(dummy.spec.source, "install", function(ctx) + ctx.stdio_sink.stdout "Hello " + ctx.stdio_sink.stderr "world!" + end) + + local callback = spy.new() + runner:execute({ debug = true }, callback) + + assert.wait(function() + assert.spy(callback).was_called() + assert.spy(callback).was_called_with(true, nil) + end) + assert.is_true(fs.sync.file_exists(location:package "dummy/mason-debug.log")) + assert.equals("Hello world!", fs.sync.read_file(location:package "dummy/mason-debug.log")) + end) + + it("should not retain installation directory on failure", function() + local location = InstallLocation.new(settings.current.install_root_dir) + local handle = InstallHandle.new(registry.get_package "dummy") + local runner = InstallRunner.new(location, handle, Semaphore.new(1)) + + stub(dummy.spec.source, "install", function(ctx) + ctx.stdio_sink.stderr "Something will go terribly wrong.\n" + error("This went terribly wrong.", 0) + end) + + local callback = spy.new() + runner:execute({}, callback) + + assert.wait(function() + assert.spy(callback).was_called() + assert.spy(callback).was_called_with(false, "This went terribly wrong.") + end) + assert.is_false(fs.sync.dir_exists(location:staging "dummy")) + assert.is_false(fs.sync.dir_exists(location:package "dummy")) + end) + + it("should retain installation directory on failure and debug=true", function() + local location = InstallLocation.new(settings.current.install_root_dir) + local handle = InstallHandle.new(registry.get_package "dummy") + local runner = InstallRunner.new(location, handle, Semaphore.new(1)) + + stub(dummy.spec.source, "install", function(ctx) + ctx.stdio_sink.stderr "Something will go terribly wrong.\n" + error("This went terribly wrong.", 0) + end) + + local callback = spy.new() + runner:execute({ debug = true }, callback) + + assert.wait(function() + assert.spy(callback).was_called() + assert.spy(callback).was_called_with(false, "This went terribly wrong.") + end) + assert.is_true(fs.sync.dir_exists(location:staging "dummy")) + assert.equals( + "Something will go terribly wrong.\nThis went terribly wrong.\n", + fs.sync.read_file(location:staging "dummy/mason-debug.log") + ) + end) +end) diff --git a/tests/mason-core/package/package_spec.lua b/tests/mason-core/package/package_spec.lua index 67d49387..622a2ee4 100644 --- a/tests/mason-core/package/package_spec.lua +++ b/tests/mason-core/package/package_spec.lua @@ -7,6 +7,16 @@ local spy = require "luassert.spy" local stub = require "luassert.stub" describe("package", function() + local snapshot + + before_each(function() + snapshot = assert.snapshot() + end) + + after_each(function() + snapshot:revert() + end) + before_each(function() registry.get_package("dummy"):uninstall() package.loaded["dummy_package"] = nil @@ -37,7 +47,7 @@ describe("package", function() source = { id = "pkg:mason/package@1", install = function() end, - } + }, } local function spec(fields) return setmetatable(fields, { __index = valid_spec }) @@ -105,26 +115,25 @@ describe("package", function() dummy.handle = nil end) - it( - "should successfully install package", - async_test(function() - local dummy = registry.get_package "dummy" - local package_install_success_handler = spy.new() - local package_install_failed_handler = spy.new() - local install_success_handler = spy.new() - local install_failed_handler = spy.new() - registry:once("package:install:success", package_install_success_handler) - registry:once("package:install:failed", package_install_failed_handler) - dummy:once("install:success", install_success_handler) - dummy:once("install:failed", install_failed_handler) + it("should successfully install package", function() + local dummy = registry.get_package "dummy" + local package_install_success_handler = spy.new() + local package_install_failed_handler = spy.new() + local install_success_handler = spy.new() + local install_failed_handler = spy.new() + registry:once("package:install:success", package_install_success_handler) + registry:once("package:install:failed", package_install_failed_handler) + dummy:once("install:success", install_success_handler) + dummy:once("install:failed", install_failed_handler) - local handle = dummy:install { version = "1337" } + local handle = dummy:install { version = "1337" } - assert.wait_for(function() - assert.is_true(handle:is_closed()) - assert.is_true(dummy:is_installed()) - end) + assert.wait(function() + assert.is_true(handle:is_closed()) + assert.is_true(dummy:is_installed()) + end) + assert.wait(function() assert.spy(install_success_handler).was_called(1) assert.spy(install_success_handler).was_called_with(match.is_ref(handle)) assert.spy(package_install_success_handler).was_called(1) @@ -132,45 +141,45 @@ describe("package", function() assert.spy(package_install_failed_handler).was_called(0) assert.spy(install_failed_handler).was_called(0) end) - ) + end) - it( - "should fail to install package", - async_test(function() - local dummy = registry.get_package "dummy" - stub(dummy.spec.source, "install", function() - error "I simply refuse to be installed." - end) - local package_install_success_handler = spy.new() - local package_install_failed_handler = spy.new() - local install_success_handler = spy.new() - local install_failed_handler = spy.new() - registry:once("package:install:success", package_install_success_handler) - registry:once("package:install:failed", package_install_failed_handler) - dummy:once("install:success", install_success_handler) - dummy:once("install:failed", install_failed_handler) + it("should fail to install package", function() + local dummy = registry.get_package "dummy" + stub(dummy.spec.source, "install", function() + error("I simply refuse to be installed.", 0) + end) + local package_install_success_handler = spy.new() + local package_install_failed_handler = spy.new() + local install_success_handler = spy.new() + local install_failed_handler = spy.new() + registry:once("package:install:success", package_install_success_handler) + registry:once("package:install:failed", package_install_failed_handler) + dummy:once("install:success", install_success_handler) + dummy:once("install:failed", install_failed_handler) - local handle = dummy:install { version = "1337" } + local handle = dummy:install { version = "1337" } - assert.wait_for(function() - assert.is_true(handle:is_closed()) - assert.is_false(dummy:is_installed()) - end) + assert.wait(function() + assert.is_true(handle:is_closed()) + assert.is_false(dummy:is_installed()) + end) + assert.wait(function() assert.spy(install_failed_handler).was_called(1) - assert.spy(install_failed_handler).was_called_with(match.is_ref(handle)) + assert.spy(install_failed_handler).was_called_with(match.is_ref(handle), "I simply refuse to be installed.") assert.spy(package_install_failed_handler).was_called(1) - assert.spy(package_install_failed_handler).was_called_with(match.is_ref(dummy), match.is_ref(handle)) + assert + .spy(package_install_failed_handler) + .was_called_with(match.is_ref(dummy), match.is_ref(handle), "I simply refuse to be installed.") assert.spy(package_install_success_handler).was_called(0) assert.spy(install_success_handler).was_called(0) end) - ) + end) - it( - "should be able to start package installation outside of main loop", - async_test(function() - local dummy = registry.get_package "dummy" + it("should be able to start package installation outside of main loop", function() + local dummy = registry.get_package "dummy" + local handle = a.run_blocking(function() -- Move outside the main loop a.wait(function(resolve) local timer = vim.loop.new_timer() @@ -179,25 +188,19 @@ describe("package", function() resolve() end) end) - assert.is_true(vim.in_fast_event()) - local handle = assert.is_not.has_error(function() + return assert.is_not.has_error(function() return dummy:install() end) - - assert.wait_for(function() - assert.is_true(handle:is_closed()) - end) end) - ) + end) - it( - "should be able to instantiate package outside of main loop", - async_test(function() - local dummy = registry.get_package "registry" + it("should be able to instantiate package outside of main loop", function() + local dummy = registry.get_package "registry" - -- Move outside the main loop + -- Move outside the main loop + a.run_blocking(function () a.wait(function(resolve) local timer = vim.loop.new_timer() timer:start(0, 0, function() @@ -207,12 +210,10 @@ describe("package", function() end) assert.is_true(vim.in_fast_event()) - local pkg = assert.is_not.has_error(function() return Pkg.new(dummy.spec) end) - assert.same(dummy.spec, pkg.spec) end) - ) + end) end) diff --git a/tests/mason-core/platform_spec.lua b/tests/mason-core/platform_spec.lua index 48484707..88e2b42a 100644 --- a/tests/mason-core/platform_spec.lua +++ b/tests/mason-core/platform_spec.lua @@ -15,6 +15,16 @@ local function stub_etc_os_release(contents) end describe("platform", function() + local snapshot + + before_each(function() + snapshot = assert.snapshot() + end) + + after_each(function() + snapshot:revert() + end) + local function platform() package.loaded["mason-core.platform"] = nil return require "mason-core.platform" diff --git a/tests/mason-core/process_spec.lua b/tests/mason-core/process_spec.lua index 38ea94de..06330cdd 100644 --- a/tests/mason-core/process_spec.lua +++ b/tests/mason-core/process_spec.lua @@ -4,25 +4,22 @@ local spy = require "luassert.spy" describe("process.spawn", function() -- Unix only - it( - "should spawn command and feed output to sink", - async_test(function() - local stdio = process.in_memory_sink() - local callback = spy.new() - process.spawn("env", { - args = {}, - env = { - "HELLO=world", - "MY_ENV=var", - }, - stdio_sink = stdio.sink, - }, callback) + it("should spawn command and feed output to sink", function() + local stdio = process.in_memory_sink() + local callback = spy.new() + process.spawn("env", { + args = {}, + env = { + "HELLO=world", + "MY_ENV=var", + }, + stdio_sink = stdio.sink, + }, callback) - assert.wait_for(function() - assert.spy(callback).was_called(1) - assert.spy(callback).was_called_with(true, 0, match.is_number()) - assert.equals(table.concat(stdio.buffers.stdout, ""), "HELLO=world\nMY_ENV=var\n") - end) + assert.wait(function() + assert.spy(callback).was_called(1) + assert.spy(callback).was_called_with(true, 0, match.is_number()) + assert.equals(table.concat(stdio.buffers.stdout, ""), "HELLO=world\nMY_ENV=var\n") end) - ) + end) end) diff --git a/tests/mason-core/result_spec.lua b/tests/mason-core/result_spec.lua index 46f4d35c..227e53ae 100644 --- a/tests/mason-core/result_spec.lua +++ b/tests/mason-core/result_spec.lua @@ -250,27 +250,28 @@ describe("Result.try", function() assert.equals("42", failure:err_or_nil()) end) - it( - "should allow calling async functions inside try blocks", - async_test(function() - assert.same( - Result.success "Hello, world!", - Result.try(function(try) + it("should allow calling async functions inside try blocks", function() + assert.same( + Result.success "Hello, world!", + a.run_blocking(function() + return Result.try(function(try) a.sleep(10) local hello = try(Result.success "Hello, ") local world = try(Result.success "world!") return hello .. world end) - ) - local failure = Result.try(function(try) + end) + ) + local failure = a.run_blocking(function() + return Result.try(function(try) a.sleep(10) local err = try(Result.success "42") error(err) end) - assert.is_true(failure:is_failure()) - assert.is_true(match.matches ": 42$"(failure:err_or_nil())) end) - ) + assert.is_true(failure:is_failure()) + assert.is_true(match.matches ": 42$"(failure:err_or_nil())) + end) it("should not unwrap result values in try blocks", function() assert.same( @@ -300,12 +301,11 @@ describe("Result.try", function() ) end) - it( - "should allow nesting try blocks in async scope", - async_test(function() - assert.same( - Result.success "Hello from the underworld!", - Result.try(function(try) + it("should allow nesting try blocks in async scope", function() + assert.same( + Result.success "Hello from the underworld!", + a.run_blocking(function() + return Result.try(function(try) a.sleep(10) local greeting = try(Result.success "Hello from the %s!") a.sleep(10) @@ -316,7 +316,7 @@ describe("Result.try", function() return value end))) end) - ) - end) - ) + end) + ) + end) end) diff --git a/tests/mason-core/spawn_spec.lua b/tests/mason-core/spawn_spec.lua index 15b9fe7d..a1432294 100644 --- a/tests/mason-core/spawn_spec.lua +++ b/tests/mason-core/spawn_spec.lua @@ -1,3 +1,4 @@ +local a = require "mason-core.async" local match = require "luassert.match" local process = require "mason-core.process" local spawn = require "mason-core.spawn" @@ -5,212 +6,186 @@ local spy = require "luassert.spy" local stub = require "luassert.stub" describe("async spawn", function() - it( - "should spawn commands and return stdout & stderr", - async_test(function() - local result = spawn.env { - env_raw = { "FOO=bar" }, - } - assert.is_true(result:is_success()) - assert.equals("FOO=bar\n", result:get_or_nil().stdout) - assert.equals("", result:get_or_nil().stderr) - end) - ) + local snapshot - it( - "should use provided stdio_sink", - async_test(function() - local stdio = process.in_memory_sink() - local result = spawn.env { - env_raw = { "FOO=bar" }, - stdio_sink = stdio.sink, - } - assert.is_true(result:is_success()) - assert.equals(nil, result:get_or_nil().stdout) - assert.equals(nil, result:get_or_nil().stderr) - assert.equals("FOO=bar\n", table.concat(stdio.buffers.stdout, "")) - assert.equals("", table.concat(stdio.buffers.stderr, "")) - end) - ) + before_each(function() + snapshot = assert.snapshot() + end) - it( - "should pass command arguments", - async_test(function() - local result = spawn.bash { - "-c", - 'echo "Hello $VAR"', - env = { VAR = "world" }, - } + after_each(function() + snapshot:revert() + end) - assert.is_true(result:is_success()) - assert.equals("Hello world\n", result:get_or_nil().stdout) - assert.equals("", result:get_or_nil().stderr) - end) - ) + it("should spawn commands and return stdout & stderr", function() + local result = a.run_blocking(spawn.env, { + env_raw = { "FOO=bar" }, + }) + assert.is_true(result:is_success()) + assert.equals("FOO=bar\n", result:get_or_nil().stdout) + assert.equals("", result:get_or_nil().stderr) + end) - it( - "should ignore vim.NIL args", - async_test(function() - spy.on(process, "spawn") - local result = spawn.bash { - vim.NIL, - vim.NIL, - "-c", - { vim.NIL, vim.NIL }, - 'echo "Hello $VAR"', - env = { VAR = "world" }, - } + it("should use provided stdio_sink", function() + local stdio = process.in_memory_sink() + local result = a.run_blocking(spawn.env, { + env_raw = { "FOO=bar" }, + stdio_sink = stdio.sink, + }) + assert.is_true(result:is_success()) + assert.equals(nil, result:get_or_nil().stdout) + assert.equals(nil, result:get_or_nil().stderr) + assert.equals("FOO=bar\n", table.concat(stdio.buffers.stdout, "")) + assert.equals("", table.concat(stdio.buffers.stderr, "")) + end) - assert.is_true(result:is_success()) - assert.equals("Hello world\n", result:get_or_nil().stdout) - assert.equals("", result:get_or_nil().stderr) - assert.spy(process.spawn).was_called(1) - assert.spy(process.spawn).was_called_with( - "bash", - match.tbl_containing { - stdio_sink = match.tbl_containing { - stdout = match.is_function(), - stderr = match.is_function(), - }, - env = match.list_containing "VAR=world", - args = match.tbl_containing { - "-c", - 'echo "Hello $VAR"', - }, - }, - match.is_function() - ) - end) - ) + it("should pass command arguments", function() + local result = a.run_blocking(spawn.bash, { + "-c", + 'echo "Hello $VAR"', + env = { VAR = "world" }, + }) - it( - "should flatten table args", - async_test(function() - local result = spawn.bash { - { "-c", 'echo "Hello $VAR"' }, - env = { VAR = "world" }, - } + assert.is_true(result:is_success()) + assert.equals("Hello world\n", result:get_or_nil().stdout) + assert.equals("", result:get_or_nil().stderr) + end) - assert.is_true(result:is_success()) - assert.equals("Hello world\n", result:get_or_nil().stdout) - assert.equals("", result:get_or_nil().stderr) - end) - ) + it("should ignore vim.NIL args", function() + spy.on(process, "spawn") + local result = a.run_blocking(spawn.bash, { + vim.NIL, + vim.NIL, + "-c", + { vim.NIL, vim.NIL }, + 'echo "Hello $VAR"', + env = { VAR = "world" }, + }) - it( - "should call on_spawn", - async_test(function() - local on_spawn = spy.new(function(_, stdio) - local stdin = stdio[1] - stdin:write "im so piped rn" - stdin:close() - end) + assert.is_true(result:is_success()) + assert.equals("Hello world\n", result:get_or_nil().stdout) + assert.equals("", result:get_or_nil().stderr) + assert.spy(process.spawn).was_called(1) + assert.spy(process.spawn).was_called_with( + "bash", + match.tbl_containing { + stdio_sink = match.tbl_containing { + stdout = match.is_function(), + stderr = match.is_function(), + }, + env = match.list_containing "VAR=world", + args = match.tbl_containing { + "-c", + 'echo "Hello $VAR"', + }, + }, + match.is_function() + ) + end) - local result = spawn.cat { - { "-" }, - on_spawn = on_spawn, - } + it("should flatten table args", function() + local result = a.run_blocking(spawn.bash, { + { "-c", 'echo "Hello $VAR"' }, + env = { VAR = "world" }, + }) - assert.spy(on_spawn).was_called(1) - assert.spy(on_spawn).was_called_with(match.is_not.is_nil(), match.is_table(), match.is_number()) - assert.is_true(result:is_success()) - assert.equals("im so piped rn", result:get_or_nil().stdout) + assert.is_true(result:is_success()) + assert.equals("Hello world\n", result:get_or_nil().stdout) + assert.equals("", result:get_or_nil().stderr) + end) + + it("should call on_spawn", function() + local on_spawn = spy.new(function(_, stdio) + local stdin = stdio[1] + stdin:write "im so piped rn" + stdin:close() end) - ) - it( - "should not call on_spawn if spawning fails", - async_test(function() - local on_spawn = spy.new() + local result = a.run_blocking(spawn.cat, { + { "-" }, + on_spawn = on_spawn, + }) - local result = spawn.this_cmd_doesnt_exist { - on_spawn = on_spawn, - } + assert.spy(on_spawn).was_called(1) + assert.spy(on_spawn).was_called_with(match.is_not.is_nil(), match.is_table(), match.is_number()) + assert.is_true(result:is_success()) + assert.equals("im so piped rn", result:get_or_nil().stdout) + end) - assert.spy(on_spawn).was_called(0) - assert.is_true(result:is_failure()) - end) - ) + it("should not call on_spawn if spawning fails", function() + local on_spawn = spy.new() - it( - "should handle failure to spawn process", - async_test(function() - stub(process, "spawn", function(_, _, callback) - callback(false) - end) + local result = a.run_blocking(spawn.this_cmd_doesnt_exist, { + on_spawn = on_spawn, + }) - local result = spawn.my_cmd {} - assert.is_true(result:is_failure()) - assert.is_nil(result:err_or_nil().exit_code) + assert.spy(on_spawn).was_called(0) + assert.is_true(result:is_failure()) + end) + + it("should handle failure to spawn process", function() + stub(process, "spawn", function(_, _, callback) + callback(false) end) - ) - it( - "should format failure message", - async_test(function() - stub(process, "spawn", function(cmd, opts, callback) - opts.stdio_sink.stderr(("This is an error message for %s!"):format(cmd)) - callback(false, 127) - end) + local result = a.run_blocking(spawn.my_cmd, {}) + assert.is_true(result:is_failure()) + assert.is_nil(result:err_or_nil().exit_code) + end) - local result = spawn.bash {} - assert.is_true(result:is_failure()) - assert.equals( - "spawn: bash failed with exit code 127 and signal -. This is an error message for bash!", - tostring(result:err_or_nil()) - ) + it("should format failure message", function() + stub(process, "spawn", function(cmd, opts, callback) + opts.stdio_sink.stderr(("This is an error message for %s!"):format(cmd)) + callback(false, 127) end) - ) - it( - "should check whether command is executable", - async_test(function() - local result = spawn.my_cmd {} - assert.is_true(result:is_failure()) - assert.equals( - "spawn: my_cmd failed with exit code - and signal -. my_cmd is not executable", - tostring(result:err_or_nil()) - ) - end) - ) + local result = a.run_blocking(spawn.bash, {}) + assert.is_true(result:is_failure()) + assert.equals( + "spawn: bash failed with exit code 127 and signal -. This is an error message for bash!", + tostring(result:err_or_nil()) + ) + end) - it( - "should skip checking whether command is executable", - async_test(function() - stub(process, "spawn", function(_, _, callback) - callback(false, 127) - end) + it("should check whether command is executable", function() + local result = a.run_blocking(spawn.my_cmd, {}) + assert.is_true(result:is_failure()) + assert.equals( + "spawn: my_cmd failed with exit code - and signal -. my_cmd is not executable", + tostring(result:err_or_nil()) + ) + end) - local result = spawn.my_cmd { "arg1", check_executable = false } - assert.is_true(result:is_failure()) - assert.spy(process.spawn).was_called(1) - assert.spy(process.spawn).was_called_with( - "my_cmd", - match.tbl_containing { - args = match.same { "arg1" }, - }, - match.is_function() - ) + it("should skip checking whether command is executable", function() + stub(process, "spawn", function(_, _, callback) + callback(false, 127) end) - ) - it( - "should skip checking whether command is executable if with_paths is provided", - async_test(function() - stub(process, "spawn", function(_, _, callback) - callback(false, 127) - end) + local result = a.run_blocking(spawn.my_cmd, { "arg1", check_executable = false }) + assert.is_true(result:is_failure()) + assert.spy(process.spawn).was_called(1) + assert.spy(process.spawn).was_called_with( + "my_cmd", + match.tbl_containing { + args = match.same { "arg1" }, + }, + match.is_function() + ) + end) - local result = spawn.my_cmd { "arg1", with_paths = {} } - assert.is_true(result:is_failure()) - assert.spy(process.spawn).was_called(1) - assert.spy(process.spawn).was_called_with( - "my_cmd", - match.tbl_containing { - args = match.same { "arg1" }, - }, - match.is_function() - ) + it("should skip checking whether command is executable if with_paths is provided", function() + stub(process, "spawn", function(_, _, callback) + callback(false, 127) end) - ) + + local result = a.run_blocking(spawn.my_cmd, { "arg1", with_paths = {} }) + assert.is_true(result:is_failure()) + assert.spy(process.spawn).was_called(1) + assert.spy(process.spawn).was_called_with( + "my_cmd", + match.tbl_containing { + args = match.same { "arg1" }, + }, + match.is_function() + ) + end) end) diff --git a/tests/mason-core/terminator_spec.lua b/tests/mason-core/terminator_spec.lua index 24c1ec25..29a3a1dd 100644 --- a/tests/mason-core/terminator_spec.lua +++ b/tests/mason-core/terminator_spec.lua @@ -8,112 +8,113 @@ local stub = require "luassert.stub" local terminator = require "mason-core.terminator" describe("terminator", function() - it( - "should terminate all active handles on nvim exit", - async_test(function() - spy.on(InstallHandle, "terminate") - local dummy = registry.get_package "dummy" - local dummy2 = registry.get_package "dummy2" - for _, pkg in ipairs { dummy, dummy2 } do - stub(pkg.spec.source, "install", function() - a.sleep(10000) - end) - end + local snapshot - local dummy_handle = dummy:install() - local dummy2_handle = dummy2:install() + before_each(function() + snapshot = assert.snapshot() + end) - assert.wait_for(function() - assert.spy(dummy.spec.source.install).was_called() - assert.spy(dummy2.spec.source.install).was_called() + after_each(function() + -- wait for scheduled calls to expire + a.run_blocking(a.wait, vim.schedule) + snapshot:revert() + end) + + it("should terminate all active handles on nvim exit", function() + spy.on(InstallHandle, "terminate") + local dummy = registry.get_package "dummy" + local dummy2 = registry.get_package "dummy2" + for _, pkg in ipairs { dummy, dummy2 } do + stub(pkg.spec.source, "install", function() + a.sleep(10000) end) + end - terminator.terminate(5000) + local dummy_handle = dummy:install() + local dummy2_handle = dummy2:install() - assert.spy(InstallHandle.terminate).was_called(2) - assert.spy(InstallHandle.terminate).was_called_with(match.is_ref(dummy_handle)) - assert.spy(InstallHandle.terminate).was_called_with(match.is_ref(dummy2_handle)) - assert.wait_for(function() - assert.is_true(dummy_handle:is_closed()) - assert.is_true(dummy2_handle:is_closed()) - end) + assert.wait(function() + assert.spy(dummy.spec.source.install).was_called() + assert.spy(dummy2.spec.source.install).was_called() end) - ) - it( - "should print warning messages", - async_test(function() - spy.on(vim.api, "nvim_echo") - spy.on(vim.api, "nvim_err_writeln") - spy.on(InstallHandle, "terminate") - local dummy = registry.get_package "dummy" - local dummy2 = registry.get_package "dummy2" - for _, pkg in ipairs { dummy, dummy2 } do - stub(pkg.spec.source, "install", function() - a.sleep(10000) - end) - end + terminator.terminate(5000) - local dummy_handle = dummy:install() - local dummy2_handle = dummy2:install() + assert.spy(InstallHandle.terminate).was_called(2) + assert.spy(InstallHandle.terminate).was_called_with(match.is_ref(dummy_handle)) + assert.spy(InstallHandle.terminate).was_called_with(match.is_ref(dummy2_handle)) + assert.wait(function() + assert.is_true(dummy_handle:is_closed()) + assert.is_true(dummy2_handle:is_closed()) + end) + end) - assert.wait_for(function() - assert.spy(dummy.spec.source.install).was_called() - assert.spy(dummy2.spec.source.install).was_called() + it("should print warning messages", function() + spy.on(vim.api, "nvim_echo") + spy.on(vim.api, "nvim_err_writeln") + local dummy = registry.get_package "dummy" + local dummy2 = registry.get_package "dummy2" + for _, pkg in ipairs { dummy, dummy2 } do + stub(pkg.spec.source, "install", function() + a.sleep(10000) end) + end - terminator.terminate(5000) + local dummy_handle = dummy:install() + local dummy2_handle = dummy2:install() - assert.spy(vim.api.nvim_echo).was_called(1) - assert.spy(vim.api.nvim_echo).was_called_with({ - { - "[mason.nvim] Neovim is exiting while packages are still installing. Terminating all installations…", - "WarningMsg", - }, - }, true, {}) + assert.wait(function() + assert.spy(dummy.spec.source.install).was_called() + assert.spy(dummy2.spec.source.install).was_called() + end) - a.wait(vim.schedule) + terminator.terminate(5000) - assert.spy(vim.api.nvim_err_writeln).was_called(1) - assert.spy(vim.api.nvim_err_writeln).was_called_with(_.dedent [[ + assert.spy(vim.api.nvim_echo).was_called(1) + assert.spy(vim.api.nvim_echo).was_called_with({ + { + "[mason.nvim] Neovim is exiting while packages are still installing. Terminating all installations…", + "WarningMsg", + }, + }, true, {}) + + a.run_blocking(a.wait, vim.schedule) + + assert.spy(vim.api.nvim_err_writeln).was_called(1) + assert.spy(vim.api.nvim_err_writeln).was_called_with(_.dedent [[ [mason.nvim] Neovim exited while the following packages were installing. Installation was aborted. - dummy - dummy2 ]]) - assert.wait_for(function() - assert.is_true(dummy_handle:is_closed()) - assert.is_true(dummy2_handle:is_closed()) - end) + assert.wait(function() + assert.is_true(dummy_handle:is_closed()) + assert.is_true(dummy2_handle:is_closed()) end) - ) + end) - it( - "should send SIGTERM and then SIGKILL after grace period", - async_test(function() - spy.on(InstallHandle, "kill") - local dummy = registry.get_package "dummy" - stub(dummy.spec.source, "install") - dummy.spec.source.install.invokes(function(ctx) - -- your signals have no power here - ctx.spawn.bash { "-c", "function noop { :; }; trap noop SIGTERM; sleep 999999;" } - end) + it("should send SIGTERM and then SIGKILL after grace period", function() + spy.on(InstallHandle, "kill") + local dummy = registry.get_package "dummy" + stub(dummy.spec.source, "install", function(ctx) + -- your signals have no power here + ctx.spawn.bash { "-c", "function noop { :; }; trap noop SIGTERM; sleep 999999;" } + end) - local handle = dummy:install() + local handle = dummy:install() - assert.wait_for(function() - assert.spy(dummy.spec.source.install).was_called() - end) - terminator.terminate(50) + assert.wait(function() + assert.spy(dummy.spec.source.install).was_called() + end) + terminator.terminate(50) - assert.wait_for(function() - assert.spy(InstallHandle.kill).was_called(2) - assert.spy(InstallHandle.kill).was_called_with(match.is_ref(handle), 15) -- SIGTERM - assert.spy(InstallHandle.kill).was_called_with(match.is_ref(handle), 9) -- SIGKILL - end) + assert.wait(function() + assert.spy(InstallHandle.kill).was_called(2) + assert.spy(InstallHandle.kill).was_called_with(match.is_ref(handle), 15) -- SIGTERM + assert.spy(InstallHandle.kill).was_called_with(match.is_ref(handle), 9) -- SIGKILL + end) - assert.wait_for(function() - assert.is_true(handle:is_closed()) - end) + assert.wait(function() + assert.is_true(handle:is_closed()) end) - ) + end) end) diff --git a/tests/mason-core/ui_spec.lua b/tests/mason-core/ui_spec.lua index 17087045..efd60712 100644 --- a/tests/mason-core/ui_spec.lua +++ b/tests/mason-core/ui_spec.lua @@ -142,187 +142,177 @@ describe("ui", function() end) describe("integration test", function() - it( - "calls vim APIs as expected during rendering", - async_test(function() - local window = display.new_view_only_win("test", "my-filetype") + it("calls vim APIs as expected during rendering", function() + local window = display.new_view_only_win("test", "my-filetype") - window.view(function(state) - return Ui.Node { - Ui.Keybind("U", "EFFECT", nil, true), - Ui.Text { - "Line number 1!", - state.text, - }, - Ui.Keybind("R", "R_EFFECT", { state.text }), - Ui.HlTextNode { - { - { "My highlighted text", "MyHighlightGroup" }, - }, - }, - } - end) - - local mutate_state = window.state { text = "Initial state" } - - local clear_namespace = spy.on(vim.api, "nvim_buf_clear_namespace") - local buf_set_option = spy.on(vim.api, "nvim_buf_set_option") - local win_set_option = spy.on(vim.api, "nvim_win_set_option") - local set_lines = spy.on(vim.api, "nvim_buf_set_lines") - local set_extmark = spy.on(vim.api, "nvim_buf_set_extmark") - local add_highlight = spy.on(vim.api, "nvim_buf_add_highlight") - local set_keymap = spy.on(vim.keymap, "set") - - window.init { - effects = { - ["EFFECT"] = function() end, - ["R_EFFECT"] = function() end, + window.view(function(state) + return Ui.Node { + Ui.Keybind("U", "EFFECT", nil, true), + Ui.Text { + "Line number 1!", + state.text, }, - winhighlight = { - "NormalFloat:MasonNormal", - "CursorLine:MasonCursorLine", + Ui.Keybind("R", "R_EFFECT", { state.text }), + Ui.HlTextNode { + { + { "My highlighted text", "MyHighlightGroup" }, + }, }, } - window.open() + end) - -- Initial window and buffer creation + initial render - a.wait(vim.schedule) + local mutate_state = window.state { text = "Initial state" } - assert.spy(win_set_option).was_called(9) - assert.spy(win_set_option).was_called_with(match.is_number(), "number", false) - assert.spy(win_set_option).was_called_with(match.is_number(), "relativenumber", false) - assert.spy(win_set_option).was_called_with(match.is_number(), "wrap", false) - assert.spy(win_set_option).was_called_with(match.is_number(), "spell", false) - assert.spy(win_set_option).was_called_with(match.is_number(), "foldenable", false) - assert.spy(win_set_option).was_called_with(match.is_number(), "signcolumn", "no") - assert.spy(win_set_option).was_called_with(match.is_number(), "colorcolumn", "") - assert.spy(win_set_option).was_called_with(match.is_number(), "cursorline", true) - assert - .spy(win_set_option) - .was_called_with(match.is_number(), "winhighlight", "NormalFloat:MasonNormal,CursorLine:MasonCursorLine") + local clear_namespace = spy.on(vim.api, "nvim_buf_clear_namespace") + local buf_set_option = spy.on(vim.api, "nvim_buf_set_option") + local win_set_option = spy.on(vim.api, "nvim_win_set_option") + local set_lines = spy.on(vim.api, "nvim_buf_set_lines") + local set_extmark = spy.on(vim.api, "nvim_buf_set_extmark") + local add_highlight = spy.on(vim.api, "nvim_buf_add_highlight") + local set_keymap = spy.on(vim.keymap, "set") - assert.spy(buf_set_option).was_called(10) - assert.spy(buf_set_option).was_called_with(match.is_number(), "modifiable", false) - assert.spy(buf_set_option).was_called_with(match.is_number(), "swapfile", false) - assert.spy(buf_set_option).was_called_with(match.is_number(), "textwidth", 0) - assert.spy(buf_set_option).was_called_with(match.is_number(), "buftype", "nofile") - assert.spy(buf_set_option).was_called_with(match.is_number(), "bufhidden", "wipe") - assert.spy(buf_set_option).was_called_with(match.is_number(), "buflisted", false) - assert.spy(buf_set_option).was_called_with(match.is_number(), "filetype", "my-filetype") - assert.spy(buf_set_option).was_called_with(match.is_number(), "undolevels", -1) + window.init { + effects = { + ["EFFECT"] = function() end, + ["R_EFFECT"] = function() end, + }, + winhighlight = { + "NormalFloat:MasonNormal", + "CursorLine:MasonCursorLine", + }, + } + window.open() - assert.spy(set_lines).was_called(1) - assert - .spy(set_lines) - .was_called_with(match.is_number(), 0, -1, false, { "Line number 1!", "Initial state", "My highlighted text" }) + -- Initial window and buffer creation + initial render + a.run_blocking(a.wait, vim.schedule) - assert.spy(set_extmark).was_called(0) + assert.spy(win_set_option).was_called(9) + assert.spy(win_set_option).was_called_with(match.is_number(), "number", false) + assert.spy(win_set_option).was_called_with(match.is_number(), "relativenumber", false) + assert.spy(win_set_option).was_called_with(match.is_number(), "wrap", false) + assert.spy(win_set_option).was_called_with(match.is_number(), "spell", false) + assert.spy(win_set_option).was_called_with(match.is_number(), "foldenable", false) + assert.spy(win_set_option).was_called_with(match.is_number(), "signcolumn", "no") + assert.spy(win_set_option).was_called_with(match.is_number(), "colorcolumn", "") + assert.spy(win_set_option).was_called_with(match.is_number(), "cursorline", true) + assert + .spy(win_set_option) + .was_called_with(match.is_number(), "winhighlight", "NormalFloat:MasonNormal,CursorLine:MasonCursorLine") - assert.spy(add_highlight).was_called(1) - assert - .spy(add_highlight) - .was_called_with(match.is_number(), match.is_number(), "MyHighlightGroup", 2, 0, 19) + assert.spy(buf_set_option).was_called(10) + assert.spy(buf_set_option).was_called_with(match.is_number(), "modifiable", false) + assert.spy(buf_set_option).was_called_with(match.is_number(), "swapfile", false) + assert.spy(buf_set_option).was_called_with(match.is_number(), "textwidth", 0) + assert.spy(buf_set_option).was_called_with(match.is_number(), "buftype", "nofile") + assert.spy(buf_set_option).was_called_with(match.is_number(), "bufhidden", "wipe") + assert.spy(buf_set_option).was_called_with(match.is_number(), "buflisted", false) + assert.spy(buf_set_option).was_called_with(match.is_number(), "filetype", "my-filetype") + assert.spy(buf_set_option).was_called_with(match.is_number(), "undolevels", -1) - assert.spy(set_keymap).was_called(2) - assert.spy(set_keymap).was_called_with( - "n", - "U", - match.is_function(), - match.tbl_containing { nowait = true, silent = true, buffer = match.is_number() } - ) - assert.spy(set_keymap).was_called_with( - "n", - "R", - match.is_function(), - match.tbl_containing { nowait = true, silent = true, buffer = match.is_number() } - ) + assert.spy(set_lines).was_called(1) + assert + .spy(set_lines) + .was_called_with(match.is_number(), 0, -1, false, { "Line number 1!", "Initial state", "My highlighted text" }) - assert.spy(clear_namespace).was_called(1) - assert.spy(clear_namespace).was_called_with(match.is_number(), match.is_number(), 0, -1) + assert.spy(set_extmark).was_called(0) - mutate_state(function(state) - state.text = "New state" - end) + assert.spy(add_highlight).was_called(1) + assert.spy(add_highlight).was_called_with(match.is_number(), match.is_number(), "MyHighlightGroup", 2, 0, 19) - assert.spy(set_lines).was_called(1) - a.wait(vim.schedule) - assert.spy(set_lines).was_called(2) + assert.spy(set_keymap).was_called(2) + assert.spy(set_keymap).was_called_with( + "n", + "U", + match.is_function(), + match.tbl_containing { nowait = true, silent = true, buffer = match.is_number() } + ) + assert.spy(set_keymap).was_called_with( + "n", + "R", + match.is_function(), + match.tbl_containing { nowait = true, silent = true, buffer = match.is_number() } + ) + + assert.spy(clear_namespace).was_called(1) + assert.spy(clear_namespace).was_called_with(match.is_number(), match.is_number(), 0, -1) - assert - .spy(set_lines) - .was_called_with(match.is_number(), 0, -1, false, { "Line number 1!", "New state", "My highlighted text" }) + mutate_state(function(state) + state.text = "New state" end) - ) - it( - "anchors to sticky cursor", - async_test(function() - local window = display.new_view_only_win("test", "my-filetype") - window.view(function(state) - local extra_lines = state.show_extra_lines - and Ui.Text { - "More", - "Lines", - "Here", - } - or Ui.Node {} - return Ui.Node { - extra_lines, - Ui.Text { - "Line 1", - "Line 2", - "Line 3", - "Line 4", - "Special line", - }, - Ui.StickyCursor { id = "special" }, - Ui.Text { - "Line 6", - "Line 7", - "Line 8", - "Line 9", - "Line 10", - }, - } - end) - local mutate_state = window.state { show_extra_lines = false } - window.init {} - window.open() - a.wait(vim.schedule) - window.set_cursor { 5, 3 } -- move cursor to sticky line - mutate_state(function(state) - state.show_extra_lines = true - end) - a.wait(vim.schedule) - local cursor = window.get_cursor() - assert.same({ 8, 3 }, cursor) + assert.spy(set_lines).was_called(1) + a.run_blocking(a.wait, vim.schedule) + assert.spy(set_lines).was_called(2) + + assert + .spy(set_lines) + .was_called_with(match.is_number(), 0, -1, false, { "Line number 1!", "New state", "My highlighted text" }) + end) + + it("anchors to sticky cursor", function() + local window = display.new_view_only_win("test", "my-filetype") + window.view(function(state) + local extra_lines = state.show_extra_lines + and Ui.Text { + "More", + "Lines", + "Here", + } + or Ui.Node {} + return Ui.Node { + extra_lines, + Ui.Text { + "Line 1", + "Line 2", + "Line 3", + "Line 4", + "Special line", + }, + Ui.StickyCursor { id = "special" }, + Ui.Text { + "Line 6", + "Line 7", + "Line 8", + "Line 9", + "Line 10", + }, + } end) - ) - it( - "should respect border ui setting", - async_test(function() - local nvim_open_win = spy.on(vim.api, "nvim_open_win") + local mutate_state = window.state { show_extra_lines = false } + window.init {} + window.open() + a.run_blocking(a.wait, vim.schedule) + window.set_cursor { 5, 3 } -- move cursor to sticky line + mutate_state(function(state) + state.show_extra_lines = true + end) + a.run_blocking(a.wait, vim.schedule) + local cursor = window.get_cursor() + assert.same({ 8, 3 }, cursor) + end) - local window = display.new_view_only_win("test", "my-filetype") - window.view(function() - return Ui.Node {} - end) - window.state {} - window.init { border = "rounded" } - window.open() - a.wait(vim.schedule) + it("should respect border ui setting", function() + local nvim_open_win = spy.on(vim.api, "nvim_open_win") - assert.spy(nvim_open_win).was_called(1) - assert.spy(nvim_open_win).was_called_with( - match.is_number(), - true, - match.tbl_containing { - border = "rounded", - } - ) + local window = display.new_view_only_win("test", "my-filetype") + window.view(function() + return Ui.Node {} end) - ) + window.state {} + window.init { border = "rounded" } + window.open() + a.run_blocking(a.wait, vim.schedule) + + assert.spy(nvim_open_win).was_called(1) + assert.spy(nvim_open_win).was_called_with( + match.is_number(), + true, + match.tbl_containing { + border = "rounded", + } + ) + end) it("should not apply cascading styles to empty lines", function() local render_output = display._render_node( diff --git a/tests/mason-registry/api_spec.lua b/tests/mason-registry/api_spec.lua index 039d0959..8164c901 100644 --- a/tests/mason-registry/api_spec.lua +++ b/tests/mason-registry/api_spec.lua @@ -3,6 +3,16 @@ local match = require "luassert.match" local stub = require "luassert.stub" describe("mason-registry API", function() + local snapshot + + before_each(function() + snapshot = assert.snapshot() + end) + + after_each(function() + snapshot:revert() + end) + ---@module "mason-registry.api" local api local fetch diff --git a/tests/mason/api/command_spec.lua b/tests/mason/api/command_spec.lua index 6cae3e0c..6945340d 100644 --- a/tests/mason/api/command_spec.lua +++ b/tests/mason/api/command_spec.lua @@ -9,73 +9,56 @@ local api = require "mason.api.command" local registry = require "mason-registry" describe(":Mason", function() - it( - "should open the UI window", - async_test(function() - api.Mason() - a.wait(vim.schedule) - local win = vim.api.nvim_get_current_win() - local buf = vim.api.nvim_win_get_buf(win) - assert.equals("mason", vim.api.nvim_buf_get_option(buf, "filetype")) - end) - ) + it("should open the UI window", function() + api.Mason() + a.run_blocking(a.wait, vim.schedule) + local win = vim.api.nvim_get_current_win() + local buf = vim.api.nvim_win_get_buf(win) + assert.equals("mason", vim.api.nvim_buf_get_option(buf, "filetype")) + end) end) describe(":MasonInstall", function() - it( - "should install the provided packages", - async_test(function() - local dummy = registry.get_package "dummy" - local dummy2 = registry.get_package "dummy2" - spy.on(Pkg, "install") - api.MasonInstall { "dummy@1.0.0", "dummy2" } - assert.spy(Pkg.install).was_called(2) - assert.spy(Pkg.install).was_called_with(match.is_ref(dummy), { version = "1.0.0" }) - assert - .spy(Pkg.install) - .was_called_with(match.is_ref(dummy2), match.tbl_containing { version = match.is_nil() }) - end) - ) + it("should install the provided packages", function() + local dummy = registry.get_package "dummy" + local dummy2 = registry.get_package "dummy2" + spy.on(Pkg, "install") + api.MasonInstall { "dummy@1.0.0", "dummy2" } + assert.spy(Pkg.install).was_called(2) + assert.spy(Pkg.install).was_called_with(match.is_ref(dummy), { version = "1.0.0" }) + assert.spy(Pkg.install).was_called_with(match.is_ref(dummy2), match.tbl_containing { version = match.is_nil() }) + end) - it( - "should install provided packages in debug mode", - async_test(function() - local dummy = registry.get_package "dummy" - local dummy2 = registry.get_package "dummy2" - spy.on(Pkg, "install") - vim.cmd [[MasonInstall --debug dummy dummy2]] - assert.spy(Pkg.install).was_called(2) - assert.spy(Pkg.install).was_called_with(match.is_ref(dummy), { version = nil, debug = true }) - assert.spy(Pkg.install).was_called_with(match.is_ref(dummy2), { version = nil, debug = true }) - end) - ) + it("should install provided packages in debug mode", function() + local dummy = registry.get_package "dummy" + local dummy2 = registry.get_package "dummy2" + spy.on(Pkg, "install") + vim.cmd [[MasonInstall --debug dummy dummy2]] + assert.spy(Pkg.install).was_called(2) + assert.spy(Pkg.install).was_called_with(match.is_ref(dummy), { version = nil, debug = true }) + assert.spy(Pkg.install).was_called_with(match.is_ref(dummy2), { version = nil, debug = true }) + end) - it( - "should open the UI window", - async_test(function() - local dummy = registry.get_package "dummy" - spy.on(dummy, "install") - api.MasonInstall { "dummy" } - local win = vim.api.nvim_get_current_win() - local buf = vim.api.nvim_win_get_buf(win) - assert.equals("mason", vim.api.nvim_buf_get_option(buf, "filetype")) - end) - ) + it("should open the UI window", function() + local dummy = registry.get_package "dummy" + spy.on(dummy, "install") + api.MasonInstall { "dummy" } + local win = vim.api.nvim_get_current_win() + local buf = vim.api.nvim_win_get_buf(win) + assert.equals("mason", vim.api.nvim_buf_get_option(buf, "filetype")) + end) end) describe(":MasonUninstall", function() - it( - "should uninstall the provided packages", - async_test(function() - local dummy = registry.get_package "dummy" - local dummy2 = registry.get_package "dummy" - spy.on(Pkg, "uninstall") - api.MasonUninstall { "dummy", "dummy2" } - assert.spy(Pkg.uninstall).was_called(2) - assert.spy(Pkg.uninstall).was_called_with(match.is_ref(dummy)) - assert.spy(Pkg.uninstall).was_called_with(match.is_ref(dummy2)) - end) - ) + it("should uninstall the provided packages", function() + local dummy = registry.get_package "dummy" + local dummy2 = registry.get_package "dummy" + spy.on(Pkg, "uninstall") + api.MasonUninstall { "dummy", "dummy2" } + assert.spy(Pkg.uninstall).was_called(2) + assert.spy(Pkg.uninstall).was_called_with(match.is_ref(dummy)) + assert.spy(Pkg.uninstall).was_called_with(match.is_ref(dummy2)) + end) end) describe(":MasonLog", function() @@ -91,39 +74,43 @@ describe(":MasonLog", function() end) describe(":MasonUpdate", function() - it( - "should update registries", - async_test(function() - stub(registry, "update", function(cb) - cb(true, { {} }) - end) - spy.on(vim, "notify") - api.MasonUpdate() - assert.spy(vim.notify).was_called(2) - assert.spy(vim.notify).was_called_with("Updating registries…", vim.log.levels.INFO, { - title = "mason.nvim", - }) - assert.spy(vim.notify).was_called_with("Successfully updated 1 registry.", vim.log.levels.INFO, { - title = "mason.nvim", - }) + local snapshot + + before_each(function() + snapshot = assert.snapshot() + end) + + after_each(function() + snapshot:revert() + end) + + it("should update registries", function() + stub(registry, "update", function(cb) + cb(true, { {} }) end) - ) + spy.on(vim, "notify") + api.MasonUpdate() + assert.spy(vim.notify).was_called(2) + assert.spy(vim.notify).was_called_with("Updating registries…", vim.log.levels.INFO, { + title = "mason.nvim", + }) + assert.spy(vim.notify).was_called_with("Successfully updated 1 registry.", vim.log.levels.INFO, { + title = "mason.nvim", + }) + end) - it( - "should notify errors", - async_test(function() - stub(registry, "update", function(cb) - cb(false, "Some error.") - end) - spy.on(vim, "notify") - api.MasonUpdate() - assert.spy(vim.notify).was_called(2) - assert.spy(vim.notify).was_called_with("Updating registries…", vim.log.levels.INFO, { - title = "mason.nvim", - }) - assert.spy(vim.notify).was_called_with("Failed to update registries: Some error.", vim.log.levels.ERROR, { - title = "mason.nvim", - }) + it("should notify errors", function() + stub(registry, "update", function(cb) + cb(false, "Some error.") end) - ) + spy.on(vim, "notify") + api.MasonUpdate() + assert.spy(vim.notify).was_called(2) + assert.spy(vim.notify).was_called_with("Updating registries…", vim.log.levels.INFO, { + title = "mason.nvim", + }) + assert.spy(vim.notify).was_called_with("Failed to update registries: Some error.", vim.log.levels.ERROR, { + title = "mason.nvim", + }) + end) end) diff --git a/tests/minimal_init.vim b/tests/minimal_init.vim index abd07fa3..43e8367f 100644 --- a/tests/minimal_init.vim +++ b/tests/minimal_init.vim @@ -13,7 +13,22 @@ set packpath=$dependencies packloadall lua require("luassertx") -lua require("test_helpers") + +lua <<EOF +mockx = { + just_runs = function() end, + returns = function(val) + return function() + return val + end + end, + throws = function(exception) + return function() + error(exception, 2) + end + end, +} +EOF lua <<EOF local path = require "mason-core.path" @@ -8,23 +8,10 @@ globals: property: read-only vim: any: true - async_test: + assert.wait: args: - type: function - assert.wait_for: - args: - - type: function - create_dummy_context: - args: - - type: table - required: false - InstallHandleGenerator: - args: - - type: string - InstallContextGenerator: - args: - - type: any - - type: table + - type: number required: false mockx.throws: args: |
