diff options
| author | William Boman <william@redwill.se> | 2023-10-11 16:31:50 +0200 |
|---|---|---|
| committer | William Boman <william@redwill.se> | 2025-02-19 09:22:40 +0100 |
| commit | 047ec18da56ad8f331e5c6bc7417dc5a9a6e71cc (patch) | |
| tree | c50c22cd05d3605fc5a1e8eb902ffeb11e339697 | |
| parent | refactor(receipt): change receipt structure and remove old builder APIs (#1521) (diff) | |
| download | mason-047ec18da56ad8f331e5c6bc7417dc5a9a6e71cc.tar mason-047ec18da56ad8f331e5c6bc7417dc5a9a6e71cc.tar.gz mason-047ec18da56ad8f331e5c6bc7417dc5a9a6e71cc.tar.bz2 mason-047ec18da56ad8f331e5c6bc7417dc5a9a6e71cc.tar.lz mason-047ec18da56ad8f331e5c6bc7417dc5a9a6e71cc.tar.xz mason-047ec18da56ad8f331e5c6bc7417dc5a9a6e71cc.tar.zst mason-047ec18da56ad8f331e5c6bc7417dc5a9a6e71cc.zip | |
refactor!: refactor installer internals and add new Package class methods (#1523)
This contains the following changes:
1) `Package:install()` now accepts a second, optional, callback argument which is called when installation finishes
(successfully or not).
2) Adds a `Package:is_installing()` method.
This contains the following breaking changes:
1) `Package:install()` will now error when called while an installation is already ongoing. Use the new
`Package:is_installing()` method to check whether an installation is already running.
This also refactors large portions of the tests by removing test globals, removing async_test, and adding the
`mason-test` Lua module instead. Test helpers via globals are problematic to work with due to not being detected through
tools like the Lua language server without additional configuration. This has been replaced with a Lua module
`mason-test`. `async_test` has also been removed in favour of explicitly making use of the `mason-core.async` API. These
changes stands for a significant portion of the diff.
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: |
