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 /tests/mason-core/installer/runner_spec.lua | |
| 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.
Diffstat (limited to 'tests/mason-core/installer/runner_spec.lua')
| -rw-r--r-- | tests/mason-core/installer/runner_spec.lua | 300 |
1 files changed, 300 insertions, 0 deletions
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) |
