Skip to content

Commit

Permalink
fix(nextls): allow extension to force a download
Browse files Browse the repository at this point in the history
In the case that the auto updater in Next LS has a bug, this
patch allows the extension to force a fresh download through the
extension.

It sets a key in the extensions globalState, which is persisted across
restarts. If the key isn't there, then it forces the download.

To force a download a second time, you need to increment the key
version, which is basically setting a new key to use as a sentinel.
  • Loading branch information
mhanberg committed Mar 4, 2024
1 parent f2ce7b3 commit a677884
Show file tree
Hide file tree
Showing 8 changed files with 106 additions and 18 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ node_modules
.vscode-test/
*.vsix
test-bin
.elixir-tools
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@
"build-base": "esbuild ./src/extension.ts --bundle --outfile=out/extension.js --external:vscode --format=cjs --platform=node --target=node16",
"build": "yarn build-base --sourcemap",
"watch": "yarn build-base --sourcemap --watch",
"pretest": "yarn typecheck && yarn run build",
"pretest": "yarn typecheck && yarn compile-dist && yarn build",
"test": "node ./out/test/runTest.js"
},
"dependencies": {
Expand All @@ -225,4 +225,4 @@
"sinon": "^17.0.1",
"typescript": "^4.9.5"
}
}
}
29 changes: 26 additions & 3 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,29 @@ async function activateCredo(
}
}

// In case the auto updater gets busted, we want the ability to force a download.
// By incremented the key here, we should be able to force a download when the extension updates.
export function forceDownload(context: vscode.ExtensionContext): boolean {
let forceDownload: boolean = context.globalState.get(
"elixir-tools-force-next-ls-download-v1"
);
channel.info(
`value of elixir-tools-force-next-ls-download-v1: ${forceDownload}`
);
if (forceDownload === undefined) {
forceDownload = true;
}

context.globalState.update("elixir-tools-force-next-ls-download-v1", false);

return forceDownload;
}

