From 37c745fa73b983c86904132efb30ef2a4a76df5e Mon Sep 17 00:00:00 2001 From: William Boman Date: Sun, 30 Oct 2022 17:43:46 +0100 Subject: feat: add provider interface (#601) --- README.md | 9 ++++ doc/mason.txt | 9 ++++ lua/mason-core/functional/function.lua | 5 ++ lua/mason-core/functional/init.lua | 3 ++ lua/mason-core/functional/list.lua | 7 +++ lua/mason-core/functional/string.lua | 4 ++ lua/mason-core/managers/github/client.lua | 13 ++--- lua/mason-core/managers/npm/init.lua | 4 +- lua/mason-core/managers/pip3/init.lua | 48 +++++++---------- lua/mason-core/optional.lua | 8 ++- lua/mason-core/providers/init.lua | 77 +++++++++++++++++++++++++++ lua/mason-core/spawn.lua | 2 +- lua/mason-registry/api.lua | 9 ++++ lua/mason/providers/client/gh.lua | 43 +++++++++++++++ lua/mason/providers/client/init.lua | 6 +++ lua/mason/providers/client/npm.lua | 16 ++++++ lua/mason/providers/client/pypi.lua | 52 ++++++++++++++++++ lua/mason/providers/registry-api/init.lua | 40 ++++++++++++++ lua/mason/settings.lua | 9 ++++ tests/mason-core/functional/function_spec.lua | 7 +++ tests/mason-core/functional/list_spec.lua | 8 +++ tests/mason-core/functional/string_spec.lua | 5 ++ tests/mason-core/managers/pip3_spec.lua | 30 ++++++----- tests/mason-core/providers/provider_spec.lua | 61 +++++++++++++++++++++ 24 files changed, 419 insertions(+), 56 deletions(-) create mode 100644 lua/mason-core/providers/init.lua create mode 100644 lua/mason/providers/client/gh.lua create mode 100644 lua/mason/providers/client/init.lua create mode 100644 lua/mason/providers/client/npm.lua create mode 100644 lua/mason/providers/client/pypi.lua create mode 100644 lua/mason/providers/registry-api/init.lua create mode 100644 tests/mason-core/providers/provider_spec.lua diff --git a/README.md b/README.md index f21aa12e..0c0cfaba 100644 --- a/README.md +++ b/README.md @@ -187,6 +187,15 @@ local DEFAULT_SETTINGS = { download_url_template = "https://github.com/%s/releases/download/%s/%s", }, + -- The provider implementations to use for resolving package metadata (latest version, available versions, etc.). + -- Accepts multiple entries, where later entries will be used as fallback should prior providers fail. + -- Builtin providers are: + -- - mason.providers.registry-api (default) - uses the https://api.mason-registry.dev API + -- - mason.providers.client - uses only client-side tooling to resolve metadata + providers = { + "mason.providers.registry-api", + }, + ui = { -- Whether to automatically check for new versions when opening the :Mason window. check_outdated_packages_on_open = true, diff --git a/doc/mason.txt b/doc/mason.txt index f75b36c4..3a3a9ac6 100644 --- a/doc/mason.txt +++ b/doc/mason.txt @@ -239,6 +239,15 @@ Example: download_url_template = "https://github.com/%s/releases/download/%s/%s", }, + -- The provider implementations to use for resolving package metadata (latest version, available versions, etc.). + -- Accepts multiple entries, where later entries will be used as fallback should prior providers fail. + -- Builtin providers are: + -- - mason.providers.registry-api (default) - uses the https://api.mason-registry.dev API + -- - mason.providers.client - uses only client-side tooling to resolve metadata + providers = { + "mason.providers.registry-api", + }, + ui = { -- Whether to automatically check for new versions when opening the :Mason window. check_outdated_packages_on_open = true, diff --git a/lua/mason-core/functional/function.lua b/lua/mason-core/functional/function.lua index c6d460a5..f1475229 100644 --- a/lua/mason-core/functional/function.lua +++ b/lua/mason-core/functional/function.lua @@ -89,4 +89,9 @@ _.lazy = function(fn) end end +_.tap = _.curryN(function(fn, value) + fn(value) + return value +end, 2) + return _ diff --git a/lua/mason-core/functional/init.lua b/lua/mason-core/functional/init.lua index ed502a4b..04ef1534 100644 --- a/lua/mason-core/functional/init.lua +++ b/lua/mason-core/functional/init.lua @@ -27,6 +27,7 @@ _.T = fun.T _.F = fun.F _.memoize = fun.memoize _.lazy = fun.lazy +_.tap = fun.tap ---@module "mason-core.functional.list" local list = lazy_require "mason-core.functional.list" @@ -45,6 +46,7 @@ _.prepend = list.prepend _.zip_table = list.zip_table _.nth = list.nth _.head = list.head +_.last = list.last _.length = list.length _.flatten = list.flatten _.sort_by = list.sort_by @@ -80,6 +82,7 @@ _.dec = number.dec ---@module "mason-core.functional.string" local string = lazy_require "mason-core.functional.string" _.matches = string.matches +_.match = string.match _.format = string.format _.split = string.split _.gsub = string.gsub diff --git a/lua/mason-core/functional/list.lua b/lua/mason-core/functional/list.lua index 31b631b8..2a5378fe 100644 --- a/lua/mason-core/functional/list.lua +++ b/lua/mason-core/functional/list.lua @@ -148,6 +148,13 @@ end, 2) _.head = _.nth(1) +---@generic T +---@param list T[] +---@return T? +_.last = function(list) + return list[#list] +end + ---@param value string|any[] _.length = function(value) return #value diff --git a/lua/mason-core/functional/string.lua b/lua/mason-core/functional/string.lua index 7726c8e1..9b7da979 100644 --- a/lua/mason-core/functional/string.lua +++ b/lua/mason-core/functional/string.lua @@ -8,6 +8,10 @@ _.matches = fun.curryN(function(pattern, str) return str:match(pattern) ~= nil end, 2) +_.match = fun.curryN(function(pattern, str) + return { str:match(pattern) } +end, 2) + ---@param template string ---@param str string _.format = fun.curryN(function(template, str) diff --git a/lua/mason-core/managers/github/client.lua b/lua/mason-core/managers/github/client.lua index 3ef307c1..699e3fa4 100644 --- a/lua/mason-core/managers/github/client.lua +++ b/lua/mason-core/managers/github/client.lua @@ -2,13 +2,10 @@ local _ = require "mason-core.functional" local log = require "mason-core.log" local fetch = require "mason-core.fetch" local spawn = require "mason-core.spawn" -local api = require "mason-registry.api" +local providers = require "mason-core.providers" local M = {} ----@alias GitHubReleaseAsset {url: string, id: integer, name: string, browser_download_url: string, created_at: string, updated_at: string, size: integer, download_count: integer} ----@alias GitHubRelease {tag_name: string, prerelease: boolean, draft: boolean, assets:GitHubReleaseAsset[]} ----@alias GitHubTag {name: string} ---@alias GitHubCommit {sha: string} local stringify_params = _.compose(_.join "&", _.map(_.join "="), _.sort_by(_.head), _.to_pairs) @@ -66,11 +63,7 @@ end ---@return Result # Result function M.fetch_latest_release(repo, opts) opts = opts or { include_prerelease = false } - return api.repo.releases.latest({ repo = repo }, { - params = { - include_prerelease = opts.include_prerelease and "true" or "false", - }, - }) + return providers.github.get_latest_release(repo, { include_prerelease = opts.include_prerelease }) end ---@async @@ -87,7 +80,7 @@ end ---@param repo string The GitHub repo ("username/repo"). ---@return Result # Result The latest tag name. function M.fetch_latest_tag(repo) - return api.repo.tags.latest({ repo = repo }):map(_.prop "tag") + return providers.github.get_latest_tag(repo):map(_.prop "tag") end ---@async diff --git a/lua/mason-core/managers/npm/init.lua b/lua/mason-core/managers/npm/init.lua index 61ee4042..96571420 100644 --- a/lua/mason-core/managers/npm/init.lua +++ b/lua/mason-core/managers/npm/init.lua @@ -4,7 +4,7 @@ local Result = require "mason-core.result" local path = require "mason-core.path" local _ = require "mason-core.functional" local platform = require "mason-core.platform" -local api = require "mason-registry.api" +local providers = require "mason-core.providers" local list_copy = _.list_copy @@ -112,7 +112,7 @@ function M.check_outdated_primary_package(receipt, install_dir) local primary_package = receipt.primary_source.package return M.get_installed_primary_package_version(receipt, install_dir) :and_then(function(installed_version) - return api.npm.versions.latest({ package = primary_package }):map(function(response) + return providers.npm.get_latest_version(primary_package):map(function(response) return { installed = installed_version, latest = response.version, diff --git a/lua/mason-core/managers/pip3/init.lua b/lua/mason-core/managers/pip3/init.lua index 4581bc0b..67f70a89 100644 --- a/lua/mason-core/managers/pip3/init.lua +++ b/lua/mason-core/managers/pip3/init.lua @@ -7,6 +7,7 @@ local Optional = require "mason-core.optional" local installer = require "mason-core.installer" local Result = require "mason-core.result" local spawn = require "mason-core.spawn" +local providers = require "mason-core.providers" local VENV_DIR = "venv" @@ -103,36 +104,27 @@ function M.check_outdated_primary_package(receipt, install_dir) return Result.failure "Receipt does not have a primary source of type pip3" end local normalized_package = M.normalize_package(receipt.primary_source.package) - return spawn - .python({ - "-m", - "pip", - "list", - "--outdated", - "--format=json", - cwd = install_dir, - with_paths = { M.venv_path(install_dir) }, - }) - :map_catching(function(result) - ---@alias PipOutdatedPackage {name: string, version: string, latest_version: string} - ---@type PipOutdatedPackage[] - local packages = vim.json.decode(result.stdout) - - local outdated_primary_package = _.find_first(function(outdated_package) - return outdated_package.name == normalized_package - and outdated_package.version ~= outdated_package.latest_version - end, packages) - - return Optional.of_nilable(outdated_primary_package) - :map(function(pkg) - return { + return M.get_installed_primary_package_version(receipt, install_dir):and_then(function(installed_version) + return providers.pypi + .get_latest_version(normalized_package) + :map(function(latest) + return { + current = installed_version, + latest = latest.version, + } + end) + :and_then(function(versions) + if versions.current ~= versions.latest then + return Result.success { name = normalized_package, - current_version = assert(pkg.version, "missing current pip3 package version"), - latest_version = assert(pkg.latest_version, "missing latest pip3 package version"), + current_version = versions.current, + latest_version = versions.latest, } - end) - :or_else_throw "Primary package is not outdated." - end) + else + return Result.failure "Primary package is not outdated." + end + end) + end) end ---@async diff --git a/lua/mason-core/optional.lua b/lua/mason-core/optional.lua index 6eb0719e..cc40da77 100644 --- a/lua/mason-core/optional.lua +++ b/lua/mason-core/optional.lua @@ -105,13 +105,17 @@ function Optional:is_present() return self._value ~= nil end ----@param err fun(): any +---@param err (fun(): any)|string function Optional:ok_or(err) local Result = require "mason-core.result" if self:is_present() then return Result.success(self:get()) else - return Result.failure(err()) + if type(err) == "string" then + return Result.failure(err) + else + return Result.failure(err()) + end end end diff --git a/lua/mason-core/providers/init.lua b/lua/mason-core/providers/init.lua new file mode 100644 index 00000000..76bd431b --- /dev/null +++ b/lua/mason-core/providers/init.lua @@ -0,0 +1,77 @@ +local settings = require "mason.settings" +local log = require "mason-core.log" +local Result = require "mason-core.result" + +---@alias GitHubReleaseAsset {url: string, id: integer, name: string, browser_download_url: string, created_at: string, updated_at: string, size: integer, download_count: integer} +---@alias GitHubRelease { tag_name: string, prerelease: boolean, draft: boolean, assets:GitHubReleaseAsset[] } +---@alias GitHubTag { name: string } + +---@class GitHubProvider +---@field get_latest_release? async fun(repo: string, opts?: { include_prerelease?: boolean }): Result # Result +---@field get_all_release_versions? async fun(repo: string): Result # Result +---@field get_latest_tag? async fun(repo: string): Result # Result +---@field get_all_tags? async fun(repo: string): Result # Result + +---@alias NpmPackage { name: string, version: string } + +---@class NpmProvider +---@field get_latest_version? async fun(pkg: string): Result # Result +---@field get_all_versions? async fun(pkg: string): Result # Result + +---@alias PyPiPackage { name: string, version: string } + +---@class PyPiProvider +---@field get_latest_version? async fun(pkg: string): Result # Result +---@field get_all_versions? async fun(pkg: string): Result # Result # Sorting should not be relied upon due to "proprietary" sorting algo in pip that is difficult to replicate in mason-registry-api. + +---@class Provider +---@field github? GitHubProvider +---@field npm? NpmProvider +---@field pypi? PyPiProvider + +local function service_mt(service) + return setmetatable({}, { + __index = function(_, method) + return function(...) + if #settings.current.providers < 1 then + log.error "No providers configured." + return Result.failure "1 or more providers are required." + end + for _, provider_module in ipairs(settings.current.providers) do + local ok, provider = pcall(require, provider_module) + if ok and provider then + local impl = provider[service] and provider[service][method] + if impl then + ---@type boolean, Result + local ok, result = pcall(impl, ...) + if ok and result:is_success() then + return result + else + if getmetatable(result) == Result then + log.fmt_error("Provider %s %s failed: %s", service, method, result:err_or_nil()) + else + log.fmt_error("Provider %s %s errored: %s", service, method, result) + end + end + end + else + log.fmt_error("Unable to find provider %s is not registered. %s", provider_module, provider) + end + end + local err = ("No provider implementation found for %s.%s"):format(service, method) + log.error(err) + return Result.failure(err) + end + end, + }) +end + +---@type Provider +local providers = setmetatable({}, { + __index = function(tbl, service) + tbl[service] = service_mt(service) + return tbl[service] + end, +}) + +return providers diff --git a/lua/mason-core/spawn.lua b/lua/mason-core/spawn.lua index fd01c97a..814ef8f7 100644 --- a/lua/mason-core/spawn.lua +++ b/lua/mason-core/spawn.lua @@ -5,7 +5,7 @@ local process = require "mason-core.process" local platform = require "mason-core.platform" local log = require "mason-core.log" ----@alias JobSpawn table +---@alias JobSpawn table ---@type JobSpawn local spawn = { _aliases = { diff --git a/lua/mason-registry/api.lua b/lua/mason-registry/api.lua index b700ba47..25a9a8bb 100644 --- a/lua/mason-registry/api.lua +++ b/lua/mason-registry/api.lua @@ -69,4 +69,13 @@ api.npm = { }, } +api.pypi = { + versions = { + ---@type ApiSignature<{ package: string }> + latest = get "/api/pypi/{package}/versions/latest", + ---@type ApiSignature<{ package: string }> + all = get "/api/pypi/{package}/versions/all", + }, +} + return api diff --git a/lua/mason/providers/client/gh.lua b/lua/mason/providers/client/gh.lua new file mode 100644 index 00000000..21d916a3 --- /dev/null +++ b/lua/mason/providers/client/gh.lua @@ -0,0 +1,43 @@ +local spawn = require "mason-core.spawn" +local _ = require "mason-core.functional" +local Result = require "mason-core.result" +local Optional = require "mason-core.optional" + +---@type GitHubProvider +return { + get_latest_release = function(repo, opts) + opts = opts or {} + if not opts.include_prerelease then + return spawn + .gh({ "api", ("repos/%s/releases/latest"):format(repo) }) + :map(_.prop "stdout") + :map_catching(vim.json.decode) + else + return spawn + .gh({ "api", ("repos/%s/releases"):format(repo) }) + :map(_.prop "stdout") + :map_catching(vim.json.decode) + :map(_.find_first(_.prop_eq("draft", false))) + :and_then(function(release) + return Optional.of_nilable(release):ok_or "Failed to find latest release." + end) + end + end, + get_all_release_versions = function(repo) + return spawn + .gh({ "api", ("repos/%s/releases"):format(repo) }) + :map(_.prop "stdout") + :map_catching(vim.json.decode) + :map(_.map(_.prop "tag_name")) + end, + get_latest_tag = function(repo) + return Result.failure "Unimplemented" + end, + get_all_tags = function(repo) + return spawn + .gh({ "api", ("repos/%s/git/matching-refs/tags"):format(repo) }) + :map(_.prop "stdout") + :map_catching(vim.json.decode) + :map(_.map(_.compose(_.gsub("^refs/tags/", ""), _.prop "ref"))) + end, +} diff --git a/lua/mason/providers/client/init.lua b/lua/mason/providers/client/init.lua new file mode 100644 index 00000000..6bb8afee --- /dev/null +++ b/lua/mason/providers/client/init.lua @@ -0,0 +1,6 @@ +---@type Provider +return { + gh = require "mason.providers.client.gh", + npm = require "mason.providers.client.npm", + pypi = require "mason.providers.client.pypi", +} diff --git a/lua/mason/providers/client/npm.lua b/lua/mason/providers/client/npm.lua new file mode 100644 index 00000000..d5bf6da5 --- /dev/null +++ b/lua/mason/providers/client/npm.lua @@ -0,0 +1,16 @@ +local spawn = require "mason-core.spawn" +local _ = require "mason-core.functional" + +---@type NpmProvider +return { + get_latest_version = function(pkg) + return spawn + .npm({ "view", "--json", pkg .. "@latest" }) + :map(_.prop "stdout") + :map_catching(vim.json.decode) + :map(_.pick { "name", "version" }) + end, + get_all_versions = function(pkg) + return spawn.npm({ "view", pkg, "versions" }):map(_.prop "stdout"):map_catching(vim.json.decode) + end, +} diff --git a/lua/mason/providers/client/pypi.lua b/lua/mason/providers/client/pypi.lua new file mode 100644 index 00000000..5dc357d7 --- /dev/null +++ b/lua/mason/providers/client/pypi.lua @@ -0,0 +1,52 @@ +local a = require "mason-core.async" +local fs = require "mason-core.fs" +local spawn = require "mason-core.spawn" +local platform = require "mason-core.platform" +local _ = require "mason-core.functional" +local Optional = require "mason-core.optional" + +---@param args SpawnArgs +local function python(args) + a.scheduler() + local py_exec = platform.is.win and "python" or "python3" + -- run in tmpdir in case pip inadvertently produces some output + args.cwd = vim.fn.tempname() + fs.async.mkdir(args.cwd) + return spawn[py_exec](args) +end + +---@async +---@param pkg string +local function get_all_versions(pkg) + -- https://stackoverflow.com/a/26664162 + return python({ + "-m", + "pip", + "install", + "--disable-pip-version-check", + "--use-deprecated=legacy-resolver", -- for pip >= 20.3 + ("%s=="):format(pkg), -- invalid version specifier to trigger the wanted error message + }) + :map_err(_.compose(_.split ", ", _.head, _.match "%(from versions: (.+)%)", _.prop "stderr")) + :recover(_.identity) +end + +---@param pkg string +local function synthesize_pkg(pkg) + ---@param version Optional + return function(version) + return version + :map(function(v) + return { name = pkg, version = v } + end) + :ok_or "Unable to find latest version." + end +end + +---@type PyPiProvider +return { + get_latest_version = function(pkg) + return get_all_versions(pkg):map(_.compose(Optional.of_nilable, _.last)):and_then(synthesize_pkg(pkg)) + end, + get_all_versions_unsorted = get_all_versions, +} diff --git a/lua/mason/providers/registry-api/init.lua b/lua/mason/providers/registry-api/init.lua new file mode 100644 index 00000000..24c35ee8 --- /dev/null +++ b/lua/mason/providers/registry-api/init.lua @@ -0,0 +1,40 @@ +local api = require "mason-registry.api" + +---@type Provider +return { + github = { + get_latest_release = function(repo, opts) + opts = opts or {} + return api.repo.releases.latest({ repo = repo }, { + params = { + include_prerelease = (opts and opts.include_prerelease) and "true" or "false", + }, + }) + end, + get_all_release_versions = function(repo) + return api.repo.releases.all { repo = repo } + end, + get_latest_tag = function(repo) + return api.repo.tags.latest { repo = repo } + end, + get_all_tags = function(repo) + return api.repo.tags.all { repo = repo } + end, + }, + npm = { + get_latest_version = function(pkg) + return api.npm.versions.latest { package = pkg } + end, + get_all_versions = function(pkg) + return api.npm.versions.all { package = pkg } + end, + }, + pypi = { + get_latest_version = function(pkg) + return api.pypi.versions.latest { package = pkg } + end, + get_all_versions = function(pkg) + return api.pypi.versions.all { package = pkg } + end, + }, +} diff --git a/lua/mason/settings.lua b/lua/mason/settings.lua index afe77953..4322988f 100644 --- a/lua/mason/settings.lua +++ b/lua/mason/settings.lua @@ -39,6 +39,15 @@ local DEFAULT_SETTINGS = { download_url_template = "https://github.com/%s/releases/download/%s/%s", }, + -- The provider implementations to use for resolving package metadata (latest version, available versions, etc.). + -- Accepts multiple entries, where later entries will be used as fallback should prior providers fail. + -- Builtin providers are: + -- - mason.providers.registry-api (default) - uses the https://api.mason-registry.dev API + -- - mason.providers.client - uses only client-side tooling to resolve metadata + providers = { + "mason.providers.registry-api", + }, + ui = { -- Whether to automatically check for new versions when opening the :Mason window. check_outdated_packages_on_open = true, diff --git a/tests/mason-core/functional/function_spec.lua b/tests/mason-core/functional/function_spec.lua index 4da41d40..28abce20 100644 --- a/tests/mason-core/functional/function_spec.lua +++ b/tests/mason-core/functional/function_spec.lua @@ -139,4 +139,11 @@ describe("functional: function", function() assert.is_true(_.T()) assert.is_false(_.F()) end) + + it("should tap values", function() + local fn = spy.new() + assert.equals(42, _.tap(fn, 42)) + assert.spy(fn).was_called() + assert.spy(fn).was_called_with(42) + end) end) diff --git a/tests/mason-core/functional/list_spec.lua b/tests/mason-core/functional/list_spec.lua index add9076e..bbc60763 100644 --- a/tests/mason-core/functional/list_spec.lua +++ b/tests/mason-core/functional/list_spec.lua @@ -203,4 +203,12 @@ describe("functional: list", function() { "person", "camera" }, }, _.partition(_.matches "%u", words)) end) + + it("should return head", function() + assert.equals("Head", _.head { "Head", "Tail", "Tail" }) + end) + + it("should return last", function() + assert.equals("Last", _.last { "Head", "List", "Last" }) + end) end) diff --git a/tests/mason-core/functional/string_spec.lua b/tests/mason-core/functional/string_spec.lua index 25409f64..6fb99c45 100644 --- a/tests/mason-core/functional/string_spec.lua +++ b/tests/mason-core/functional/string_spec.lua @@ -10,6 +10,11 @@ describe("functional: string", function() assert.is_false(_.matches("bar", "foobaz")) end) + it("returns string pattern matches", function() + assert.same({ "foo" }, _.match("foo", "foo")) + assert.same({ "foo", "bar", "baz" }, _.match("(foo) (bar) (baz)", "foo bar baz")) + end) + it("should format strings", function() assert.equals("Hello World!", _.format("%s", "Hello World!")) assert.equals("special manouvers", _.format("%s manouvers", "special")) diff --git a/tests/mason-core/managers/pip3_spec.lua b/tests/mason-core/managers/pip3_spec.lua index 6f78cdb4..1ec2dc28 100644 --- a/tests/mason-core/managers/pip3_spec.lua +++ b/tests/mason-core/managers/pip3_spec.lua @@ -1,5 +1,6 @@ local mock = require "luassert.mock" local spy = require "luassert.spy" +local stub = require "luassert.stub" local path = require "mason-core.path" local a = require "mason-core.async" @@ -8,6 +9,7 @@ local installer = require "mason-core.installer" local Result = require "mason-core.result" local settings = require "mason.settings" local spawn = require "mason-core.spawn" +local api = require "mason-registry.api" describe("pip3 manager", function() it("normalizes pip3 packages", function() @@ -192,11 +194,16 @@ describe("pip3 version check", function() it( "should return outdated primary package", async_test(function() + stub(api, "get") + api.get.on_call_with("/api/pypi/python-lsp-server/versions/latest").returns(Result.success { + name = "python-lsp-server", + version = "1.4.0", + }) spawn.python = spy.new(function() return Result.success { stdout = [[ -[{"name": "astroid", "version": "2.9.3", "latest_version": "2.11.0", "latest_filetype": "wheel"}, {"name": "mccabe", "version": "0.6.1", "latest_version": "0.7.0", "latest_filetype": "wheel"}, {"name": "python-lsp-server", "version": "1.3.0", "latest_version": "1.4.0", "latest_filetype": "wheel"}, {"name": "wrapt", "version": "1.13.3", "latest_version": "1.14.0", "latest_filetype": "wheel"}] - ]], + [{"name": "astroid", "version": "2.9.3"}, {"name": "mccabe", "version": "0.6.1"}, {"name": "python-lsp-server", "version": "1.3.0", "latest_version": "1.4.0", "latest_filetype": "wheel"}, {"name": "wrapt", "version": "1.13.3", "latest_version": "1.14.0", "latest_filetype": "wheel"}] + ]], } end) @@ -210,16 +217,6 @@ describe("pip3 version check", function() path.package_prefix "dummy" ) - assert.spy(spawn.python).was_called(1) - assert.spy(spawn.python).was_called_with { - "-m", - "pip", - "list", - "--outdated", - "--format=json", - cwd = path.package_prefix "dummy", - with_paths = { path.concat { path.package_prefix "dummy", "venv", "bin" } }, - } assert.is_true(result:is_success()) assert.same({ name = "python-lsp-server", @@ -236,9 +233,16 @@ describe("pip3 version check", function() async_test(function() spawn.python = spy.new(function() return Result.success { - stdout = "[]", + stdout = [[ + [{"name": "astroid", "version": "2.9.3"}, {"name": "mccabe", "version": "0.6.1"}, {"name": "python-lsp-server", "version": "1.3.0", "latest_version": "1.4.0", "latest_filetype": "wheel"}, {"name": "wrapt", "version": "1.13.3", "latest_version": "1.14.0", "latest_filetype": "wheel"}] + ]], } end) + stub(api, "get") + api.get.on_call_with("/api/pypi/python-lsp-server/versions/latest").returns(Result.success { + name = "python-lsp-server", + version = "1.3.0", + }) local result = pip3.check_outdated_primary_package( mock.new { diff --git a/tests/mason-core/providers/provider_spec.lua b/tests/mason-core/providers/provider_spec.lua new file mode 100644 index 00000000..ba2d8f1f --- /dev/null +++ b/tests/mason-core/providers/provider_spec.lua @@ -0,0 +1,61 @@ +local spy = require "luassert.spy" +local Result = require "mason-core.result" + +describe("providers", function() + ---@module "mason-core.providers" + local provider + ---@module "mason.settings" + local settings + + before_each(function() + package.loaded["mason-core.providers"] = nil + package.loaded["mason.settings"] = nil + provider = require "mason-core.providers" + settings = require "mason.settings" + end) + + it("should run provided providers", function() + package.loaded["failing-provider"] = { + github = { + get_all_release_versions = spy.new(function() + return Result.failure "Failed." + end), + }, + } + package.loaded["really-failing-provider"] = { + github = { + get_all_release_versions = spy.new(function() + error "Failed." + end), + }, + } + package.loaded["successful-provider"] = { + github = { + get_all_release_versions = spy.new(function() + return Result.success { "1.0.0", "2.0.0" } + end), + }, + } + + settings.set { + providers = { "failing-provider", "really-failing-provider", "successful-provider" }, + } + + assert.same( + Result.success { "1.0.0", "2.0.0" }, + provider.github.get_all_release_versions "sumneko/lua-language-server" + ) + assert.spy(package.loaded["failing-provider"].github.get_all_release_versions).was_called() + assert + .spy(package.loaded["failing-provider"].github.get_all_release_versions) + .was_called_with "sumneko/lua-language-server" + assert.spy(package.loaded["really-failing-provider"].github.get_all_release_versions).was_called() + assert + .spy(package.loaded["really-failing-provider"].github.get_all_release_versions) + .was_called_with "sumneko/lua-language-server" + assert.spy(package.loaded["successful-provider"].github.get_all_release_versions).was_called() + assert + .spy(package.loaded["successful-provider"].github.get_all_release_versions) + .was_called_with "sumneko/lua-language-server" + end) +end) -- cgit v1.2.3-70-g09d2