async function activateNextLS(
context: vscode.ExtensionContext,
_mixfile: vscode.Uri
) {
channel.info("activating next ls");
let config = vscode.workspace.getConfiguration("elixir-tools.nextLS");

if (config.get("enable")) {
Expand All @@ -113,12 +132,11 @@ async function activateNextLS(
switch (config.get("adapter")) {
case "stdio":
let cacheDir: string = config.get("installationDirectory")!;

if (cacheDir[0] === "~") {
cacheDir = path.join(os.homedir(), cacheDir.slice(1));
}
const command = await ensureNextLSDownloaded(cacheDir, {
force: false,
force: forceDownload(context),
});

serverOptions = {
Expand Down Expand Up @@ -181,13 +199,18 @@ async function activateNextLS(
}
}

export async function activate(context: vscode.ExtensionContext) {
export async function activate(
context: vscode.ExtensionContext
): Promise<vscode.ExtensionContext> {
let files = await vscode.workspace.findFiles("mix.exs");
channel.info(`files: ${files[0]}`);

if (files[0]) {
await activateCredo(context, files[0]);
await activateNextLS(context, files[0]);
}

return context;
}

export function deactivate() {
Expand Down
3 changes: 3 additions & 0 deletions src/test/fixtures/basic/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"elixir-tools.nextLS.installationDirectory": "./src/test/fixtures/basic/test-bin"
}
5 changes: 5 additions & 0 deletions src/test/fixtures/basic/lib/basic.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
defmodule Basic do
def run do
Enum.map([:one, :two], &Function.identity/1)
end
end
32 changes: 32 additions & 0 deletions src/test/fixtures/basic/mix.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
defmodule Basic.MixProject do
use Mix.Project

@version "0.1.0"

def project do
[
app: :basic,
description: "Basic app",
version: @version,
elixir: "~> 1.13",
elixirc_paths: elixirc_paths(Mix.env()),
start_permanent: Mix.env() == :prod,
deps: deps()
]
end

# Run "mix help compile.app" to learn about applications.
def application do
[
extra_applications: [:logger, :crypto]
]
end

defp elixirc_paths(:test), do: ["lib", "test/support"]
defp elixirc_paths(_), do: ["lib"]

# Run "mix help deps" to learn about dependencies.
defp deps do
[]
end
end
7 changes: 6 additions & 1 deletion src/test/runTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,14 @@ async function main() {
// The path to test runner
// Passed to --extensionTestsPath
const extensionTestsPath = path.resolve(__dirname, "./suite/index");
const testWorkspace = path.resolve(__dirname, "../../src/test/fixtures/basic");

Check failure on line 14 in src/test/runTest.ts

View workflow job for this annotation

GitHub Actions / lint

Replace `__dirname,·"../../src/test/fixtures/basic"` with `⏎······__dirname,⏎······"../../src/test/fixtures/basic"⏎····`

// Download VS Code, unzip it and run the integration test
await runTests({ extensionDevelopmentPath, extensionTestsPath });
await runTests({
extensionDevelopmentPath,
extensionTestsPath,
launchArgs: [testWorkspace],
});
} catch (err) {
console.error("Failed to run tests", err);
process.exit(1);
Expand Down
43 changes: 31 additions & 12 deletions src/test/suite/extension.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,27 @@ suite("Extension Test Suite", () => {
vscode.window.showInformationMessage("Start all tests.");
let showInformationMessage;

setup(function () {
setup(function() {

Check failure on line 17 in src/test/suite/extension.test.ts

View workflow job for this annotation

GitHub Actions / lint

Insert `·`
fs.rmSync("./test-bin", { recursive: true, force: true });
showInformationMessage = sinon
.stub(vscode.window, "showInformationMessage")
.returns(
new Promise((resolve) => {
return resolve({ title: "Yes" });
})
);
showInformationMessage = sinon.stub(
vscode.window,
"showInformationMessage"
);
});

teardown(function () {
teardown(function() {

Check failure on line 25 in src/test/suite/extension.test.ts

View workflow job for this annotation

GitHub Actions / lint

Insert `·`
sinon.restore();
});

// TODO: should probably mock out the api calls to github
test("downloads Next LS", async function () {
test("downloads Next LS", async function() {

Check failure on line 30 in src/test/suite/extension.test.ts

View workflow job for this annotation

GitHub Actions / lint

Insert `·`
fs.mkdirSync("./test-bin", { recursive: true });

let result = await myExtension.ensureNextLSDownloaded("test-bin");
assert.equal(path.normalize(result), path.normalize("test-bin/nextls"));
});

test("uninstalls Next LS", async function () {
test("uninstalls Next LS", async function() {

Check failure on line 37 in src/test/suite/extension.test.ts

View workflow job for this annotation

GitHub Actions / lint

Insert `·`
fs.mkdirSync("./test-bin", { recursive: true });
fs.writeFileSync("./test-bin/nextls", "hello word");

Expand All @@ -49,7 +46,7 @@ suite("Extension Test Suite", () => {
);
});

test("fails to uninstalls Next LS", async function () {
test("fails to uninstalls Next LS", async function() {

Check failure on line 49 in src/test/suite/extension.test.ts

View workflow job for this annotation

GitHub Actions / lint

Insert `·`
let showErrorMessage = sinon.stub(vscode.window, "showErrorMessage");
await uninstall.run("./test-bin");

Expand All @@ -62,4 +59,26 @@ suite("Extension Test Suite", () => {
/due to Error: ENOENT: no such file or directory, lstat/
);
});

// TODO: make a test for the opposite case. As of right now, I'm not entirely
// sure how to set globalState inside a test before the extension activates.
test("forces a download if the special key is not set", async function() {

Check failure on line 65 in src/test/suite/extension.test.ts

View workflow job for this annotation

GitHub Actions / lint

Insert `·`
let fixpath = path.join(__dirname, "../../../src/test/fixtures/basic")

Check failure on line 66 in src/test/suite/extension.test.ts

View workflow job for this annotation

GitHub Actions / lint

Insert `;`

Check warning on line 66 in src/test/suite/extension.test.ts

View workflow job for this annotation

GitHub Actions / lint

Missing semicolon
let binpath = path.join(fixpath, "test-bin")

Check failure on line 67 in src/test/suite/extension.test.ts

View workflow job for this annotation

GitHub Actions / lint

Insert `;`

Check warning on line 67 in src/test/suite/extension.test.ts

View workflow job for this annotation

GitHub Actions / lint

Missing semicolon
fs.mkdirSync(binpath, { recursive: true });
fs.writeFileSync(path.join(binpath, "nextls"), "hello world");
let ext = vscode.extensions.getExtension("elixir-tools.elixir-tools");

await ext.activate();

const doc = await vscode.workspace.openTextDocument(
path.join(fixpath, "mix.exs")
);
await vscode.window.showTextDocument(doc);
await new Promise((resolve) => setTimeout(resolve, 1000));

let nextls = fs.readFileSync(path.join(binpath, "nextls"));
assert.notEqual("hello world", nextls);

Check failure on line 81 in src/test/suite/extension.test.ts

View workflow job for this annotation

GitHub Actions / lint

Delete `⏎`

}).timeout(5000);
});

0 comments on commit a677884

Please sign in to comment.