From 88e696898ae12b2a4d274e2b0b431c30666cde4b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Nov 2024 15:08:21 -0500 Subject: [PATCH 01/22] Bump the all-dependencies group with 2 updates (#1211) Bumps the all-dependencies group with 2 updates: [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) and [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser). Updates `@typescript-eslint/eslint-plugin` from 8.14.0 to 8.15.0 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.15.0/packages/eslint-plugin) Updates `@typescript-eslint/parser` from 8.14.0 to 8.15.0 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.15.0/packages/parser) --- updated-dependencies: - dependency-name: "@typescript-eslint/eslint-plugin" dependency-type: direct:development update-type: version-update:semver-minor dependency-group: all-dependencies - dependency-name: "@typescript-eslint/parser" dependency-type: direct:development update-type: version-update:semver-minor dependency-group: all-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 204 ++++++++++++++++++++++++++-------------------- package.json | 4 +- 2 files changed, 118 insertions(+), 90 deletions(-) diff --git a/package-lock.json b/package-lock.json index c172709f4..5cfa3978d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,8 +27,8 @@ "@types/sinon-chai": "^3.2.12", "@types/vscode": "^1.88.0", "@types/xml2js": "^0.4.14", - "@typescript-eslint/eslint-plugin": "^8.14.0", - "@typescript-eslint/parser": "^8.14.0", + "@typescript-eslint/eslint-plugin": "^8.15.0", + "@typescript-eslint/parser": "^8.15.0", "@vscode/test-cli": "^0.0.10", "@vscode/test-electron": "^2.4.1", "@vscode/vsce": "^2.32.0", @@ -1128,16 +1128,16 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.14.0.tgz", - "integrity": "sha512-tqp8H7UWFaZj0yNO6bycd5YjMwxa6wIHOLZvWPkidwbgLCsBMetQoGj7DPuAlWa2yGO3H48xmPwjhsSPPCGU5w==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.15.0.tgz", + "integrity": "sha512-+zkm9AR1Ds9uLWN3fkoeXgFppaQ+uEVtfOV62dDmsy9QCNqlRHWNEck4yarvRNrvRcHQLGfqBNui3cimoz8XAg==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.14.0", - "@typescript-eslint/type-utils": "8.14.0", - "@typescript-eslint/utils": "8.14.0", - "@typescript-eslint/visitor-keys": "8.14.0", + "@typescript-eslint/scope-manager": "8.15.0", + "@typescript-eslint/type-utils": "8.15.0", + "@typescript-eslint/utils": "8.15.0", + "@typescript-eslint/visitor-keys": "8.15.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -1161,15 +1161,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.14.0.tgz", - "integrity": "sha512-2p82Yn9juUJq0XynBXtFCyrBDb6/dJombnz6vbo6mgQEtWHfvHbQuEa9kAOVIt1c9YFwi7H6WxtPj1kg+80+RA==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.15.0.tgz", + "integrity": "sha512-7n59qFpghG4uazrF9qtGKBZXn7Oz4sOMm8dwNWDQY96Xlm2oX67eipqcblDj+oY1lLCbf1oltMZFpUso66Kl1A==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.14.0", - "@typescript-eslint/types": "8.14.0", - "@typescript-eslint/typescript-estree": "8.14.0", - "@typescript-eslint/visitor-keys": "8.14.0", + "@typescript-eslint/scope-manager": "8.15.0", + "@typescript-eslint/types": "8.15.0", + "@typescript-eslint/typescript-estree": "8.15.0", + "@typescript-eslint/visitor-keys": "8.15.0", "debug": "^4.3.4" }, "engines": { @@ -1189,13 +1189,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.14.0.tgz", - "integrity": "sha512-aBbBrnW9ARIDn92Zbo7rguLnqQ/pOrUguVpbUwzOhkFg2npFDwTgPGqFqE0H5feXcOoJOfX3SxlJaKEVtq54dw==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.15.0.tgz", + "integrity": "sha512-QRGy8ADi4J7ii95xz4UoiymmmMd/zuy9azCaamnZ3FM8T5fZcex8UfJcjkiEZjJSztKfEBe3dZ5T/5RHAmw2mA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.14.0", - "@typescript-eslint/visitor-keys": "8.14.0" + "@typescript-eslint/types": "8.15.0", + "@typescript-eslint/visitor-keys": "8.15.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1206,13 +1206,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.14.0.tgz", - "integrity": "sha512-Xcz9qOtZuGusVOH5Uk07NGs39wrKkf3AxlkK79RBK6aJC1l03CobXjJbwBPSidetAOV+5rEVuiT1VSBUOAsanQ==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.15.0.tgz", + "integrity": "sha512-UU6uwXDoI3JGSXmcdnP5d8Fffa2KayOhUUqr/AiBnG1Gl7+7ut/oyagVeSkh7bxQ0zSXV9ptRh/4N15nkCqnpw==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "8.14.0", - "@typescript-eslint/utils": "8.14.0", + "@typescript-eslint/typescript-estree": "8.15.0", + "@typescript-eslint/utils": "8.15.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -1223,6 +1223,9 @@ "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + }, "peerDependenciesMeta": { "typescript": { "optional": true @@ -1230,9 +1233,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.14.0.tgz", - "integrity": "sha512-yjeB9fnO/opvLJFAsPNYlKPnEM8+z4og09Pk504dkqonT02AyL5Z9SSqlE0XqezS93v6CXn49VHvB2G7XSsl0g==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.15.0.tgz", + "integrity": "sha512-n3Gt8Y/KyJNe0S3yDCD2RVKrHBC4gTUcLTebVBXacPy091E6tNspFLKRXlk3hwT4G55nfr1n2AdFqi/XMxzmPQ==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1243,13 +1246,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.14.0.tgz", - "integrity": "sha512-OPXPLYKGZi9XS/49rdaCbR5j/S14HazviBlUQFvSKz3npr3NikF+mrgK7CFVur6XEt95DZp/cmke9d5i3vtVnQ==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.15.0.tgz", + "integrity": "sha512-1eMp2JgNec/niZsR7ioFBlsh/Fk0oJbhaqO0jRyQBMgkz7RrFfkqF9lYYmBoGBaSiLnu8TAPQTwoTUiSTUW9dg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.14.0", - "@typescript-eslint/visitor-keys": "8.14.0", + "@typescript-eslint/types": "8.15.0", + "@typescript-eslint/visitor-keys": "8.15.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -1295,15 +1298,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.14.0.tgz", - "integrity": "sha512-OGqj6uB8THhrHj0Fk27DcHPojW7zKwKkPmHXHvQ58pLYp4hy8CSUdTKykKeh+5vFqTTVmjz0zCOOPKRovdsgHA==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.15.0.tgz", + "integrity": "sha512-k82RI9yGhr0QM3Dnq+egEpz9qB6Un+WLYhmoNcvl8ltMEededhh7otBVVIDDsEEttauwdY/hQoSsOv13lxrFzQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.14.0", - "@typescript-eslint/types": "8.14.0", - "@typescript-eslint/typescript-estree": "8.14.0" + "@typescript-eslint/scope-manager": "8.15.0", + "@typescript-eslint/types": "8.15.0", + "@typescript-eslint/typescript-estree": "8.15.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1314,16 +1317,21 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.14.0.tgz", - "integrity": "sha512-vG0XZo8AdTH9OE6VFRwAZldNc7qtJ/6NLGWak+BtENuEUXGZgFpihILPiBvKXvJ2nFu27XNGC6rKiwuaoMbYzQ==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.15.0.tgz", + "integrity": "sha512-h8vYOulWec9LhpwfAdZf2bjr8xIp0KNKnpgqSz0qqYYKAW/QZKw3ktRndbiAtUz4acH4QLQavwZBYCc0wulA/Q==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.14.0", - "eslint-visitor-keys": "^3.4.3" + "@typescript-eslint/types": "8.15.0", + "eslint-visitor-keys": "^4.2.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1333,6 +1341,18 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", @@ -6679,16 +6699,16 @@ } }, "@typescript-eslint/eslint-plugin": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.14.0.tgz", - "integrity": "sha512-tqp8H7UWFaZj0yNO6bycd5YjMwxa6wIHOLZvWPkidwbgLCsBMetQoGj7DPuAlWa2yGO3H48xmPwjhsSPPCGU5w==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.15.0.tgz", + "integrity": "sha512-+zkm9AR1Ds9uLWN3fkoeXgFppaQ+uEVtfOV62dDmsy9QCNqlRHWNEck4yarvRNrvRcHQLGfqBNui3cimoz8XAg==", "dev": true, "requires": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.14.0", - "@typescript-eslint/type-utils": "8.14.0", - "@typescript-eslint/utils": "8.14.0", - "@typescript-eslint/visitor-keys": "8.14.0", + "@typescript-eslint/scope-manager": "8.15.0", + "@typescript-eslint/type-utils": "8.15.0", + "@typescript-eslint/utils": "8.15.0", + "@typescript-eslint/visitor-keys": "8.15.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -6696,54 +6716,54 @@ } }, "@typescript-eslint/parser": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.14.0.tgz", - "integrity": "sha512-2p82Yn9juUJq0XynBXtFCyrBDb6/dJombnz6vbo6mgQEtWHfvHbQuEa9kAOVIt1c9YFwi7H6WxtPj1kg+80+RA==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.15.0.tgz", + "integrity": "sha512-7n59qFpghG4uazrF9qtGKBZXn7Oz4sOMm8dwNWDQY96Xlm2oX67eipqcblDj+oY1lLCbf1oltMZFpUso66Kl1A==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "8.14.0", - "@typescript-eslint/types": "8.14.0", - "@typescript-eslint/typescript-estree": "8.14.0", - "@typescript-eslint/visitor-keys": "8.14.0", + "@typescript-eslint/scope-manager": "8.15.0", + "@typescript-eslint/types": "8.15.0", + "@typescript-eslint/typescript-estree": "8.15.0", + "@typescript-eslint/visitor-keys": "8.15.0", "debug": "^4.3.4" } }, "@typescript-eslint/scope-manager": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.14.0.tgz", - "integrity": "sha512-aBbBrnW9ARIDn92Zbo7rguLnqQ/pOrUguVpbUwzOhkFg2npFDwTgPGqFqE0H5feXcOoJOfX3SxlJaKEVtq54dw==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.15.0.tgz", + "integrity": "sha512-QRGy8ADi4J7ii95xz4UoiymmmMd/zuy9azCaamnZ3FM8T5fZcex8UfJcjkiEZjJSztKfEBe3dZ5T/5RHAmw2mA==", "dev": true, "requires": { - "@typescript-eslint/types": "8.14.0", - "@typescript-eslint/visitor-keys": "8.14.0" + "@typescript-eslint/types": "8.15.0", + "@typescript-eslint/visitor-keys": "8.15.0" } }, "@typescript-eslint/type-utils": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.14.0.tgz", - "integrity": "sha512-Xcz9qOtZuGusVOH5Uk07NGs39wrKkf3AxlkK79RBK6aJC1l03CobXjJbwBPSidetAOV+5rEVuiT1VSBUOAsanQ==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.15.0.tgz", + "integrity": "sha512-UU6uwXDoI3JGSXmcdnP5d8Fffa2KayOhUUqr/AiBnG1Gl7+7ut/oyagVeSkh7bxQ0zSXV9ptRh/4N15nkCqnpw==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "8.14.0", - "@typescript-eslint/utils": "8.14.0", + "@typescript-eslint/typescript-estree": "8.15.0", + "@typescript-eslint/utils": "8.15.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" } }, "@typescript-eslint/types": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.14.0.tgz", - "integrity": "sha512-yjeB9fnO/opvLJFAsPNYlKPnEM8+z4og09Pk504dkqonT02AyL5Z9SSqlE0XqezS93v6CXn49VHvB2G7XSsl0g==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.15.0.tgz", + "integrity": "sha512-n3Gt8Y/KyJNe0S3yDCD2RVKrHBC4gTUcLTebVBXacPy091E6tNspFLKRXlk3hwT4G55nfr1n2AdFqi/XMxzmPQ==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.14.0.tgz", - "integrity": "sha512-OPXPLYKGZi9XS/49rdaCbR5j/S14HazviBlUQFvSKz3npr3NikF+mrgK7CFVur6XEt95DZp/cmke9d5i3vtVnQ==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.15.0.tgz", + "integrity": "sha512-1eMp2JgNec/niZsR7ioFBlsh/Fk0oJbhaqO0jRyQBMgkz7RrFfkqF9lYYmBoGBaSiLnu8TAPQTwoTUiSTUW9dg==", "dev": true, "requires": { - "@typescript-eslint/types": "8.14.0", - "@typescript-eslint/visitor-keys": "8.14.0", + "@typescript-eslint/types": "8.15.0", + "@typescript-eslint/visitor-keys": "8.15.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -6773,25 +6793,33 @@ } }, "@typescript-eslint/utils": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.14.0.tgz", - "integrity": "sha512-OGqj6uB8THhrHj0Fk27DcHPojW7zKwKkPmHXHvQ58pLYp4hy8CSUdTKykKeh+5vFqTTVmjz0zCOOPKRovdsgHA==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.15.0.tgz", + "integrity": "sha512-k82RI9yGhr0QM3Dnq+egEpz9qB6Un+WLYhmoNcvl8ltMEededhh7otBVVIDDsEEttauwdY/hQoSsOv13lxrFzQ==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.14.0", - "@typescript-eslint/types": "8.14.0", - "@typescript-eslint/typescript-estree": "8.14.0" + "@typescript-eslint/scope-manager": "8.15.0", + "@typescript-eslint/types": "8.15.0", + "@typescript-eslint/typescript-estree": "8.15.0" } }, "@typescript-eslint/visitor-keys": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.14.0.tgz", - "integrity": "sha512-vG0XZo8AdTH9OE6VFRwAZldNc7qtJ/6NLGWak+BtENuEUXGZgFpihILPiBvKXvJ2nFu27XNGC6rKiwuaoMbYzQ==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.15.0.tgz", + "integrity": "sha512-h8vYOulWec9LhpwfAdZf2bjr8xIp0KNKnpgqSz0qqYYKAW/QZKw3ktRndbiAtUz4acH4QLQavwZBYCc0wulA/Q==", "dev": true, "requires": { - "@typescript-eslint/types": "8.14.0", - "eslint-visitor-keys": "^3.4.3" + "@typescript-eslint/types": "8.15.0", + "eslint-visitor-keys": "^4.2.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true + } } }, "@ungap/structured-clone": { diff --git a/package.json b/package.json index ac96a2121..a73107822 100644 --- a/package.json +++ b/package.json @@ -1302,8 +1302,8 @@ "@types/sinon-chai": "^3.2.12", "@types/vscode": "^1.88.0", "@types/xml2js": "^0.4.14", - "@typescript-eslint/eslint-plugin": "^8.14.0", - "@typescript-eslint/parser": "^8.14.0", + "@typescript-eslint/eslint-plugin": "^8.15.0", + "@typescript-eslint/parser": "^8.15.0", "@vscode/test-cli": "^0.0.10", "@vscode/test-electron": "^2.4.1", "@vscode/vsce": "^2.32.0", From b87f8f314287d60522f030bcebd39a77fd05cc7e Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Tue, 19 Nov 2024 09:58:30 -0500 Subject: [PATCH 02/22] Explicitly activate/deactivate extension for cleaner integration test runs (#1194) * Explicitly activate/deactivate extension to ensure cleaner test runs The `globalWorkspaceContextPromise` would activate the extension once and then always return the same workspace context for every test run. Its a limitation that we cannot actually activate the extension multiple times in tests (there is no extension deactivate API offered by VS Code), however we can dispose and recreate the WorkspaceContext at will to ensure a cleaner test environment. `activateExtension` will call `extension.activate()` the first time its used, and then `deactivateExtension` will dispose of the returned WorkspaceContext. Subsequent calls to `activateExtension` will use the activation method returned by the Swift extension's entrypoint API to recreate the `WorkspaceContext`. If `activateExtension` is called again without a matching call to `deactivateExtension` an error is thrown. --- assets/test/.vscode/launch.json | 6 - package.json | 2 +- src/TestExplorer/TestExplorer.ts | 119 ++++++---- src/WorkspaceContext.ts | 6 +- src/extension.ts | 36 ++- src/tasks/TaskQueue.ts | 12 +- src/ui/SwiftOutputChannel.ts | 19 +- .../BackgroundCompilation.test.ts | 19 +- .../DiagnosticsManager.test.ts | 23 +- .../ExtensionActivation.test.ts | 56 +++++ .../WorkspaceContext.test.ts | 10 +- test/integration-tests/commands/build.test.ts | 15 +- .../commands/dependency.test.ts | 71 +++--- .../commands/runTestMultipleTimes.test.ts | 13 +- test/integration-tests/debugger/lldb.test.ts | 8 +- test/integration-tests/extension.test.ts | 42 +--- .../tasks/SwiftExecution.test.ts | 8 +- .../tasks/SwiftPluginTaskProvider.test.ts | 20 +- .../tasks/SwiftTaskProvider.test.ts | 14 +- .../tasks/TaskManager.test.ts | 8 +- .../integration-tests/tasks/TaskQueue.test.ts | 8 +- .../TestExplorerIntegration.test.ts | 15 +- .../testexplorer/TestRunArguments.test.ts | 6 +- .../testexplorer/utilities.ts | 79 +------ .../ui/PackageDependencyProvider.test.ts | 20 +- .../utilities/testutilities.ts | 219 ++++++++++++++++++ test/utilities.ts | 15 +- 27 files changed, 611 insertions(+), 258 deletions(-) create mode 100644 test/integration-tests/ExtensionActivation.test.ts create mode 100644 test/integration-tests/utilities/testutilities.ts diff --git a/assets/test/.vscode/launch.json b/assets/test/.vscode/launch.json index 54fd2f838..8417501f1 100644 --- a/assets/test/.vscode/launch.json +++ b/assets/test/.vscode/launch.json @@ -3,9 +3,6 @@ { "type": "swift-lldb", "request": "launch", - "sourceLanguages": [ - "swift" - ], "name": "Debug PackageExe (defaultPackage)", "program": "${workspaceFolder:test}/defaultPackage/.build/debug/PackageExe", "args": [], @@ -15,9 +12,6 @@ { "type": "swift-lldb", "request": "launch", - "sourceLanguages": [ - "swift" - ], "name": "Release PackageExe (defaultPackage)", "program": "${workspaceFolder:test}/defaultPackage/.build/release/PackageExe", "args": [], diff --git a/package.json b/package.json index a73107822..7ded34bee 100644 --- a/package.json +++ b/package.json @@ -1275,7 +1275,7 @@ "pretest": "npm run compile-tests", "soundness": "docker compose -f docker/docker-compose.yaml -p swift-vscode-soundness-prb run --rm soundness", "test-soundness": "scripts/soundness.sh", - "test": "vscode-test", + "test": "VSCODE_TEST=1 vscode-test", "test-ci": "docker/test-ci.sh ci", "test-nightly": "docker/test-ci.sh nightly", "integration-test": "npm test -- --label integrationTests", diff --git a/src/TestExplorer/TestExplorer.ts b/src/TestExplorer/TestExplorer.ts index 370d05b96..cd164e453 100644 --- a/src/TestExplorer/TestExplorer.ts +++ b/src/TestExplorer/TestExplorer.ts @@ -37,6 +37,7 @@ export class TestExplorer { private lspTestDiscovery: LSPTestDiscovery; private subscriptions: { dispose(): unknown }[]; private testFileEdited = true; + private tokenSource = new vscode.CancellationTokenSource(); // Emits after the `vscode.TestController` has been updated. private onTestItemsDidChangeEmitter = new vscode.EventEmitter(); @@ -56,7 +57,7 @@ export class TestExplorer { this.controller.resolveHandler = async item => { if (!item) { - await this.discoverTestsInWorkspace(); + await this.discoverTestsInWorkspace(this.tokenSource.token); } }; @@ -86,7 +87,7 @@ export class TestExplorer { this.testFileEdited = false; // only run discover tests if the library has tests if (this.folderContext.swiftPackage.getTargets(TargetType.test).length > 0) { - this.discoverTestsInWorkspace(); + this.discoverTestsInWorkspace(this.tokenSource.token); } } }); @@ -99,6 +100,7 @@ export class TestExplorer { }); this.subscriptions = [ + this.tokenSource, fileWatcher, onDidEndTask, this.controller, @@ -110,6 +112,9 @@ export class TestExplorer { } dispose() { + this.controller.refreshHandler = undefined; + this.controller.resolveHandler = undefined; + this.tokenSource.cancel(); this.subscriptions.forEach(element => element.dispose()); } @@ -120,47 +125,64 @@ export class TestExplorer { * @returns Observer disposable */ static observeFolders(workspaceContext: WorkspaceContext): vscode.Disposable { - return workspaceContext.onDidChangeFolders(({ folder, operation, workspace }) => { - switch (operation) { - case FolderOperation.add: - if (folder) { - if (folder.swiftPackage.getTargets(TargetType.test).length > 0) { - folder.addTestExplorer(); - // discover tests in workspace but only if disableAutoResolve is not on. - // discover tests will kick off a resolve if required - if (!configuration.folder(folder.workspaceFolder).disableAutoResolve) { - folder.testExplorer?.discoverTestsInWorkspace(); + const tokenSource = new vscode.CancellationTokenSource(); + const disposable = workspaceContext.onDidChangeFolders( + ({ folder, operation, workspace }) => { + switch (operation) { + case FolderOperation.add: + if (folder) { + if (folder.swiftPackage.getTargets(TargetType.test).length > 0) { + folder.addTestExplorer(); + // discover tests in workspace but only if disableAutoResolve is not on. + // discover tests will kick off a resolve if required + if ( + !configuration.folder(folder.workspaceFolder).disableAutoResolve + ) { + folder.testExplorer?.discoverTestsInWorkspace( + tokenSource.token + ); + } } } - } - break; - case FolderOperation.packageUpdated: - if (folder) { - const hasTestTargets = - folder.swiftPackage.getTargets(TargetType.test).length > 0; - if (hasTestTargets && !folder.hasTestExplorer()) { - folder.addTestExplorer(); - // discover tests in workspace but only if disableAutoResolve is not on. - // discover tests will kick off a resolve if required - if (!configuration.folder(folder.workspaceFolder).disableAutoResolve) { - folder.testExplorer?.discoverTestsInWorkspace(); + break; + case FolderOperation.packageUpdated: + if (folder) { + const hasTestTargets = + folder.swiftPackage.getTargets(TargetType.test).length > 0; + if (hasTestTargets && !folder.hasTestExplorer()) { + folder.addTestExplorer(); + // discover tests in workspace but only if disableAutoResolve is not on. + // discover tests will kick off a resolve if required + if ( + !configuration.folder(folder.workspaceFolder).disableAutoResolve + ) { + folder.testExplorer?.discoverTestsInWorkspace( + tokenSource.token + ); + } + } else if (!hasTestTargets && folder.hasTestExplorer()) { + folder.removeTestExplorer(); + } else if (folder.hasTestExplorer()) { + folder.refreshTestExplorer(); } - } else if (!hasTestTargets && folder.hasTestExplorer()) { - folder.removeTestExplorer(); - } else if (folder.hasTestExplorer()) { - folder.refreshTestExplorer(); } - } - break; - case FolderOperation.focus: - if (folder) { - workspace.languageClientManager.documentSymbolWatcher = ( - document, - symbols - ) => TestExplorer.onDocumentSymbols(folder, document, symbols); - } + break; + case FolderOperation.focus: + if (folder) { + workspace.languageClientManager.documentSymbolWatcher = ( + document, + symbols + ) => TestExplorer.onDocumentSymbols(folder, document, symbols); + } + } } - }); + ); + return { + dispose: () => { + tokenSource.dispose(); + disposable.dispose(); + }, + }; } /** @@ -224,17 +246,17 @@ export class TestExplorer { /** * Discover tests */ - async discoverTestsInWorkspace() { + async discoverTestsInWorkspace(token: vscode.CancellationToken) { try { // If the LSP cannot produce a list of tests it throws and // we fall back to discovering tests with SPM. - await this.discoverTestsInWorkspaceLSP(); + await this.discoverTestsInWorkspaceLSP(token); } catch { this.folderContext.workspaceContext.outputChannel.logDiagnostic( "workspace/tests LSP request not supported, falling back to SPM to discover tests.", "Test Discovery" ); - await this.discoverTestsInWorkspaceSPM(); + await this.discoverTestsInWorkspaceSPM(token); } } @@ -242,7 +264,7 @@ export class TestExplorer { * Discover tests * Uses `swift test --list-tests` to get the list of tests */ - async discoverTestsInWorkspaceSPM() { + async discoverTestsInWorkspaceSPM(token: vscode.CancellationToken) { async function runDiscover(explorer: TestExplorer, firstTry: boolean) { try { const toolchain = explorer.folderContext.workspaceContext.toolchain; @@ -263,6 +285,11 @@ export class TestExplorer { return; } } + + if (token.isCancellationRequested) { + return; + } + // get list of tests from `swift test --list-tests` let listTestArguments: string[]; if (toolchain.swiftVersion.isGreaterThanOrEqual(new Version(5, 8, 0))) { @@ -284,7 +311,7 @@ export class TestExplorer { explorer.updateTests(explorer.controller, tests); } ); - await explorer.folderContext.taskQueue.queueOperation(listTestsOperation); + await explorer.folderContext.taskQueue.queueOperation(listTestsOperation, token); } catch (error) { // If a test list fails its possible the tests have not been built. // Build them and try again, and if we still fail then notify the user. @@ -336,10 +363,14 @@ export class TestExplorer { /** * Discover tests */ - async discoverTestsInWorkspaceLSP() { + async discoverTestsInWorkspaceLSP(token: vscode.CancellationToken) { const tests = await this.lspTestDiscovery.getWorkspaceTests( this.folderContext.swiftPackage ); + if (token.isCancellationRequested) { + return; + } + TestDiscovery.updateTestsFromClasses( this.controller, this.folderContext.swiftPackage, diff --git a/src/WorkspaceContext.ts b/src/WorkspaceContext.ts index f711c48eb..afa4d0394 100644 --- a/src/WorkspaceContext.ts +++ b/src/WorkspaceContext.ts @@ -173,7 +173,9 @@ export class WorkspaceContext implements vscode.Disposable { dispose() { this.folders.forEach(f => f.dispose()); + this.folders.length = 0; this.subscriptions.forEach(item => item.dispose()); + this.subscriptions.length = 0; } get swiftVersion() { @@ -388,7 +390,7 @@ export class WorkspaceContext implements vscode.Disposable { * @param folder folder being removed */ async removeWorkspaceFolder(workspaceFolder: vscode.WorkspaceFolder) { - this.folders.forEach(async folder => { + for (const folder of this.folders) { if (folder.workspaceFolder !== workspaceFolder) { return; } @@ -404,7 +406,7 @@ export class WorkspaceContext implements vscode.Disposable { await observer({ folder, operation: FolderOperation.remove, workspace: this }); } folder.dispose(); - }); + } this.folders = this.folders.filter(folder => folder.workspaceFolder !== workspaceFolder); } diff --git a/src/extension.ts b/src/extension.ts index ee9367c60..8a1eb74f5 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -44,19 +44,23 @@ import { resolveFolderDependencies } from "./commands/dependencies/resolve"; * or by the integration test runner for VS Code extensions. */ export interface Api { - workspaceContext: WorkspaceContext; + workspaceContext?: WorkspaceContext; + outputChannel: SwiftOutputChannel; + activate(): Promise; + deactivate(): void; } /** * Activate the extension. This is the main entry point. */ -export async function activate(context: vscode.ExtensionContext): Promise { +export async function activate(context: vscode.ExtensionContext): Promise { try { - console.debug("Activating Swift for Visual Studio Code..."); - const outputChannel = new SwiftOutputChannel("Swift"); + const outputChannel = new SwiftOutputChannel("Swift", !process.env["VSCODE_TEST"]); + outputChannel.log("Activating Swift for Visual Studio Code..."); checkAndWarnAboutWindowsSymlinks(outputChannel); + context.subscriptions.push(outputChannel); context.subscriptions.push(new SwiftEnvironmentVariablesManager(context)); context.subscriptions.push( vscode.window.registerTerminalProfileProvider( @@ -106,7 +110,12 @@ export async function activate(context: vscode.ExtensionContext): Promise activate(context), + deactivate: () => deactivate(context), + }; } const workspaceContext = await WorkspaceContext.create(outputChannel, toolchain); @@ -237,13 +246,19 @@ export async function activate(context: vscode.ExtensionContext): Promise activate(context), + deactivate: () => deactivate(context), + }; } catch (error) { const errorMessage = getErrorDescription(error); // show this error message as the VS Code error message only shows when running @@ -252,3 +267,10 @@ export async function activate(context: vscode.ExtensionContext): Promise { + console.debug("Deactivating Swift for Visual Studio Code..."); + contextKeys.isActivated = false; + context.subscriptions.forEach(subscription => subscription.dispose()); + context.subscriptions.length = 0; +} diff --git a/src/tasks/TaskQueue.ts b/src/tasks/TaskQueue.ts index a85d570b4..a8e530752 100644 --- a/src/tasks/TaskQueue.ts +++ b/src/tasks/TaskQueue.ts @@ -85,6 +85,9 @@ export class TaskOperation implements SwiftOperation { workspaceContext: WorkspaceContext, token?: vscode.CancellationToken ): Promise { + if (token?.isCancellationRequested) { + return Promise.resolve(undefined); + } workspaceContext.outputChannel.log(`Exec Task: ${this.task.detail ?? this.task.name}`); return workspaceContext.tasks.executeTaskAndWait(this.task, token); } @@ -230,6 +233,7 @@ export class TaskQueue { if (!this.activeOperation) { // get task from queue const operation = this.queue.shift(); + if (operation) { //const task = operation.task; this.activeOperation = operation; @@ -250,7 +254,7 @@ export class TaskQueue { .run(this.workspaceContext) .then(result => { // log result - if (operation.log) { + if (operation.log && !operation.token?.isCancellationRequested) { switch (result) { case 0: this.workspaceContext.outputChannel.log( @@ -258,12 +262,6 @@ export class TaskQueue { this.folderContext.name ); break; - case undefined: - this.workspaceContext.outputChannel.log( - `${operation.log}: ... cancelled.`, - this.folderContext.name - ); - break; default: this.workspaceContext.outputChannel.log( `${operation.log}: ... failed.`, diff --git a/src/ui/SwiftOutputChannel.ts b/src/ui/SwiftOutputChannel.ts index 6e6d86ffd..17446aac2 100644 --- a/src/ui/SwiftOutputChannel.ts +++ b/src/ui/SwiftOutputChannel.ts @@ -30,7 +30,7 @@ export class SwiftOutputChannel implements vscode.OutputChannel { ) { this.name = name; this.logToConsole = process.env["CI"] !== "1" && logToConsole; - this.channel = vscode.window.createOutputChannel(name); + this.channel = vscode.window.createOutputChannel(name, "Swift"); this.logStore = new RollingLog(logStoreLinesSize); } @@ -59,6 +59,7 @@ export class SwiftOutputChannel implements vscode.OutputChannel { clear(): void { this.channel.clear(); + this.logStore.clear(); } show(_column?: unknown, preserveFocus?: boolean | undefined): void { @@ -71,6 +72,7 @@ export class SwiftOutputChannel implements vscode.OutputChannel { dispose() { this.channel.dispose(); + this.logStore.dispose(); } log(message: string, label?: string) { @@ -110,12 +112,11 @@ export class SwiftOutputChannel implements vscode.OutputChannel { } } -class RollingLog { +class RollingLog implements vscode.Disposable { private _logs: (string | null)[]; private startIndex: number = 0; private endIndex: number = 0; private logCount: number = 0; - private appending: boolean = false; constructor(private maxLogs: number) { this._logs = new Array(maxLogs).fill(null); @@ -133,6 +134,17 @@ class RollingLog { return (index + 1) % this.maxLogs; } + dispose() { + this.clear(); + } + + clear() { + this._logs = new Array(this.maxLogs).fill(null); + this.startIndex = 0; + this.endIndex = 0; + this.logCount = 0; + } + appendLine(log: string) { // Writing to a new line that isn't the very first, increment the end index if (this.logCount > 0) { @@ -155,7 +167,6 @@ class RollingLog { } const newLogLine = (this._logs[this.endIndex] ?? "") + log; this._logs[this.endIndex] = newLogLine; - this.appending = true; } replace(log: string) { diff --git a/test/integration-tests/BackgroundCompilation.test.ts b/test/integration-tests/BackgroundCompilation.test.ts index e39ecd3f7..f87f8042b 100644 --- a/test/integration-tests/BackgroundCompilation.test.ts +++ b/test/integration-tests/BackgroundCompilation.test.ts @@ -14,24 +14,33 @@ import * as assert from "assert"; import * as vscode from "vscode"; +import { beforeEach, afterEach } from "mocha"; import { WorkspaceContext } from "../../src/WorkspaceContext"; -import { globalWorkspaceContextPromise } from "./extension.test"; import { testAssetUri } from "../fixtures"; import { waitForNoRunningTasks } from "../utilities"; import { Workbench } from "../../src/utilities/commands"; +import { activateExtension, deactivateExtension, updateSettings } from "./utilities/testutilities"; suite("BackgroundCompilation Test Suite", () => { let workspaceContext: WorkspaceContext; + let settingsTeardown: () => Promise; + + beforeEach(async function () { + workspaceContext = await activateExtension(this.currentTest); - suiteSetup(async () => { - workspaceContext = await globalWorkspaceContextPromise; assert.notEqual(workspaceContext.folders.length, 0); await waitForNoRunningTasks(); - await vscode.workspace.getConfiguration("swift").update("backgroundCompilation", true); + settingsTeardown = await updateSettings({ + "swift.backgroundCompilation": true, + }); + }); + + afterEach(async () => { + await settingsTeardown(); + await deactivateExtension(); }); suiteTeardown(async () => { - await vscode.workspace.getConfiguration("swift").update("backgroundCompilation", undefined); await vscode.commands.executeCommand(Workbench.ACTION_CLOSEALLEDITORS); }); diff --git a/test/integration-tests/DiagnosticsManager.test.ts b/test/integration-tests/DiagnosticsManager.test.ts index e84d2d18c..3d2de70e1 100644 --- a/test/integration-tests/DiagnosticsManager.test.ts +++ b/test/integration-tests/DiagnosticsManager.test.ts @@ -22,8 +22,12 @@ import { createBuildAllTask } from "../../src/tasks/SwiftTaskProvider"; import { DiagnosticsManager } from "../../src/DiagnosticsManager"; import { FolderContext } from "../../src/FolderContext"; import { Version } from "../../src/utilities/version"; -import { folderContextPromise, globalWorkspaceContextPromise } from "./extension.test"; import { Workbench } from "../../src/utilities/commands"; +import { + activateExtension, + deactivateExtension, + folderInRootWorkspace, +} from "./utilities/testutilities"; const waitForDiagnostics = (uris: vscode.Uri[], allowEmpty: boolean = true) => new Promise(res => @@ -92,14 +96,14 @@ suite("DiagnosticsManager Test Suite", async function () { let cppHeaderUri: vscode.Uri; suiteSetup(async function () { - workspaceContext = await globalWorkspaceContextPromise; + workspaceContext = await activateExtension(this.currentTest); toolchain = workspaceContext.toolchain; workspaceFolder = testAssetWorkspaceFolder("diagnostics"); cWorkspaceFolder = testAssetWorkspaceFolder("diagnosticsC"); cppWorkspaceFolder = testAssetWorkspaceFolder("diagnosticsCpp"); - folderContext = await folderContextPromise("diagnostics"); - cFolderContext = await folderContextPromise("diagnosticsC"); - cppFolderContext = await folderContextPromise("diagnosticsCpp"); + folderContext = await folderInRootWorkspace("diagnostics", workspaceContext); + cFolderContext = await folderInRootWorkspace("diagnosticsC", workspaceContext); + cppFolderContext = await folderInRootWorkspace("diagnosticsCpp", workspaceContext); mainUri = vscode.Uri.file(`${workspaceFolder.uri.path}/Sources/main.swift`); funcUri = vscode.Uri.file(`${workspaceFolder.uri.path}/Sources/func.swift`); cUri = vscode.Uri.file(`${cWorkspaceFolder.uri.path}/Sources/MyPoint/MyPoint.c`); @@ -109,6 +113,10 @@ suite("DiagnosticsManager Test Suite", async function () { ); }); + suiteTeardown(async () => { + await deactivateExtension(); + }); + suite("Parse diagnostics", async () => { suite("Parse from task output", async () => { const expectedWarningDiagnostic = new vscode.Diagnostic( @@ -141,11 +149,6 @@ suite("DiagnosticsManager Test Suite", async function () { await executeTaskAndWaitForResult(task); }); - setup(async () => { - await waitForNoRunningTasks(); - workspaceContext.diagnostics.clear(); - }); - suiteTeardown(async () => { await swiftConfig.update("diagnosticsStyle", undefined); }); diff --git a/test/integration-tests/ExtensionActivation.test.ts b/test/integration-tests/ExtensionActivation.test.ts new file mode 100644 index 000000000..395514412 --- /dev/null +++ b/test/integration-tests/ExtensionActivation.test.ts @@ -0,0 +1,56 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the VS Code Swift open source project +// +// Copyright (c) 2021-2022 the VS Code Swift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of VS Code Swift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import * as vscode from "vscode"; +import * as assert from "assert"; +import { afterEach } from "mocha"; +import { activateExtension, deactivateExtension } from "./utilities/testutilities"; + +suite("Extension Activation/Deactivation Tests", () => { + suite("Extension Activation", () => { + afterEach(async () => { + await deactivateExtension(); + }); + + async function activate(currentTest?: Mocha.Test) { + assert.ok(await activateExtension(currentTest), "Extension did not return its API"); + const ext = vscode.extensions.getExtension("sswg.swift-lang"); + assert.ok(ext, "Extension is not found"); + assert.strictEqual(ext.isActive, true); + } + + test("Activation", async function () { + await activate(this.currentTest); + }); + + test("Duplicate Activation", async function () { + await activate(this.currentTest); + assert.rejects(activateExtension(this.currentTest), err => { + const msg = (err as unknown as any).message; + return ( + msg.includes("Extension is already activated") && + msg.includes(this.currentTest?.fullTitle()) + ); + }); + }); + }); + + test("Deactivation", async function () { + const workspaceContext = await activateExtension(this.currentTest); + await deactivateExtension(); + const ext = vscode.extensions.getExtension("sswg.swift-lang"); + assert(ext); + assert.equal(workspaceContext.subscriptions.length, 0); + }); +}); diff --git a/test/integration-tests/WorkspaceContext.test.ts b/test/integration-tests/WorkspaceContext.test.ts index 4471b8565..150ac4867 100644 --- a/test/integration-tests/WorkspaceContext.test.ts +++ b/test/integration-tests/WorkspaceContext.test.ts @@ -17,16 +17,20 @@ import * as assert from "assert"; import { testAssetUri } from "../fixtures"; import { FolderOperation, WorkspaceContext } from "../../src/WorkspaceContext"; import { createBuildAllTask } from "../../src/tasks/SwiftTaskProvider"; -import { globalWorkspaceContextPromise } from "./extension.test"; import { Version } from "../../src/utilities/version"; import { SwiftExecution } from "../../src/tasks/SwiftExecution"; +import { activateExtension, deactivateExtension } from "./utilities/testutilities"; suite("WorkspaceContext Test Suite", () => { let workspaceContext: WorkspaceContext; const packageFolder: vscode.Uri = testAssetUri("defaultPackage"); - suiteSetup(async () => { - workspaceContext = await globalWorkspaceContextPromise; + suiteSetup(async function () { + workspaceContext = await activateExtension(); + }); + + suiteTeardown(async () => { + await deactivateExtension(); }); suite("Folder Events", () => { diff --git a/test/integration-tests/commands/build.test.ts b/test/integration-tests/commands/build.test.ts index 9d59656a5..938eaf02f 100644 --- a/test/integration-tests/commands/build.test.ts +++ b/test/integration-tests/commands/build.test.ts @@ -16,7 +16,6 @@ import * as vscode from "vscode"; import * as fs from "fs"; import * as path from "path"; import { expect } from "chai"; -import { folderContextPromise, globalWorkspaceContextPromise } from "../extension.test"; import { waitForNoRunningTasks } from "../../utilities"; import { testAssetUri } from "../../fixtures"; import { FolderContext } from "../../../src/FolderContext"; @@ -25,21 +24,26 @@ import { Commands } from "../../../src/commands"; import { makeDebugConfigurations } from "../../../src/debugger/launch"; import { Workbench } from "../../../src/utilities/commands"; import { continueSession, waitForDebugAdapterCommand } from "../../utilities/debug"; -import { SettingsMap, updateSettings } from "../testexplorer/utilities"; +import { + activateExtension, + deactivateExtension, + folderInRootWorkspace, + updateSettings, +} from "../utilities/testutilities"; suite("Build Commands", function () { let folderContext: FolderContext; let workspaceContext: WorkspaceContext; - let settingsTeardown: () => Promise; + let settingsTeardown: () => Promise; const uri = testAssetUri("defaultPackage/Sources/PackageExe/main.swift"); const breakpoints = [ new vscode.SourceBreakpoint(new vscode.Location(uri, new vscode.Position(2, 0))), ]; suiteSetup(async function () { - workspaceContext = await globalWorkspaceContextPromise; + workspaceContext = await activateExtension(); await waitForNoRunningTasks(); - folderContext = await folderContextPromise("defaultPackage"); + folderContext = await folderInRootWorkspace("defaultPackage", workspaceContext); await workspaceContext.focusFolder(folderContext); await vscode.window.showTextDocument(uri); settingsTeardown = await updateSettings({ @@ -51,6 +55,7 @@ suite("Build Commands", function () { suiteTeardown(async () => { await settingsTeardown(); await vscode.commands.executeCommand(Workbench.ACTION_CLOSEALLEDITORS); + await deactivateExtension(); }); test("Swift: Run Build", async () => { diff --git a/test/integration-tests/commands/dependency.test.ts b/test/integration-tests/commands/dependency.test.ts index 019b22aba..231abb553 100644 --- a/test/integration-tests/commands/dependency.test.ts +++ b/test/integration-tests/commands/dependency.test.ts @@ -18,7 +18,6 @@ import { PackageDependenciesProvider, PackageNode, } from "../../../src/ui/PackageDependencyProvider"; -import { folderContextPromise, globalWorkspaceContextPromise } from "../extension.test"; import { executeTaskAndWaitForResult, waitForNoRunningTasks } from "../../utilities"; import { getBuildAllTask, SwiftTask } from "../../../src/tasks/SwiftTaskProvider"; import { testAssetUri } from "../../fixtures"; @@ -26,6 +25,11 @@ import { FolderContext } from "../../../src/FolderContext"; import { WorkspaceContext } from "../../../src/WorkspaceContext"; import * as sinon from "sinon"; import { Commands } from "../../../src/commands"; +import { + activateExtension, + deactivateExtension, + folderInRootWorkspace, +} from "../utilities/testutilities"; suite("Dependency Commmands Test Suite", function () { // full workflow's interaction with spm is longer than the default timeout @@ -36,13 +40,17 @@ suite("Dependency Commmands Test Suite", function () { let folderContext: FolderContext; let workspaceContext: WorkspaceContext; - suiteSetup(async function () { - workspaceContext = await globalWorkspaceContextPromise; + suiteSetup(async () => { + workspaceContext = await activateExtension(); await waitForNoRunningTasks(); - folderContext = await folderContextPromise("dependencies"); + folderContext = await folderInRootWorkspace("dependencies", workspaceContext); await workspaceContext.focusFolder(folderContext); }); + suiteTeardown(async () => { + await deactivateExtension(); + }); + test("Contract: spm resolve", async () => { const result = await vscode.commands.executeCommand(Commands.RESOLVE_DEPENDENCIES); expect(result).to.be.true; @@ -56,40 +64,32 @@ suite("Dependency Commmands Test Suite", function () { let treeProvider: PackageDependenciesProvider; let item: PackageNode; - suiteSetup(async function () { - workspaceContext = await globalWorkspaceContextPromise; + setup(async () => { + // Check before each test case start: + // Expect to fail without setting up local version + workspaceContext = await activateExtension(); await waitForNoRunningTasks(); - folderContext = await folderContextPromise("dependencies"); + folderContext = await folderInRootWorkspace("dependencies", workspaceContext); await workspaceContext.focusFolder(folderContext); - treeProvider = new PackageDependenciesProvider(workspaceContext); - - const items = await treeProvider.getChildren(); - item = items.find(n => n.name === "swift-markdown") as PackageNode; - }); - suiteTeardown(() => { - treeProvider?.dispose(); - }); - - setup(async function () { - // Check before each test case start: - // Expect to fail without setting up local version tasks = (await getBuildAllTask(folderContext)) as SwiftTask; const { exitCode, output } = await executeTaskAndWaitForResult(tasks); expect(exitCode).to.not.equal(0); expect(output).to.include("PackageLib"); expect(output).to.include("required"); + + treeProvider = new PackageDependenciesProvider(workspaceContext); + + const items = await treeProvider.getChildren(); + item = items.find(n => n.name === "swift-markdown") as PackageNode; }); - teardown(async function () { - // Expect to fail again now dependency is missing - const { exitCode, output } = await executeTaskAndWaitForResult(tasks); - expect(exitCode).to.not.equal(0); - expect(output).to.include("PackageLib"); - expect(output).to.include("required"); + teardown(async () => { + treeProvider?.dispose(); + await deactivateExtension(); }); - const useLocalDependencyTest = async () => { + async function useLocalDependencyTest() { // Contract: spm edit with user supplied local version of dependency const windowMock = sinon.stub(vscode.window, "showOpenDialog"); windowMock.resolves([testAssetUri("Swift-Markdown")]); @@ -102,10 +102,21 @@ suite("Dependency Commmands Test Suite", function () { // This will now pass as we have the required library const { exitCode, output } = await executeTaskAndWaitForResult(tasks); + if (exitCode !== 0) { + console.warn("Exit code non zero, command output:\n", output); + } expect(exitCode).to.equal(0); expect(output).to.include("defaultpackage"); expect(output).to.include("not used by any target"); - }; + } + + async function assertDependencyNoLongerExists() { + // Expect to fail again now dependency is missing + const { exitCode, output } = await executeTaskAndWaitForResult(tasks); + expect(exitCode).to.not.equal(0); + expect(output).to.include("PackageLib"); + expect(output).to.include("required"); + } test("Use local dependency - Reset", async () => { await useLocalDependencyTest(); @@ -113,6 +124,8 @@ suite("Dependency Commmands Test Suite", function () { // Contract: spm reset const result = await vscode.commands.executeCommand(Commands.RESET_PACKAGE); expect(result).to.be.true; + + await assertDependencyNoLongerExists(); }); test("Use local dependency - Add to workspace - Unedit", async () => { @@ -121,6 +134,8 @@ suite("Dependency Commmands Test Suite", function () { // Contract: spm unedit const result = await vscode.commands.executeCommand(Commands.UNEDIT_DEPENDENCY, item); expect(result).to.be.true; + + await assertDependencyNoLongerExists(); }); test("Contract: spm update", async function () { @@ -137,6 +152,8 @@ suite("Dependency Commmands Test Suite", function () { // Clean up result = await vscode.commands.executeCommand(Commands.UNEDIT_DEPENDENCY, item); expect(result).to.be.true; + + await assertDependencyNoLongerExists(); }); }); }); diff --git a/test/integration-tests/commands/runTestMultipleTimes.test.ts b/test/integration-tests/commands/runTestMultipleTimes.test.ts index fdd7835b8..608f4f81c 100644 --- a/test/integration-tests/commands/runTestMultipleTimes.test.ts +++ b/test/integration-tests/commands/runTestMultipleTimes.test.ts @@ -18,7 +18,11 @@ import { runTestMultipleTimes } from "../../../src/commands/testMultipleTimes"; import { mockGlobalObject } from "../../MockUtils"; import { FolderContext } from "../../../src/FolderContext"; import { TestRunProxy } from "../../../src/TestExplorer/TestRunner"; -import { folderContextPromise } from "../extension.test"; +import { + activateExtension, + deactivateExtension, + folderInRootWorkspace, +} from "../utilities/testutilities"; suite("Test Multiple Times Command Test Suite", () => { const windowMock = mockGlobalObject(vscode, "window"); @@ -27,7 +31,8 @@ suite("Test Multiple Times Command Test Suite", () => { let testItem: vscode.TestItem; suiteSetup(async () => { - folderContext = await folderContextPromise("diagnostics"); + const workspaceContext = await activateExtension(); + folderContext = await folderInRootWorkspace("diagnostics", workspaceContext); folderContext.addTestExplorer(); const item = folderContext.testExplorer?.controller.createTestItem( @@ -39,6 +44,10 @@ suite("Test Multiple Times Command Test Suite", () => { testItem = item!; }); + suiteTeardown(async () => { + await deactivateExtension(); + }); + test("Runs successfully after testing 0 times", async () => { windowMock.showInputBox.resolves("0"); const runState = await runTestMultipleTimes(folderContext, testItem, false); diff --git a/test/integration-tests/debugger/lldb.test.ts b/test/integration-tests/debugger/lldb.test.ts index dd2e56085..c08ae3164 100644 --- a/test/integration-tests/debugger/lldb.test.ts +++ b/test/integration-tests/debugger/lldb.test.ts @@ -14,14 +14,18 @@ import { expect } from "chai"; import { getLLDBLibPath, getLldbProcess } from "../../../src/debugger/lldb"; -import { globalWorkspaceContextPromise } from "../extension.test"; import { WorkspaceContext } from "../../../src/WorkspaceContext"; +import { activateExtension, deactivateExtension } from "../utilities/testutilities"; suite("lldb contract test suite", () => { let workspaceContext: WorkspaceContext; suiteSetup(async () => { - workspaceContext = await globalWorkspaceContextPromise; + workspaceContext = await activateExtension(); + }); + + suiteTeardown(async () => { + await deactivateExtension(); }); test("getLldbProcess Contract Test, make sure the command returns", async () => { diff --git a/test/integration-tests/extension.test.ts b/test/integration-tests/extension.test.ts index 528c9cc98..5c3f61967 100644 --- a/test/integration-tests/extension.test.ts +++ b/test/integration-tests/extension.test.ts @@ -12,48 +12,22 @@ // //===----------------------------------------------------------------------===// -import * as vscode from "vscode"; import * as assert from "assert"; -import { Api } from "../../src/extension"; +import { beforeEach, afterEach } from "mocha"; import { WorkspaceContext } from "../../src/WorkspaceContext"; import { getBuildAllTask } from "../../src/tasks/SwiftTaskProvider"; import { SwiftExecution } from "../../src/tasks/SwiftExecution"; -import { testAssetUri } from "../fixtures"; -import { FolderContext } from "../../src/FolderContext"; - -function getRootWorkspaceFolder(): vscode.WorkspaceFolder { - const result = vscode.workspace.workspaceFolders?.at(0); - assert(result, "No workspace folders were opened for the tests to use"); - return result; -} - -export const globalWorkspaceContextPromise: Promise = (async () => { - const workspaceFolder = getRootWorkspaceFolder(); - const ext = vscode.extensions.getExtension("sswg.swift-lang"); - if (!ext) { - throw new Error(`Unable to find extension "sswg.swift-lang"`); - } - const api = await ext.activate(); - const packageFolder = testAssetUri("defaultPackage"); - await api.workspaceContext.addPackageFolder(packageFolder, workspaceFolder); - return api.workspaceContext; -})(); - -export const folderContextPromise = async (name: string): Promise => { - const workspaceFolder = getRootWorkspaceFolder(); - const workspaceContext = await globalWorkspaceContextPromise; - let folder = workspaceContext.folders.find(f => f.workspaceFolder.name === `test/${name}`); - if (!folder) { - folder = await workspaceContext.addPackageFolder(testAssetUri(name), workspaceFolder); - } - return folder; -}; +import { activateExtension, deactivateExtension } from "./utilities/testutilities"; suite("Extension Test Suite", () => { let workspaceContext: WorkspaceContext; - suiteSetup(async () => { - workspaceContext = await globalWorkspaceContextPromise; + beforeEach(async function () { + workspaceContext = await activateExtension(this.currentTest); + }); + + afterEach(async () => { + await deactivateExtension(); }); suite("Temporary Folder Test Suite", () => { diff --git a/test/integration-tests/tasks/SwiftExecution.test.ts b/test/integration-tests/tasks/SwiftExecution.test.ts index a94294073..df708e87e 100644 --- a/test/integration-tests/tasks/SwiftExecution.test.ts +++ b/test/integration-tests/tasks/SwiftExecution.test.ts @@ -16,9 +16,9 @@ import * as vscode from "vscode"; import * as assert from "assert"; import { testSwiftTask } from "../../fixtures"; import { WorkspaceContext } from "../../../src/WorkspaceContext"; -import { globalWorkspaceContextPromise } from "../extension.test"; import { executeTaskAndWaitForResult, waitForNoRunningTasks } from "../../utilities"; import { SwiftToolchain } from "../../../src/toolchain/toolchain"; +import { activateExtension, deactivateExtension } from "../utilities/testutilities"; suite("SwiftExecution Tests Suite", () => { let workspaceContext: WorkspaceContext; @@ -26,12 +26,16 @@ suite("SwiftExecution Tests Suite", () => { let workspaceFolder: vscode.WorkspaceFolder; suiteSetup(async () => { - workspaceContext = await globalWorkspaceContextPromise; + workspaceContext = await activateExtension(); toolchain = await SwiftToolchain.create(); assert.notEqual(workspaceContext.folders.length, 0); workspaceFolder = workspaceContext.folders[0].workspaceFolder; }); + suiteTeardown(async () => { + await deactivateExtension(); + }); + setup(async () => { await waitForNoRunningTasks(); }); diff --git a/test/integration-tests/tasks/SwiftPluginTaskProvider.test.ts b/test/integration-tests/tasks/SwiftPluginTaskProvider.test.ts index 38a781f51..323c5cb20 100644 --- a/test/integration-tests/tasks/SwiftPluginTaskProvider.test.ts +++ b/test/integration-tests/tasks/SwiftPluginTaskProvider.test.ts @@ -15,23 +15,31 @@ import * as vscode from "vscode"; import * as assert from "assert"; import { WorkspaceContext } from "../../../src/WorkspaceContext"; -import { folderContextPromise, globalWorkspaceContextPromise } from "../extension.test"; import { SwiftPluginTaskProvider } from "../../../src/tasks/SwiftPluginTaskProvider"; import { FolderContext } from "../../../src/FolderContext"; import { executeTaskAndWaitForResult, mutable, waitForEndTaskProcess } from "../../utilities"; +import { + activateExtension, + deactivateExtension, + folderInRootWorkspace, +} from "../utilities/testutilities"; suite("SwiftPluginTaskProvider Test Suite", () => { let workspaceContext: WorkspaceContext; let folderContext: FolderContext; suiteSetup(async () => { - workspaceContext = await globalWorkspaceContextPromise; - folderContext = await folderContextPromise("command-plugin"); + workspaceContext = await activateExtension(); + folderContext = await folderInRootWorkspace("command-plugin", workspaceContext); assert.notEqual(workspaceContext.folders.length, 0); await folderContext.loadSwiftPlugins(); assert.notEqual(folderContext.swiftPackage.plugins.length, 0); }); + suiteTeardown(async () => { + await deactivateExtension(); + }); + suite("createSwiftPluginTask", () => { let taskProvider: SwiftPluginTaskProvider; @@ -46,7 +54,11 @@ suite("SwiftPluginTaskProvider Test Suite", () => { }); const { exitCode, output } = await executeTaskAndWaitForResult(task); assert.equal(exitCode, 0); - assert.equal(output.trim(), "Hello, World!"); + assert.equal( + output.trim().endsWith("Hello, World!"), + true, + "Expceted output to end with 'Hello, World!'" + ); }).timeout(10000); test("Exit code on failure", async () => { diff --git a/test/integration-tests/tasks/SwiftTaskProvider.test.ts b/test/integration-tests/tasks/SwiftTaskProvider.test.ts index 6b631faae..bf8052bf1 100644 --- a/test/integration-tests/tasks/SwiftTaskProvider.test.ts +++ b/test/integration-tests/tasks/SwiftTaskProvider.test.ts @@ -15,7 +15,6 @@ import * as vscode from "vscode"; import * as assert from "assert"; import { WorkspaceContext } from "../../../src/WorkspaceContext"; -import { folderContextPromise, globalWorkspaceContextPromise } from "../extension.test"; import { SwiftTaskProvider, createSwiftTask, @@ -31,6 +30,11 @@ import { import { Version } from "../../../src/utilities/version"; import { FolderContext } from "../../../src/FolderContext"; import { mockGlobalObject } from "../../MockUtils"; +import { + activateExtension, + deactivateExtension, + folderInRootWorkspace, +} from "../utilities/testutilities"; suite("SwiftTaskProvider Test Suite", () => { let workspaceContext: WorkspaceContext; @@ -39,13 +43,17 @@ suite("SwiftTaskProvider Test Suite", () => { let folderContext: FolderContext; suiteSetup(async () => { - workspaceContext = await globalWorkspaceContextPromise; + workspaceContext = await activateExtension(); toolchain = workspaceContext.toolchain; assert.notEqual(workspaceContext.folders.length, 0); workspaceFolder = workspaceContext.folders[0].workspaceFolder; // Make sure have another folder - folderContext = await folderContextPromise("diagnostics"); + folderContext = await folderInRootWorkspace("diagnostics", workspaceContext); + }); + + suiteTeardown(async () => { + await deactivateExtension(); }); suite("createSwiftTask", () => { diff --git a/test/integration-tests/tasks/TaskManager.test.ts b/test/integration-tests/tasks/TaskManager.test.ts index cbe12d4f9..833728dc9 100644 --- a/test/integration-tests/tasks/TaskManager.test.ts +++ b/test/integration-tests/tasks/TaskManager.test.ts @@ -16,19 +16,23 @@ import * as vscode from "vscode"; import * as assert from "assert"; import { TaskManager } from "../../../src/tasks/TaskManager"; import { WorkspaceContext } from "../../../src/WorkspaceContext"; -import { globalWorkspaceContextPromise } from "../extension.test"; import { waitForNoRunningTasks } from "../../utilities"; +import { activateExtension, deactivateExtension } from "../utilities/testutilities"; suite("TaskManager Test Suite", () => { let workspaceContext: WorkspaceContext; let taskManager: TaskManager; suiteSetup(async () => { - workspaceContext = await globalWorkspaceContextPromise; + workspaceContext = await activateExtension(); taskManager = workspaceContext.tasks; assert.notEqual(workspaceContext.folders.length, 0); }); + suiteTeardown(async () => { + await deactivateExtension(); + }); + setup(async () => { await waitForNoRunningTasks(); }); diff --git a/test/integration-tests/tasks/TaskQueue.test.ts b/test/integration-tests/tasks/TaskQueue.test.ts index d29ae09e4..300013d74 100644 --- a/test/integration-tests/tasks/TaskQueue.test.ts +++ b/test/integration-tests/tasks/TaskQueue.test.ts @@ -17,19 +17,23 @@ import * as assert from "assert"; import { testAssetPath } from "../../fixtures"; import { WorkspaceContext } from "../../../src/WorkspaceContext"; import { SwiftExecOperation, TaskOperation, TaskQueue } from "../../../src/tasks/TaskQueue"; -import { globalWorkspaceContextPromise } from "../extension.test"; import { waitForNoRunningTasks } from "../../utilities"; +import { activateExtension, deactivateExtension } from "../utilities/testutilities"; suite("TaskQueue Test Suite", () => { let workspaceContext: WorkspaceContext; let taskQueue: TaskQueue; suiteSetup(async () => { - workspaceContext = await globalWorkspaceContextPromise; + workspaceContext = await activateExtension(); assert.notEqual(workspaceContext.folders.length, 0); taskQueue = workspaceContext.folders[0].taskQueue; }); + suiteTeardown(async () => { + await deactivateExtension(); + }); + setup(async () => { await waitForNoRunningTasks(); }); diff --git a/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts b/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts index 81183e538..67f2d0c5f 100644 --- a/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts +++ b/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts @@ -24,11 +24,9 @@ import { eventPromise, gatherTests, runTest, - SettingsMap, setupTestExplorerTest, waitForTestExplorerReady, } from "./utilities"; -import { globalWorkspaceContextPromise } from "../extension.test"; import { WorkspaceContext } from "../../../src/WorkspaceContext"; import { Version } from "../../../src/utilities/version"; import { TestKind } from "../../../src/TestExplorer/TestKind"; @@ -42,6 +40,7 @@ import { reduceTestItemChildren, } from "../../../src/TestExplorer/TestUtils"; import { runnableTag } from "../../../src/TestExplorer/TestDiscovery"; +import { activateExtension, deactivateExtension } from "../utilities/testutilities"; import { Commands } from "../../../src/commands"; suite("Test Explorer Suite", function () { @@ -53,7 +52,7 @@ suite("Test Explorer Suite", function () { let testExplorer: TestExplorer; suite("Debugging", function () { - let settingsTeardown: () => Promise; + let settingsTeardown: () => Promise; async function runXCTest() { const suiteId = "PackageTests.PassingXCTestSuite"; @@ -121,12 +120,18 @@ suite("Test Explorer Suite", function () { }); }); - afterEach(() => settingsTeardown()); + afterEach(async () => { + await settingsTeardown(); + }); }); suite("Standard", () => { suiteSetup(async () => { - workspaceContext = await globalWorkspaceContextPromise; + workspaceContext = await activateExtension(); + }); + + suiteTeardown(async () => { + await deactivateExtension(); }); beforeEach(async () => { diff --git a/test/integration-tests/testexplorer/TestRunArguments.test.ts b/test/integration-tests/testexplorer/TestRunArguments.test.ts index 9dc660c11..ec9f51a77 100644 --- a/test/integration-tests/testexplorer/TestRunArguments.test.ts +++ b/test/integration-tests/testexplorer/TestRunArguments.test.ts @@ -14,7 +14,7 @@ import * as vscode from "vscode"; import * as assert from "assert"; -import { beforeEach } from "mocha"; +import { beforeEach, afterEach } from "mocha"; import { TestRunArguments } from "../../../src/TestExplorer/TestRunArguments"; import { flattenTestItemCollection } from "../../../src/TestExplorer/TestUtils"; @@ -102,6 +102,10 @@ suite("TestRunArguments Suite", () => { ); }); + afterEach(() => { + controller.dispose(); + }); + suite("Basic Tests", () => { beforeEach(() => { const dsl = ` diff --git a/test/integration-tests/testexplorer/utilities.ts b/test/integration-tests/testexplorer/utilities.ts index ec310067d..577adaa57 100644 --- a/test/integration-tests/testexplorer/utilities.ts +++ b/test/integration-tests/testexplorer/utilities.ts @@ -19,8 +19,13 @@ import { TestRunProxy } from "../../../src/TestExplorer/TestRunner"; import { TestExplorer } from "../../../src/TestExplorer/TestExplorer"; import { TestKind } from "../../../src/TestExplorer/TestKind"; import { WorkspaceContext } from "../../../src/WorkspaceContext"; -import { globalWorkspaceContextPromise } from "../extension.test"; import { testAssetUri } from "../../fixtures"; +import { + activateExtension, + deactivateExtension, + SettingsMap, + updateSettings, +} from "../utilities/testutilities"; /** * Sets up a test that leverages the TestExplorer, returning the TestExplorer, @@ -34,7 +39,7 @@ export async function setupTestExplorerTest(settings: SettingsMap = {}) { const testProject = testAssetUri("defaultPackage"); - const workspaceContext = await globalWorkspaceContextPromise; + const workspaceContext = await activateExtension(); const testExplorer = testExplorerFor(workspaceContext, testProject); // Set up the listener before bringing the text explorer in to focus, @@ -42,7 +47,10 @@ export async function setupTestExplorerTest(settings: SettingsMap = {}) { await waitForTestExplorerReady(testExplorer); return { - settingsTeardown, + settingsTeardown: async () => { + await settingsTeardown(); + await deactivateExtension(); + }, workspaceContext, testExplorer, }; @@ -162,71 +170,6 @@ export function eventPromise(event: vscode.Event): Promise { }); } -function decomposeSettingName(setting: string): { section: string; name: string } { - const splitNames = setting.split("."); - const name = splitNames.pop(); - const section = splitNames.join("."); - if (name === undefined) { - throw new Error(`Invalid setting name: ${setting}, must be in the form swift.settingName`); - } - return { section, name }; -} - -export type SettingsMap = { [key: string]: unknown }; - -/** - * Updates VS Code workspace settings and provides a callback to revert them. This - * should be called before the extension is activated. - * - * This function modifies VS Code workspace settings based on the provided - * `settings` object. Each key in the `settings` object corresponds to a setting - * name in the format "section.name", and the value is the new setting value to be applied. - * The original settings are stored, and a callback is returned, which when invoked, - * reverts the settings back to their original values. - * - * @param settings - A map where each key is a string representing the setting name in - * "section.name" format, and the value is the new setting value. - * @returns A function that, when called, resets the settings back to their original values. - */ -export async function updateSettings(settings: SettingsMap): Promise<() => Promise> { - const applySettings = async (settings: SettingsMap) => { - const savedOriginalSettings: SettingsMap = {}; - Object.keys(settings).forEach(async setting => { - const { section, name } = decomposeSettingName(setting); - const config = vscode.workspace.getConfiguration(section, { languageId: "swift" }); - savedOriginalSettings[setting] = config.get(name); - await config.update( - name, - settings[setting] === "" ? undefined : settings[setting], - vscode.ConfigurationTarget.Workspace - ); - }); - - // There is actually a delay between when the config.update promise resolves and when - // the setting is actually written. If we exit this function right away the test might - // start before the settings are actually written. Verify that all the settings are set - // to their new value before continuing. - for (const setting of Object.keys(settings)) { - const { section, name } = decomposeSettingName(setting); - while ( - vscode.workspace.getConfiguration(section, { languageId: "swift" }).get(name) !== - settings[setting] - ) { - // Not yet, wait a bit and try again. - await new Promise(resolve => setTimeout(resolve, 30)); - } - } - - return savedOriginalSettings; - }; - - // Updates the settings - const savedOriginalSettings = await applySettings(settings); - - // Clients call the callback to reset updated settings to their original value - return async () => await applySettings(savedOriginalSettings); -} - /** * After extension activation the test explorer needs to be initialized before the controller is ready. * Use this method to wait for the test explorer be available for test items diff --git a/test/integration-tests/ui/PackageDependencyProvider.test.ts b/test/integration-tests/ui/PackageDependencyProvider.test.ts index 57c4ce6f4..a00e4a122 100644 --- a/test/integration-tests/ui/PackageDependencyProvider.test.ts +++ b/test/integration-tests/ui/PackageDependencyProvider.test.ts @@ -17,31 +17,31 @@ import { PackageDependenciesProvider, PackageNode, } from "../../../src/ui/PackageDependencyProvider"; -import { folderContextPromise, globalWorkspaceContextPromise } from "../extension.test"; import { executeTaskAndWaitForResult, waitForNoRunningTasks } from "../../utilities"; import { getBuildAllTask, SwiftTask } from "../../../src/tasks/SwiftTaskProvider"; import { testAssetPath } from "../../fixtures"; -import { Version } from "../../../src/utilities/version"; +import { + activateExtension, + deactivateExtension, + folderInRootWorkspace, +} from "../utilities/testutilities"; suite("PackageDependencyProvider Test Suite", function () { let treeProvider: PackageDependenciesProvider; this.timeout(2 * 60 * 1000); // Allow up to 2 minutes to build suiteSetup(async function () { - const workspaceContext = await globalWorkspaceContextPromise; - // workspace-state.json was not introduced until swift 5.7 - if (workspaceContext.toolchain.swiftVersion.isLessThan(new Version(5, 7, 0))) { - this.skip(); - } + const workspaceContext = await activateExtension(); await waitForNoRunningTasks(); - const folderContext = await folderContextPromise("dependencies"); + const folderContext = await folderInRootWorkspace("dependencies", workspaceContext); await executeTaskAndWaitForResult((await getBuildAllTask(folderContext)) as SwiftTask); await workspaceContext.focusFolder(folderContext); treeProvider = new PackageDependenciesProvider(workspaceContext); }); - suiteTeardown(() => { - treeProvider?.dispose(); + suiteTeardown(async () => { + treeProvider.dispose(); + await deactivateExtension(); }); test("Includes remote dependency", async () => { diff --git a/test/integration-tests/utilities/testutilities.ts b/test/integration-tests/utilities/testutilities.ts new file mode 100644 index 000000000..5313ae6b1 --- /dev/null +++ b/test/integration-tests/utilities/testutilities.ts @@ -0,0 +1,219 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the VS Code Swift open source project +// +// Copyright (c) 2024 the VS Code Swift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of VS Code Swift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import * as vscode from "vscode"; +import * as assert from "assert"; +import * as mocha from "mocha"; +import { Api } from "../../../src/extension"; +import { testAssetUri } from "../../fixtures"; +import { WorkspaceContext } from "../../../src/WorkspaceContext"; +import { FolderContext } from "../../../src/FolderContext"; +import { waitForNoRunningTasks } from "../../utilities"; + +function getRootWorkspaceFolder(): vscode.WorkspaceFolder { + const result = vscode.workspace.workspaceFolders?.at(0); + assert(result, "No workspace folders were opened for the tests to use"); + return result; +} + +const extensionBootstrapper = (() => { + let activator: (() => Promise) | undefined = undefined; + let activatedAPI: Api | undefined = undefined; + let lastTestName: string | undefined = undefined; + let lastTestLogs: string[] = []; + const testTitle = (currentTest: Mocha.Test) => currentTest.titlePath().join(" → "); + + mocha.afterEach(function () { + if (this.currentTest && this.currentTest.isFailed()) { + console.log(`Captured logs during ${testTitle(this.currentTest)}:`); + if (lastTestLogs.length === 0) { + console.log("No logs captured."); + } + for (const log of lastTestLogs) { + console.log(log); + } + } + }); + + mocha.beforeEach(function () { + if (this.currentTest && activatedAPI && process.env["VSCODE_TEST"]) { + activatedAPI.outputChannel.clear(); + activatedAPI.outputChannel.appendLine(`Starting test: ${testTitle(this.currentTest)}`); + } + }); + + return { + // Activates the extension and adds the defaultPackage to the workspace. + // We can only truly call `vscode.Extension.activate()` once for an entire + // test run, so after it is called once we switch over to calling activate on + // the returned API object which behaves like the extension is being launched for + // the first time _as long as everything is disposed of properly in `deactivate()`_. + activateExtension: async function (currentTest?: Mocha.Test) { + if (activatedAPI) { + throw new Error( + `Extension is already activated. Last test that activated the extension: ${lastTestName}` + ); + } + const extensionId = "sswg.swift-lang"; + const ext = vscode.extensions.getExtension(extensionId); + if (!ext) { + throw new Error(`Unable to find extension "${extensionId}"`); + } + + let workspaceContext: WorkspaceContext | undefined; + + // We can only _really_ call activate through + // `vscode.extensions.getExtension("sswg.swift-lang")` once. + // Subsequent activations must be done through the returned API object. + if (!activator) { + activatedAPI = await ext.activate(); + activator = activatedAPI.activate; + workspaceContext = activatedAPI.workspaceContext; + } else { + activatedAPI = await activator(); + workspaceContext = activatedAPI.workspaceContext; + } + + if (!workspaceContext) { + throw new Error("Extension did not activate. Workspace context is not available."); + } + + // Always adds defaultPackage to the workspace. This may need to be refactored if we want + // to scope tests more narowly to sub packages within the test assets. + const workspaceFolder = getRootWorkspaceFolder(); + const packageFolder = testAssetUri("defaultPackage"); + await workspaceContext.addPackageFolder(packageFolder, workspaceFolder); + + // Save the test name so if the test doesn't clean up by deactivating properly the next + // test that tries to activate can throw an error with the name of the test that needs to clean up. + lastTestName = currentTest?.fullTitle(); + + return workspaceContext; + }, + deactivateExtension: async () => { + if (!activatedAPI) { + throw new Error("Extension is not activated. Call activateExtension() first."); + } + lastTestLogs = activatedAPI.outputChannel.logs; + + // Wait for up to 10 seconds for all tasks to complete before deactivating. + // Long running tasks should be avoided in tests, but this is a safety net. + await waitForNoRunningTasks({ timeout: 10000 }); + + // Close all editors before deactivating the extension. + await vscode.commands.executeCommand("workbench.action.closeAllEditors"); + + await activatedAPI.workspaceContext?.removeWorkspaceFolder(getRootWorkspaceFolder()); + activatedAPI.deactivate(); + activatedAPI = undefined; + lastTestName = undefined; + }, + }; +})(); + +/** + * Activate the extension in tests. + */ +export const activateExtension = extensionBootstrapper.activateExtension; + +/** + * Deactivates the extension in tests. + */ +export const deactivateExtension = extensionBootstrapper.deactivateExtension; + +/** + * Given a name of a folder in the root test workspace, adds that folder to the + * workspace context and then returns the folder context. + * @param name The name of the folder in the root workspace + * @param workspaceContext The existing workspace context + * @returns The folder context for the folder in the root workspace + */ +export const folderInRootWorkspace = async ( + name: string, + workspaceContext: WorkspaceContext +): Promise => { + const workspaceFolder = getRootWorkspaceFolder(); + let folder = workspaceContext.folders.find(f => f.workspaceFolder.name === `test/${name}`); + if (!folder) { + folder = await workspaceContext.addPackageFolder(testAssetUri(name), workspaceFolder); + } + return folder; +}; + +export type SettingsMap = { [key: string]: unknown }; + +/** + * Updates VS Code workspace settings and provides a callback to revert them. This + * should be called before the extension is activated. + * + * This function modifies VS Code workspace settings based on the provided + * `settings` object. Each key in the `settings` object corresponds to a setting + * name in the format "section.name", and the value is the new setting value to be applied. + * The original settings are stored, and a callback is returned, which when invoked, + * reverts the settings back to their original values. + * + * @param settings - A map where each key is a string representing the setting name in + * "section.name" format, and the value is the new setting value. + * @returns A function that, when called, resets the settings back to their original values. + */ +export async function updateSettings(settings: SettingsMap): Promise<() => Promise> { + const applySettings = async (settings: SettingsMap) => { + const savedOriginalSettings: SettingsMap = {}; + Object.keys(settings).forEach(async setting => { + const { section, name } = decomposeSettingName(setting); + const config = vscode.workspace.getConfiguration(section, { languageId: "swift" }); + savedOriginalSettings[setting] = config.get(name); + await config.update( + name, + settings[setting] === "" ? undefined : settings[setting], + vscode.ConfigurationTarget.Workspace + ); + }); + + // There is actually a delay between when the config.update promise resolves and when + // the setting is actually written. If we exit this function right away the test might + // start before the settings are actually written. Verify that all the settings are set + // to their new value before continuing. + for (const setting of Object.keys(settings)) { + const { section, name } = decomposeSettingName(setting); + while ( + vscode.workspace.getConfiguration(section, { languageId: "swift" }).get(name) !== + settings[setting] + ) { + // Not yet, wait a bit and try again. + await new Promise(resolve => setTimeout(resolve, 30)); + } + } + + return savedOriginalSettings; + }; + + // Updates the settings + const savedOriginalSettings = await applySettings(settings); + + // Clients call the callback to reset updated settings to their original value + return async () => { + await applySettings(savedOriginalSettings); + }; +} + +function decomposeSettingName(setting: string): { section: string; name: string } { + const splitNames = setting.split("."); + const name = splitNames.pop(); + const section = splitNames.join("."); + if (name === undefined) { + throw new Error(`Invalid setting name: ${setting}, must be in the form swift.settingName`); + } + return { section, name }; +} diff --git a/test/utilities.ts b/test/utilities.ts index 475e49955..e851f5c05 100644 --- a/test/utilities.ts +++ b/test/utilities.ts @@ -91,19 +91,30 @@ export async function waitForClose(fixture: { * utility can be used to make sure no task is running * before starting a new test */ -export function waitForNoRunningTasks(): Promise { - return new Promise(res => { +export function waitForNoRunningTasks(options?: { timeout: number }): Promise { + return new Promise((res, reject) => { if (vscode.tasks.taskExecutions.length === 0) { res(); return; } + let timeout: NodeJS.Timeout; const disposable = vscode.tasks.onDidEndTask(() => { if (vscode.tasks.taskExecutions.length > 0) { return; } disposable?.dispose(); + clearTimeout(timeout); res(); }); + if (options?.timeout) { + timeout = setTimeout(() => { + disposable.dispose(); + const runningTasks = vscode.tasks.taskExecutions.map(e => e.task.name); + reject( + `Timed out waiting for tasks to complete. The following ${runningTasks.length} tasks are still running: ${runningTasks}.` + ); + }, options.timeout); + } }); } From f2300d344f2c739adfbdab086b83d559d77094fb Mon Sep 17 00:00:00 2001 From: Michael Weng Date: Tue, 19 Nov 2024 16:32:01 -0500 Subject: [PATCH 03/22] Make sure build command tests has no ordering requisite (#1213) * Make sure build command tests can has no ordering requisite Right now the clean command test relies on the build command test to complete. That's not behaviour we want. Ideally tests should be able to run in isolation. Issue: #1184 * Retain the old test lay out - Add in per test tear down and required test step for clean command test --- test/integration-tests/commands/build.test.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/test/integration-tests/commands/build.test.ts b/test/integration-tests/commands/build.test.ts index 938eaf02f..d63f59379 100644 --- a/test/integration-tests/commands/build.test.ts +++ b/test/integration-tests/commands/build.test.ts @@ -35,6 +35,7 @@ suite("Build Commands", function () { let folderContext: FolderContext; let workspaceContext: WorkspaceContext; let settingsTeardown: () => Promise; + let buildPath: string; const uri = testAssetUri("defaultPackage/Sources/PackageExe/main.swift"); const breakpoints = [ new vscode.SourceBreakpoint(new vscode.Location(uri, new vscode.Position(2, 0))), @@ -44,6 +45,7 @@ suite("Build Commands", function () { workspaceContext = await activateExtension(); await waitForNoRunningTasks(); folderContext = await folderInRootWorkspace("defaultPackage", workspaceContext); + buildPath = path.join(folderContext.folder.fsPath, ".build"); await workspaceContext.focusFolder(folderContext); await vscode.window.showTextDocument(uri); settingsTeardown = await updateSettings({ @@ -58,6 +60,13 @@ suite("Build Commands", function () { await deactivateExtension(); }); + teardown(() => { + // Remove the build directory after each test case + if (fs.existsSync(buildPath)) { + fs.rmSync(buildPath, { recursive: true, force: true }); + } + }); + test("Swift: Run Build", async () => { // A breakpoint will have not effect on the Run command. vscode.debug.addBreakpoints(breakpoints); @@ -69,10 +78,12 @@ suite("Build Commands", function () { }); test("Swift: Clean Build", async () => { - const buildPath = path.join(folderContext.folder.fsPath, ".build"); + let result = await vscode.commands.executeCommand(Commands.RUN); + expect(result).to.be.true; + const beforeItemCount = fs.readdirSync(buildPath).length; - const result = await vscode.commands.executeCommand(Commands.CLEAN_BUILD); + result = await vscode.commands.executeCommand(Commands.CLEAN_BUILD); expect(result).to.be.true; const afterItemCount = fs.readdirSync(buildPath).length; From 075db6f0137b398fb115efe2d671b88d458892ac Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Wed, 20 Nov 2024 11:40:13 -0500 Subject: [PATCH 04/22] Fix debugging entire XCTest targets (#1209) When right clicking a test target and debugging the tests, no XCTests are run. The test run argument simplification code would simplify too much when debugging a test target; the `xctest` binary does not accept a test target as an `-XCTest` argument. Only fully specified test names or test suites are permitted. Issue: #1207 --- src/TestExplorer/TestRunArguments.ts | 16 ++++++++-- .../testexplorer/TestRunArguments.test.ts | 31 +++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/TestExplorer/TestRunArguments.ts b/src/TestExplorer/TestRunArguments.ts index 413acbee7..027e3c2ec 100644 --- a/src/TestExplorer/TestRunArguments.ts +++ b/src/TestExplorer/TestRunArguments.ts @@ -123,7 +123,8 @@ export class TestRunArguments { const { xcTestResult, swiftTestResult } = this.simplifyTestArgs( testItem, xcTestArgs, - swiftTestArgs + swiftTestArgs, + isDebug ); return { @@ -156,12 +157,23 @@ export class TestRunArguments { private simplifyTestArgs( testItem: vscode.TestItem, xcTestArgs: vscode.TestItem[], - swiftTestArgs: vscode.TestItem[] + swiftTestArgs: vscode.TestItem[], + isDebug: boolean ): { xcTestResult: vscode.TestItem[]; swiftTestResult: vscode.TestItem[] } { // If we've worked all the way up to a test target, it may have both swift-testing // and XCTests. const isTestTarget = !!testItem.tags.find(tag => tag.id === "test-target"); + if (isTestTarget) { + // We cannot simplify away test suites leaving only the target if we are debugging, + // since the exact names of test suites to run need to be passed to the xctest binary. + // It will not debug all tests with only the target name. + if (isDebug) { + return { + xcTestResult: xcTestArgs, + swiftTestResult: swiftTestArgs, + }; + } return { // Add a trailing .* to match a test target name exactly. // This prevents TestTarget matching TestTarget2. diff --git a/test/integration-tests/testexplorer/TestRunArguments.test.ts b/test/integration-tests/testexplorer/TestRunArguments.test.ts index ec9f51a77..6fc75aa8f 100644 --- a/test/integration-tests/testexplorer/TestRunArguments.test.ts +++ b/test/integration-tests/testexplorer/TestRunArguments.test.ts @@ -317,4 +317,35 @@ suite("TestRunArguments Suite", () => { testItems: [testTargetId, xcSuiteId, xcTestId, anotherXcSuiteId, anotherXcTestId1], }); }); + + test("Full XCTest Target (debug mode)", () => { + const xcTestId2 = "XCTest Item 2"; + const anotherXcSuiteId = "Another XCTest Suite"; + const anotherXcTestId1 = "Another XCTest Item 1"; + const anotherXcTestId2 = "Another XCTest Item 2"; + const dsl = ` + tt:${testTargetId} + xc:${xcSuiteId} + xc:${xcTestId} + xc:${xcTestId2} + xc:${anotherXcSuiteId} + xc:${anotherXcTestId1} + xc:${anotherXcTestId2} + `; + createTestItemTree(controller, dsl); + const testArgs = new TestRunArguments(runRequestByIds([testTargetId]), true); + assertRunArguments(testArgs, { + xcTestArgs: [xcSuiteId, anotherXcSuiteId], + swiftTestArgs: [], + testItems: [ + anotherXcTestId1, + anotherXcTestId2, + anotherXcSuiteId, + xcSuiteId, + testTargetId, + xcTestId2, + xcTestId, + ], + }); + }); }); From 1ea79a98a95390802bf98bce37f1f1b08ba9c2f4 Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Wed, 20 Nov 2024 11:43:48 -0500 Subject: [PATCH 05/22] Simpler test activation/deactivation in tests (#1214) Favour using the new `activateExtensionForSuite` and `activateExtensionForTest` methods, which automatically deactivate the extension when the suite or test completes. These methods take optional setup/teardown methods that run on suite/test setup/teardown. If the setup method returns a promise, this is run automatically on teardown. This is useful for setup methods that modify settings, as they can now return the promise directly from `updateSettings` and any modified settings will be reverted automatically. This also prints any logs captured to the output channel if a suite/test setup/teardown method fails with an error, which was being silently lost before. --- .vscode/launch.json | 3 +- src/DiagnosticsManager.ts | 6 + src/WorkspaceContext.ts | 8 ++ src/extension.ts | 13 +- src/sourcekit-lsp/LanguageClientManager.ts | 9 +- .../BackgroundCompilation.test.ts | 26 ++-- .../DiagnosticsManager.test.ts | 46 ++++--- .../ExtensionActivation.test.ts | 46 ++++++- .../WorkspaceContext.test.ts | 12 +- test/integration-tests/commands/build.test.ts | 40 +++--- .../commands/dependency.test.ts | 66 +++++----- .../commands/runTestMultipleTimes.test.ts | 31 ++--- test/integration-tests/debugger/lldb.test.ts | 12 +- test/integration-tests/extension.test.ts | 13 +- .../tasks/SwiftExecution.test.ts | 18 ++- .../tasks/SwiftPluginTaskProvider.test.ts | 25 ++-- .../tasks/SwiftTaskProvider.test.ts | 27 ++--- .../tasks/TaskManager.test.ts | 16 ++- .../integration-tests/tasks/TaskQueue.test.ts | 16 ++- .../TestExplorerIntegration.test.ts | 104 ++++++++++------ .../testexplorer/utilities.ts | 2 +- .../ui/PackageDependencyProvider.test.ts | 30 ++--- .../utilities/testutilities.ts | 114 +++++++++++++++++- 23 files changed, 422 insertions(+), 261 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index f10b5ffcc..70366aa11 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -33,7 +33,8 @@ "${workspaceFolder}/dist/**/*.js" ], "env": { - "VSCODE_DEBUG": "1" + "VSCODE_DEBUG": "1", + "VSCODE_TEST": "1" }, "preLaunchTask": "compile-tests" }, diff --git a/src/DiagnosticsManager.ts b/src/DiagnosticsManager.ts index 6c4ce732b..c10404452 100644 --- a/src/DiagnosticsManager.ts +++ b/src/DiagnosticsManager.ts @@ -59,6 +59,7 @@ export class DiagnosticsManager implements vscode.Disposable { private diagnosticCollection: vscode.DiagnosticCollection = vscode.languages.createDiagnosticCollection("swift"); private allDiagnostics: Map = new Map(); + private disposed = false; constructor(context: WorkspaceContext) { this.onDidChangeConfigurationDisposible = vscode.workspace.onDidChangeConfiguration(e => { @@ -114,6 +115,10 @@ export class DiagnosticsManager implements vscode.Disposable { sourcePredicate: SourcePredicate, newDiagnostics: vscode.Diagnostic[] ): void { + if (this.disposed) { + return; + } + const isFromSourceKit = !sourcePredicate(DiagnosticsManager.swiftc); // Is a descrepency between SourceKit-LSP and older versions // of Swift as to whether the first letter is capitalized or not, @@ -230,6 +235,7 @@ export class DiagnosticsManager implements vscode.Disposable { } dispose() { + this.disposed = true; this.diagnosticCollection.dispose(); this.onDidStartTaskDisposible.dispose(); this.onDidChangeConfigurationDisposible.dispose(); diff --git a/src/WorkspaceContext.ts b/src/WorkspaceContext.ts index afa4d0394..fdc26f697 100644 --- a/src/WorkspaceContext.ts +++ b/src/WorkspaceContext.ts @@ -171,6 +171,14 @@ export class WorkspaceContext implements vscode.Disposable { this.lastFocusUri = vscode.window.activeTextEditor?.document.uri; } + async stop() { + try { + await this.languageClientManager.stop(); + } catch { + // ignore + } + } + dispose() { this.folders.forEach(f => f.dispose()); this.folders.length = 0; diff --git a/src/extension.ts b/src/extension.ts index 8a1eb74f5..f2dc998c2 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -47,7 +47,7 @@ export interface Api { workspaceContext?: WorkspaceContext; outputChannel: SwiftOutputChannel; activate(): Promise; - deactivate(): void; + deactivate(): Promise; } /** @@ -114,7 +114,10 @@ export async function activate(context: vscode.ExtensionContext): Promise { workspaceContext: undefined, outputChannel, activate: () => activate(context), - deactivate: () => deactivate(context), + deactivate: async () => { + await workspaceContext.stop(); + await deactivate(context); + }, }; } @@ -257,7 +260,10 @@ export async function activate(context: vscode.ExtensionContext): Promise { workspaceContext, outputChannel, activate: () => activate(context), - deactivate: () => deactivate(context), + deactivate: async () => { + await workspaceContext.stop(); + await deactivate(context); + }, }; } catch (error) { const errorMessage = getErrorDescription(error); @@ -269,7 +275,6 @@ export async function activate(context: vscode.ExtensionContext): Promise { } async function deactivate(context: vscode.ExtensionContext): Promise { - console.debug("Deactivating Swift for Visual Studio Code..."); contextKeys.isActivated = false; context.subscriptions.forEach(subscription => subscription.dispose()); context.subscriptions.length = 0; diff --git a/src/sourcekit-lsp/LanguageClientManager.ts b/src/sourcekit-lsp/LanguageClientManager.ts index 98701c0eb..239062740 100644 --- a/src/sourcekit-lsp/LanguageClientManager.ts +++ b/src/sourcekit-lsp/LanguageClientManager.ts @@ -245,6 +245,14 @@ export class LanguageClientManager { this.cancellationToken = new vscode.CancellationTokenSource(); } + // The language client stops asnyhronously, so we need to wait for it to stop + // instead of doing it in dispose, which must be synchronous. + async stop() { + if (this.languageClient && this.languageClient.state === langclient.State.Running) { + await this.languageClient.dispose(); + } + } + dispose() { this.cancellationToken?.cancel(); this.cancellationToken?.dispose(); @@ -252,7 +260,6 @@ export class LanguageClientManager { this.peekDocuments?.dispose(); this.getReferenceDocument?.dispose(); this.subscriptions.forEach(item => item.dispose()); - this.languageClient?.stop(); this.namedOutputChannels.forEach(channel => channel.dispose()); } diff --git a/test/integration-tests/BackgroundCompilation.test.ts b/test/integration-tests/BackgroundCompilation.test.ts index f87f8042b..54f4d4456 100644 --- a/test/integration-tests/BackgroundCompilation.test.ts +++ b/test/integration-tests/BackgroundCompilation.test.ts @@ -14,30 +14,24 @@ import * as assert from "assert"; import * as vscode from "vscode"; -import { beforeEach, afterEach } from "mocha"; import { WorkspaceContext } from "../../src/WorkspaceContext"; import { testAssetUri } from "../fixtures"; import { waitForNoRunningTasks } from "../utilities"; import { Workbench } from "../../src/utilities/commands"; -import { activateExtension, deactivateExtension, updateSettings } from "./utilities/testutilities"; +import { activateExtensionForTest, updateSettings } from "./utilities/testutilities"; suite("BackgroundCompilation Test Suite", () => { let workspaceContext: WorkspaceContext; - let settingsTeardown: () => Promise; - beforeEach(async function () { - workspaceContext = await activateExtension(this.currentTest); - - assert.notEqual(workspaceContext.folders.length, 0); - await waitForNoRunningTasks(); - settingsTeardown = await updateSettings({ - "swift.backgroundCompilation": true, - }); - }); - - afterEach(async () => { - await settingsTeardown(); - await deactivateExtension(); + activateExtensionForTest({ + async setup(ctx) { + workspaceContext = ctx; + assert.notEqual(workspaceContext.folders.length, 0); + await waitForNoRunningTasks(); + return await updateSettings({ + "swift.backgroundCompilation": true, + }); + }, }); suiteTeardown(async () => { diff --git a/test/integration-tests/DiagnosticsManager.test.ts b/test/integration-tests/DiagnosticsManager.test.ts index 3d2de70e1..b9b4885e0 100644 --- a/test/integration-tests/DiagnosticsManager.test.ts +++ b/test/integration-tests/DiagnosticsManager.test.ts @@ -23,11 +23,7 @@ import { DiagnosticsManager } from "../../src/DiagnosticsManager"; import { FolderContext } from "../../src/FolderContext"; import { Version } from "../../src/utilities/version"; import { Workbench } from "../../src/utilities/commands"; -import { - activateExtension, - deactivateExtension, - folderInRootWorkspace, -} from "./utilities/testutilities"; +import { activateExtensionForSuite, folderInRootWorkspace } from "./utilities/testutilities"; const waitForDiagnostics = (uris: vscode.Uri[], allowEmpty: boolean = true) => new Promise(res => @@ -95,26 +91,26 @@ suite("DiagnosticsManager Test Suite", async function () { let cppUri: vscode.Uri; let cppHeaderUri: vscode.Uri; - suiteSetup(async function () { - workspaceContext = await activateExtension(this.currentTest); - toolchain = workspaceContext.toolchain; - workspaceFolder = testAssetWorkspaceFolder("diagnostics"); - cWorkspaceFolder = testAssetWorkspaceFolder("diagnosticsC"); - cppWorkspaceFolder = testAssetWorkspaceFolder("diagnosticsCpp"); - folderContext = await folderInRootWorkspace("diagnostics", workspaceContext); - cFolderContext = await folderInRootWorkspace("diagnosticsC", workspaceContext); - cppFolderContext = await folderInRootWorkspace("diagnosticsCpp", workspaceContext); - mainUri = vscode.Uri.file(`${workspaceFolder.uri.path}/Sources/main.swift`); - funcUri = vscode.Uri.file(`${workspaceFolder.uri.path}/Sources/func.swift`); - cUri = vscode.Uri.file(`${cWorkspaceFolder.uri.path}/Sources/MyPoint/MyPoint.c`); - cppUri = vscode.Uri.file(`${cppWorkspaceFolder.uri.path}/Sources/MyPoint/MyPoint.cpp`); - cppHeaderUri = vscode.Uri.file( - `${cppWorkspaceFolder.uri.path}/Sources/MyPoint/include/MyPoint.h` - ); - }); - - suiteTeardown(async () => { - await deactivateExtension(); + activateExtensionForSuite({ + async setup(ctx) { + this.timeout(60000); + + workspaceContext = ctx; + toolchain = workspaceContext.toolchain; + workspaceFolder = testAssetWorkspaceFolder("diagnostics"); + cWorkspaceFolder = testAssetWorkspaceFolder("diagnosticsC"); + cppWorkspaceFolder = testAssetWorkspaceFolder("diagnosticsCpp"); + folderContext = await folderInRootWorkspace("diagnostics", workspaceContext); + cFolderContext = await folderInRootWorkspace("diagnosticsC", workspaceContext); + cppFolderContext = await folderInRootWorkspace("diagnosticsCpp", workspaceContext); + mainUri = vscode.Uri.file(`${workspaceFolder.uri.path}/Sources/main.swift`); + funcUri = vscode.Uri.file(`${workspaceFolder.uri.path}/Sources/func.swift`); + cUri = vscode.Uri.file(`${cWorkspaceFolder.uri.path}/Sources/MyPoint/MyPoint.c`); + cppUri = vscode.Uri.file(`${cppWorkspaceFolder.uri.path}/Sources/MyPoint/MyPoint.cpp`); + cppHeaderUri = vscode.Uri.file( + `${cppWorkspaceFolder.uri.path}/Sources/MyPoint/include/MyPoint.h` + ); + }, }); suite("Parse diagnostics", async () => { diff --git a/test/integration-tests/ExtensionActivation.test.ts b/test/integration-tests/ExtensionActivation.test.ts index 395514412..a06daab7b 100644 --- a/test/integration-tests/ExtensionActivation.test.ts +++ b/test/integration-tests/ExtensionActivation.test.ts @@ -15,7 +15,13 @@ import * as vscode from "vscode"; import * as assert from "assert"; import { afterEach } from "mocha"; -import { activateExtension, deactivateExtension } from "./utilities/testutilities"; +import { + activateExtension, + activateExtensionForSuite, + activateExtensionForTest, + deactivateExtension, +} from "./utilities/testutilities"; +import { WorkspaceContext } from "../../src/WorkspaceContext"; suite("Extension Activation/Deactivation Tests", () => { suite("Extension Activation", () => { @@ -53,4 +59,42 @@ suite("Extension Activation/Deactivation Tests", () => { assert(ext); assert.equal(workspaceContext.subscriptions.length, 0); }); + + suite("Extension Activation per suite", () => { + let workspaceContext: WorkspaceContext | undefined; + let capturedWorkspaceContext: WorkspaceContext | undefined; + activateExtensionForSuite({ + async setup(ctx) { + workspaceContext = ctx; + }, + }); + + test("Assert workspace context is created", () => { + assert.ok(workspaceContext); + capturedWorkspaceContext = workspaceContext; + }); + + test("Assert workspace context is not recreated", () => { + assert.strictEqual(workspaceContext, capturedWorkspaceContext); + }); + }); + + suite("Extension activation per test", () => { + let workspaceContext: WorkspaceContext | undefined; + let capturedWorkspaceContext: WorkspaceContext | undefined; + activateExtensionForTest({ + async setup(ctx) { + workspaceContext = ctx; + }, + }); + + test("Assert workspace context is created", () => { + assert.ok(workspaceContext); + capturedWorkspaceContext = workspaceContext; + }); + + test("Assert workspace context is recreated per test", () => { + assert.notStrictEqual(workspaceContext, capturedWorkspaceContext); + }); + }); }); diff --git a/test/integration-tests/WorkspaceContext.test.ts b/test/integration-tests/WorkspaceContext.test.ts index 150ac4867..ae40aa0fd 100644 --- a/test/integration-tests/WorkspaceContext.test.ts +++ b/test/integration-tests/WorkspaceContext.test.ts @@ -19,18 +19,16 @@ import { FolderOperation, WorkspaceContext } from "../../src/WorkspaceContext"; import { createBuildAllTask } from "../../src/tasks/SwiftTaskProvider"; import { Version } from "../../src/utilities/version"; import { SwiftExecution } from "../../src/tasks/SwiftExecution"; -import { activateExtension, deactivateExtension } from "./utilities/testutilities"; +import { activateExtensionForSuite } from "./utilities/testutilities"; suite("WorkspaceContext Test Suite", () => { let workspaceContext: WorkspaceContext; const packageFolder: vscode.Uri = testAssetUri("defaultPackage"); - suiteSetup(async function () { - workspaceContext = await activateExtension(); - }); - - suiteTeardown(async () => { - await deactivateExtension(); + activateExtensionForSuite({ + async setup(ctx) { + workspaceContext = ctx; + }, }); suite("Folder Events", () => { diff --git a/test/integration-tests/commands/build.test.ts b/test/integration-tests/commands/build.test.ts index d63f59379..3c8decc75 100644 --- a/test/integration-tests/commands/build.test.ts +++ b/test/integration-tests/commands/build.test.ts @@ -25,8 +25,7 @@ import { makeDebugConfigurations } from "../../../src/debugger/launch"; import { Workbench } from "../../../src/utilities/commands"; import { continueSession, waitForDebugAdapterCommand } from "../../utilities/debug"; import { - activateExtension, - deactivateExtension, + activateExtensionForSuite, folderInRootWorkspace, updateSettings, } from "../utilities/testutilities"; @@ -34,33 +33,32 @@ import { suite("Build Commands", function () { let folderContext: FolderContext; let workspaceContext: WorkspaceContext; - let settingsTeardown: () => Promise; let buildPath: string; const uri = testAssetUri("defaultPackage/Sources/PackageExe/main.swift"); const breakpoints = [ new vscode.SourceBreakpoint(new vscode.Location(uri, new vscode.Position(2, 0))), ]; - suiteSetup(async function () { - workspaceContext = await activateExtension(); - await waitForNoRunningTasks(); - folderContext = await folderInRootWorkspace("defaultPackage", workspaceContext); - buildPath = path.join(folderContext.folder.fsPath, ".build"); - await workspaceContext.focusFolder(folderContext); - await vscode.window.showTextDocument(uri); - settingsTeardown = await updateSettings({ - "swift.autoGenerateLaunchConfigurations": true, - }); - await makeDebugConfigurations(folderContext, undefined, true); + activateExtensionForSuite({ + async setup(ctx) { + workspaceContext = ctx; + await waitForNoRunningTasks(); + folderContext = await folderInRootWorkspace("defaultPackage", workspaceContext); + buildPath = path.join(folderContext.folder.fsPath, ".build"); + await workspaceContext.focusFolder(folderContext); + await vscode.window.showTextDocument(uri); + const settingsTeardown = await updateSettings({ + "swift.autoGenerateLaunchConfigurations": true, + }); + await makeDebugConfigurations(folderContext, undefined, true); + return settingsTeardown; + }, + async teardown() { + await vscode.commands.executeCommand(Workbench.ACTION_CLOSEALLEDITORS); + }, }); - suiteTeardown(async () => { - await settingsTeardown(); - await vscode.commands.executeCommand(Workbench.ACTION_CLOSEALLEDITORS); - await deactivateExtension(); - }); - - teardown(() => { + teardown(async () => { // Remove the build directory after each test case if (fs.existsSync(buildPath)) { fs.rmSync(buildPath, { recursive: true, force: true }); diff --git a/test/integration-tests/commands/dependency.test.ts b/test/integration-tests/commands/dependency.test.ts index 231abb553..a0f6592e6 100644 --- a/test/integration-tests/commands/dependency.test.ts +++ b/test/integration-tests/commands/dependency.test.ts @@ -26,8 +26,8 @@ import { WorkspaceContext } from "../../../src/WorkspaceContext"; import * as sinon from "sinon"; import { Commands } from "../../../src/commands"; import { - activateExtension, - deactivateExtension, + activateExtensionForSuite, + activateExtensionForTest, folderInRootWorkspace, } from "../utilities/testutilities"; @@ -40,15 +40,13 @@ suite("Dependency Commmands Test Suite", function () { let folderContext: FolderContext; let workspaceContext: WorkspaceContext; - suiteSetup(async () => { - workspaceContext = await activateExtension(); - await waitForNoRunningTasks(); - folderContext = await folderInRootWorkspace("dependencies", workspaceContext); - await workspaceContext.focusFolder(folderContext); - }); - - suiteTeardown(async () => { - await deactivateExtension(); + activateExtensionForSuite({ + async setup(ctx) { + workspaceContext = ctx; + await waitForNoRunningTasks(); + folderContext = await folderInRootWorkspace("dependencies", workspaceContext); + await workspaceContext.focusFolder(folderContext); + }, }); test("Contract: spm resolve", async () => { @@ -64,29 +62,29 @@ suite("Dependency Commmands Test Suite", function () { let treeProvider: PackageDependenciesProvider; let item: PackageNode; - setup(async () => { - // Check before each test case start: - // Expect to fail without setting up local version - workspaceContext = await activateExtension(); - await waitForNoRunningTasks(); - folderContext = await folderInRootWorkspace("dependencies", workspaceContext); - await workspaceContext.focusFolder(folderContext); - - tasks = (await getBuildAllTask(folderContext)) as SwiftTask; - const { exitCode, output } = await executeTaskAndWaitForResult(tasks); - expect(exitCode).to.not.equal(0); - expect(output).to.include("PackageLib"); - expect(output).to.include("required"); - - treeProvider = new PackageDependenciesProvider(workspaceContext); - - const items = await treeProvider.getChildren(); - item = items.find(n => n.name === "swift-markdown") as PackageNode; - }); - - teardown(async () => { - treeProvider?.dispose(); - await deactivateExtension(); + activateExtensionForTest({ + async setup(ctx) { + // Check before each test case start: + // Expect to fail without setting up local version + workspaceContext = ctx; + await waitForNoRunningTasks(); + folderContext = await folderInRootWorkspace("dependencies", workspaceContext); + await workspaceContext.focusFolder(folderContext); + + tasks = (await getBuildAllTask(folderContext)) as SwiftTask; + const { exitCode, output } = await executeTaskAndWaitForResult(tasks); + expect(exitCode).to.not.equal(0); + expect(output).to.include("PackageLib"); + expect(output).to.include("required"); + + treeProvider = new PackageDependenciesProvider(workspaceContext); + + const items = await treeProvider.getChildren(); + item = items.find(n => n.name === "swift-markdown") as PackageNode; + }, + async teardown() { + treeProvider?.dispose(); + }, }); async function useLocalDependencyTest() { diff --git a/test/integration-tests/commands/runTestMultipleTimes.test.ts b/test/integration-tests/commands/runTestMultipleTimes.test.ts index 608f4f81c..96eabce10 100644 --- a/test/integration-tests/commands/runTestMultipleTimes.test.ts +++ b/test/integration-tests/commands/runTestMultipleTimes.test.ts @@ -18,11 +18,7 @@ import { runTestMultipleTimes } from "../../../src/commands/testMultipleTimes"; import { mockGlobalObject } from "../../MockUtils"; import { FolderContext } from "../../../src/FolderContext"; import { TestRunProxy } from "../../../src/TestExplorer/TestRunner"; -import { - activateExtension, - deactivateExtension, - folderInRootWorkspace, -} from "../utilities/testutilities"; +import { activateExtensionForSuite, folderInRootWorkspace } from "../utilities/testutilities"; suite("Test Multiple Times Command Test Suite", () => { const windowMock = mockGlobalObject(vscode, "window"); @@ -30,22 +26,19 @@ suite("Test Multiple Times Command Test Suite", () => { let folderContext: FolderContext; let testItem: vscode.TestItem; - suiteSetup(async () => { - const workspaceContext = await activateExtension(); - folderContext = await folderInRootWorkspace("diagnostics", workspaceContext); - folderContext.addTestExplorer(); + activateExtensionForSuite({ + async setup(ctx) { + folderContext = await folderInRootWorkspace("diagnostics", ctx); + folderContext.addTestExplorer(); - const item = folderContext.testExplorer?.controller.createTestItem( - "testId", - "Test Item For Testing" - ); - - expect(item).to.not.be.undefined; - testItem = item!; - }); + const item = folderContext.testExplorer?.controller.createTestItem( + "testId", + "Test Item For Testing" + ); - suiteTeardown(async () => { - await deactivateExtension(); + expect(item).to.not.be.undefined; + testItem = item!; + }, }); test("Runs successfully after testing 0 times", async () => { diff --git a/test/integration-tests/debugger/lldb.test.ts b/test/integration-tests/debugger/lldb.test.ts index c08ae3164..6731e67a5 100644 --- a/test/integration-tests/debugger/lldb.test.ts +++ b/test/integration-tests/debugger/lldb.test.ts @@ -15,17 +15,15 @@ import { expect } from "chai"; import { getLLDBLibPath, getLldbProcess } from "../../../src/debugger/lldb"; import { WorkspaceContext } from "../../../src/WorkspaceContext"; -import { activateExtension, deactivateExtension } from "../utilities/testutilities"; +import { activateExtensionForSuite } from "../utilities/testutilities"; suite("lldb contract test suite", () => { let workspaceContext: WorkspaceContext; - suiteSetup(async () => { - workspaceContext = await activateExtension(); - }); - - suiteTeardown(async () => { - await deactivateExtension(); + activateExtensionForSuite({ + async setup(ctx) { + workspaceContext = ctx; + }, }); test("getLldbProcess Contract Test, make sure the command returns", async () => { diff --git a/test/integration-tests/extension.test.ts b/test/integration-tests/extension.test.ts index 5c3f61967..b9dad4080 100644 --- a/test/integration-tests/extension.test.ts +++ b/test/integration-tests/extension.test.ts @@ -13,21 +13,18 @@ //===----------------------------------------------------------------------===// import * as assert from "assert"; -import { beforeEach, afterEach } from "mocha"; import { WorkspaceContext } from "../../src/WorkspaceContext"; import { getBuildAllTask } from "../../src/tasks/SwiftTaskProvider"; import { SwiftExecution } from "../../src/tasks/SwiftExecution"; -import { activateExtension, deactivateExtension } from "./utilities/testutilities"; +import { activateExtensionForTest } from "./utilities/testutilities"; suite("Extension Test Suite", () => { let workspaceContext: WorkspaceContext; - beforeEach(async function () { - workspaceContext = await activateExtension(this.currentTest); - }); - - afterEach(async () => { - await deactivateExtension(); + activateExtensionForTest({ + async setup(ctx) { + workspaceContext = ctx; + }, }); suite("Temporary Folder Test Suite", () => { diff --git a/test/integration-tests/tasks/SwiftExecution.test.ts b/test/integration-tests/tasks/SwiftExecution.test.ts index df708e87e..7558a8912 100644 --- a/test/integration-tests/tasks/SwiftExecution.test.ts +++ b/test/integration-tests/tasks/SwiftExecution.test.ts @@ -18,22 +18,20 @@ import { testSwiftTask } from "../../fixtures"; import { WorkspaceContext } from "../../../src/WorkspaceContext"; import { executeTaskAndWaitForResult, waitForNoRunningTasks } from "../../utilities"; import { SwiftToolchain } from "../../../src/toolchain/toolchain"; -import { activateExtension, deactivateExtension } from "../utilities/testutilities"; +import { activateExtensionForSuite } from "../utilities/testutilities"; suite("SwiftExecution Tests Suite", () => { let workspaceContext: WorkspaceContext; let toolchain: SwiftToolchain; let workspaceFolder: vscode.WorkspaceFolder; - suiteSetup(async () => { - workspaceContext = await activateExtension(); - toolchain = await SwiftToolchain.create(); - assert.notEqual(workspaceContext.folders.length, 0); - workspaceFolder = workspaceContext.folders[0].workspaceFolder; - }); - - suiteTeardown(async () => { - await deactivateExtension(); + activateExtensionForSuite({ + async setup(ctx) { + workspaceContext = ctx; + toolchain = await SwiftToolchain.create(); + assert.notEqual(workspaceContext.folders.length, 0); + workspaceFolder = workspaceContext.folders[0].workspaceFolder; + }, }); setup(async () => { diff --git a/test/integration-tests/tasks/SwiftPluginTaskProvider.test.ts b/test/integration-tests/tasks/SwiftPluginTaskProvider.test.ts index 323c5cb20..cbb99142e 100644 --- a/test/integration-tests/tasks/SwiftPluginTaskProvider.test.ts +++ b/test/integration-tests/tasks/SwiftPluginTaskProvider.test.ts @@ -17,27 +17,22 @@ import * as assert from "assert"; import { WorkspaceContext } from "../../../src/WorkspaceContext"; import { SwiftPluginTaskProvider } from "../../../src/tasks/SwiftPluginTaskProvider"; import { FolderContext } from "../../../src/FolderContext"; +import { activateExtensionForSuite, folderInRootWorkspace } from "../utilities/testutilities"; import { executeTaskAndWaitForResult, mutable, waitForEndTaskProcess } from "../../utilities"; -import { - activateExtension, - deactivateExtension, - folderInRootWorkspace, -} from "../utilities/testutilities"; +import { expect } from "chai"; suite("SwiftPluginTaskProvider Test Suite", () => { let workspaceContext: WorkspaceContext; let folderContext: FolderContext; - suiteSetup(async () => { - workspaceContext = await activateExtension(); - folderContext = await folderInRootWorkspace("command-plugin", workspaceContext); - assert.notEqual(workspaceContext.folders.length, 0); - await folderContext.loadSwiftPlugins(); - assert.notEqual(folderContext.swiftPackage.plugins.length, 0); - }); - - suiteTeardown(async () => { - await deactivateExtension(); + activateExtensionForSuite({ + async setup(ctx) { + workspaceContext = ctx; + folderContext = await folderInRootWorkspace("command-plugin", workspaceContext); + expect(workspaceContext.folders).to.not.have.lengthOf(0); + await folderContext.loadSwiftPlugins(); + expect(workspaceContext.folders).to.not.have.lengthOf(0); + }, }); suite("createSwiftPluginTask", () => { diff --git a/test/integration-tests/tasks/SwiftTaskProvider.test.ts b/test/integration-tests/tasks/SwiftTaskProvider.test.ts index bf8052bf1..31531e4bc 100644 --- a/test/integration-tests/tasks/SwiftTaskProvider.test.ts +++ b/test/integration-tests/tasks/SwiftTaskProvider.test.ts @@ -30,11 +30,8 @@ import { import { Version } from "../../../src/utilities/version"; import { FolderContext } from "../../../src/FolderContext"; import { mockGlobalObject } from "../../MockUtils"; -import { - activateExtension, - deactivateExtension, - folderInRootWorkspace, -} from "../utilities/testutilities"; +import { activateExtensionForSuite, folderInRootWorkspace } from "../utilities/testutilities"; +import { expect } from "chai"; suite("SwiftTaskProvider Test Suite", () => { let workspaceContext: WorkspaceContext; @@ -42,18 +39,16 @@ suite("SwiftTaskProvider Test Suite", () => { let workspaceFolder: vscode.WorkspaceFolder; let folderContext: FolderContext; - suiteSetup(async () => { - workspaceContext = await activateExtension(); - toolchain = workspaceContext.toolchain; - assert.notEqual(workspaceContext.folders.length, 0); - workspaceFolder = workspaceContext.folders[0].workspaceFolder; - - // Make sure have another folder - folderContext = await folderInRootWorkspace("diagnostics", workspaceContext); - }); + activateExtensionForSuite({ + async setup(ctx) { + workspaceContext = ctx; + toolchain = workspaceContext.toolchain; + expect(workspaceContext.folders).to.not.have.lengthOf(0); + workspaceFolder = workspaceContext.folders[0].workspaceFolder; - suiteTeardown(async () => { - await deactivateExtension(); + // Make sure have another folder + folderContext = await folderInRootWorkspace("diagnostics", workspaceContext); + }, }); suite("createSwiftTask", () => { diff --git a/test/integration-tests/tasks/TaskManager.test.ts b/test/integration-tests/tasks/TaskManager.test.ts index 833728dc9..9f6cae9b1 100644 --- a/test/integration-tests/tasks/TaskManager.test.ts +++ b/test/integration-tests/tasks/TaskManager.test.ts @@ -17,20 +17,18 @@ import * as assert from "assert"; import { TaskManager } from "../../../src/tasks/TaskManager"; import { WorkspaceContext } from "../../../src/WorkspaceContext"; import { waitForNoRunningTasks } from "../../utilities"; -import { activateExtension, deactivateExtension } from "../utilities/testutilities"; +import { activateExtensionForSuite } from "../utilities/testutilities"; suite("TaskManager Test Suite", () => { let workspaceContext: WorkspaceContext; let taskManager: TaskManager; - suiteSetup(async () => { - workspaceContext = await activateExtension(); - taskManager = workspaceContext.tasks; - assert.notEqual(workspaceContext.folders.length, 0); - }); - - suiteTeardown(async () => { - await deactivateExtension(); + activateExtensionForSuite({ + async setup(ctx) { + workspaceContext = ctx; + taskManager = workspaceContext.tasks; + assert.notEqual(workspaceContext.folders.length, 0); + }, }); setup(async () => { diff --git a/test/integration-tests/tasks/TaskQueue.test.ts b/test/integration-tests/tasks/TaskQueue.test.ts index 300013d74..b80c28af6 100644 --- a/test/integration-tests/tasks/TaskQueue.test.ts +++ b/test/integration-tests/tasks/TaskQueue.test.ts @@ -18,20 +18,18 @@ import { testAssetPath } from "../../fixtures"; import { WorkspaceContext } from "../../../src/WorkspaceContext"; import { SwiftExecOperation, TaskOperation, TaskQueue } from "../../../src/tasks/TaskQueue"; import { waitForNoRunningTasks } from "../../utilities"; -import { activateExtension, deactivateExtension } from "../utilities/testutilities"; +import { activateExtensionForSuite } from "../utilities/testutilities"; suite("TaskQueue Test Suite", () => { let workspaceContext: WorkspaceContext; let taskQueue: TaskQueue; - suiteSetup(async () => { - workspaceContext = await activateExtension(); - assert.notEqual(workspaceContext.folders.length, 0); - taskQueue = workspaceContext.folders[0].taskQueue; - }); - - suiteTeardown(async () => { - await deactivateExtension(); + activateExtensionForSuite({ + async setup(ctx) { + workspaceContext = ctx; + assert.notEqual(workspaceContext.folders.length, 0); + taskQueue = workspaceContext.folders[0].taskQueue; + }, }); setup(async () => { diff --git a/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts b/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts index 67f2d0c5f..6f3c9be4c 100644 --- a/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts +++ b/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts @@ -14,7 +14,7 @@ import * as assert from "assert"; import * as vscode from "vscode"; -import { beforeEach, afterEach } from "mocha"; +import { beforeEach } from "mocha"; import { testAssetUri } from "../../fixtures"; import { TestExplorer } from "../../../src/TestExplorer/TestExplorer"; import { @@ -24,7 +24,7 @@ import { eventPromise, gatherTests, runTest, - setupTestExplorerTest, + testExplorerFor, waitForTestExplorerReady, } from "./utilities"; import { WorkspaceContext } from "../../../src/WorkspaceContext"; @@ -40,8 +40,13 @@ import { reduceTestItemChildren, } from "../../../src/TestExplorer/TestUtils"; import { runnableTag } from "../../../src/TestExplorer/TestDiscovery"; -import { activateExtension, deactivateExtension } from "../utilities/testutilities"; +import { + activateExtensionForSuite, + activateExtensionForTest, + updateSettings, +} from "../utilities/testutilities"; import { Commands } from "../../../src/commands"; +import { SwiftToolchain } from "../../../src/toolchain/toolchain"; suite("Test Explorer Suite", function () { const MAX_TEST_RUN_TIME_MINUTES = 5; @@ -52,8 +57,6 @@ suite("Test Explorer Suite", function () { let testExplorer: TestExplorer; suite("Debugging", function () { - let settingsTeardown: () => Promise; - async function runXCTest() { const suiteId = "PackageTests.PassingXCTestSuite"; const testId = `${suiteId}/testPassing`; @@ -74,19 +77,27 @@ suite("Test Explorer Suite", function () { } suite("lldb-dap", () => { - beforeEach(async function () { - const testContext = await setupTestExplorerTest({ - "swift.debugger.useDebugAdapterFromToolchain": true, - }); + activateExtensionForTest({ + async setup(ctx) { + // lldb-dap is only present in the toolchain in 6.0 and up. + if (ctx.swiftVersion.isLessThan(new Version(6, 0, 0))) { + this.skip(); + } + + workspaceContext = ctx; + testExplorer = testExplorerFor( + workspaceContext, + testAssetUri("defaultPackage") + ); - workspaceContext = testContext.workspaceContext; - testExplorer = testContext.testExplorer; - settingsTeardown = testContext.settingsTeardown; + // Set up the listener before bringing the text explorer in to focus, + // which starts searching the workspace for tests. + await waitForTestExplorerReady(testExplorer); - // lldb-dap is only present in the toolchain in 6.0 and up. - if (workspaceContext.swiftVersion.isLessThan(new Version(6, 0, 0))) { - this.skip(); - } + return await updateSettings({ + "swift.debugger.useDebugAdapterFromToolchain": true, + }); + }, }); test("Debugs specified XCTest test", runXCTest); @@ -94,15 +105,45 @@ suite("Test Explorer Suite", function () { }); suite("CodeLLDB", () => { - beforeEach(async function () { - const testContext = await setupTestExplorerTest({ - "swift.debugger.useDebugAdapterFromToolchain": false, - ...(process.env["CI"] === "1" ? { "lldb.library": "/usr/lib/liblldb.so" } : {}), - }); + async function getLLDBDebugAdapterPath() { + switch (process.platform) { + case "linux": + return "/usr/lib/liblldb.so"; + case "win32": + return await (await SwiftToolchain.create()).getLLDBDebugAdapter(); + default: + throw new Error("Please provide the path to lldb for this platform"); + } + } - workspaceContext = testContext.workspaceContext; - testExplorer = testContext.testExplorer; - settingsTeardown = testContext.settingsTeardown; + activateExtensionForTest({ + async setup(ctx) { + // CodeLLDB on windows doesn't print output and so cannot be parsed + if (process.platform === "win32") { + this.skip(); + return; + } + + workspaceContext = ctx; + testExplorer = testExplorerFor( + workspaceContext, + testAssetUri("defaultPackage") + ); + + // Set up the listener before bringing the text explorer in to focus, + // which starts searching the workspace for tests. + await waitForTestExplorerReady(testExplorer); + + const lldbPath = + process.env["CI"] === "1" + ? { "lldb.library": await getLLDBDebugAdapterPath() } + : {}; + + return await updateSettings({ + "swift.debugger.useDebugAdapterFromToolchain": false, + ...lldbPath, + }); + }, }); test("Debugs specified XCTest test", async function () { @@ -112,6 +153,7 @@ suite("Test Explorer Suite", function () { } await runXCTest(); }); + test("Debugs specified swift-testing test", async function () { if (workspaceContext.swiftVersion.isLessThan(new Version(6, 0, 0))) { this.skip(); @@ -119,19 +161,13 @@ suite("Test Explorer Suite", function () { await runSwiftTesting(); }); }); - - afterEach(async () => { - await settingsTeardown(); - }); }); suite("Standard", () => { - suiteSetup(async () => { - workspaceContext = await activateExtension(); - }); - - suiteTeardown(async () => { - await deactivateExtension(); + activateExtensionForSuite({ + async setup(ctx) { + workspaceContext = ctx; + }, }); beforeEach(async () => { diff --git a/test/integration-tests/testexplorer/utilities.ts b/test/integration-tests/testexplorer/utilities.ts index 577adaa57..6a86b9a26 100644 --- a/test/integration-tests/testexplorer/utilities.ts +++ b/test/integration-tests/testexplorer/utilities.ts @@ -63,7 +63,7 @@ export async function setupTestExplorerTest(settings: SettingsMap = {}) { * @param packageFolder The package folder within the workspace * @returns The TestExplorer for the package */ -function testExplorerFor( +export function testExplorerFor( workspaceContext: WorkspaceContext, packageFolder: vscode.Uri ): TestExplorer { diff --git a/test/integration-tests/ui/PackageDependencyProvider.test.ts b/test/integration-tests/ui/PackageDependencyProvider.test.ts index a00e4a122..686eb5614 100644 --- a/test/integration-tests/ui/PackageDependencyProvider.test.ts +++ b/test/integration-tests/ui/PackageDependencyProvider.test.ts @@ -20,28 +20,24 @@ import { import { executeTaskAndWaitForResult, waitForNoRunningTasks } from "../../utilities"; import { getBuildAllTask, SwiftTask } from "../../../src/tasks/SwiftTaskProvider"; import { testAssetPath } from "../../fixtures"; -import { - activateExtension, - deactivateExtension, - folderInRootWorkspace, -} from "../utilities/testutilities"; +import { activateExtensionForSuite, folderInRootWorkspace } from "../utilities/testutilities"; suite("PackageDependencyProvider Test Suite", function () { let treeProvider: PackageDependenciesProvider; this.timeout(2 * 60 * 1000); // Allow up to 2 minutes to build - suiteSetup(async function () { - const workspaceContext = await activateExtension(); - await waitForNoRunningTasks(); - const folderContext = await folderInRootWorkspace("dependencies", workspaceContext); - await executeTaskAndWaitForResult((await getBuildAllTask(folderContext)) as SwiftTask); - await workspaceContext.focusFolder(folderContext); - treeProvider = new PackageDependenciesProvider(workspaceContext); - }); - - suiteTeardown(async () => { - treeProvider.dispose(); - await deactivateExtension(); + activateExtensionForSuite({ + async setup(ctx) { + const workspaceContext = ctx; + await waitForNoRunningTasks(); + const folderContext = await folderInRootWorkspace("dependencies", workspaceContext); + await executeTaskAndWaitForResult((await getBuildAllTask(folderContext)) as SwiftTask); + await workspaceContext.focusFolder(folderContext); + treeProvider = new PackageDependenciesProvider(workspaceContext); + }, + async teardown() { + treeProvider.dispose(); + }, }); test("Includes remote dependency", async () => { diff --git a/test/integration-tests/utilities/testutilities.ts b/test/integration-tests/utilities/testutilities.ts index 5313ae6b1..a5f6b101b 100644 --- a/test/integration-tests/utilities/testutilities.ts +++ b/test/integration-tests/utilities/testutilities.ts @@ -53,13 +53,70 @@ const extensionBootstrapper = (() => { } }); + function testRunnerSetup( + before: Mocha.HookFunction, + setup: + | (( + this: Mocha.Context, + ctx: WorkspaceContext + ) => Promise<(() => Promise) | void>) + | undefined, + after: Mocha.HookFunction, + teardown: ((this: Mocha.Context) => Promise) | undefined, + testAssets?: string[] + ) { + let workspaceContext: WorkspaceContext | undefined; + let autoTeardown: void | (() => Promise); + before(async function () { + // Always activate the extension. If no test assets are provided, + // default to adding `defaultPackage` to the workspace. + workspaceContext = await extensionBootstrapper.activateExtension( + this.currentTest, + testAssets ?? ["defaultPackage"] + ); + if (!setup) { + return; + } + try { + // If the setup returns a promise it is used to undo whatever setup it did. + // Typically this is the promise returned from `updateSettings`, which will + // undo any settings changed during setup. + autoTeardown = await setup.call(this, workspaceContext); + } catch (error) { + console.error(`Error during test/suite setup, captured logs are:`); + workspaceContext.outputChannel.logs.map(log => console.log(log)); + throw error; + } + }); + + after(async function () { + try { + // First run the users supplied teardown, then await the autoTeardown if it exists. + if (teardown) { + await teardown.call(this); + } + if (autoTeardown) { + await autoTeardown(); + } + } catch (error) { + if (workspaceContext) { + console.error(`Error during test/suite teardown, captured logs are:`); + workspaceContext.outputChannel.logs.map(log => console.log(log)); + } + throw error; + } + + await extensionBootstrapper.deactivateExtension(); + }); + } + return { // Activates the extension and adds the defaultPackage to the workspace. // We can only truly call `vscode.Extension.activate()` once for an entire // test run, so after it is called once we switch over to calling activate on // the returned API object which behaves like the extension is being launched for // the first time _as long as everything is disposed of properly in `deactivate()`_. - activateExtension: async function (currentTest?: Mocha.Test) { + activateExtension: async function (currentTest?: Mocha.Test, testAssets?: string[]) { if (activatedAPI) { throw new Error( `Extension is already activated. Last test that activated the extension: ${lastTestName}` @@ -89,11 +146,12 @@ const extensionBootstrapper = (() => { throw new Error("Extension did not activate. Workspace context is not available."); } - // Always adds defaultPackage to the workspace. This may need to be refactored if we want - // to scope tests more narowly to sub packages within the test assets. + // Add assets required for the suite/test to the workspace. const workspaceFolder = getRootWorkspaceFolder(); - const packageFolder = testAssetUri("defaultPackage"); - await workspaceContext.addPackageFolder(packageFolder, workspaceFolder); + for (const asset of testAssets ?? []) { + const packageFolder = testAssetUri(asset); + await workspaceContext.addPackageFolder(packageFolder, workspaceFolder); + } // Save the test name so if the test doesn't clean up by deactivating properly the next // test that tries to activate can throw an error with the name of the test that needs to clean up. @@ -115,10 +173,44 @@ const extensionBootstrapper = (() => { await vscode.commands.executeCommand("workbench.action.closeAllEditors"); await activatedAPI.workspaceContext?.removeWorkspaceFolder(getRootWorkspaceFolder()); - activatedAPI.deactivate(); + await activatedAPI.deactivate(); activatedAPI = undefined; lastTestName = undefined; }, + + activateExtensionForSuite: async function (config?: { + setup?: ( + this: Mocha.Context, + ctx: WorkspaceContext + ) => Promise<(() => Promise) | void>; + teardown?: (this: Mocha.Context) => Promise; + testAssets?: string[]; + }) { + testRunnerSetup( + mocha.before, + config?.setup, + mocha.after, + config?.teardown, + config?.testAssets + ); + }, + + activateExtensionForTest: async function (config?: { + setup?: ( + this: Mocha.Context, + ctx: WorkspaceContext + ) => Promise<(() => Promise) | void>; + teardown?: (this: Mocha.Context) => Promise; + testAssets?: string[]; + }) { + testRunnerSetup( + mocha.beforeEach, + config?.setup, + mocha.afterEach, + config?.teardown, + config?.testAssets + ); + }, }; })(); @@ -132,6 +224,16 @@ export const activateExtension = extensionBootstrapper.activateExtension; */ export const deactivateExtension = extensionBootstrapper.deactivateExtension; +/** + * Activates the extension for the duration of the suite, deactivating it when the suite completes. + */ +export const activateExtensionForSuite = extensionBootstrapper.activateExtensionForSuite; + +/* + * Activates the extension for the duration of the test, deactivating it when the test completes. + */ +export const activateExtensionForTest = extensionBootstrapper.activateExtensionForTest; + /** * Given a name of a folder in the root test workspace, adds that folder to the * workspace context and then returns the folder context. From c466b0091adf02202f33611785c0dcb60794e2ca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Nov 2024 12:02:11 -0500 Subject: [PATCH 06/22] Bump cross-spawn from 7.0.3 to 7.0.6 (#1212) Bumps [cross-spawn](https://github.com/moxystudio/node-cross-spawn) from 7.0.3 to 7.0.6. - [Changelog](https://github.com/moxystudio/node-cross-spawn/blob/master/CHANGELOG.md) - [Commits](https://github.com/moxystudio/node-cross-spawn/compare/v7.0.3...v7.0.6) --- updated-dependencies: - dependency-name: cross-spawn dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5cfa3978d..8a9628c4b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2415,9 +2415,9 @@ "dev": true }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "dependencies": { "path-key": "^3.1.0", @@ -7593,9 +7593,9 @@ "dev": true }, "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "requires": { "path-key": "^3.1.0", From 47eff44da0ffc6b6328ab1d4f7657d4a098bda25 Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Mon, 2 Dec 2024 08:26:25 -0500 Subject: [PATCH 07/22] Fixup newly uncovered lint error (#1223) The eslint-plugin version bump in #1222 uncovered some dead code that has no effect. Remove it so that we can rebase the @dependabot patch and update eslint-plugin. --- src/TestExplorer/TestRunner.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/TestExplorer/TestRunner.ts b/src/TestExplorer/TestRunner.ts index 51cde990d..fe736bf8f 100644 --- a/src/TestExplorer/TestRunner.ts +++ b/src/TestExplorer/TestRunner.ts @@ -670,12 +670,7 @@ export class TestRunner { outputStream.write(replaced); }); - let cancellation: vscode.Disposable; task.execution.onDidClose(code => { - if (cancellation) { - cancellation.dispose(); - } - // undefined or 0 are viewed as success if (!code) { resolve(); From f99bc1712bbd7150ac32409c851ade2f17be0100 Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Mon, 2 Dec 2024 08:27:51 -0500 Subject: [PATCH 08/22] Add "auto" mode to sourcekit-lsp backgroundIndexing setting (#1232) * Add "auto" mode to sourcekit-lsp backgroundIndexing setting Now that backgroundIndexing is no longer experimental it can be enabled by default in Swift 6.1. This patch evolves the `sourcekit-lsp.backgroundIndexing` setting to be an enum with three possible values, `true`, `false` or `auto`. The `true` and `false` options function as they did previously. The new `auto` setting will enable background indexing on Swift >= 6.1. To enable background indexing on Swift 6.0 the user must still set this setting to `true`, as this feature is experimental in 6.0. Co-authored-by: Mahdi Bahrami --- package.json | 11 ++-- src/configuration.ts | 13 +++-- src/sourcekit-lsp/LanguageClientManager.ts | 19 +++++-- .../LanguageClientManager.test.ts | 54 ++++++++++++++++++- 4 files changed, 85 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index 7ded34bee..13e173ab6 100644 --- a/package.json +++ b/package.json @@ -475,9 +475,14 @@ "order": 3 }, "swift.sourcekit-lsp.backgroundIndexing": { - "type": "boolean", - "default": false, - "markdownDescription": "**Experimental**: Enable or disable background indexing. This option has no effect in Swift versions prior to 6.0.", + "type": "string", + "enum": [ + "on", + "off", + "auto" + ], + "default": "auto", + "markdownDescription": "Turns background indexing `on` or `off`. `auto` will enable background indexing if the Swift version is >= 6.1. This option has no effect in Swift versions prior to 6.0.", "order": 4 }, "swift.sourcekit-lsp.trace.server": { diff --git a/src/configuration.ts b/src/configuration.ts index f44051492..5741e4a74 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -242,10 +242,17 @@ const configuration = { .get("backgroundCompilation", false); }, /** background indexing */ - get backgroundIndexing(): boolean { - return vscode.workspace + get backgroundIndexing(): "on" | "off" | "auto" { + const value = vscode.workspace .getConfiguration("swift.sourcekit-lsp") - .get("backgroundIndexing", false); + .get("backgroundIndexing", "auto"); + + // Legacy versions of this setting were a boolean, convert to the new string version. + if (typeof value === "boolean") { + return value ? "on" : "off"; + } else { + return value; + } }, /** focus on problems view whenever there is a build error */ get actionAfterBuildError(): ActionAfterBuildError { diff --git a/src/sourcekit-lsp/LanguageClientManager.ts b/src/sourcekit-lsp/LanguageClientManager.ts index 239062740..ba043942b 100644 --- a/src/sourcekit-lsp/LanguageClientManager.ts +++ b/src/sourcekit-lsp/LanguageClientManager.ts @@ -129,6 +129,8 @@ export class LanguageClientManager { // that are not at the root of their workspace public subFolderWorkspaces: vscode.Uri[]; private namedOutputChannels: Map = new Map(); + private swiftVersion: Version; + /** Get the current state of the underlying LanguageClient */ public get state(): langclient.State { if (!this.languageClient) { @@ -142,9 +144,8 @@ export class LanguageClientManager { LanguageClientManager.indexingLogName, new LSPOutputChannel(LanguageClientManager.indexingLogName, false, true) ); - this.singleServerSupport = workspaceContext.swiftVersion.isGreaterThanOrEqual( - new Version(5, 7, 0) - ); + this.swiftVersion = workspaceContext.swiftVersion; + this.singleServerSupport = this.swiftVersion.isGreaterThanOrEqual(new Version(5, 7, 0)); this.subscriptions = []; this.subFolderWorkspaces = []; if (this.singleServerSupport) { @@ -613,10 +614,18 @@ export class LanguageClientManager { }, }; - if (configuration.backgroundIndexing) { + // Swift 6.0.0 and later supports background indexing. + // In 6.0.0 it is experimental so only "true" enables it. + // In 6.1.0 it is no longer experimental, and so "auto" or "true" enables it. + if ( + this.swiftVersion.isGreaterThanOrEqual(new Version(6, 0, 0)) && + (configuration.backgroundIndexing === "on" || + (configuration.backgroundIndexing === "auto" && + this.swiftVersion.isGreaterThanOrEqual(new Version(6, 1, 0)))) + ) { options = { ...options, - backgroundIndexing: configuration.backgroundIndexing, + backgroundIndexing: true, backgroundPreparationMode: "enabled", }; } diff --git a/test/unit-tests/sourcekit-lsp/LanguageClientManager.test.ts b/test/unit-tests/sourcekit-lsp/LanguageClientManager.test.ts index a1e563862..bd3091910 100644 --- a/test/unit-tests/sourcekit-lsp/LanguageClientManager.test.ts +++ b/test/unit-tests/sourcekit-lsp/LanguageClientManager.test.ts @@ -65,6 +65,14 @@ suite("LanguageClientManager Suite", () => { let createFilesEmitter: AsyncEventEmitter; let deleteFilesEmitter: AsyncEventEmitter; + const doesNotHave = (prop: any) => + match(function (actual) { + if (typeof actual === "object") { + return !(prop in actual); + } + return actual[prop] === undefined; + }, "doesNotHave"); + setup(async () => { // Mock pieces of the VSCode API mockedVSCodeWindow.activeTextEditor = undefined; @@ -153,7 +161,7 @@ suite("LanguageClientManager Suite", () => { // LSP configuration defaults mockedConfig.path = ""; mockedConfig.buildArguments = []; - mockedConfig.backgroundIndexing = false; + mockedConfig.backgroundIndexing = "off"; mockedConfig.swiftEnvironmentVariables = {}; mockedLspConfig.supportCFamily = "cpptools-inactive"; mockedLspConfig.disable = false; @@ -177,6 +185,50 @@ suite("LanguageClientManager Suite", () => { expect(languageClientMock.start).to.have.been.calledOnce; }); + test("chooses the correct backgroundIndexing value is auto, swift version if 6.0.0", async () => { + mockedWorkspace.swiftVersion = new Version(6, 0, 0); + mockedConfig.backgroundIndexing = "auto"; + new LanguageClientManager(instance(mockedWorkspace)); + await waitForReturnedPromises(languageClientMock.start); + + expect(mockedLangClientModule.LanguageClient).to.have.been.calledOnceWith( + match.string, + match.string, + match.object, + match.hasNested("initializationOptions", doesNotHave("backgroundIndexing")) + ); + }); + + test("chooses the correct backgroundIndexing value is auto, swift version if 6.1.0", async () => { + mockedWorkspace.swiftVersion = new Version(6, 1, 0); + mockedConfig.backgroundIndexing = "auto"; + + new LanguageClientManager(instance(mockedWorkspace)); + await waitForReturnedPromises(languageClientMock.start); + + expect(mockedLangClientModule.LanguageClient).to.have.been.calledOnceWith( + match.string, + match.string, + match.object, + match.hasNested("initializationOptions.backgroundIndexing", match.truthy) + ); + }); + + test("chooses the correct backgroundIndexing value is true, swift version if 6.0.0", async () => { + mockedWorkspace.swiftVersion = new Version(6, 0, 0); + mockedConfig.backgroundIndexing = "on"; + + new LanguageClientManager(instance(mockedWorkspace)); + await waitForReturnedPromises(languageClientMock.start); + + expect(mockedLangClientModule.LanguageClient).to.have.been.calledOnceWith( + match.string, + match.string, + match.object, + match.hasNested("initializationOptions.backgroundIndexing", match.truthy) + ); + }); + test("notifies SourceKit-LSP of WorkspaceFolder changes", async () => { const folder1 = mockObject({ isRootFolder: false, From 74b82335a3c875451e13b3e3c00777f62c8967dc Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Mon, 2 Dec 2024 08:28:03 -0500 Subject: [PATCH 09/22] Fix location for diagnostics generated from macros via swiftc (#1234) * Fix location for diagnostics generated from macros via swiftc The format of messages generated from macro errors/warnings is different than those generated normally. The parsing regex would capture the location for the related info diagnostic with a backtick. The location for the parent regex would be "#macro expansion" which when clicked would open an error window in a VS Code tab stating that the file "#macro expansion" could not be found. This patch tweaks two things: 1. Makes the regex for capturing the filepaths of warnings/errors account for leading trivia 2. If the path for a diagnostic is invalid, wait until we parse the related infromation and use the URI from there. * No Testing on Swift < 6.0 --- assets/test/.vscode/settings.json | 1 - assets/test/diagnostics/Sources/main.swift | 5 +++ src/DiagnosticsManager.ts | 36 +++++++++++++++++-- .../DiagnosticsManager.test.ts | 30 ++++++++++++++-- 4 files changed, 67 insertions(+), 5 deletions(-) diff --git a/assets/test/.vscode/settings.json b/assets/test/.vscode/settings.json index 211bc89c9..500511990 100644 --- a/assets/test/.vscode/settings.json +++ b/assets/test/.vscode/settings.json @@ -7,5 +7,4 @@ "-DTEST_ARGUMENT_SET_VIA_TEST_BUILD_ARGUMENTS_SETTING" ], "lldb.verboseLogging": true - } \ No newline at end of file diff --git a/assets/test/diagnostics/Sources/main.swift b/assets/test/diagnostics/Sources/main.swift index 6f06f06d0..4ed71dfbe 100644 --- a/assets/test/diagnostics/Sources/main.swift +++ b/assets/test/diagnostics/Sources/main.swift @@ -12,3 +12,8 @@ repeat { line = readLine() print(line ?? "nil") } while line != nil; + +#if canImport(Testing) +import Testing +#expect(try myFunc() != 0) +#endif \ No newline at end of file diff --git a/src/DiagnosticsManager.ts b/src/DiagnosticsManager.ts index c10404452..4498ac7b8 100644 --- a/src/DiagnosticsManager.ts +++ b/src/DiagnosticsManager.ts @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import * as vscode from "vscode"; +import * as fs from "fs"; // eslint-disable-next-line @typescript-eslint/no-require-imports import stripAnsi = require("strip-ansi"); import configuration from "./configuration"; @@ -255,6 +256,7 @@ export class DiagnosticsManager implements vscode.Disposable { }; let remainingData: string | undefined; let lastDiagnostic: vscode.Diagnostic | undefined; + let lastDiagnosticNeedsSaving = false; disposables.push( swiftExecution.onDidWrite(data => { const sanitizedData = (remainingData || "") + stripAnsi(data); @@ -293,6 +295,18 @@ export class DiagnosticsManager implements vscode.Disposable { lastDiagnostic.relatedInformation = ( lastDiagnostic.relatedInformation || [] ).concat(relatedInformation); + + if (lastDiagnosticNeedsSaving) { + const expandedUri = relatedInformation.location.uri.fsPath; + const currentUriDiagnostics = diagnostics.get(expandedUri) || []; + lastDiagnostic.range = relatedInformation.location.range; + diagnostics.set(expandedUri, [ + ...currentUriDiagnostics, + lastDiagnostic, + ]); + + lastDiagnosticNeedsSaving = false; + } continue; } const { uri, diagnostic } = result as ParsedDiagnostic; @@ -312,7 +326,15 @@ export class DiagnosticsManager implements vscode.Disposable { continue; } lastDiagnostic = diagnostic; - diagnostics.set(uri, [...currentUriDiagnostics, diagnostic]); + + // If the diagnostic comes from a macro expansion the URI is going to be an invalid URI. + // Save the diagnostic for when we get the related information which has the macro expansion location + // that should be used as the correct URI. + if (this.isValidUri(uri)) { + diagnostics.set(uri, [...currentUriDiagnostics, diagnostic]); + } else { + lastDiagnosticNeedsSaving = true; + } } }), swiftExecution.onDidClose(done) @@ -320,10 +342,20 @@ export class DiagnosticsManager implements vscode.Disposable { }); } + private isValidUri(uri: string): boolean { + try { + fs.accessSync(uri, fs.constants.F_OK); + return true; + } catch { + return false; + } + } + private parseDiagnostic( line: string ): ParsedDiagnostic | vscode.DiagnosticRelatedInformation | undefined { - const diagnosticRegex = /^(.*?):(\d+)(?::(\d+))?:\s+(warning|error|note):\s+([^\\[]*)/g; + const diagnosticRegex = + /^(?:\S+\s+)?(.*?):(\d+)(?::(\d+))?:\s+(warning|error|note):\s+([^\\[]*)/g; const match = diagnosticRegex.exec(line); if (!match) { return; diff --git a/test/integration-tests/DiagnosticsManager.test.ts b/test/integration-tests/DiagnosticsManager.test.ts index b9b4885e0..04981f921 100644 --- a/test/integration-tests/DiagnosticsManager.test.ts +++ b/test/integration-tests/DiagnosticsManager.test.ts @@ -56,7 +56,7 @@ function assertHasDiagnostic(uri: vscode.Uri, expected: vscode.Diagnostic): vsco assert.notEqual( diagnostic, undefined, - `Could not find diagnostic matching:\n${JSON.stringify(expected)}` + `Could not find diagnostic matching:\n${JSON.stringify(expected)}\nDiagnostics:\n${JSON.stringify(diagnostics)}` ); return diagnostic!; } @@ -66,7 +66,7 @@ function assertWithoutDiagnostic(uri: vscode.Uri, expected: vscode.Diagnostic) { assert.equal( diagnostics.find(findDiagnostic(expected)), undefined, - `Unexpected diagnostic matching:\n${JSON.stringify(expected)}` + `Unexpected diagnostic matching:\n${JSON.stringify(expected)}\nDiagnostics:\n${JSON.stringify(diagnostics)}` ); } @@ -136,6 +136,23 @@ suite("DiagnosticsManager Test Suite", async function () { ); expectedFuncErrorDiagnostic.source = "swiftc"; + const expectedMacroDiagnostic = new vscode.Diagnostic( + new vscode.Range(new vscode.Position(17, 26), new vscode.Position(17, 26)), + "No calls to throwing functions occur within 'try' expression", + vscode.DiagnosticSeverity.Warning + ); + + expectedMacroDiagnostic.source = "swiftc"; + expectedMacroDiagnostic.relatedInformation = [ + { + location: { + uri: mainUri, + range: expectedMacroDiagnostic.range, + }, + message: "Expanded code originates here", + }, + ]; + // SourceKit-LSP sometimes sends diagnostics // after first build and can cause intermittent // failure if `swiftc` diagnostic is fixed @@ -161,6 +178,9 @@ suite("DiagnosticsManager Test Suite", async function () { // Should have parsed correct severity assertHasDiagnostic(mainUri, expectedWarningDiagnostic); assertHasDiagnostic(mainUri, expectedMainErrorDiagnostic); + if (workspaceContext.swiftVersion.isGreaterThanOrEqual(new Version(6, 0, 0))) { + assertHasDiagnostic(mainUri, expectedMacroDiagnostic); + } // Check parsed for other file assertHasDiagnostic(funcUri, expectedFuncErrorDiagnostic); }).timeout(2 * 60 * 1000); // Allow 2 minutes to build @@ -183,6 +203,9 @@ suite("DiagnosticsManager Test Suite", async function () { // Should have parsed severity assertHasDiagnostic(mainUri, expectedWarningDiagnostic); assertHasDiagnostic(mainUri, expectedMainErrorDiagnostic); + if (workspaceContext.swiftVersion.isGreaterThanOrEqual(new Version(6, 0, 0))) { + assertHasDiagnostic(mainUri, expectedMacroDiagnostic); + } // Check parsed for other file assertHasDiagnostic(funcUri, expectedFuncErrorDiagnostic); }).timeout(2 * 60 * 1000); // Allow 2 minutes to build @@ -198,6 +221,9 @@ suite("DiagnosticsManager Test Suite", async function () { // Should have parsed severity assertHasDiagnostic(mainUri, expectedWarningDiagnostic); + + // llvm style doesn't do macro diagnostics + assertWithoutDiagnostic(mainUri, expectedMacroDiagnostic); const diagnostic = assertHasDiagnostic(mainUri, expectedMainErrorDiagnostic); // Should have parsed related note assert.equal(diagnostic.relatedInformation?.length, 1); From 7bb4a767fec13d7cf77fc4e205adc8e33c4710d9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 08:54:56 -0500 Subject: [PATCH 10/22] Bump the all-dependencies group across 1 directory with 6 updates (#1233) Bumps the all-dependencies group with 6 updates in the / directory: | Package | From | To | | --- | --- | --- | | [@types/mocha](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/mocha) | `10.0.9` | `10.0.10` | | [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `18.19.64` | `18.19.67` | | [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) | `8.15.0` | `8.16.0` | | [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) | `8.15.0` | `8.16.0` | | [prettier](https://github.com/prettier/prettier) | `3.3.3` | `3.4.1` | | [typescript](https://github.com/microsoft/TypeScript) | `5.6.3` | `5.7.2` | Updates `@types/mocha` from 10.0.9 to 10.0.10 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/mocha) Updates `@types/node` from 18.19.64 to 18.19.67 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) Updates `@typescript-eslint/eslint-plugin` from 8.15.0 to 8.16.0 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.16.0/packages/eslint-plugin) Updates `@typescript-eslint/parser` from 8.15.0 to 8.16.0 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.16.0/packages/parser) Updates `prettier` from 3.3.3 to 3.4.1 - [Release notes](https://github.com/prettier/prettier/releases) - [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md) - [Commits](https://github.com/prettier/prettier/compare/3.3.3...3.4.1) Updates `typescript` from 5.6.3 to 5.7.2 - [Release notes](https://github.com/microsoft/TypeScript/releases) - [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release.yml) - [Commits](https://github.com/microsoft/TypeScript/compare/v5.6.3...v5.7.2) --- updated-dependencies: - dependency-name: "@types/mocha" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: all-dependencies - dependency-name: "@types/node" dependency-type: direct:development update-type: version-update:semver-patch dependency-group: all-dependencies - dependency-name: "@typescript-eslint/eslint-plugin" dependency-type: direct:development update-type: version-update:semver-minor dependency-group: all-dependencies - dependency-name: "@typescript-eslint/parser" dependency-type: direct:development update-type: version-update:semver-minor dependency-group: all-dependencies - dependency-name: prettier dependency-type: direct:development update-type: version-update:semver-minor dependency-group: all-dependencies - dependency-name: typescript dependency-type: direct:development update-type: version-update:semver-minor dependency-group: all-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 228 +++++++++++++++++++++++----------------------- package.json | 12 +-- 2 files changed, 120 insertions(+), 120 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8a9628c4b..993d4bdcc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,16 +19,16 @@ "@types/chai-subset": "^1.3.5", "@types/glob": "^7.1.6", "@types/lcov-parse": "^1.0.2", - "@types/mocha": "^10.0.9", + "@types/mocha": "^10.0.10", "@types/mock-fs": "^4.13.4", - "@types/node": "^18.19.64", + "@types/node": "^18.19.67", "@types/plist": "^3.0.5", "@types/sinon": "^17.0.3", "@types/sinon-chai": "^3.2.12", "@types/vscode": "^1.88.0", "@types/xml2js": "^0.4.14", - "@typescript-eslint/eslint-plugin": "^8.15.0", - "@typescript-eslint/parser": "^8.15.0", + "@typescript-eslint/eslint-plugin": "^8.16.0", + "@typescript-eslint/parser": "^8.16.0", "@vscode/test-cli": "^0.0.10", "@vscode/test-electron": "^2.4.1", "@vscode/vsce": "^2.32.0", @@ -42,12 +42,12 @@ "mocha": "^10.8.2", "mock-fs": "^5.4.1", "node-pty": "^1.0.0", - "prettier": "^3.3.3", + "prettier": "^3.4.1", "sinon": "^19.0.2", "sinon-chai": "^3.7.0", "source-map-support": "^0.5.21", "strip-ansi": "^6.0.1", - "typescript": "^5.6.3" + "typescript": "^5.7.2" }, "engines": { "vscode": "^1.88.0" @@ -1050,9 +1050,9 @@ "dev": true }, "node_modules/@types/mocha": { - "version": "10.0.9", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.9.tgz", - "integrity": "sha512-sicdRoWtYevwxjOHNMPTl3vSfJM6oyW8o1wXeI7uww6b6xHg8eBznQDNSGBCDJmsE8UMxP05JgZRtsKbTqt//Q==", + "version": "10.0.10", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", + "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==", "dev": true }, "node_modules/@types/mock-fs": { @@ -1066,9 +1066,9 @@ } }, "node_modules/@types/node": { - "version": "18.19.64", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.64.tgz", - "integrity": "sha512-955mDqvO2vFf/oL7V3WiUtiz+BugyX8uVbaT2H8oj3+8dRyH2FLiNdowe7eNqRM7IOIZvzDH76EoAT+gwm6aIQ==", + "version": "18.19.67", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.67.tgz", + "integrity": "sha512-wI8uHusga+0ZugNp0Ol/3BqQfEcCCNfojtO6Oou9iVNGPTL6QNSdnUdqq85fRgIorLhLMuPIKpsN98QE9Nh+KQ==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -1128,16 +1128,16 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.15.0.tgz", - "integrity": "sha512-+zkm9AR1Ds9uLWN3fkoeXgFppaQ+uEVtfOV62dDmsy9QCNqlRHWNEck4yarvRNrvRcHQLGfqBNui3cimoz8XAg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.16.0.tgz", + "integrity": "sha512-5YTHKV8MYlyMI6BaEG7crQ9BhSc8RxzshOReKwZwRWN0+XvvTOm+L/UYLCYxFpfwYuAAqhxiq4yae0CMFwbL7Q==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.15.0", - "@typescript-eslint/type-utils": "8.15.0", - "@typescript-eslint/utils": "8.15.0", - "@typescript-eslint/visitor-keys": "8.15.0", + "@typescript-eslint/scope-manager": "8.16.0", + "@typescript-eslint/type-utils": "8.16.0", + "@typescript-eslint/utils": "8.16.0", + "@typescript-eslint/visitor-keys": "8.16.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -1161,15 +1161,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.15.0.tgz", - "integrity": "sha512-7n59qFpghG4uazrF9qtGKBZXn7Oz4sOMm8dwNWDQY96Xlm2oX67eipqcblDj+oY1lLCbf1oltMZFpUso66Kl1A==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.16.0.tgz", + "integrity": "sha512-D7DbgGFtsqIPIFMPJwCad9Gfi/hC0PWErRRHFnaCWoEDYi5tQUDiJCTmGUbBiLzjqAck4KcXt9Ayj0CNlIrF+w==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.15.0", - "@typescript-eslint/types": "8.15.0", - "@typescript-eslint/typescript-estree": "8.15.0", - "@typescript-eslint/visitor-keys": "8.15.0", + "@typescript-eslint/scope-manager": "8.16.0", + "@typescript-eslint/types": "8.16.0", + "@typescript-eslint/typescript-estree": "8.16.0", + "@typescript-eslint/visitor-keys": "8.16.0", "debug": "^4.3.4" }, "engines": { @@ -1189,13 +1189,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.15.0.tgz", - "integrity": "sha512-QRGy8ADi4J7ii95xz4UoiymmmMd/zuy9azCaamnZ3FM8T5fZcex8UfJcjkiEZjJSztKfEBe3dZ5T/5RHAmw2mA==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.16.0.tgz", + "integrity": "sha512-mwsZWubQvBki2t5565uxF0EYvG+FwdFb8bMtDuGQLdCCnGPrDEDvm1gtfynuKlnpzeBRqdFCkMf9jg1fnAK8sg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.15.0", - "@typescript-eslint/visitor-keys": "8.15.0" + "@typescript-eslint/types": "8.16.0", + "@typescript-eslint/visitor-keys": "8.16.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1206,13 +1206,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.15.0.tgz", - "integrity": "sha512-UU6uwXDoI3JGSXmcdnP5d8Fffa2KayOhUUqr/AiBnG1Gl7+7ut/oyagVeSkh7bxQ0zSXV9ptRh/4N15nkCqnpw==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.16.0.tgz", + "integrity": "sha512-IqZHGG+g1XCWX9NyqnI/0CX5LL8/18awQqmkZSl2ynn8F76j579dByc0jhfVSnSnhf7zv76mKBQv9HQFKvDCgg==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "8.15.0", - "@typescript-eslint/utils": "8.15.0", + "@typescript-eslint/typescript-estree": "8.16.0", + "@typescript-eslint/utils": "8.16.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -1233,9 +1233,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.15.0.tgz", - "integrity": "sha512-n3Gt8Y/KyJNe0S3yDCD2RVKrHBC4gTUcLTebVBXacPy091E6tNspFLKRXlk3hwT4G55nfr1n2AdFqi/XMxzmPQ==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.16.0.tgz", + "integrity": "sha512-NzrHj6thBAOSE4d9bsuRNMvk+BvaQvmY4dDglgkgGC0EW/tB3Kelnp3tAKH87GEwzoxgeQn9fNGRyFJM/xd+GQ==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1246,13 +1246,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.15.0.tgz", - "integrity": "sha512-1eMp2JgNec/niZsR7ioFBlsh/Fk0oJbhaqO0jRyQBMgkz7RrFfkqF9lYYmBoGBaSiLnu8TAPQTwoTUiSTUW9dg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.16.0.tgz", + "integrity": "sha512-E2+9IzzXMc1iaBy9zmo+UYvluE3TW7bCGWSF41hVWUE01o8nzr1rvOQYSxelxr6StUvRcTMe633eY8mXASMaNw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.15.0", - "@typescript-eslint/visitor-keys": "8.15.0", + "@typescript-eslint/types": "8.16.0", + "@typescript-eslint/visitor-keys": "8.16.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -1298,15 +1298,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.15.0.tgz", - "integrity": "sha512-k82RI9yGhr0QM3Dnq+egEpz9qB6Un+WLYhmoNcvl8ltMEededhh7otBVVIDDsEEttauwdY/hQoSsOv13lxrFzQ==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.16.0.tgz", + "integrity": "sha512-C1zRy/mOL8Pj157GiX4kaw7iyRLKfJXBR3L82hk5kS/GyHcOFmy4YUq/zfZti72I9wnuQtA/+xzft4wCC8PJdA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.15.0", - "@typescript-eslint/types": "8.15.0", - "@typescript-eslint/typescript-estree": "8.15.0" + "@typescript-eslint/scope-manager": "8.16.0", + "@typescript-eslint/types": "8.16.0", + "@typescript-eslint/typescript-estree": "8.16.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1325,12 +1325,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.15.0.tgz", - "integrity": "sha512-h8vYOulWec9LhpwfAdZf2bjr8xIp0KNKnpgqSz0qqYYKAW/QZKw3ktRndbiAtUz4acH4QLQavwZBYCc0wulA/Q==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.16.0.tgz", + "integrity": "sha512-pq19gbaMOmFE3CbL0ZB8J8BFCo2ckfHBfaIsaOZgBIF4EoISJIdLX5xRhd0FGB0LlHReNRuzoJoMGpTjq8F2CQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.15.0", + "@typescript-eslint/types": "8.16.0", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -4858,9 +4858,9 @@ } }, "node_modules/prettier": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", - "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.1.tgz", + "integrity": "sha512-G+YdqtITVZmOJje6QkXQWzl3fSfMxFwm1tjTyo9exhkmWSqC4Yhd1+lug++IlR2mvRVAxEDDWYkQdeSztajqgg==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -5665,9 +5665,9 @@ } }, "node_modules/typescript": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", - "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", + "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -6625,9 +6625,9 @@ "dev": true }, "@types/mocha": { - "version": "10.0.9", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.9.tgz", - "integrity": "sha512-sicdRoWtYevwxjOHNMPTl3vSfJM6oyW8o1wXeI7uww6b6xHg8eBznQDNSGBCDJmsE8UMxP05JgZRtsKbTqt//Q==", + "version": "10.0.10", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", + "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==", "dev": true }, "@types/mock-fs": { @@ -6640,9 +6640,9 @@ } }, "@types/node": { - "version": "18.19.64", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.64.tgz", - "integrity": "sha512-955mDqvO2vFf/oL7V3WiUtiz+BugyX8uVbaT2H8oj3+8dRyH2FLiNdowe7eNqRM7IOIZvzDH76EoAT+gwm6aIQ==", + "version": "18.19.67", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.67.tgz", + "integrity": "sha512-wI8uHusga+0ZugNp0Ol/3BqQfEcCCNfojtO6Oou9iVNGPTL6QNSdnUdqq85fRgIorLhLMuPIKpsN98QE9Nh+KQ==", "dev": true, "requires": { "undici-types": "~5.26.4" @@ -6699,16 +6699,16 @@ } }, "@typescript-eslint/eslint-plugin": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.15.0.tgz", - "integrity": "sha512-+zkm9AR1Ds9uLWN3fkoeXgFppaQ+uEVtfOV62dDmsy9QCNqlRHWNEck4yarvRNrvRcHQLGfqBNui3cimoz8XAg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.16.0.tgz", + "integrity": "sha512-5YTHKV8MYlyMI6BaEG7crQ9BhSc8RxzshOReKwZwRWN0+XvvTOm+L/UYLCYxFpfwYuAAqhxiq4yae0CMFwbL7Q==", "dev": true, "requires": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.15.0", - "@typescript-eslint/type-utils": "8.15.0", - "@typescript-eslint/utils": "8.15.0", - "@typescript-eslint/visitor-keys": "8.15.0", + "@typescript-eslint/scope-manager": "8.16.0", + "@typescript-eslint/type-utils": "8.16.0", + "@typescript-eslint/utils": "8.16.0", + "@typescript-eslint/visitor-keys": "8.16.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -6716,54 +6716,54 @@ } }, "@typescript-eslint/parser": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.15.0.tgz", - "integrity": "sha512-7n59qFpghG4uazrF9qtGKBZXn7Oz4sOMm8dwNWDQY96Xlm2oX67eipqcblDj+oY1lLCbf1oltMZFpUso66Kl1A==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.16.0.tgz", + "integrity": "sha512-D7DbgGFtsqIPIFMPJwCad9Gfi/hC0PWErRRHFnaCWoEDYi5tQUDiJCTmGUbBiLzjqAck4KcXt9Ayj0CNlIrF+w==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "8.15.0", - "@typescript-eslint/types": "8.15.0", - "@typescript-eslint/typescript-estree": "8.15.0", - "@typescript-eslint/visitor-keys": "8.15.0", + "@typescript-eslint/scope-manager": "8.16.0", + "@typescript-eslint/types": "8.16.0", + "@typescript-eslint/typescript-estree": "8.16.0", + "@typescript-eslint/visitor-keys": "8.16.0", "debug": "^4.3.4" } }, "@typescript-eslint/scope-manager": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.15.0.tgz", - "integrity": "sha512-QRGy8ADi4J7ii95xz4UoiymmmMd/zuy9azCaamnZ3FM8T5fZcex8UfJcjkiEZjJSztKfEBe3dZ5T/5RHAmw2mA==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.16.0.tgz", + "integrity": "sha512-mwsZWubQvBki2t5565uxF0EYvG+FwdFb8bMtDuGQLdCCnGPrDEDvm1gtfynuKlnpzeBRqdFCkMf9jg1fnAK8sg==", "dev": true, "requires": { - "@typescript-eslint/types": "8.15.0", - "@typescript-eslint/visitor-keys": "8.15.0" + "@typescript-eslint/types": "8.16.0", + "@typescript-eslint/visitor-keys": "8.16.0" } }, "@typescript-eslint/type-utils": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.15.0.tgz", - "integrity": "sha512-UU6uwXDoI3JGSXmcdnP5d8Fffa2KayOhUUqr/AiBnG1Gl7+7ut/oyagVeSkh7bxQ0zSXV9ptRh/4N15nkCqnpw==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.16.0.tgz", + "integrity": "sha512-IqZHGG+g1XCWX9NyqnI/0CX5LL8/18awQqmkZSl2ynn8F76j579dByc0jhfVSnSnhf7zv76mKBQv9HQFKvDCgg==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "8.15.0", - "@typescript-eslint/utils": "8.15.0", + "@typescript-eslint/typescript-estree": "8.16.0", + "@typescript-eslint/utils": "8.16.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" } }, "@typescript-eslint/types": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.15.0.tgz", - "integrity": "sha512-n3Gt8Y/KyJNe0S3yDCD2RVKrHBC4gTUcLTebVBXacPy091E6tNspFLKRXlk3hwT4G55nfr1n2AdFqi/XMxzmPQ==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.16.0.tgz", + "integrity": "sha512-NzrHj6thBAOSE4d9bsuRNMvk+BvaQvmY4dDglgkgGC0EW/tB3Kelnp3tAKH87GEwzoxgeQn9fNGRyFJM/xd+GQ==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.15.0.tgz", - "integrity": "sha512-1eMp2JgNec/niZsR7ioFBlsh/Fk0oJbhaqO0jRyQBMgkz7RrFfkqF9lYYmBoGBaSiLnu8TAPQTwoTUiSTUW9dg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.16.0.tgz", + "integrity": "sha512-E2+9IzzXMc1iaBy9zmo+UYvluE3TW7bCGWSF41hVWUE01o8nzr1rvOQYSxelxr6StUvRcTMe633eY8mXASMaNw==", "dev": true, "requires": { - "@typescript-eslint/types": "8.15.0", - "@typescript-eslint/visitor-keys": "8.15.0", + "@typescript-eslint/types": "8.16.0", + "@typescript-eslint/visitor-keys": "8.16.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -6793,24 +6793,24 @@ } }, "@typescript-eslint/utils": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.15.0.tgz", - "integrity": "sha512-k82RI9yGhr0QM3Dnq+egEpz9qB6Un+WLYhmoNcvl8ltMEededhh7otBVVIDDsEEttauwdY/hQoSsOv13lxrFzQ==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.16.0.tgz", + "integrity": "sha512-C1zRy/mOL8Pj157GiX4kaw7iyRLKfJXBR3L82hk5kS/GyHcOFmy4YUq/zfZti72I9wnuQtA/+xzft4wCC8PJdA==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.15.0", - "@typescript-eslint/types": "8.15.0", - "@typescript-eslint/typescript-estree": "8.15.0" + "@typescript-eslint/scope-manager": "8.16.0", + "@typescript-eslint/types": "8.16.0", + "@typescript-eslint/typescript-estree": "8.16.0" } }, "@typescript-eslint/visitor-keys": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.15.0.tgz", - "integrity": "sha512-h8vYOulWec9LhpwfAdZf2bjr8xIp0KNKnpgqSz0qqYYKAW/QZKw3ktRndbiAtUz4acH4QLQavwZBYCc0wulA/Q==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.16.0.tgz", + "integrity": "sha512-pq19gbaMOmFE3CbL0ZB8J8BFCo2ckfHBfaIsaOZgBIF4EoISJIdLX5xRhd0FGB0LlHReNRuzoJoMGpTjq8F2CQ==", "dev": true, "requires": { - "@typescript-eslint/types": "8.15.0", + "@typescript-eslint/types": "8.16.0", "eslint-visitor-keys": "^4.2.0" }, "dependencies": { @@ -9398,9 +9398,9 @@ "dev": true }, "prettier": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", - "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.1.tgz", + "integrity": "sha512-G+YdqtITVZmOJje6QkXQWzl3fSfMxFwm1tjTyo9exhkmWSqC4Yhd1+lug++IlR2mvRVAxEDDWYkQdeSztajqgg==", "dev": true }, "process-nextick-args": { @@ -9961,9 +9961,9 @@ } }, "typescript": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", - "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", + "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", "dev": true }, "uc.micro": { diff --git a/package.json b/package.json index 13e173ab6..42540fa80 100644 --- a/package.json +++ b/package.json @@ -1299,16 +1299,16 @@ "@types/chai-subset": "^1.3.5", "@types/glob": "^7.1.6", "@types/lcov-parse": "^1.0.2", - "@types/mocha": "^10.0.9", + "@types/mocha": "^10.0.10", "@types/mock-fs": "^4.13.4", - "@types/node": "^18.19.64", + "@types/node": "^18.19.67", "@types/plist": "^3.0.5", "@types/sinon": "^17.0.3", "@types/sinon-chai": "^3.2.12", "@types/vscode": "^1.88.0", "@types/xml2js": "^0.4.14", - "@typescript-eslint/eslint-plugin": "^8.15.0", - "@typescript-eslint/parser": "^8.15.0", + "@typescript-eslint/eslint-plugin": "^8.16.0", + "@typescript-eslint/parser": "^8.16.0", "@vscode/test-cli": "^0.0.10", "@vscode/test-electron": "^2.4.1", "@vscode/vsce": "^2.32.0", @@ -1322,12 +1322,12 @@ "mocha": "^10.8.2", "mock-fs": "^5.4.1", "node-pty": "^1.0.0", - "prettier": "^3.3.3", + "prettier": "^3.4.1", "sinon": "^19.0.2", "sinon-chai": "^3.7.0", "source-map-support": "^0.5.21", "strip-ansi": "^6.0.1", - "typescript": "^5.6.3" + "typescript": "^5.7.2" }, "dependencies": { "lcov-parse": "^1.0.0", From 93cce88353c6c0ac2f7962eb0c054b3cb48853ad Mon Sep 17 00:00:00 2001 From: Matthew Bastien Date: Mon, 2 Dec 2024 08:55:21 -0500 Subject: [PATCH 11/22] format JSON files in the .vscode directory (#1228) --- .prettierignore | 3 ++ .vscode/extensions.json | 9 ++--- .vscode/launch.json | 84 ++++++++++++++++++----------------------- .vscode/settings.json | 22 +++++------ .vscode/tasks.json | 52 ++++++++++++------------- 5 files changed, 80 insertions(+), 90 deletions(-) diff --git a/.prettierignore b/.prettierignore index a0f895b54..42ce3550c 100644 --- a/.prettierignore +++ b/.prettierignore @@ -4,6 +4,9 @@ !*.json !*.ts +# Directories ignored by default that actually should be formatted +!.vscode/ + # NodeJS node_modules/ diff --git a/.vscode/extensions.json b/.vscode/extensions.json index e680869ad..3bfc4d061 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,8 +1,5 @@ { - // See http://go.microsoft.com/fwlink/?LinkId=827846 - // for the documentation about the extensions.json format - "recommendations": [ - "dbaeumer.vscode-eslint", - "esbenp.prettier-vscode" - ] + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"] } diff --git a/.vscode/launch.json b/.vscode/launch.json index 70366aa11..2994635f2 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -3,51 +3,41 @@ // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 { - "version": "0.2.0", - "configurations": [ - { - "name": "Run Extension", - "type": "extensionHost", - "request": "launch", - "args": [ - "--extensionDevelopmentPath=${workspaceFolder}" - ], - "env": { - "VSCODE_DEBUG": "1" - }, - "outFiles": [ - "${workspaceFolder}/dist/**/*.js" - ], - "preLaunchTask": "build" - }, - { - "name": "Extension Tests", - "type": "extensionHost", - "request": "launch", - "testConfiguration": "${workspaceFolder}/.vscode-test.js", - "testConfigurationLabel": "integrationTests", - "args": [ - "--profile=testing-debug" - ], - "outFiles": [ - "${workspaceFolder}/dist/**/*.js" - ], - "env": { - "VSCODE_DEBUG": "1", - "VSCODE_TEST": "1" - }, - "preLaunchTask": "compile-tests" - }, - { - "name": "Unit Tests", - "type": "extensionHost", - "request": "launch", - "testConfiguration": "${workspaceFolder}/.vscode-test.js", - "testConfigurationLabel": "unitTests", - "outFiles": [ - "${workspaceFolder}/dist/**/*.js" - ], - "preLaunchTask": "compile-tests" - } - ] + "version": "0.2.0", + "configurations": [ + { + "name": "Run Extension", + "type": "extensionHost", + "request": "launch", + "args": ["--extensionDevelopmentPath=${workspaceFolder}"], + "env": { + "VSCODE_DEBUG": "1" + }, + "outFiles": ["${workspaceFolder}/dist/**/*.js"], + "preLaunchTask": "build" + }, + { + "name": "Extension Tests", + "type": "extensionHost", + "request": "launch", + "testConfiguration": "${workspaceFolder}/.vscode-test.js", + "testConfigurationLabel": "integrationTests", + "args": ["--profile=testing-debug"], + "outFiles": ["${workspaceFolder}/dist/**/*.js"], + "env": { + "VSCODE_DEBUG": "1", + "VSCODE_TEST": "1" + }, + "preLaunchTask": "compile-tests" + }, + { + "name": "Unit Tests", + "type": "extensionHost", + "request": "launch", + "testConfiguration": "${workspaceFolder}/.vscode-test.js", + "testConfigurationLabel": "unitTests", + "outFiles": ["${workspaceFolder}/dist/**/*.js"], + "preLaunchTask": "compile-tests" + } + ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 1f2724106..e35e88b34 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,15 +1,15 @@ // Place your settings in this file to overwrite default and user settings. { - "files.exclude": { - "dist": false // set this to true to hide the "dist" folder with the compiled JS files - }, - "search.exclude": { - "dist": true // set this to false to include "dist" folder in search results - }, - // Turn off tsc task auto detection since we have the necessary tasks as npm scripts - "typescript.tsc.autoDetect": "off", + "files.exclude": { + "dist": false // set this to true to hide the "dist" folder with the compiled JS files + }, + "search.exclude": { + "dist": true // set this to false to include "dist" folder in search results + }, + // Turn off tsc task auto detection since we have the necessary tasks as npm scripts + "typescript.tsc.autoDetect": "off", - // Configure Prettier - "editor.formatOnSave": true, - "editor.defaultFormatter": "esbenp.prettier-vscode", + // Configure Prettier + "editor.formatOnSave": true, + "editor.defaultFormatter": "esbenp.prettier-vscode" } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index f852e361a..ed97093bc 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,30 +1,30 @@ // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format { - "version": "2.0.0", - "tasks": [ - { - "label": "build", - "type": "npm", - "script": "watch", - "problemMatcher": "$tsc-watch", - "presentation": { - "reveal": "never" - }, - "isBackground": true, - "group": { - "kind": "build", - "isDefault": true - } - }, - { - "label": "compile-tests", - "type": "npm", - "script": "compile-tests", - "problemMatcher": "$tsc", - "presentation": { - "reveal": "never" - } - } - ] + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "type": "npm", + "script": "watch", + "problemMatcher": "$tsc-watch", + "presentation": { + "reveal": "never" + }, + "isBackground": true, + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "label": "compile-tests", + "type": "npm", + "script": "compile-tests", + "problemMatcher": "$tsc", + "presentation": { + "reveal": "never" + } + } + ] } From 02d07269b0065049402d81e3b9693a047e344cb5 Mon Sep 17 00:00:00 2001 From: Matthew Bastien Date: Mon, 2 Dec 2024 09:49:36 -0500 Subject: [PATCH 12/22] Reorganize LSP extensions (#1227) --- src/TestExplorer/LSPTestDiscovery.ts | 45 ++-- src/TestExplorer/SPMTestDiscovery.ts | 2 +- src/TestExplorer/TestDiscovery.ts | 2 +- src/commands/reindexProject.ts | 4 +- src/sourcekit-lsp/LanguageClientFactory.ts | 41 ++++ src/sourcekit-lsp/LanguageClientManager.ts | 121 ++++++----- .../extensions/GetReferenceDocumentRequest.ts | 55 +++++ .../extensions/GetTestsRequest.ts | 114 ++++++++++ .../extensions/LegacyInlayHintRequest.ts | 72 ++++++ .../extensions/PeekDocumentsRequest.ts | 68 ++++++ .../extensions/ReIndexProjectRequest.ts | 35 +++ .../SourceKitLogMessageNotification.ts | 38 ++++ .../extensions/SymbolInfoRequest.ts | 156 +++++++++++++ src/sourcekit-lsp/extensions/index.ts | 23 ++ src/sourcekit-lsp/getReferenceDocument.ts | 8 +- src/sourcekit-lsp/inlayHints.ts | 4 +- src/sourcekit-lsp/lspExtensions.ts | 205 ------------------ src/sourcekit-lsp/peekDocuments.ts | 2 +- .../testexplorer/LSPTestDiscovery.test.ts | 117 ++++++---- .../LanguageClientManager.test.ts | 45 ++-- 20 files changed, 791 insertions(+), 366 deletions(-) create mode 100644 src/sourcekit-lsp/LanguageClientFactory.ts create mode 100644 src/sourcekit-lsp/extensions/GetReferenceDocumentRequest.ts create mode 100644 src/sourcekit-lsp/extensions/GetTestsRequest.ts create mode 100644 src/sourcekit-lsp/extensions/LegacyInlayHintRequest.ts create mode 100644 src/sourcekit-lsp/extensions/PeekDocumentsRequest.ts create mode 100644 src/sourcekit-lsp/extensions/ReIndexProjectRequest.ts create mode 100644 src/sourcekit-lsp/extensions/SourceKitLogMessageNotification.ts create mode 100644 src/sourcekit-lsp/extensions/SymbolInfoRequest.ts create mode 100644 src/sourcekit-lsp/extensions/index.ts delete mode 100644 src/sourcekit-lsp/lspExtensions.ts diff --git a/src/TestExplorer/LSPTestDiscovery.ts b/src/TestExplorer/LSPTestDiscovery.ts index 439c3f807..fa2ebf88f 100644 --- a/src/TestExplorer/LSPTestDiscovery.ts +++ b/src/TestExplorer/LSPTestDiscovery.ts @@ -16,29 +16,12 @@ import * as vscode from "vscode"; import * as TestDiscovery from "./TestDiscovery"; import { LSPTestItem, - textDocumentTestsRequest, - workspaceTestsRequest, -} from "../sourcekit-lsp/lspExtensions"; -import { InitializeResult, RequestType } from "vscode-languageclient/node"; + TextDocumentTestsRequest, + WorkspaceTestsRequest, +} from "../sourcekit-lsp/extensions"; import { SwiftPackage, TargetType } from "../SwiftPackage"; -import { Converter } from "vscode-languageclient/lib/common/protocolConverter"; - -interface ILanguageClient { - get initializeResult(): InitializeResult | undefined; - get protocol2CodeConverter(): Converter; - - sendRequest( - type: RequestType, - params: P, - token?: vscode.CancellationToken - ): Promise; -} - -interface ILanguageClientManager { - useLanguageClient(process: { - (client: ILanguageClient, cancellationToken: vscode.CancellationToken): Promise; - }): Promise; -} +import { LanguageClientManager } from "../sourcekit-lsp/LanguageClientManager"; +import { LanguageClient } from "vscode-languageclient/node"; /** * Used to augment test discovery via `swift test --list-tests`. @@ -49,7 +32,7 @@ interface ILanguageClientManager { * these results. */ export class LSPTestDiscovery { - constructor(private languageClient: ILanguageClientManager) {} + constructor(private languageClient: LanguageClientManager) {} /** * Return a list of tests in the supplied document. @@ -62,15 +45,15 @@ export class LSPTestDiscovery { return await this.languageClient.useLanguageClient(async (client, token) => { // Only use the lsp for this request if it supports the // textDocument/tests method, and is at least version 2. - if (this.checkExperimentalCapability(client, textDocumentTestsRequest.method, 2)) { + if (this.checkExperimentalCapability(client, TextDocumentTestsRequest.method, 2)) { const testsInDocument = await client.sendRequest( - textDocumentTestsRequest, + TextDocumentTestsRequest.type, { textDocument: { uri: document.toString() } }, token ); return this.transformToTestClass(client, swiftPackage, testsInDocument); } else { - throw new Error(`${textDocumentTestsRequest.method} requests not supported`); + throw new Error(`${TextDocumentTestsRequest.method} requests not supported`); } }); } @@ -83,11 +66,11 @@ export class LSPTestDiscovery { return await this.languageClient.useLanguageClient(async (client, token) => { // Only use the lsp for this request if it supports the // workspace/tests method, and is at least version 2. - if (this.checkExperimentalCapability(client, workspaceTestsRequest.method, 2)) { - const tests = await client.sendRequest(workspaceTestsRequest, {}, token); + if (this.checkExperimentalCapability(client, WorkspaceTestsRequest.method, 2)) { + const tests = await client.sendRequest(WorkspaceTestsRequest.type, token); return this.transformToTestClass(client, swiftPackage, tests); } else { - throw new Error(`${workspaceTestsRequest.method} requests not supported`); + throw new Error(`${WorkspaceTestsRequest.method} requests not supported`); } }); } @@ -97,7 +80,7 @@ export class LSPTestDiscovery { * above the supplied `minVersion`. */ private checkExperimentalCapability( - client: ILanguageClient, + client: LanguageClient, method: string, minVersion: number ) { @@ -114,7 +97,7 @@ export class LSPTestDiscovery { * updating the format of the location. */ private transformToTestClass( - client: ILanguageClient, + client: LanguageClient, swiftPackage: SwiftPackage, input: LSPTestItem[] ): TestDiscovery.TestClass[] { diff --git a/src/TestExplorer/SPMTestDiscovery.ts b/src/TestExplorer/SPMTestDiscovery.ts index ce5a7323e..8616e1910 100644 --- a/src/TestExplorer/SPMTestDiscovery.ts +++ b/src/TestExplorer/SPMTestDiscovery.ts @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import { TestStyle } from "../sourcekit-lsp/lspExtensions"; +import { TestStyle } from "../sourcekit-lsp/extensions"; import { TestClass } from "./TestDiscovery"; /* diff --git a/src/TestExplorer/TestDiscovery.ts b/src/TestExplorer/TestDiscovery.ts index 5da5d7dcd..c0017897b 100644 --- a/src/TestExplorer/TestDiscovery.ts +++ b/src/TestExplorer/TestDiscovery.ts @@ -14,7 +14,7 @@ import * as vscode from "vscode"; import { SwiftPackage, TargetType } from "../SwiftPackage"; -import { LSPTestItem } from "../sourcekit-lsp/lspExtensions"; +import { LSPTestItem } from "../sourcekit-lsp/extensions"; import { reduceTestItemChildren } from "./TestUtils"; /** Test class definition */ diff --git a/src/commands/reindexProject.ts b/src/commands/reindexProject.ts index ffb34e6f4..95242c7fd 100644 --- a/src/commands/reindexProject.ts +++ b/src/commands/reindexProject.ts @@ -14,7 +14,7 @@ import * as vscode from "vscode"; import { WorkspaceContext } from "../WorkspaceContext"; -import { reindexProjectRequest } from "../sourcekit-lsp/lspExtensions"; +import { ReIndexProjectRequest } from "../sourcekit-lsp/extensions"; /** * Request that the SourceKit-LSP server reindexes the workspace. @@ -22,7 +22,7 @@ import { reindexProjectRequest } from "../sourcekit-lsp/lspExtensions"; export function reindexProject(workspaceContext: WorkspaceContext): Promise { return workspaceContext.languageClientManager.useLanguageClient(async (client, token) => { try { - await client.sendRequest(reindexProjectRequest, {}, token); + await client.sendRequest(ReIndexProjectRequest.type, token); const result = await vscode.window.showWarningMessage( "Re-indexing a project should never be necessary and indicates a bug in SourceKit-LSP. Please file an issue describing which symbol was out-of-date and how you got into the state.", "Report Issue", diff --git a/src/sourcekit-lsp/LanguageClientFactory.ts b/src/sourcekit-lsp/LanguageClientFactory.ts new file mode 100644 index 000000000..ef22cbcf9 --- /dev/null +++ b/src/sourcekit-lsp/LanguageClientFactory.ts @@ -0,0 +1,41 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the VS Code Swift open source project +// +// Copyright (c) 2024 the VS Code Swift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of VS Code Swift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import { LanguageClient, LanguageClientOptions, ServerOptions } from "vscode-languageclient/node"; + +/** + * Used to create a {@link LanguageClient} for use in VS Code. + * + * This is primarily used to make unit testing easier so that we don't have to + * mock out a constructor in the `vscode-languageclient` module. + */ +export class LanguageClientFactory { + /** + * Create a new {@link LanguageClient} for use in VS Code. + * + * @param name the human-readable name for the client + * @param id the identifier for the client (used in settings) + * @param serverOptions the {@link ServerOptions} + * @param clientOptions the {@link LanguageClientOptions} + * @returns the newly created {@link LanguageClient} + */ + createLanguageClient( + name: string, + id: string, + serverOptions: ServerOptions, + clientOptions: LanguageClientOptions + ): LanguageClient { + return new LanguageClient(name, id, serverOptions, clientOptions); + } +} diff --git a/src/sourcekit-lsp/LanguageClientManager.ts b/src/sourcekit-lsp/LanguageClientManager.ts index ba043942b..e6f624dc4 100644 --- a/src/sourcekit-lsp/LanguageClientManager.ts +++ b/src/sourcekit-lsp/LanguageClientManager.ts @@ -14,7 +14,20 @@ import * as vscode from "vscode"; import * as path from "path"; -import * as langclient from "vscode-languageclient/node"; +import { + CloseAction, + CloseHandlerResult, + DidChangeWorkspaceFoldersNotification, + ErrorAction, + ErrorHandler, + ErrorHandlerResult, + LanguageClientOptions, + Message, + MessageType, + RevealOutputChannelOn, + State, + vsdiag, +} from "vscode-languageclient"; import configuration from "../configuration"; import { swiftRuntimeEnv } from "../utilities/utilities"; import { isPathInsidePath } from "../utilities/filesystem"; @@ -23,7 +36,7 @@ import { FolderOperation, WorkspaceContext } from "../WorkspaceContext"; import { activateLegacyInlayHints } from "./inlayHints"; import { activatePeekDocuments } from "./peekDocuments"; import { FolderContext } from "../FolderContext"; -import { LanguageClient } from "vscode-languageclient/node"; +import { Executable, LanguageClient, ServerOptions } from "vscode-languageclient/node"; import { ArgumentFilter, BuildFlags } from "../toolchain/BuildFlags"; import { DiagnosticsManager } from "../DiagnosticsManager"; import { LSPLogger, LSPOutputChannel } from "./LSPOutputChannel"; @@ -31,15 +44,14 @@ import { SwiftOutputChannel } from "../ui/SwiftOutputChannel"; import { promptForDiagnostics } from "../commands/captureDiagnostics"; import { activateGetReferenceDocument } from "./getReferenceDocument"; import { uriConverters } from "./uriConverters"; +import { LanguageClientFactory } from "./LanguageClientFactory"; +import { SourceKitLogMessageNotification, SourceKitLogMessageParams } from "./extensions"; -interface SourceKitLogMessageParams extends langclient.LogMessageParams { - logName?: string; -} - -/** Manages the creation and destruction of Language clients as we move between +/** + * Manages the creation and destruction of Language clients as we move between * workspace folders */ -export class LanguageClientManager { +export class LanguageClientManager implements vscode.Disposable { // known log names static indexingLogName = "SourceKit-LSP: Indexing"; @@ -110,7 +122,7 @@ export class LanguageClientManager { * undefined means not setup * null means in the process of restarting */ - private languageClient: langclient.LanguageClient | null | undefined; + private languageClient: LanguageClient | null | undefined; private cancellationToken?: vscode.CancellationTokenSource; private legacyInlayHints?: vscode.Disposable; private peekDocuments?: vscode.Disposable; @@ -132,14 +144,17 @@ export class LanguageClientManager { private swiftVersion: Version; /** Get the current state of the underlying LanguageClient */ - public get state(): langclient.State { + public get state(): State { if (!this.languageClient) { - return langclient.State.Stopped; + return State.Stopped; } return this.languageClient.state; } - constructor(public workspaceContext: WorkspaceContext) { + constructor( + public workspaceContext: WorkspaceContext, + private languageClientFactory: LanguageClientFactory = new LanguageClientFactory() + ) { this.namedOutputChannels.set( LanguageClientManager.indexingLogName, new LSPOutputChannel(LanguageClientManager.indexingLogName, false, true) @@ -197,7 +212,7 @@ export class LanguageClientManager { // Enabling/Disabling sourcekit-lsp shows a special notification if (event.affectsConfiguration("swift.sourcekit-lsp.disable")) { if (configuration.lsp.disable) { - if (this.state === langclient.State.Stopped) { + if (this.state === State.Stopped) { // Language client is already stopped return; } @@ -205,7 +220,7 @@ export class LanguageClientManager { "You have disabled the Swift language server, but it is still running. Would you like to stop it now?"; restartLSPButton = "Stop Language Server"; } else { - if (this.state !== langclient.State.Stopped) { + if (this.state !== State.Stopped) { // Langauge client is already running return; } @@ -213,7 +228,7 @@ export class LanguageClientManager { "You have enabled the Swift language server. Would you like to start it now?"; restartLSPButton = "Start Language Server"; } - } else if (configuration.lsp.disable && this.state === langclient.State.Stopped) { + } else if (configuration.lsp.disable && this.state === State.Stopped) { // Ignore configuration changes if SourceKit-LSP is disabled return; } @@ -249,7 +264,7 @@ export class LanguageClientManager { // The language client stops asnyhronously, so we need to wait for it to stop // instead of doing it in dispose, which must be synchronous. async stop() { - if (this.languageClient && this.languageClient.state === langclient.State.Running) { + if (this.languageClient && this.languageClient.state === State.Running) { await this.languageClient.dispose(); } } @@ -272,10 +287,7 @@ export class LanguageClientManager { * @returns result of process */ async useLanguageClient(process: { - ( - client: langclient.LanguageClient, - cancellationToken: vscode.CancellationToken - ): Promise; + (client: LanguageClient, cancellationToken: vscode.CancellationToken): Promise; }): Promise { if (!this.languageClient || !this.clientReadyPromise) { throw new Error(LanguageClientError.LanguageClientUnavailable); @@ -311,7 +323,7 @@ export class LanguageClientManager { uri: client.code2ProtocolConverter.asUri(uri), name: FolderContext.uriName(uri), }; - client.sendNotification(langclient.DidChangeWorkspaceFoldersNotification.type, { + client.sendNotification(DidChangeWorkspaceFoldersNotification.type, { event: { added: [workspaceFolder], removed: [] }, }); }); @@ -328,7 +340,7 @@ export class LanguageClientManager { uri: client.code2ProtocolConverter.asUri(uri), name: FolderContext.uriName(uri), }; - client.sendNotification(langclient.DidChangeWorkspaceFoldersNotification.type, { + client.sendNotification(DidChangeWorkspaceFoldersNotification.type, { event: { added: [], removed: [workspaceFolder] }, }); }); @@ -341,7 +353,7 @@ export class LanguageClientManager { uri: client.code2ProtocolConverter.asUri(uri), name: FolderContext.uriName(uri), }; - client.sendNotification(langclient.DidChangeWorkspaceFoldersNotification.type, { + client.sendNotification(DidChangeWorkspaceFoldersNotification.type, { event: { added: [workspaceFolder], removed: [] }, }); } @@ -454,7 +466,7 @@ export class LanguageClientManager { } private createLSPClient(folder?: vscode.Uri): { - client: langclient.LanguageClient; + client: LanguageClient; errorHandler: SourceKitLSPErrorHandler; } { const toolchainSourceKitLSP = @@ -472,7 +484,7 @@ export class LanguageClientManager { ), ]; - const sourcekit: langclient.Executable = { + const sourcekit: Executable = { command: serverPath, args: lspConfig.serverArguments.concat(sdkArguments), options: { @@ -500,16 +512,16 @@ export class LanguageClientManager { }; } - const serverOptions: langclient.ServerOptions = sourcekit; + const serverOptions: ServerOptions = sourcekit; let workspaceFolder = undefined; if (folder) { workspaceFolder = { uri: folder, name: FolderContext.uriName(folder), index: 0 }; } const errorHandler = new SourceKitLSPErrorHandler(5); - const clientOptions: langclient.LanguageClientOptions = { + const clientOptions: LanguageClientOptions = { documentSelector: LanguageClientManager.documentSelector, - revealOutputChannelOn: langclient.RevealOutputChannelOn.Never, + revealOutputChannelOn: RevealOutputChannelOn.Never, workspaceFolder: workspaceFolder, outputChannel: new SwiftOutputChannel("SourceKit Language Server", false), middleware: { @@ -546,7 +558,7 @@ export class LanguageClientManager { }, provideDiagnostics: async (uri, previousResultId, token, next) => { const result = await next(uri, previousResultId, token); - if (result?.kind === langclient.vsdiag.DocumentDiagnosticReportKind.unChanged) { + if (result?.kind === vsdiag.DocumentDiagnosticReportKind.unChanged) { return undefined; } const document = uri as vscode.TextDocument; @@ -591,7 +603,7 @@ export class LanguageClientManager { }; return { - client: new langclient.LanguageClient( + client: this.languageClientFactory.createLanguageClient( "swift.sourcekit-lsp", "SourceKit Language Server", serverOptions, @@ -631,21 +643,14 @@ export class LanguageClientManager { } return options; } - /* eslint-enable @typescript-eslint/no-explicit-any */ - private async startClient( - client: langclient.LanguageClient, - errorHandler: SourceKitLSPErrorHandler - ) { + private async startClient(client: LanguageClient, errorHandler: SourceKitLSPErrorHandler) { client.onDidChangeState(e => { // if state is now running add in any sub-folder workspaces that // we have cached. If this is the first time we are starting then // we won't have any sub folder workspaces, but if the server crashed // or we forced a restart then we need to do this - if ( - e.oldState === langclient.State.Starting && - e.newState === langclient.State.Running - ) { + if (e.oldState === State.Starting && e.newState === State.Running) { this.addSubFolderWorkspaces(client); } }); @@ -659,7 +664,7 @@ export class LanguageClientManager { this.workspaceContext.outputChannel.log(`SourceKit-LSP setup`); } - client.onNotification(langclient.LogMessageNotification.type, params => { + client.onNotification(SourceKitLogMessageNotification.type, params => { this.logMessage(client, params as SourceKitLogMessageParams); }); @@ -692,7 +697,7 @@ export class LanguageClientManager { return this.clientReadyPromise; } - private logMessage(client: langclient.LanguageClient, params: SourceKitLogMessageParams) { + private logMessage(client: LanguageClient, params: SourceKitLogMessageParams) { let logger: LSPLogger = client; if (params.logName) { const outputChannel = @@ -702,19 +707,19 @@ export class LanguageClientManager { logger = outputChannel; } switch (params.type) { - case langclient.MessageType.Info: + case MessageType.Info: logger.info(params.message); break; - case langclient.MessageType.Debug: + case MessageType.Debug: logger.debug(params.message); break; - case langclient.MessageType.Warning: + case MessageType.Warning: logger.warn(params.message); break; - case langclient.MessageType.Error: + case MessageType.Error: logger.error(params.message); break; - case langclient.MessageType.Log: + case MessageType.Log: logger.info(params.message); break; } @@ -726,7 +731,7 @@ export class LanguageClientManager { * an error message that asks if you want to restart the sourcekit-lsp server again * after so many crashes */ -export class SourceKitLSPErrorHandler implements langclient.ErrorHandler { +export class SourceKitLSPErrorHandler implements ErrorHandler { private restarts: number[]; private enabled: boolean = false; @@ -749,32 +754,32 @@ export class SourceKitLSPErrorHandler implements langclient.ErrorHandler { */ error( _error: Error, - _message: langclient.Message | undefined, + _message: Message | undefined, count: number | undefined - ): langclient.ErrorHandlerResult | Promise { + ): ErrorHandlerResult | Promise { if (count && count <= 3) { - return { action: langclient.ErrorAction.Continue }; + return { action: ErrorAction.Continue }; } - return { action: langclient.ErrorAction.Shutdown }; + return { action: ErrorAction.Shutdown }; } /** * The connection to the server got closed. */ - closed(): langclient.CloseHandlerResult | Promise { + closed(): CloseHandlerResult | Promise { if (!this.enabled) { return { - action: langclient.CloseAction.DoNotRestart, + action: CloseAction.DoNotRestart, handled: true, }; } this.restarts.push(Date.now()); if (this.restarts.length <= this.maxRestartCount) { - return { action: langclient.CloseAction.Restart }; + return { action: CloseAction.Restart }; } else { const diff = this.restarts[this.restarts.length - 1] - this.restarts[0]; if (diff <= 3 * 60 * 1000) { - return new Promise(resolve => { + return new Promise(resolve => { vscode.window .showErrorMessage( `The SourceKit-LSP server crashed ${ @@ -786,15 +791,15 @@ export class SourceKitLSPErrorHandler implements langclient.ErrorHandler { .then(result => { if (result === "Yes") { this.restarts = []; - resolve({ action: langclient.CloseAction.Restart }); + resolve({ action: CloseAction.Restart }); } else { - resolve({ action: langclient.CloseAction.DoNotRestart }); + resolve({ action: CloseAction.DoNotRestart }); } }); }); } else { this.restarts.shift(); - return { action: langclient.CloseAction.Restart }; + return { action: CloseAction.Restart }; } } } diff --git a/src/sourcekit-lsp/extensions/GetReferenceDocumentRequest.ts b/src/sourcekit-lsp/extensions/GetReferenceDocumentRequest.ts new file mode 100644 index 000000000..eff0c4b9a --- /dev/null +++ b/src/sourcekit-lsp/extensions/GetReferenceDocumentRequest.ts @@ -0,0 +1,55 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the VS Code Swift open source project +// +// Copyright (c) 2021-2024 the VS Code Swift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of VS Code Swift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// We use namespaces to store request information just like vscode-languageclient +/* eslint-disable @typescript-eslint/no-namespace */ + +import { DocumentUri, MessageDirection, RequestType } from "vscode-languageclient"; + +/** Parameters used to make a {@link GetReferenceDocumentRequest}. */ +export interface GetReferenceDocumentParams { + /** The {@link DocumentUri} of the custom scheme url for which content is required. */ + uri: DocumentUri; +} + +/** Response containing `content` of a {@link GetReferenceDocumentRequest}. */ +export interface GetReferenceDocumentResult { + content: string; +} + +/** + * Request from the client to the server asking for contents of a URI having a custom scheme **(LSP Extension)** + * For example: "sourcekit-lsp:" + * + * - Parameters: + * - uri: The `DocumentUri` of the custom scheme url for which content is required + * + * - Returns: `GetReferenceDocumentResponse` which contains the `content` to be displayed. + * + * ### LSP Extension + * + * This request is an extension to LSP supported by SourceKit-LSP. + * + * Enable the experimental client capability `"workspace/getReferenceDocument"` so that the server responds with + * reference document URLs for certain requests or commands whenever possible. + */ +export namespace GetReferenceDocumentRequest { + export const method = "workspace/getReferenceDocument" as const; + export const messageDirection: MessageDirection = MessageDirection.clientToServer; + export const type = new RequestType< + GetReferenceDocumentParams, + GetReferenceDocumentResult, + never + >(method); +} diff --git a/src/sourcekit-lsp/extensions/GetTestsRequest.ts b/src/sourcekit-lsp/extensions/GetTestsRequest.ts new file mode 100644 index 000000000..21da8317e --- /dev/null +++ b/src/sourcekit-lsp/extensions/GetTestsRequest.ts @@ -0,0 +1,114 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the VS Code Swift open source project +// +// Copyright (c) 2021-2024 the VS Code Swift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of VS Code Swift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// We use namespaces to store request information just like vscode-languageclient +/* eslint-disable @typescript-eslint/no-namespace */ + +import { + Location, + TextDocumentIdentifier, + MessageDirection, + RequestType0, + RequestType, +} from "vscode-languageclient"; + +/** Test styles where test-target represents a test target that contains tests. */ +export type TestStyle = "XCTest" | "swift-testing" | "test-target"; + +/** Represents a single test returned from a {@link WorkspaceTestsRequest} or {@link TextDocumentTestsRequest}. */ +export interface LSPTestItem { + /** + * This identifier uniquely identifies the test case or test suite. It can be used to run an individual test (suite). + */ + id: string; + + /** + * Display name describing the test. + */ + label: string; + + /** + * Optional description that appears next to the label. + */ + description?: string; + + /** + * A string that should be used when comparing this item with other items. + * + * When `undefined` the `label` is used. + */ + sortText?: string; + + /** + * Whether the test is disabled. + */ + disabled: boolean; + + /** + * The type of test, eg. the testing framework that was used to declare the test. + */ + style: TestStyle; + + /** + * The location of the test item in the source code. + */ + location: Location; + + /** + * The children of this test item. + * + * For a test suite, this may contain the individual test cases or nested suites. + */ + children: LSPTestItem[]; + + /** + * Tags associated with this test item. + */ + tags: { id: string }[]; +} + +/** + * A request that returns symbols for all the test classes and test methods within the current workspace. + * + * ### LSP Extension + * + * This request is an extension to LSP supported by SourceKit-LSP. + * + * It requires the experimental client capability `"workspace/tests"` to use. + */ +export namespace WorkspaceTestsRequest { + export const method = "workspace/tests" as const; + export const messageDirection: MessageDirection = MessageDirection.clientToServer; + export const type = new RequestType0(method); +} + +/** Parameters used to make a {@link TextDocumentTestsRequest}. */ +export interface TextDocumentTestsParams { + textDocument: TextDocumentIdentifier; +} + +/** + * A request that returns symbols for all the test classes and test methods within a file. + * + * ### LSP Extension + * + * This request is an extension to LSP supported by SourceKit-LSP. + * + * It requires the experimental client capability `"textDocument/tests"` to use. + */ +export namespace TextDocumentTestsRequest { + export const method = "textDocument/tests" as const; + export const messageDirection: MessageDirection = MessageDirection.clientToServer; + export const type = new RequestType(method); +} diff --git a/src/sourcekit-lsp/extensions/LegacyInlayHintRequest.ts b/src/sourcekit-lsp/extensions/LegacyInlayHintRequest.ts new file mode 100644 index 000000000..1c23386b6 --- /dev/null +++ b/src/sourcekit-lsp/extensions/LegacyInlayHintRequest.ts @@ -0,0 +1,72 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the VS Code Swift open source project +// +// Copyright (c) 2021-2024 the VS Code Swift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of VS Code Swift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// We use namespaces to store request information just like vscode-languageclient +/* eslint-disable @typescript-eslint/no-namespace */ + +import { + TextDocumentIdentifier, + Range, + Position, + MessageDirection, + RequestType, +} from "vscode-languageclient"; + +/** Parameters used to make a {@link LegacyInlayHintRequest}. */ +export interface LegacyInlayHintsParams { + /** + * The text document. + */ + textDocument: TextDocumentIdentifier; + + /** + * If set, the reange for which inlay hints are + * requested. If unset, hints for the entire document + * are returned. + */ + range?: Range; + + /** + * The categories of inlay hints that are requested. + * If unset, all categories are returned. + */ + only?: string[]; +} + +/** Inlay Hint (pre Swift 5.6) */ +export interface LegacyInlayHint { + /** + * The position within the code that this hint is + * attached to. + */ + position: Position; + + /** + * The hint's kind, used for more flexible client-side + * styling of the hint. + */ + category?: string; + + /** + * The hint's rendered label. + */ + label: string; +} + +/** Inlay Hints (pre Swift 5.6) */ +export namespace LegacyInlayHintRequest { + export const method = "sourcekit-lsp/inlayHints" as const; + export const messageDirection: MessageDirection = MessageDirection.clientToServer; + export const type = new RequestType(method); +} diff --git a/src/sourcekit-lsp/extensions/PeekDocumentsRequest.ts b/src/sourcekit-lsp/extensions/PeekDocumentsRequest.ts new file mode 100644 index 000000000..ea1302b40 --- /dev/null +++ b/src/sourcekit-lsp/extensions/PeekDocumentsRequest.ts @@ -0,0 +1,68 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the VS Code Swift open source project +// +// Copyright (c) 2021-2024 the VS Code Swift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of VS Code Swift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// We use namespaces to store request information just like vscode-languageclient +/* eslint-disable @typescript-eslint/no-namespace */ + +import { DocumentUri, Position, MessageDirection, RequestType } from "vscode-languageclient"; + +/** Parameters used to make a {@link PeekDocumentsRequest}. */ +export interface PeekDocumentsParams { + /** + * The `DocumentUri` of the text document in which to show the "peeked" editor + */ + uri: DocumentUri; + + /** + * The `Position` in the given text document in which to show the "peeked editor" + */ + position: Position; + + /** + * An array `DocumentUri` of the documents to appear inside the "peeked" editor + */ + locations: DocumentUri[]; +} + +/** Response to indicate the `success` of the {@link PeekDocumentsRequest}. */ +export interface PeekDocumentsResponse { + success: boolean; +} + +/** + * Request from the server to the client to show the given documents in a "peeked" editor **(LSP Extension)** + * + * This request is handled by the client to show the given documents in a + * "peeked" editor (i.e. inline with / inside the editor canvas). This is + * similar to VS Code's built-in "editor.action.peekLocations" command. + * + * - Parameters: + * - uri: The {@link DocumentUri} of the text document in which to show the "peeked" editor + * - position: The {@link Position} in the given text document in which to show the "peeked editor" + * - locations: The {@link DocumentUri} of documents to appear inside the "peeked" editor + * + * - Returns: {@link PeekDocumentsResponse} which indicates the `success` of the request. + * + * ### LSP Extension + * + * This request is an extension to LSP supported by SourceKit-LSP. + * + * It requires the experimental client capability `"workspace/peekDocuments"` to use. + * It also needs the client to handle the request and present the "peeked" editor. + */ +export namespace PeekDocumentsRequest { + export const method = "workspace/peekDocuments" as const; + export const messageDirection: MessageDirection = MessageDirection.clientToServer; + export const type = new RequestType(method); +} diff --git a/src/sourcekit-lsp/extensions/ReIndexProjectRequest.ts b/src/sourcekit-lsp/extensions/ReIndexProjectRequest.ts new file mode 100644 index 000000000..184ef12ab --- /dev/null +++ b/src/sourcekit-lsp/extensions/ReIndexProjectRequest.ts @@ -0,0 +1,35 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the VS Code Swift open source project +// +// Copyright (c) 2021-2024 the VS Code Swift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of VS Code Swift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// We use namespaces to store request information just like vscode-languageclient +/* eslint-disable @typescript-eslint/no-namespace */ + +import { MessageDirection, RequestType0 } from "vscode-languageclient"; + +/** + * Re-index all files open in the SourceKit-LSP server. + * + * Users should not need to rely on this request. The index should always be updated automatically in the background. + * Having to invoke this request means there is a bug in SourceKit-LSP's automatic re-indexing. It does, however, offer + * a workaround to re-index files when such a bug occurs where otherwise there would be no workaround. + * + * ### LSP Extension + * + * This request is an extension to LSP supported by SourceKit-LSP. + */ +export namespace ReIndexProjectRequest { + export const method = "workspace/triggerReindex" as const; + export const messageDirection: MessageDirection = MessageDirection.clientToServer; + export const type = new RequestType0("workspace/triggerReindex"); +} diff --git a/src/sourcekit-lsp/extensions/SourceKitLogMessageNotification.ts b/src/sourcekit-lsp/extensions/SourceKitLogMessageNotification.ts new file mode 100644 index 000000000..f81412817 --- /dev/null +++ b/src/sourcekit-lsp/extensions/SourceKitLogMessageNotification.ts @@ -0,0 +1,38 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the VS Code Swift open source project +// +// Copyright (c) 2021-2024 the VS Code Swift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of VS Code Swift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// We use namespaces to store request information just like vscode-languageclient +/* eslint-disable @typescript-eslint/no-namespace */ + +import { LogMessageParams, MessageDirection, NotificationType } from "vscode-languageclient"; + +/** Parameters sent in a {@link SourceKitLogMessageNotification}. */ +export interface SourceKitLogMessageParams extends LogMessageParams { + logName?: string; +} + +/** + * The log message notification is sent from the server to the client to ask the client to + * log a particular message. + * + * ### LSP Extension + * + * This notification has the same behaviour as the `window/logMessage` notification built + * into the LSP. However, SourceKit-LSP adds extra information to the parameters. + */ +export namespace SourceKitLogMessageNotification { + export const method = "window/logMessage" as const; + export const messageDirection: MessageDirection = MessageDirection.serverToClient; + export const type = new NotificationType(method); +} diff --git a/src/sourcekit-lsp/extensions/SymbolInfoRequest.ts b/src/sourcekit-lsp/extensions/SymbolInfoRequest.ts new file mode 100644 index 000000000..0fdcbbf96 --- /dev/null +++ b/src/sourcekit-lsp/extensions/SymbolInfoRequest.ts @@ -0,0 +1,156 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the VS Code Swift open source project +// +// Copyright (c) 2021-2024 the VS Code Swift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of VS Code Swift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// We use namespaces to store request information just like vscode-languageclient +/* eslint-disable @typescript-eslint/no-namespace */ + +import { + TextDocumentIdentifier, + Position, + Location, + SymbolKind, + MessageDirection, + RequestType, +} from "vscode-languageclient"; + +/** Parameters used to make a {@link SymbolInfoRequest}. */ +export interface SymbolInfoParams { + /** The document in which to lookup the symbol location. */ + textDocument: TextDocumentIdentifier; + + /** The document location at which to lookup symbol information. */ + position: Position; +} + +/** Information about which module a symbol is defined in. */ +export interface ModuleInfo { + /** The name of the module in which the symbol is defined. */ + moduleName: string; + + /** If the symbol is defined within a subgroup of a module, the name of the group. */ + groupName?: string; +} + +/** Detailed information about a symbol, such as the response to a {@link SymbolInfoRequest}. */ +export interface SymbolDetails { + /** The name of the symbol, if any. */ + name?: string; + + /** + * The name of the containing type for the symbol, if any. + * + * For example, in the following snippet, the `containerName` of `foo()` is `C`. + * + * ```c++ + * class C { + * void foo() {} + * } + * ``` + */ + containerName?: string; + + /** The USR of the symbol, if any. */ + usr?: string; + + /** + * Best known declaration or definition location without global knowledge. + * + * For a local or private variable, this is generally the canonical definition location - + * appropriate as a response to a `textDocument/definition` request. For global symbols this is + * the best known location within a single compilation unit. For example, in C++ this might be + * the declaration location from a header as opposed to the definition in some other + * translation unit. + * */ + bestLocalDeclaration?: Location; + + /** The kind of the symbol */ + kind?: SymbolKind; + + /** + * Whether the symbol is a dynamic call for which it isn't known which method will be invoked at runtime. This is + * the case for protocol methods and class functions. + * + * Optional because `clangd` does not return whether a symbol is dynamic. + */ + isDynamic?: boolean; + + /** + * Whether this symbol is defined in the SDK or standard library. + * + * This property only applies to Swift symbols. + */ + isSystem?: boolean; + + /** + * If the symbol is dynamic, the USRs of the types that might be called. + * + * This is relevant in the following cases: + * ```swift + * class A { + * func doThing() {} + * } + * class B: A {} + * class C: B { + * override func doThing() {} + * } + * class D: A { + * override func doThing() {} + * } + * func test(value: B) { + * value.doThing() + * } + * ``` + * + * The USR of the called function in `value.doThing` is `A.doThing` (or its + * mangled form) but it can never call `D.doThing`. In this case, the + * receiver USR would be `B`, indicating that only overrides of subtypes in + * `B` may be called dynamically. + */ + receiverUsrs?: string[]; + + /** + * If the symbol is defined in a module that doesn't have source information associated with it, the name and group + * and group name that defines this symbol. + * + * This property only applies to Swift symbols. + */ + systemModule?: ModuleInfo; +} + +/** + * Request for semantic information about the symbol at a given location **(LSP Extension)**. + * + * This request looks up the symbol (if any) at a given text document location and returns + * SymbolDetails for that location, including information such as the symbol's USR. The symbolInfo + * request is not primarily designed for editors, but instead as an implementation detail of how + * one LSP implementation (e.g. SourceKit) gets information from another (e.g. clangd) to use in + * performing index queries or otherwise implementing the higher level requests such as definition. + * + * - Parameters: + * - textDocument: The document in which to lookup the symbol location. + * - position: The document location at which to lookup symbol information. + * + * - Returns: `[SymbolDetails]` for the given location, which may have multiple elements if there are + * multiple references, or no elements if there is no symbol at the given location. + * + * ### LSP Extension + * + * This request is an extension to LSP supported by SourceKit-LSP and clangd. It does *not* require + * any additional client or server capabilities to use. + */ +export namespace SymbolInfoRequest { + export const method = "textDocument/documentSymbol" as const; + export const messageDirection: MessageDirection = MessageDirection.clientToServer; + export const type = new RequestType(method); +} diff --git a/src/sourcekit-lsp/extensions/index.ts b/src/sourcekit-lsp/extensions/index.ts new file mode 100644 index 000000000..93553f663 --- /dev/null +++ b/src/sourcekit-lsp/extensions/index.ts @@ -0,0 +1,23 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the VS Code Swift open source project +// +// Copyright (c) 2021-2024 the VS Code Swift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of VS Code Swift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// Definitions for non-standard requests used by sourcekit-lsp + +export * from "./GetReferenceDocumentRequest"; +export * from "./GetTestsRequest"; +export * from "./LegacyInlayHintRequest"; +export * from "./PeekDocumentsRequest"; +export * from "./ReIndexProjectRequest"; +export * from "./SourceKitLogMessageNotification"; +export * from "./SymbolInfoRequest"; diff --git a/src/sourcekit-lsp/getReferenceDocument.ts b/src/sourcekit-lsp/getReferenceDocument.ts index b913a18cf..7a29b98be 100644 --- a/src/sourcekit-lsp/getReferenceDocument.ts +++ b/src/sourcekit-lsp/getReferenceDocument.ts @@ -14,7 +14,7 @@ import * as vscode from "vscode"; import * as langclient from "vscode-languageclient/node"; -import { GetReferenceDocumentParams, GetReferenceDocumentRequest } from "./lspExtensions"; +import { GetReferenceDocumentParams, GetReferenceDocumentRequest } from "./extensions"; export function activateGetReferenceDocument(client: langclient.LanguageClient): vscode.Disposable { const getReferenceDocument = vscode.workspace.registerTextDocumentContentProvider( @@ -25,7 +25,11 @@ export function activateGetReferenceDocument(client: langclient.LanguageClient): uri: client.code2ProtocolConverter.asUri(uri), }; - const result = await client.sendRequest(GetReferenceDocumentRequest, params, token); + const result = await client.sendRequest( + GetReferenceDocumentRequest.type, + params, + token + ); if (result) { return result.content; diff --git a/src/sourcekit-lsp/inlayHints.ts b/src/sourcekit-lsp/inlayHints.ts index 400329ee1..c73eebf42 100644 --- a/src/sourcekit-lsp/inlayHints.ts +++ b/src/sourcekit-lsp/inlayHints.ts @@ -16,7 +16,7 @@ import * as vscode from "vscode"; import * as langclient from "vscode-languageclient/node"; import configuration from "../configuration"; import { LanguageClientManager } from "./LanguageClientManager"; -import { legacyInlayHintsRequest } from "./lspExtensions"; +import { LegacyInlayHintRequest } from "./extensions"; /** Provide Inlay Hints using sourcekit-lsp */ class SwiftLegacyInlayHintsProvider implements vscode.InlayHintsProvider { @@ -37,7 +37,7 @@ class SwiftLegacyInlayHintsProvider implements vscode.InlayHintsProvider { textDocument: this.client.code2ProtocolConverter.asTextDocumentIdentifier(document), range: { start: range.start, end: range.end }, }; - const result = this.client.sendRequest(legacyInlayHintsRequest, params, token); + const result = this.client.sendRequest(LegacyInlayHintRequest.type, params, token); return result.then( hints => { return hints.map(hint => { diff --git a/src/sourcekit-lsp/lspExtensions.ts b/src/sourcekit-lsp/lspExtensions.ts deleted file mode 100644 index af5cda62a..000000000 --- a/src/sourcekit-lsp/lspExtensions.ts +++ /dev/null @@ -1,205 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the VS Code Swift open source project -// -// Copyright (c) 2021-2024 the VS Code Swift project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of VS Code Swift project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -import * as ls from "vscode-languageserver-protocol"; -import * as langclient from "vscode-languageclient/node"; -import * as vscode from "vscode"; - -// Definitions for non-standard requests used by sourcekit-lsp - -// Peek Documents -export interface PeekDocumentsParams { - /** - * The `DocumentUri` of the text document in which to show the "peeked" editor - */ - uri: langclient.DocumentUri; - - /** - * The `Position` in the given text document in which to show the "peeked editor" - */ - position: vscode.Position; - - /** - * An array `DocumentUri` of the documents to appear inside the "peeked" editor - */ - locations: langclient.DocumentUri[]; -} - -/** - * Response to indicate the `success` of the `PeekDocumentsRequest` - */ -export interface PeekDocumentsResult { - success: boolean; -} - -/** - * Request from the server to the client to show the given documents in a "peeked" editor. - * - * This request is handled by the client to show the given documents in a "peeked" editor (i.e. inline with / inside the editor canvas). - * - * It requires the experimental client capability `"workspace/peekDocuments"` to use. - */ -export const PeekDocumentsRequest = new langclient.RequestType< - PeekDocumentsParams, - PeekDocumentsResult, - unknown ->("workspace/peekDocuments"); - -// Get Reference Document -export interface GetReferenceDocumentParams { - /** - * The `DocumentUri` of the custom scheme url for which content is required - */ - uri: langclient.DocumentUri; -} - -/** - * Response containing `content` of `GetReferenceDocumentRequest` - */ -export interface GetReferenceDocumentResult { - content: string; -} - -/** - * Request from the client to the server asking for contents of a URI having a custom scheme - * For example: "sourcekit-lsp:" - */ -export const GetReferenceDocumentRequest = new langclient.RequestType< - GetReferenceDocumentParams, - GetReferenceDocumentResult, - unknown ->("workspace/getReferenceDocument"); - -// Inlay Hints (pre Swift 5.6) -export interface LegacyInlayHintsParams { - /** - * The text document. - */ - textDocument: langclient.TextDocumentIdentifier; - - /** - * If set, the reange for which inlay hints are - * requested. If unset, hints for the entire document - * are returned. - */ - range?: langclient.Range; - - /** - * The categories of inlay hints that are requested. - * If unset, all categories are returned. - */ - only?: string[]; -} - -export interface LegacyInlayHint { - /** - * The position within the code that this hint is - * attached to. - */ - position: langclient.Position; - - /** - * The hint's kind, used for more flexible client-side - * styling of the hint. - */ - category?: string; - - /** - * The hint's rendered label. - */ - label: string; -} - -export const legacyInlayHintsRequest = new langclient.RequestType< - LegacyInlayHintsParams, - LegacyInlayHint[], - unknown ->("sourcekit-lsp/inlayHints"); - -// Test styles where test-target represents a test target that contains tests -export type TestStyle = "XCTest" | "swift-testing" | "test-target"; - -// Listing tests -export interface LSPTestItem { - /** - * This identifier uniquely identifies the test case or test suite. It can be used to run an individual test (suite). - */ - id: string; - - /** - * Display name describing the test. - */ - label: string; - - /** - * Optional description that appears next to the label. - */ - description?: string; - - /** - * A string that should be used when comparing this item with other items. - * - * When `undefined` the `label` is used. - */ - sortText?: string; - - /** - * Whether the test is disabled. - */ - disabled: boolean; - - /** - * The type of test, eg. the testing framework that was used to declare the test. - */ - style: TestStyle; - - /** - * The location of the test item in the source code. - */ - location: ls.Location; - - /** - * The children of this test item. - * - * For a test suite, this may contain the individual test cases or nested suites. - */ - children: LSPTestItem[]; - - /** - * Tags associated with this test item. - */ - tags: { id: string }[]; -} - -export const workspaceTestsRequest = new langclient.RequestType< - Record, - LSPTestItem[], - unknown ->("workspace/tests"); - -interface DocumentTestsParams { - textDocument: { - uri: ls.URI; - }; -} - -export const textDocumentTestsRequest = new langclient.RequestType< - DocumentTestsParams, - LSPTestItem[], - unknown ->("textDocument/tests"); - -export const reindexProjectRequest = new langclient.RequestType( - "workspace/triggerReindex" -); diff --git a/src/sourcekit-lsp/peekDocuments.ts b/src/sourcekit-lsp/peekDocuments.ts index 26b818176..51bc5a99b 100644 --- a/src/sourcekit-lsp/peekDocuments.ts +++ b/src/sourcekit-lsp/peekDocuments.ts @@ -14,7 +14,7 @@ import * as vscode from "vscode"; import * as langclient from "vscode-languageclient/node"; -import { PeekDocumentsParams, PeekDocumentsRequest } from "./lspExtensions"; +import { PeekDocumentsParams, PeekDocumentsRequest } from "./extensions"; /** * Opens a peeked editor in `uri` at `position` having contents from `locations`. diff --git a/test/integration-tests/testexplorer/LSPTestDiscovery.test.ts b/test/integration-tests/testexplorer/LSPTestDiscovery.test.ts index 88625afd9..48a60ccc4 100644 --- a/test/integration-tests/testexplorer/LSPTestDiscovery.test.ts +++ b/test/integration-tests/testexplorer/LSPTestDiscovery.test.ts @@ -14,34 +14,34 @@ import * as assert from "assert"; import * as vscode from "vscode"; -import * as ls from "vscode-languageserver-protocol"; -import * as p2c from "vscode-languageclient/lib/common/protocolConverter"; import { beforeEach } from "mocha"; -import { InitializeResult, RequestType } from "vscode-languageclient"; +import { + LanguageClient, + MessageSignature, + RequestType0, + RequestType, + Location, + Range, + Position, +} from "vscode-languageclient/node"; +import * as p2c from "vscode-languageclient/lib/common/protocolConverter"; import { LSPTestDiscovery } from "../../../src/TestExplorer/LSPTestDiscovery"; import { SwiftPackage, Target, TargetType } from "../../../src/SwiftPackage"; import { TestClass } from "../../../src/TestExplorer/TestDiscovery"; import { SwiftToolchain } from "../../../src/toolchain/toolchain"; import { LSPTestItem, - textDocumentTestsRequest, - workspaceTestsRequest, -} from "../../../src/sourcekit-lsp/lspExtensions"; + TextDocumentTestsRequest, + WorkspaceTestsRequest, +} from "../../../src/sourcekit-lsp/extensions"; +import { instance, mockFn, mockObject } from "../../MockUtils"; +import { LanguageClientManager } from "../../../src/sourcekit-lsp/LanguageClientManager"; class TestLanguageClient { private responses = new Map(); private responseVersions = new Map(); - - setResponse(type: RequestType, response: R) { - this.responses.set(type.method, response); - } - - setResponseVersion(type: RequestType, version: number) { - this.responseVersions.set(type.method, version); - } - - get initializeResult(): InitializeResult | undefined { - return { + private client = mockObject({ + initializeResult: { capabilities: { experimental: { "textDocument/tests": { @@ -52,15 +52,30 @@ class TestLanguageClient { }, }, }, - }; + }, + protocol2CodeConverter: p2c.createConverter(undefined, true, true), + sendRequest: mockFn(s => + s.callsFake((type: MessageSignature): Promise => { + const response = this.responses.get(type.method); + return response + ? Promise.resolve(response) + : Promise.reject("Method not implemented"); + }) + ), + }); + + public get languageClient(): LanguageClient { + return instance(this.client); } - get protocol2CodeConverter(): p2c.Converter { - return p2c.createConverter(undefined, true, true); + + setResponse(type: RequestType0, response: R): void; + setResponse(type: RequestType, response: R): void; + setResponse(type: MessageSignature, response: unknown) { + this.responses.set(type.method, response); } - sendRequest(type: RequestType): Promise { - const response = this.responses.get(type.method) as R | undefined; - return response ? Promise.resolve(response) : Promise.reject("Method not implemented"); + setResponseVersion(type: MessageSignature, version: number) { + this.responseVersions.set(type.method, version); } } @@ -70,27 +85,37 @@ suite("LSPTestDiscovery Suite", () => { let pkg: SwiftPackage; const file = vscode.Uri.file("file:///some/file.swift"); - beforeEach(async () => { + beforeEach(async function () { + this.timeout(10000000); pkg = await SwiftPackage.create(file, await SwiftToolchain.create()); client = new TestLanguageClient(); - discoverer = new LSPTestDiscovery({ - useLanguageClient(process) { - return process(client, new vscode.CancellationTokenSource().token); - }, - }); + discoverer = new LSPTestDiscovery( + instance( + mockObject({ + useLanguageClient: mockFn(s => + s.callsFake(process => { + return process( + client.languageClient, + new vscode.CancellationTokenSource().token + ); + }) + ), + }) + ) + ); }); suite("Empty responses", () => { - test(textDocumentTestsRequest.method, async () => { - client.setResponse(textDocumentTestsRequest, []); + test(TextDocumentTestsRequest.method, async () => { + client.setResponse(TextDocumentTestsRequest.type, []); const testClasses = await discoverer.getDocumentTests(pkg, file); assert.deepStrictEqual(testClasses, []); }); - test(workspaceTestsRequest.method, async () => { - client.setResponse(workspaceTestsRequest, []); + test(WorkspaceTestsRequest.method, async () => { + client.setResponse(WorkspaceTestsRequest.type, []); const testClasses = await discoverer.getWorkspaceTests(pkg); @@ -99,14 +124,14 @@ suite("LSPTestDiscovery Suite", () => { }); suite("Unsupported LSP version", () => { - test(textDocumentTestsRequest.method, async () => { - client.setResponseVersion(textDocumentTestsRequest, 0); + test(TextDocumentTestsRequest.method, async () => { + client.setResponseVersion(TextDocumentTestsRequest.type, 0); await assert.rejects(() => discoverer.getDocumentTests(pkg, file)); }); - test(workspaceTestsRequest.method, async () => { - client.setResponseVersion(workspaceTestsRequest, 0); + test(WorkspaceTestsRequest.method, async () => { + client.setResponseVersion(WorkspaceTestsRequest.type, 0); await assert.rejects(() => discoverer.getWorkspaceTests(pkg)); }); @@ -140,9 +165,9 @@ suite("LSPTestDiscovery Suite", () => { disabled: false, style: "swift-testing", tags: [], - location: ls.Location.create( + location: Location.create( file.fsPath, - ls.Range.create(ls.Position.create(1, 0), ls.Position.create(2, 0)) + Range.create(Position.create(1, 0), Position.create(2, 0)) ), children: [], }, @@ -150,21 +175,21 @@ suite("LSPTestDiscovery Suite", () => { expected = items.map(item => ({ ...item, - location: client.protocol2CodeConverter.asLocation(item.location), + location: client.languageClient.protocol2CodeConverter.asLocation(item.location), children: [], })); }); - test(textDocumentTestsRequest.method, async () => { - client.setResponse(textDocumentTestsRequest, items); + test(TextDocumentTestsRequest.method, async () => { + client.setResponse(TextDocumentTestsRequest.type, items); const testClasses = await discoverer.getDocumentTests(pkg, file); assert.deepStrictEqual(testClasses, expected); }); - test(workspaceTestsRequest.method, async () => { - client.setResponse(workspaceTestsRequest, items); + test(WorkspaceTestsRequest.method, async () => { + client.setResponse(WorkspaceTestsRequest.type, items); const testClasses = await discoverer.getWorkspaceTests(pkg); @@ -179,7 +204,7 @@ suite("LSPTestDiscovery Suite", () => { style: "XCTest", })); - client.setResponse(workspaceTestsRequest, items); + client.setResponse(WorkspaceTestsRequest.type, items); const testClasses = await discoverer.getWorkspaceTests(pkg); @@ -193,7 +218,7 @@ suite("LSPTestDiscovery Suite", () => { id: `${testTargetName}.topLevelTest()`, })); - client.setResponse(workspaceTestsRequest, items); + client.setResponse(WorkspaceTestsRequest.type, items); const target: Target = { c99name: testTargetName, diff --git a/test/unit-tests/sourcekit-lsp/LanguageClientManager.test.ts b/test/unit-tests/sourcekit-lsp/LanguageClientManager.test.ts index bd3091910..5c94e5f06 100644 --- a/test/unit-tests/sourcekit-lsp/LanguageClientManager.test.ts +++ b/test/unit-tests/sourcekit-lsp/LanguageClientManager.test.ts @@ -31,7 +31,6 @@ import { mockGlobalValue, mockFn, } from "../../MockUtils"; -import * as langClient from "vscode-languageclient/node"; import { Code2ProtocolConverter, DidChangeWorkspaceFoldersNotification, @@ -43,8 +42,10 @@ import { import { LanguageClientManager } from "../../../src/sourcekit-lsp/LanguageClientManager"; import configuration from "../../../src/configuration"; import { FolderContext } from "../../../src/FolderContext"; +import { LanguageClientFactory } from "../../../src/sourcekit-lsp/LanguageClientFactory"; suite("LanguageClientManager Suite", () => { + let languageClientFactoryMock: MockedObject; let languageClientMock: MockedObject; let mockedConverter: MockedObject; let changeStateEmitter: AsyncEventEmitter; @@ -54,7 +55,6 @@ suite("LanguageClientManager Suite", () => { let mockedToolchain: MockedObject; let mockedBuildFlags: MockedObject; - const mockedLangClientModule = mockGlobalModule(langClient); const mockedConfig = mockGlobalModule(configuration); const mockedEnvironment = mockGlobalValue(process, "env"); const mockedLspConfig = mockGlobalObject(configuration, "lsp"); @@ -157,7 +157,9 @@ suite("LanguageClientManager Suite", () => { onDidChangeState: mockFn(s => s.callsFake(changeStateEmitter.event)), }); // `new LanguageClient()` will always return the mocked LanguageClient - mockedLangClientModule.LanguageClient.returns(instance(languageClientMock)); + languageClientFactoryMock = mockObject({ + createLanguageClient: mockFn(s => s.returns(instance(languageClientMock))), + }); // LSP configuration defaults mockedConfig.path = ""; mockedConfig.buildArguments = []; @@ -172,11 +174,11 @@ suite("LanguageClientManager Suite", () => { }); test("launches SourceKit-LSP on startup", async () => { - const sut = new LanguageClientManager(instance(mockedWorkspace)); + const sut = new LanguageClientManager(instance(mockedWorkspace), languageClientFactoryMock); await waitForReturnedPromises(languageClientMock.start); expect(sut.state).to.equal(State.Running); - expect(mockedLangClientModule.LanguageClient).to.have.been.calledOnceWith( + expect(languageClientFactoryMock.createLanguageClient).to.have.been.calledOnceWith( /* id */ match.string, /* name */ match.string, /* serverOptions */ match.has("command", "/path/to/toolchain/bin/sourcekit-lsp"), @@ -250,7 +252,7 @@ suite("LanguageClientManager Suite", () => { }, workspaceContext: instance(mockedWorkspace), }); - new LanguageClientManager(instance(mockedWorkspace)); + new LanguageClientManager(instance(mockedWorkspace), languageClientFactoryMock); await waitForReturnedPromises(languageClientMock.start); // Add the first folder @@ -311,21 +313,21 @@ suite("LanguageClientManager Suite", () => { test("doesn't launch SourceKit-LSP if disabled by the user", async () => { mockedLspConfig.disable = true; - const sut = new LanguageClientManager(instance(mockedWorkspace)); + const sut = new LanguageClientManager(instance(mockedWorkspace), languageClientFactoryMock); await waitForReturnedPromises(languageClientMock.start); expect(sut.state).to.equal(State.Stopped); - expect(mockedLangClientModule.LanguageClient).to.not.have.been.called; + expect(languageClientFactoryMock.createLanguageClient).to.not.have.been.called; expect(languageClientMock.start).to.not.have.been.called; }); test("user can provide a custom SourceKit-LSP executable", async () => { mockedLspConfig.serverPath = "/path/to/my/custom/sourcekit-lsp"; - const sut = new LanguageClientManager(instance(mockedWorkspace)); + const sut = new LanguageClientManager(instance(mockedWorkspace), languageClientFactoryMock); await waitForReturnedPromises(languageClientMock.start); expect(sut.state).to.equal(State.Running); - expect(mockedLangClientModule.LanguageClient).to.have.been.calledOnceWith( + expect(languageClientFactoryMock.createLanguageClient).to.have.been.calledOnceWith( /* id */ match.string, /* name */ match.string, /* serverOptions */ match.has("command", "/path/to/my/custom/sourcekit-lsp"), @@ -364,11 +366,14 @@ suite("LanguageClientManager Suite", () => { }); test("doesn't launch SourceKit-LSP on startup", async () => { - const sut = new LanguageClientManager(instance(mockedWorkspace)); + const sut = new LanguageClientManager( + instance(mockedWorkspace), + languageClientFactoryMock + ); await waitForReturnedPromises(languageClientMock.start); expect(sut.state).to.equal(State.Stopped); - expect(mockedLangClientModule.LanguageClient).to.not.have.been.called; + expect(languageClientFactoryMock.createLanguageClient).to.not.have.been.called; expect(languageClientMock.start).to.not.have.been.called; }); @@ -382,7 +387,10 @@ suite("LanguageClientManager Suite", () => { ), }) ); - const sut = new LanguageClientManager(instance(mockedWorkspace)); + const sut = new LanguageClientManager( + instance(mockedWorkspace), + languageClientFactoryMock + ); await waitForReturnedPromises(languageClientMock.start); // Add the folder to the workspace @@ -393,7 +401,7 @@ suite("LanguageClientManager Suite", () => { }); expect(sut.state).to.equal(State.Running); - expect(mockedLangClientModule.LanguageClient).to.have.been.calledOnceWith( + expect(languageClientFactoryMock.createLanguageClient).to.have.been.calledOnceWith( /* id */ match.string, /* name */ match.string, /* serverOptions */ match.object, @@ -411,7 +419,10 @@ suite("LanguageClientManager Suite", () => { document: instance(mockedTextDocument), }) ); - const sut = new LanguageClientManager(instance(mockedWorkspace)); + const sut = new LanguageClientManager( + instance(mockedWorkspace), + languageClientFactoryMock + ); await waitForReturnedPromises(languageClientMock.start); // Add the first folder to the workspace @@ -431,8 +442,8 @@ suite("LanguageClientManager Suite", () => { }); expect(sut.state).to.equal(State.Running); - expect(mockedLangClientModule.LanguageClient).to.have.been.calledTwice; - expect(mockedLangClientModule.LanguageClient).to.have.been.calledWith( + expect(languageClientFactoryMock.createLanguageClient).to.have.been.calledTwice; + expect(languageClientFactoryMock.createLanguageClient).to.have.been.calledWith( /* id */ match.string, /* name */ match.string, /* serverOptions */ match.object, From 36cd225b0ea3f072caf245c0214b01b2b964e5b5 Mon Sep 17 00:00:00 2001 From: Matthew Bastien Date: Mon, 2 Dec 2024 16:13:29 -0500 Subject: [PATCH 13/22] Fix LanguageClientManager unit tests (#1237) --- test/integration-tests/commands/build.test.ts | 11 ++++++----- .../sourcekit-lsp/LanguageClientManager.test.ts | 13 +++++++------ 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/test/integration-tests/commands/build.test.ts b/test/integration-tests/commands/build.test.ts index 3c8decc75..2fc2e178d 100644 --- a/test/integration-tests/commands/build.test.ts +++ b/test/integration-tests/commands/build.test.ts @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import * as vscode from "vscode"; -import * as fs from "fs"; +import * as fs from "fs/promises"; import * as path from "path"; import { expect } from "chai"; import { waitForNoRunningTasks } from "../../utilities"; @@ -29,6 +29,7 @@ import { folderInRootWorkspace, updateSettings, } from "../utilities/testutilities"; +import { pathExists } from "../../../src/utilities/filesystem"; suite("Build Commands", function () { let folderContext: FolderContext; @@ -60,8 +61,8 @@ suite("Build Commands", function () { teardown(async () => { // Remove the build directory after each test case - if (fs.existsSync(buildPath)) { - fs.rmSync(buildPath, { recursive: true, force: true }); + if (await pathExists(buildPath)) { + await fs.rm(buildPath, { recursive: true, force: true }); } }); @@ -79,12 +80,12 @@ suite("Build Commands", function () { let result = await vscode.commands.executeCommand(Commands.RUN); expect(result).to.be.true; - const beforeItemCount = fs.readdirSync(buildPath).length; + const beforeItemCount = (await fs.readdir(buildPath)).length; result = await vscode.commands.executeCommand(Commands.CLEAN_BUILD); expect(result).to.be.true; - const afterItemCount = fs.readdirSync(buildPath).length; + const afterItemCount = (await fs.readdir(buildPath)).length; // This test will run in order after the Swift: Run Build test, // where .build folder is going to be filled with built artifacts. // After executing the clean command the build directory is guranteed to have less entry. diff --git a/test/unit-tests/sourcekit-lsp/LanguageClientManager.test.ts b/test/unit-tests/sourcekit-lsp/LanguageClientManager.test.ts index 5c94e5f06..d82e76bc9 100644 --- a/test/unit-tests/sourcekit-lsp/LanguageClientManager.test.ts +++ b/test/unit-tests/sourcekit-lsp/LanguageClientManager.test.ts @@ -190,10 +190,11 @@ suite("LanguageClientManager Suite", () => { test("chooses the correct backgroundIndexing value is auto, swift version if 6.0.0", async () => { mockedWorkspace.swiftVersion = new Version(6, 0, 0); mockedConfig.backgroundIndexing = "auto"; - new LanguageClientManager(instance(mockedWorkspace)); + + new LanguageClientManager(instance(mockedWorkspace), languageClientFactoryMock); await waitForReturnedPromises(languageClientMock.start); - expect(mockedLangClientModule.LanguageClient).to.have.been.calledOnceWith( + expect(languageClientFactoryMock.createLanguageClient).to.have.been.calledOnceWith( match.string, match.string, match.object, @@ -205,10 +206,10 @@ suite("LanguageClientManager Suite", () => { mockedWorkspace.swiftVersion = new Version(6, 1, 0); mockedConfig.backgroundIndexing = "auto"; - new LanguageClientManager(instance(mockedWorkspace)); + new LanguageClientManager(instance(mockedWorkspace), languageClientFactoryMock); await waitForReturnedPromises(languageClientMock.start); - expect(mockedLangClientModule.LanguageClient).to.have.been.calledOnceWith( + expect(languageClientFactoryMock.createLanguageClient).to.have.been.calledOnceWith( match.string, match.string, match.object, @@ -220,10 +221,10 @@ suite("LanguageClientManager Suite", () => { mockedWorkspace.swiftVersion = new Version(6, 0, 0); mockedConfig.backgroundIndexing = "on"; - new LanguageClientManager(instance(mockedWorkspace)); + new LanguageClientManager(instance(mockedWorkspace), languageClientFactoryMock); await waitForReturnedPromises(languageClientMock.start); - expect(mockedLangClientModule.LanguageClient).to.have.been.calledOnceWith( + expect(languageClientFactoryMock.createLanguageClient).to.have.been.calledOnceWith( match.string, match.string, match.object, From e7f81c930e752bb049fc7c5fae8e98684bd8b54d Mon Sep 17 00:00:00 2001 From: Michael Weng Date: Tue, 3 Dec 2024 14:14:01 -0500 Subject: [PATCH 14/22] Address some issues found when running test locally (#1217) * Use expect message to make code more concise - Give a little bit more leeway for timeout since CI machine will be slower than development machine * Shift test step around to improve test stabliltiy - We want to make sure we don't run spm command quickly in succession - Running unedit command right after spm update command sometime causes CI git to complain about git HEAD corruption * Test spm update on a simple default package - The spm update command is not stable when used alongside with spm edit, so let's just keep things simple by running the command on defaultPackage instead. * Minor clean up to the comment and unused import * Adjust to match latest backgroundCompilation change --- assets/test/.vscode/settings.json | 3 +- test/integration-tests/commands/build.test.ts | 17 +++---- .../commands/dependency.test.ts | 45 +++++++++---------- 3 files changed, 29 insertions(+), 36 deletions(-) diff --git a/assets/test/.vscode/settings.json b/assets/test/.vscode/settings.json index 500511990..4a09afde5 100644 --- a/assets/test/.vscode/settings.json +++ b/assets/test/.vscode/settings.json @@ -6,5 +6,6 @@ "-Xswiftc", "-DTEST_ARGUMENT_SET_VIA_TEST_BUILD_ARGUMENTS_SETTING" ], - "lldb.verboseLogging": true + "lldb.verboseLogging": true, + "swift.backgroundCompilation": false } \ No newline at end of file diff --git a/test/integration-tests/commands/build.test.ts b/test/integration-tests/commands/build.test.ts index 2fc2e178d..0b5d5e9e2 100644 --- a/test/integration-tests/commands/build.test.ts +++ b/test/integration-tests/commands/build.test.ts @@ -29,12 +29,13 @@ import { folderInRootWorkspace, updateSettings, } from "../utilities/testutilities"; -import { pathExists } from "../../../src/utilities/filesystem"; suite("Build Commands", function () { + // Default timeout is a bit too short, give it a little bit more time + this.timeout(30 * 1000); + let folderContext: FolderContext; let workspaceContext: WorkspaceContext; - let buildPath: string; const uri = testAssetUri("defaultPackage/Sources/PackageExe/main.swift"); const breakpoints = [ new vscode.SourceBreakpoint(new vscode.Location(uri, new vscode.Position(2, 0))), @@ -45,7 +46,6 @@ suite("Build Commands", function () { workspaceContext = ctx; await waitForNoRunningTasks(); folderContext = await folderInRootWorkspace("defaultPackage", workspaceContext); - buildPath = path.join(folderContext.folder.fsPath, ".build"); await workspaceContext.focusFolder(folderContext); await vscode.window.showTextDocument(uri); const settingsTeardown = await updateSettings({ @@ -59,13 +59,6 @@ suite("Build Commands", function () { }, }); - teardown(async () => { - // Remove the build directory after each test case - if (await pathExists(buildPath)) { - await fs.rm(buildPath, { recursive: true, force: true }); - } - }); - test("Swift: Run Build", async () => { // A breakpoint will have not effect on the Run command. vscode.debug.addBreakpoints(breakpoints); @@ -80,14 +73,14 @@ suite("Build Commands", function () { let result = await vscode.commands.executeCommand(Commands.RUN); expect(result).to.be.true; + const buildPath = path.join(folderContext.folder.fsPath, ".build"); const beforeItemCount = (await fs.readdir(buildPath)).length; result = await vscode.commands.executeCommand(Commands.CLEAN_BUILD); expect(result).to.be.true; const afterItemCount = (await fs.readdir(buildPath)).length; - // This test will run in order after the Swift: Run Build test, - // where .build folder is going to be filled with built artifacts. + // .build folder is going to be filled with built artifacts after Commands.RUN command // After executing the clean command the build directory is guranteed to have less entry. expect(afterItemCount).to.be.lessThan(beforeItemCount); }); diff --git a/test/integration-tests/commands/dependency.test.ts b/test/integration-tests/commands/dependency.test.ts index a0f6592e6..55d5543c0 100644 --- a/test/integration-tests/commands/dependency.test.ts +++ b/test/integration-tests/commands/dependency.test.ts @@ -36,7 +36,27 @@ suite("Dependency Commmands Test Suite", function () { // 15 seconds for each test should be more than enough this.timeout(15 * 1000); - suite("spm Resolve Update Contract Tests", function () { + suite("spm Update Contract Tests", function () { + let folderContext: FolderContext; + let workspaceContext: WorkspaceContext; + + activateExtensionForSuite({ + async setup(ctx) { + workspaceContext = ctx; + await waitForNoRunningTasks(); + folderContext = await folderInRootWorkspace("defaultPackage", workspaceContext); + await workspaceContext.focusFolder(folderContext); + }, + }); + + test("Contract: spm update", async function () { + // Contract: spm update + const result = await vscode.commands.executeCommand(Commands.UPDATE_DEPENDENCIES); + expect(result).to.be.true; + }); + }); + + suite("spm Resolve Contract Tests", function () { let folderContext: FolderContext; let workspaceContext: WorkspaceContext; @@ -100,10 +120,7 @@ suite("Dependency Commmands Test Suite", function () { // This will now pass as we have the required library const { exitCode, output } = await executeTaskAndWaitForResult(tasks); - if (exitCode !== 0) { - console.warn("Exit code non zero, command output:\n", output); - } - expect(exitCode).to.equal(0); + expect(exitCode, `${output}`).to.equal(0); expect(output).to.include("defaultpackage"); expect(output).to.include("not used by any target"); } @@ -135,23 +152,5 @@ suite("Dependency Commmands Test Suite", function () { await assertDependencyNoLongerExists(); }); - - test("Contract: spm update", async function () { - // This test is flaky, test in CI setting when the below change get merged in and find - // out exactly where the command fails. - // https://github.com/swiftlang/vscode-swift/pull/1194 - this.skip(); - await useLocalDependencyTest(); - - // Contract: spm update - let result = await vscode.commands.executeCommand(Commands.UPDATE_DEPENDENCIES); - expect(result).to.be.true; - - // Clean up - result = await vscode.commands.executeCommand(Commands.UNEDIT_DEPENDENCY, item); - expect(result).to.be.true; - - await assertDependencyNoLongerExists(); - }); }); }); From 3c116a470996c97a926b6f5a2c2d007a68563f3d Mon Sep 17 00:00:00 2001 From: award999 Date: Tue, 3 Dec 2024 15:05:04 -0500 Subject: [PATCH 15/22] Setup Windows GH actions job (#1195) * Setup Windows GH actions jobs We cannot use the existing windows job setup because it launches a docker container which we cannot open a VS Code window in to run the tests as no tool like Xvfb exists for Windows This patch goes through and fixes up tests and behaviours that were incorrect on Windows. Co-authored-by: Paul LeMarquand Co-authored-by: Michael (SPG) Weng --- .github/workflows/pull_request.yml | 11 +- .../scripts/windows/install-nodejs.ps1 | 24 ++ .vscode-test.js | 1 + docker/test-windows.ps1 | 13 + package.json | 2 +- src/DiagnosticsManager.ts | 6 +- src/FolderContext.ts | 5 +- .../TestParsers/XCTestOutputParser.ts | 7 +- src/TestExplorer/TestRunner.ts | 4 +- src/commands/build.ts | 14 +- src/debugger/launch.ts | 14 +- src/debugger/lldb.ts | 7 +- src/tasks/SwiftProcess.ts | 2 +- src/toolchain/toolchain.ts | 1 + src/ui/SwiftOutputChannel.ts | 9 +- .../DiagnosticsManager.test.ts | 246 +++++++++++------- .../ExtensionActivation.test.ts | 10 +- .../WorkspaceContext.test.ts | 114 +++++--- test/integration-tests/commands/build.test.ts | 7 + .../commands/dependency.test.ts | 10 +- test/integration-tests/debugger/lldb.test.ts | 14 +- .../tasks/SwiftPluginTaskProvider.test.ts | 30 +-- .../tasks/SwiftTaskProvider.test.ts | 59 ++--- .../testexplorer/LSPTestDiscovery.test.ts | 2 +- .../TestExplorerIntegration.test.ts | 158 +++++------ .../testexplorer/utilities.ts | 33 ++- .../ui/PackageDependencyProvider.test.ts | 49 +++- .../utilities/testutilities.ts | 19 +- test/unit-tests/debugger/lldb.test.ts | 6 + test/utilities.ts | 13 + 30 files changed, 571 insertions(+), 319 deletions(-) create mode 100644 .github/workflows/scripts/windows/install-nodejs.ps1 create mode 100644 docker/test-windows.ps1 diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index f9aee2a51..796ab2521 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -9,6 +9,7 @@ jobs: name: Test uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main with: + # Linux linux_exclude_swift_versions: '[{"swift_version": "nightly-main"}]' linux_env_vars: | NODE_VERSION=v18.19.0 @@ -23,7 +24,15 @@ jobs: /bin/bash -c "source $NVM_DIR/nvm.sh && nvm install $NODE_VERSION" echo "$NODE_PATH" >> $GITHUB_PATH linux_build_command: ./docker/test.sh - enable_windows_checks: false + # Windows + windows_exclude_swift_versions: '[{"swift_version": "nightly"}]' + windows_env_vars: | + CI=1 + VSCODE_TEST=1 + FAST_TEST_RUN=1 + windows_pre_build_command: .github\workflows\scripts\windows\install-nodejs.ps1 + windows_build_command: docker\test-windows.ps1 + enable_windows_docker: false soundness: name: Soundness diff --git a/.github/workflows/scripts/windows/install-nodejs.ps1 b/.github/workflows/scripts/windows/install-nodejs.ps1 new file mode 100644 index 000000000..2105d63ad --- /dev/null +++ b/.github/workflows/scripts/windows/install-nodejs.ps1 @@ -0,0 +1,24 @@ +$NODEJS='https://nodejs.org/dist/v18.20.4/node-v18.20.4-x64.msi' +$NODEJS_SHA256='c2654d3557abd59de08474c6dd009b1d358f420b8e4010e4debbf130b1dfb90a' +Set-Variable ErrorActionPreference Stop +Set-Variable ProgressPreference SilentlyContinue +Write-Host -NoNewLine ('Downloading {0} ... ' -f ${NODEJS}) +Invoke-WebRequest -Uri ${NODEJS} -OutFile $env:TEMP\node.msi +Write-Host 'SUCCESS' +Write-Host -NoNewLine ('Verifying SHA256 ({0}) ... ' -f ${NODEJS_SHA256}) +$Hash = Get-FileHash $env:TEMP\node.msi -Algorithm sha256 +if ($Hash.Hash -eq ${NODEJS_SHA256}) { + Write-Host 'SUCCESS' +} else { + Write-Host ('FAILED ({0})' -f $Hash.Hash) + exit 1 +} +Write-Host -NoNewLine 'Installing node.js for Windows ... ' +$Process = Start-Process msiexec "/i $env:TEMP\node.msi /norestart /qn" -Wait -PassThru +if ($Process.ExitCode -eq 0) { + Write-Host 'SUCCESS' +} else { + Write-Host ('FAILED ({0})' -f $Process.ExitCode) + exit 1 +} +Remove-Item -Force $env:TEMP\node.msi \ No newline at end of file diff --git a/.vscode-test.js b/.vscode-test.js index 43e2dcc09..5fa7ce6ba 100644 --- a/.vscode-test.js +++ b/.vscode-test.js @@ -53,6 +53,7 @@ module.exports = defineConfig({ }, }, reuseMachineInstall: !isCIBuild, + installExtensions: ["vadimcn.vscode-lldb"], }, { label: "unitTests", diff --git a/docker/test-windows.ps1 b/docker/test-windows.ps1 new file mode 100644 index 000000000..ddee27e8e --- /dev/null +++ b/docker/test-windows.ps1 @@ -0,0 +1,13 @@ +$env:CI = "1" +$env:FAST_TEST_RUN = "1" +npm ci -ignore-script node-pty +npm run lint +npm run format +npm run package +$Process = Start-Process npm "run integration-test" -Wait -PassThru -NoNewWindow +if ($Process.ExitCode -eq 0) { + Write-Host 'SUCCESS' +} else { + Write-Host ('FAILED ({0})' -f $Process.ExitCode) + exit 1 +} diff --git a/package.json b/package.json index 42540fa80..830713b71 100644 --- a/package.json +++ b/package.json @@ -1280,7 +1280,7 @@ "pretest": "npm run compile-tests", "soundness": "docker compose -f docker/docker-compose.yaml -p swift-vscode-soundness-prb run --rm soundness", "test-soundness": "scripts/soundness.sh", - "test": "VSCODE_TEST=1 vscode-test", + "test": "vscode-test", "test-ci": "docker/test-ci.sh ci", "test-nightly": "docker/test-ci.sh nightly", "integration-test": "npm test -- --label integrationTests", diff --git a/src/DiagnosticsManager.ts b/src/DiagnosticsManager.ts index 4498ac7b8..924d691e8 100644 --- a/src/DiagnosticsManager.ts +++ b/src/DiagnosticsManager.ts @@ -89,13 +89,13 @@ export class DiagnosticsManager implements vscode.Disposable { .then(map => { // Clean up old "swiftc" diagnostics this.removeSwiftcDiagnostics(); - map.forEach((diagnostics, uri) => + map.forEach((diagnostics, uri) => { this.handleDiagnostics( vscode.Uri.file(uri), DiagnosticsManager.isSwiftc, diagnostics - ) - ); + ); + }); }) .catch(e => context.outputChannel.log(`${e}`, 'Failed to provide "swiftc" diagnostics') diff --git a/src/FolderContext.ts b/src/FolderContext.ts index 387daea06..f95fd8a66 100644 --- a/src/FolderContext.ts +++ b/src/FolderContext.ts @@ -152,7 +152,10 @@ export class FolderContext implements vscode.Disposable { /** Create Test explorer for this folder */ addTestExplorer() { - this.testExplorer = new TestExplorer(this); + if (this.testExplorer === undefined) { + this.testExplorer = new TestExplorer(this); + } + return this.testExplorer; } /** Create Test explorer for this folder */ diff --git a/src/TestExplorer/TestParsers/XCTestOutputParser.ts b/src/TestExplorer/TestParsers/XCTestOutputParser.ts index 49b067fdd..e373e8517 100644 --- a/src/TestExplorer/TestParsers/XCTestOutputParser.ts +++ b/src/TestExplorer/TestParsers/XCTestOutputParser.ts @@ -15,6 +15,8 @@ import { ITestRunState, TestIssueDiff } from "./TestRunState"; import { sourceLocationToVSCodeLocation } from "../../utilities/utilities"; import { MarkdownString, Location } from "vscode"; +// eslint-disable-next-line @typescript-eslint/no-require-imports +import stripAnsi = require("strip-ansi"); /** Regex for parsing XCTest output */ interface TestRegex { @@ -148,7 +150,10 @@ export class XCTestOutputParser implements IXCTestOutputParser { * Parse results from `swift test` and update tests accordingly * @param output Output from `swift test` */ - public parseResult(output: string, runState: ITestRunState) { + public parseResult(rawOutput: string, runState: ITestRunState) { + // Windows is inserting ANSI codes into the output to do things like clear the cursor, + // which we don't care about. + const output = process.platform === "win32" ? stripAnsi(rawOutput) : rawOutput; const output2 = output.replace(/\r\n/g, "\n"); const lines = output2.split("\n"); if (runState.excess) { diff --git a/src/TestExplorer/TestRunner.ts b/src/TestExplorer/TestRunner.ts index fe736bf8f..bf4cd80a1 100644 --- a/src/TestExplorer/TestRunner.ts +++ b/src/TestExplorer/TestRunner.ts @@ -40,6 +40,8 @@ import { BuildConfigurationFactory, TestingConfigurationFactory } from "../debug import { TestKind, isDebugging, isRelease } from "./TestKind"; import { reduceTestItemChildren } from "./TestUtils"; import { CompositeCancellationToken } from "../utilities/cancellation"; +// eslint-disable-next-line @typescript-eslint/no-require-imports +import stripAnsi = require("strip-ansi"); export enum TestLibrary { xctest = "XCTest", @@ -264,7 +266,7 @@ export class TestRunProxy { test?: vscode.TestItem ) { testRun.appendOutput(output, location, test); - this.runState.output.push(output); + this.runState.output.push(stripAnsi(output)); } private prependIterationToOutput(output: string): string { diff --git a/src/commands/build.ts b/src/commands/build.ts index afd2bfe67..03e66489a 100644 --- a/src/commands/build.ts +++ b/src/commands/build.ts @@ -74,16 +74,28 @@ export async function debugBuildWithOptions( ) { const current = ctx.currentFolder; if (!current) { + ctx.outputChannel.appendLine( + "debugBuildWithOptions: No current folder on WorkspaceContext" + ); return; } const file = vscode.window.activeTextEditor?.document.fileName; if (!file) { + ctx.outputChannel.appendLine("debugBuildWithOptions: No active text editor"); return; } const target = current.swiftPackage.getTarget(file); - if (!target || target.type !== "executable") { + if (!target) { + ctx.outputChannel.appendLine("debugBuildWithOptions: No active target"); + return; + } + + if (target.type !== "executable") { + ctx.outputChannel.appendLine( + `debugBuildWithOptions: Target is not an executable, instead is ${target.type}` + ); return; } diff --git a/src/debugger/launch.ts b/src/debugger/launch.ts index 87bf04edf..549d8d13a 100644 --- a/src/debugger/launch.ts +++ b/src/debugger/launch.ts @@ -113,17 +113,27 @@ export function getLaunchConfiguration( const wsLaunchSection = vscode.workspace.getConfiguration("launch", folderCtx.workspaceFolder); const launchConfigs = wsLaunchSection.get("configurations") || []; const { folder } = getFolderAndNameSuffix(folderCtx); - const buildDirectory = BuildFlags.buildDirectoryFromWorkspacePath(folder, true); + const targetPath = path.join( + BuildFlags.buildDirectoryFromWorkspacePath(folder, true), + "debug", + target + ); + // Users could be on different platforms with different path annotations, + // so normalize before we compare. return launchConfigs.find( - config => config.program === path.join(buildDirectory, "debug", target) + config => path.normalize(config.program) === path.normalize(targetPath) ); } // Return array of DebugConfigurations for executables based on what is in Package.swift function createExecutableConfigurations(ctx: FolderContext): vscode.DebugConfiguration[] { const executableProducts = ctx.swiftPackage.executableProducts; + + // Windows understand the forward slashes, so make the configuration unified as posix path + // to make it easier for users switching between platforms. const { folder, nameSuffix } = getFolderAndNameSuffix(ctx, undefined, "posix"); const buildDirectory = BuildFlags.buildDirectoryFromWorkspacePath(folder, true, "posix"); + return executableProducts.flatMap(product => { const baseConfig = { type: DebugAdapter.getLaunchConfigType(ctx.workspaceContext.swiftVersion), diff --git a/src/debugger/lldb.ts b/src/debugger/lldb.ts index ff4eccbe1..803913c27 100644 --- a/src/debugger/lldb.ts +++ b/src/debugger/lldb.ts @@ -142,7 +142,7 @@ export async function getLldbProcess( "platform process list --show-args --all-users", ]); const entries = stdout.split("\n"); - return entries.flatMap(line => { + const processes = entries.flatMap(line => { const match = /^(\d+)\s+\d+\s+\S+\s+\S+\s+(.+)$/.exec(line); if (match) { return [{ pid: parseInt(match[1]), label: `${match[1]}: ${match[2]}` }]; @@ -150,7 +150,10 @@ export async function getLldbProcess( return []; } }); + return processes; } catch (error) { - vscode.window.showErrorMessage(`Failed to run LLDB: ${getErrorDescription(error)}`); + const errorMessage = `Failed to run LLDB: ${getErrorDescription(error)}`; + ctx.outputChannel.log(errorMessage); + vscode.window.showErrorMessage(errorMessage); } } diff --git a/src/tasks/SwiftProcess.ts b/src/tasks/SwiftProcess.ts index bbf923aad..1c0ce05e8 100644 --- a/src/tasks/SwiftProcess.ts +++ b/src/tasks/SwiftProcess.ts @@ -104,7 +104,7 @@ export class SwiftPtyProcess implements SwiftProcess { useConpty, // https://github.com/swiftlang/vscode-swift/issues/1074 // Causing weird truncation issues - cols: !isWindows || useConpty ? undefined : 2147483647, // Max int32 + cols: isWindows ? 4096 : undefined, }); this.spawnEmitter.fire(); this.spawnedProcess.onData(data => { diff --git a/src/toolchain/toolchain.ts b/src/toolchain/toolchain.ts index 7f78f6ff1..9ee7b3938 100644 --- a/src/toolchain/toolchain.ts +++ b/src/toolchain/toolchain.ts @@ -468,6 +468,7 @@ export class SwiftToolchain { get diagnostics(): string { let str = ""; str += this.swiftVersionString; + str += `\nPlatform: ${process.platform}`; str += `\nSwift Path: ${this.swiftFolderPath}`; str += `\nToolchain Path: ${this.toolchainPath}`; if (this.runtimePath) { diff --git a/src/ui/SwiftOutputChannel.ts b/src/ui/SwiftOutputChannel.ts index 17446aac2..04291b0ba 100644 --- a/src/ui/SwiftOutputChannel.ts +++ b/src/ui/SwiftOutputChannel.ts @@ -86,15 +86,10 @@ export class SwiftOutputChannel implements vscode.OutputChannel { } logDiagnostic(message: string, label?: string) { - if (!configuration.diagnostics) { + if (!configuration.diagnostics && process.env["CI"] !== "1") { return; } - let fullMessage: string; - if (label !== undefined) { - fullMessage = `${label}: ${message}`; - } else { - fullMessage = message; - } + const fullMessage = label !== undefined ? `${label}: ${message}` : message; this.appendLine(`${this.nowFormatted}: ${fullMessage}`); } diff --git a/test/integration-tests/DiagnosticsManager.test.ts b/test/integration-tests/DiagnosticsManager.test.ts index 04981f921..8e0938e9c 100644 --- a/test/integration-tests/DiagnosticsManager.test.ts +++ b/test/integration-tests/DiagnosticsManager.test.ts @@ -25,27 +25,14 @@ import { Version } from "../../src/utilities/version"; import { Workbench } from "../../src/utilities/commands"; import { activateExtensionForSuite, folderInRootWorkspace } from "./utilities/testutilities"; -const waitForDiagnostics = (uris: vscode.Uri[], allowEmpty: boolean = true) => - new Promise(res => - vscode.languages.onDidChangeDiagnostics(e => { - const paths = e.uris.map(u => u.path); - for (const uri of uris) { - if (!paths.includes(uri.path)) { - return; - } - if (!allowEmpty && !vscode.languages.getDiagnostics(uri).length) { - return; - } - } - res(); - }) +const isEqual = (d1: vscode.Diagnostic, d2: vscode.Diagnostic) => { + return ( + d1.severity === d2.severity && + d1.source === d2.source && + d1.message === d2.message && + d1.range.isEqual(d2.range) ); - -const isEqual = (d1: vscode.Diagnostic, d2: vscode.Diagnostic) => - d1.severity === d2.severity && - d1.source === d2.source && - d1.message === d2.message && - d1.range.isEqual(d2.range); +}; const findDiagnostic = (expected: vscode.Diagnostic) => (d: vscode.Diagnostic) => isEqual(d, expected); @@ -56,7 +43,7 @@ function assertHasDiagnostic(uri: vscode.Uri, expected: vscode.Diagnostic): vsco assert.notEqual( diagnostic, undefined, - `Could not find diagnostic matching:\n${JSON.stringify(expected)}\nDiagnostics:\n${JSON.stringify(diagnostics)}` + `Could not find diagnostic matching:\n${JSON.stringify(expected)}\nDiagnostics found:\n${JSON.stringify(diagnostics)}` ); return diagnostic!; } @@ -90,10 +77,55 @@ suite("DiagnosticsManager Test Suite", async function () { let cUri: vscode.Uri; let cppUri: vscode.Uri; let cppHeaderUri: vscode.Uri; + let diagnosticWaiterDisposable: vscode.Disposable | undefined; + let remainingExpectedDiagnostics: + | { + [uri: string]: vscode.Diagnostic[]; + } + | undefined; + + // Wait for all the expected diagnostics to be recieved. This may happen over several `onChangeDiagnostics` events. + const waitForDiagnostics = (expectedDiagnostics: { [uri: string]: vscode.Diagnostic[] }) => { + return new Promise(resolve => { + if (diagnosticWaiterDisposable) { + console.warn( + "Wait for diagnostics was called before the previous wait was resolved. Only one waitForDiagnostics should run per test." + ); + diagnosticWaiterDisposable?.dispose(); + } + // Keep a lookup of diagnostics we haven't encountered yet. When all array values in + // this lookup are empty then we've seen all diagnostics and we can resolve successfully. + const expected = { ...expectedDiagnostics }; + diagnosticWaiterDisposable = vscode.languages.onDidChangeDiagnostics(e => { + const matchingPaths = Object.keys(expectedDiagnostics).filter(uri => + e.uris.some(u => u.fsPath === uri) + ); + for (const uri of matchingPaths) { + const actualDiagnostics = vscode.languages.getDiagnostics(vscode.Uri.file(uri)); + expected[uri] = expected[uri].filter(expectedDiagnostic => { + return !actualDiagnostics.some(actualDiagnostic => + isEqual(actualDiagnostic, expectedDiagnostic) + ); + }); + remainingExpectedDiagnostics = expected; + } + + const allDiagnosticsFulfilled = Object.values(expected).every( + diagnostics => diagnostics.length === 0 + ); + + if (allDiagnosticsFulfilled) { + diagnosticWaiterDisposable?.dispose(); + diagnosticWaiterDisposable = undefined; + resolve(); + } + }); + }); + }; activateExtensionForSuite({ async setup(ctx) { - this.timeout(60000); + this.timeout(60000 * 2); workspaceContext = ctx; toolchain = workspaceContext.toolchain; @@ -113,7 +145,27 @@ suite("DiagnosticsManager Test Suite", async function () { }, }); - suite("Parse diagnostics", async () => { + teardown(function () { + diagnosticWaiterDisposable?.dispose(); + diagnosticWaiterDisposable = undefined; + const allDiagnosticsFulfilled = Object.values(remainingExpectedDiagnostics ?? {}).every( + diagnostics => diagnostics.length === 0 + ); + if (!allDiagnosticsFulfilled) { + const title = this.currentTest?.fullTitle() ?? ""; + const remainingDiagnostics = Object.entries(remainingExpectedDiagnostics ?? {}).filter( + ([_uri, diagnostics]) => diagnostics.length > 0 + ); + console.error( + `${title} - Not all diagnostics were fulfilled`, + JSON.stringify(remainingDiagnostics, undefined, " ") + ); + } + }); + + suite("Parse diagnostics", async function () { + this.timeout(60000 * 2); + suite("Parse from task output", async () => { const expectedWarningDiagnostic = new vscode.Diagnostic( new vscode.Range(new vscode.Position(1, 8), new vscode.Position(1, 8)), @@ -166,64 +218,76 @@ suite("DiagnosticsManager Test Suite", async function () { await swiftConfig.update("diagnosticsStyle", undefined); }); - test("default diagnosticsStyle", async () => { + test("default diagnosticsStyle", async function () { + // Swift 5.10 and 6.0 on Windows have a bug where the + // diagnostics are not emitted on their own line. + const swiftVersion = workspaceContext.toolchain.swiftVersion; + if ( + process.platform === "win32" && + swiftVersion.isGreaterThanOrEqual(new Version(5, 10, 0)) && + swiftVersion.isLessThanOrEqual(new Version(6, 0, 999)) + ) { + this.skip(); + } await swiftConfig.update("diagnosticsStyle", "default"); - const task = createBuildAllTask(folderContext); - // Run actual task - const promise = waitForDiagnostics([mainUri, funcUri]); - await executeTaskAndWaitForResult(task); - await promise; - await waitForNoRunningTasks(); - // Should have parsed correct severity - assertHasDiagnostic(mainUri, expectedWarningDiagnostic); - assertHasDiagnostic(mainUri, expectedMainErrorDiagnostic); - if (workspaceContext.swiftVersion.isGreaterThanOrEqual(new Version(6, 0, 0))) { - assertHasDiagnostic(mainUri, expectedMacroDiagnostic); - } - // Check parsed for other file - assertHasDiagnostic(funcUri, expectedFuncErrorDiagnostic); - }).timeout(2 * 60 * 1000); // Allow 2 minutes to build + await Promise.all([ + waitForDiagnostics({ + [mainUri.fsPath]: [ + expectedWarningDiagnostic, + expectedMainErrorDiagnostic, + ...(workspaceContext.swiftVersion.isGreaterThanOrEqual( + new Version(6, 0, 0) + ) + ? [expectedMacroDiagnostic] + : []), + ], // Should have parsed correct severity + [funcUri.fsPath]: [expectedFuncErrorDiagnostic], // Check parsed for other file + }), + executeTaskAndWaitForResult(createBuildAllTask(folderContext)), + ]); + + await waitForNoRunningTasks(); + }); test("swift diagnosticsStyle", async function () { - // This is only supported in swift versions >=5.10.0 + // This is only supported in swift versions >=5.10.0. + // Swift 5.10 and 6.0 on Windows have a bug where the + // diagnostics are not emitted on their own line. const swiftVersion = workspaceContext.toolchain.swiftVersion; - if (swiftVersion.isLessThan(new Version(5, 10, 0))) { + if ( + swiftVersion.isLessThan(new Version(5, 10, 0)) || + (process.platform === "win32" && + swiftVersion.isGreaterThanOrEqual(new Version(5, 10, 0)) && + swiftVersion.isLessThanOrEqual(new Version(6, 0, 999))) + ) { this.skip(); - return; } await swiftConfig.update("diagnosticsStyle", "swift"); - const task = createBuildAllTask(folderContext); - // Run actual task - const promise = waitForDiagnostics([mainUri, funcUri]); - await executeTaskAndWaitForResult(task); - await promise; - await waitForNoRunningTasks(); - // Should have parsed severity - assertHasDiagnostic(mainUri, expectedWarningDiagnostic); - assertHasDiagnostic(mainUri, expectedMainErrorDiagnostic); - if (workspaceContext.swiftVersion.isGreaterThanOrEqual(new Version(6, 0, 0))) { - assertHasDiagnostic(mainUri, expectedMacroDiagnostic); - } - // Check parsed for other file - assertHasDiagnostic(funcUri, expectedFuncErrorDiagnostic); - }).timeout(2 * 60 * 1000); // Allow 2 minutes to build + await Promise.all([ + waitForDiagnostics({ + [mainUri.fsPath]: [expectedWarningDiagnostic, expectedMainErrorDiagnostic], // Should have parsed correct severity + [funcUri.fsPath]: [expectedFuncErrorDiagnostic], // Check parsed for other file + }), + executeTaskAndWaitForResult(createBuildAllTask(folderContext)), + ]); + await waitForNoRunningTasks(); + }); test("llvm diagnosticsStyle", async () => { await swiftConfig.update("diagnosticsStyle", "llvm"); - const task = createBuildAllTask(folderContext); - // Run actual task - const promise = waitForDiagnostics([mainUri, funcUri]); - await executeTaskAndWaitForResult(task); - await promise; + + await Promise.all([ + waitForDiagnostics({ + [mainUri.fsPath]: [expectedWarningDiagnostic, expectedMainErrorDiagnostic], // Should have parsed correct severity + [funcUri.fsPath]: [expectedFuncErrorDiagnostic], // Check parsed for other file + }), + executeTaskAndWaitForResult(createBuildAllTask(folderContext)), + ]); await waitForNoRunningTasks(); // Should have parsed severity - assertHasDiagnostic(mainUri, expectedWarningDiagnostic); - - // llvm style doesn't do macro diagnostics - assertWithoutDiagnostic(mainUri, expectedMacroDiagnostic); const diagnostic = assertHasDiagnostic(mainUri, expectedMainErrorDiagnostic); // Should have parsed related note assert.equal(diagnostic.relatedInformation?.length, 1); @@ -238,9 +302,7 @@ suite("DiagnosticsManager Test Suite", async function () { ), true ); - // Check parsed for other file - assertHasDiagnostic(funcUri, expectedFuncErrorDiagnostic); - }).timeout(2 * 60 * 1000); // Allow 2 minutes to build + }); test("Parses C diagnostics", async function () { const swiftVersion = workspaceContext.toolchain.swiftVersion; @@ -251,12 +313,6 @@ suite("DiagnosticsManager Test Suite", async function () { } await swiftConfig.update("diagnosticsStyle", "llvm"); - const task = createBuildAllTask(cFolderContext); - // Run actual task - const promise = waitForDiagnostics([cUri]); - await executeTaskAndWaitForResult(task); - await promise; - await waitForNoRunningTasks(); // Should have parsed severity const expectedDiagnostic1 = new vscode.Diagnostic( @@ -272,8 +328,13 @@ suite("DiagnosticsManager Test Suite", async function () { ); expectedDiagnostic2.source = "swiftc"; - assertHasDiagnostic(cUri, expectedDiagnostic1); - assertHasDiagnostic(cUri, expectedDiagnostic2); + await Promise.all([ + waitForDiagnostics({ + [cUri.fsPath]: [expectedDiagnostic1, expectedDiagnostic2], + }), + executeTaskAndWaitForResult(createBuildAllTask(cFolderContext)), + ]); + await waitForNoRunningTasks(); }); test("Parses C++ diagnostics", async function () { @@ -285,12 +346,6 @@ suite("DiagnosticsManager Test Suite", async function () { } await swiftConfig.update("diagnosticsStyle", "llvm"); - const task = createBuildAllTask(cppFolderContext); - // Run actual task - const promise = waitForDiagnostics([cppUri]); - await executeTaskAndWaitForResult(task); - await promise; - await waitForNoRunningTasks(); // Should have parsed severity const expectedDiagnostic1 = new vscode.Diagnostic( @@ -299,7 +354,6 @@ suite("DiagnosticsManager Test Suite", async function () { vscode.DiagnosticSeverity.Error ); expectedDiagnostic1.source = "swiftc"; - assertHasDiagnostic(cppUri, expectedDiagnostic1); // Should have parsed releated information const expectedDiagnostic2 = new vscode.Diagnostic( @@ -308,6 +362,15 @@ suite("DiagnosticsManager Test Suite", async function () { vscode.DiagnosticSeverity.Error ); expectedDiagnostic2.source = "swiftc"; + + await Promise.all([ + waitForDiagnostics({ + [cppUri.fsPath]: [expectedDiagnostic1, expectedDiagnostic2], + }), + executeTaskAndWaitForResult(createBuildAllTask(cppFolderContext)), + ]); + await waitForNoRunningTasks(); + const diagnostic = assertHasDiagnostic(cppUri, expectedDiagnostic2); assert.equal( diagnostic.relatedInformation![0].location.uri.fsPath, @@ -338,14 +401,12 @@ suite("DiagnosticsManager Test Suite", async function () { test("Parse partial line", async () => { const fixture = testSwiftTask("swift", ["build"], workspaceFolder, toolchain); await vscode.tasks.executeTask(fixture.task); - const diagnosticsPromise = waitForDiagnostics([mainUri]); // Wait to spawn before writing fixture.process.write(`${mainUri.fsPath}:13:5: err`, ""); fixture.process.write("or: Cannot find 'fo", ""); fixture.process.write("o' in scope"); fixture.process.close(1); await waitForNoRunningTasks(); - await diagnosticsPromise; // Should have parsed assertHasDiagnostic(mainUri, outputDiagnostic); }); @@ -354,7 +415,6 @@ suite("DiagnosticsManager Test Suite", async function () { test("Ignore duplicates", async () => { const fixture = testSwiftTask("swift", ["build"], workspaceFolder, toolchain); await vscode.tasks.executeTask(fixture.task); - const diagnosticsPromise = waitForDiagnostics([mainUri]); // Wait to spawn before writing const output = `${mainUri.fsPath}:13:5: error: Cannot find 'foo' in scope`; fixture.process.write(output); @@ -362,7 +422,6 @@ suite("DiagnosticsManager Test Suite", async function () { fixture.process.write(output); fixture.process.close(1); await waitForNoRunningTasks(); - await diagnosticsPromise; const diagnostics = vscode.languages.getDiagnostics(mainUri); // Should only include one assert.equal(diagnostics.length, 1); @@ -372,12 +431,10 @@ suite("DiagnosticsManager Test Suite", async function () { test("New set of swiftc diagnostics clear old list", async () => { let fixture = testSwiftTask("swift", ["build"], workspaceFolder, toolchain); await vscode.tasks.executeTask(fixture.task); - let diagnosticsPromise = waitForDiagnostics([mainUri]); // Wait to spawn before writing fixture.process.write(`${mainUri.fsPath}:13:5: error: Cannot find 'foo' in scope`); fixture.process.close(1); await waitForNoRunningTasks(); - await diagnosticsPromise; let diagnostics = vscode.languages.getDiagnostics(mainUri); // Should only include one assert.equal(diagnostics.length, 1); @@ -386,10 +443,8 @@ suite("DiagnosticsManager Test Suite", async function () { // Run again but no diagnostics returned fixture = testSwiftTask("swift", ["build"], workspaceFolder, toolchain); await vscode.tasks.executeTask(fixture.task); - diagnosticsPromise = waitForDiagnostics([mainUri]); fixture.process.close(0); await waitForNoRunningTasks(); - await diagnosticsPromise; diagnostics = vscode.languages.getDiagnostics(mainUri); // Should have cleaned up assert.equal(diagnostics.length, 0); @@ -917,7 +972,8 @@ suite("DiagnosticsManager Test Suite", async function () { }); }); - suite("SourceKit-LSP diagnostics", () => { + // Skipped until we enable it in a nightly build + suite("SourceKit-LSP diagnostics @slow", () => { suiteSetup(async function () { if (workspaceContext.swiftVersion.isLessThan(new Version(5, 7, 0))) { this.skip(); @@ -942,7 +998,7 @@ suite("DiagnosticsManager Test Suite", async function () { await executeTaskAndWaitForResult(task); // Open file - const promise = waitForDiagnostics([mainUri], false); + const promise = Promise.resolve(); // waitForDiagnostics([mainUri], false); const document = await vscode.workspace.openTextDocument(mainUri); await vscode.languages.setTextDocumentLanguage(document, "swift"); await vscode.window.showTextDocument(document); @@ -983,7 +1039,7 @@ suite("DiagnosticsManager Test Suite", async function () { await executeTaskAndWaitForResult(task); // Open file - const promise = waitForDiagnostics([cUri], false); + const promise = Promise.resolve(); // waitForDiagnostics([cUri], false); const document = await vscode.workspace.openTextDocument(cUri); await vscode.languages.setTextDocumentLanguage(document, "c"); await vscode.window.showTextDocument(document); diff --git a/test/integration-tests/ExtensionActivation.test.ts b/test/integration-tests/ExtensionActivation.test.ts index a06daab7b..52913d17a 100644 --- a/test/integration-tests/ExtensionActivation.test.ts +++ b/test/integration-tests/ExtensionActivation.test.ts @@ -37,23 +37,23 @@ suite("Extension Activation/Deactivation Tests", () => { } test("Activation", async function () { - await activate(this.currentTest); + await activate(this.test as Mocha.Test); }); test("Duplicate Activation", async function () { - await activate(this.currentTest); - assert.rejects(activateExtension(this.currentTest), err => { + await activate(this.test as Mocha.Test); + assert.rejects(activateExtension(this.test as Mocha.Test), err => { const msg = (err as unknown as any).message; return ( msg.includes("Extension is already activated") && - msg.includes(this.currentTest?.fullTitle()) + msg.includes((this.test as Mocha.Test)?.titlePath().join(" → ")) ); }); }); }); test("Deactivation", async function () { - const workspaceContext = await activateExtension(this.currentTest); + const workspaceContext = await activateExtension(this.test as Mocha.Test); await deactivateExtension(); const ext = vscode.extensions.getExtension("sswg.swift-lang"); assert(ext); diff --git a/test/integration-tests/WorkspaceContext.test.ts b/test/integration-tests/WorkspaceContext.test.ts index ae40aa0fd..d7f2030aa 100644 --- a/test/integration-tests/WorkspaceContext.test.ts +++ b/test/integration-tests/WorkspaceContext.test.ts @@ -20,40 +20,75 @@ import { createBuildAllTask } from "../../src/tasks/SwiftTaskProvider"; import { Version } from "../../src/utilities/version"; import { SwiftExecution } from "../../src/tasks/SwiftExecution"; import { activateExtensionForSuite } from "./utilities/testutilities"; +import { FolderContext } from "../../src/FolderContext"; +import { assertContains } from "./testexplorer/utilities"; + +function assertContainsArg(execution: SwiftExecution, arg: string) { + assert(execution?.args.find(a => a === arg)); +} + +function assertNotContainsArg(execution: SwiftExecution, arg: string) { + assert.equal( + execution?.args.find(a => a.includes(arg)), + undefined + ); +} suite("WorkspaceContext Test Suite", () => { let workspaceContext: WorkspaceContext; const packageFolder: vscode.Uri = testAssetUri("defaultPackage"); - activateExtensionForSuite({ - async setup(ctx) { - workspaceContext = ctx; - }, - }); - suite("Folder Events", () => { + activateExtensionForSuite({ + async setup(ctx) { + workspaceContext = ctx; + }, + // No default assets as we want to verify against a clean workspace. + testAssets: [], + }); + test("Add", async () => { - let count = 0; - const observer = workspaceContext?.onDidChangeFolders(({ folder, operation }) => { - assert(folder !== null); - assert.strictEqual(folder!.swiftPackage.name, "package2"); - switch (operation) { - case FolderOperation.add: - count++; - break; - } - }); - const workspaceFolder = vscode.workspace.workspaceFolders?.values().next().value; - if (!workspaceFolder) { - throw new Error("No workspace folders found in workspace"); + let observer: vscode.Disposable | undefined; + const recordedFolders: { + folder: FolderContext | null; + operation: FolderOperation; + }[] = []; + + try { + observer = workspaceContext.onDidChangeFolders(changedFolderRecord => { + recordedFolders.push(changedFolderRecord); + }); + + const workspaceFolder = vscode.workspace.workspaceFolders?.values().next().value; + + assert.ok(workspaceFolder, "No workspace folders found in workspace"); + + await workspaceContext.addPackageFolder(testAssetUri("package2"), workspaceFolder); + + const foldersNames = recordedFolders.map(({ folder }) => folder?.swiftPackage.name); + assertContains(foldersNames, "package2"); + + const addedCount = recordedFolders.filter( + ({ operation }) => operation === FolderOperation.add + ).length; + assert.strictEqual( + addedCount, + 1, + `Expected only one add folder operation, instead got folders: ${recordedFolders.map(folder => folder.folder?.name)}` + ); + } finally { + observer?.dispose(); } - await workspaceContext?.addPackageFolder(testAssetUri("package2"), workspaceFolder); - assert.strictEqual(count, 1); - observer?.dispose(); - }).timeout(15000); + }).timeout(60000 * 2); }); suite("Tasks", async function () { + activateExtensionForSuite({ + async setup(ctx) { + workspaceContext = ctx; + }, + }); + // Was hitting a timeout in suiteSetup during CI build once in a while this.timeout(5000); @@ -75,10 +110,10 @@ suite("WorkspaceContext Test Suite", () => { const execution = buildAllTask.execution; assert.strictEqual(buildAllTask.definition.type, "swift"); assert.strictEqual(buildAllTask.name, "Build All (defaultPackage)"); - assert.strictEqual(execution?.args[0], "build"); - assert.strictEqual(execution?.args[1], "--build-tests"); - assert.strictEqual(execution?.args[2], "-Xswiftc"); - assert.strictEqual(execution?.args[3], "-diagnostic-style=llvm"); + assertContainsArg(execution, "build"); + assertContainsArg(execution, "--build-tests"); + assertContainsArg(execution, "-Xswiftc"); + assertContainsArg(execution, "-diagnostic-style=llvm"); assert.strictEqual(buildAllTask.scope, folder.workspaceFolder); }); @@ -92,8 +127,9 @@ suite("WorkspaceContext Test Suite", () => { const execution = buildAllTask.execution; assert.strictEqual(buildAllTask.definition.type, "swift"); assert.strictEqual(buildAllTask.name, "Build All (defaultPackage)"); - assert.strictEqual(execution?.args[0], "build"); - assert.strictEqual(execution?.args[1], "--build-tests"); + assertContainsArg(execution, "build"); + assertContainsArg(execution, "--build-tests"); + assertNotContainsArg(execution, "-diagnostic-style"); assert.strictEqual(buildAllTask.scope, folder.workspaceFolder); }); @@ -107,10 +143,10 @@ suite("WorkspaceContext Test Suite", () => { const execution = buildAllTask.execution; assert.strictEqual(buildAllTask.definition.type, "swift"); assert.strictEqual(buildAllTask.name, "Build All (defaultPackage)"); - assert.strictEqual(execution?.args[0], "build"); - assert.strictEqual(execution?.args[1], "--build-tests"); - assert.strictEqual(execution?.args[2], "-Xswiftc"); - assert.strictEqual(execution?.args[3], "-diagnostic-style=swift"); + assertContainsArg(execution, "build"); + assertContainsArg(execution, "--build-tests"); + assertContainsArg(execution, "-Xswiftc"); + assertContainsArg(execution, "-diagnostic-style=swift"); assert.strictEqual(buildAllTask.scope, folder.workspaceFolder); }); @@ -123,11 +159,7 @@ suite("WorkspaceContext Test Suite", () => { await swiftConfig.update("buildArguments", ["--sanitize=thread"]); const buildAllTask = createBuildAllTask(folder); const execution = buildAllTask.execution as SwiftExecution; - assert.strictEqual(execution?.args[0], "build"); - assert.strictEqual(execution?.args[1], "--build-tests"); - assert.strictEqual(execution?.args[2], "-Xswiftc"); - assert.strictEqual(execution?.args[3], "-diagnostic-style=llvm"); - assert.strictEqual(execution?.args[4], "--sanitize=thread"); + assertContainsArg(execution, "--sanitize=thread"); await swiftConfig.update("buildArguments", []); }); @@ -146,6 +178,12 @@ suite("WorkspaceContext Test Suite", () => { }); suite("Toolchain", () => { + activateExtensionForSuite({ + async setup(ctx) { + workspaceContext = ctx; + }, + }); + test("get project templates", async () => { // This is only supported in swift versions >=5.8.0 const swiftVersion = workspaceContext.toolchain.swiftVersion; diff --git a/test/integration-tests/commands/build.test.ts b/test/integration-tests/commands/build.test.ts index 0b5d5e9e2..00d45f8b1 100644 --- a/test/integration-tests/commands/build.test.ts +++ b/test/integration-tests/commands/build.test.ts @@ -43,6 +43,13 @@ suite("Build Commands", function () { activateExtensionForSuite({ async setup(ctx) { + // The description of this package is crashing on Windows with Swift 5.9.x and below, + // preventing it from being built. The cleanup in the teardown is failng as well with + // an EBUSY error. Skip this test on Windows until the issue is resolved. + if (process.platform === "win32") { + this.skip(); + } + workspaceContext = ctx; await waitForNoRunningTasks(); folderContext = await folderInRootWorkspace("defaultPackage", workspaceContext); diff --git a/test/integration-tests/commands/dependency.test.ts b/test/integration-tests/commands/dependency.test.ts index 55d5543c0..cc4f9e264 100644 --- a/test/integration-tests/commands/dependency.test.ts +++ b/test/integration-tests/commands/dependency.test.ts @@ -33,8 +33,8 @@ import { suite("Dependency Commmands Test Suite", function () { // full workflow's interaction with spm is longer than the default timeout - // 15 seconds for each test should be more than enough - this.timeout(15 * 1000); + // 60 seconds for each test should be more than enough + this.timeout(60 * 1000); suite("spm Update Contract Tests", function () { let folderContext: FolderContext; @@ -133,7 +133,11 @@ suite("Dependency Commmands Test Suite", function () { expect(output).to.include("required"); } - test("Use local dependency - Reset", async () => { + test("Use local dependency - Reset", async function () { + // spm reset after using local dependency is broken on windows + if (process.platform === "win32") { + this.skip(); + } await useLocalDependencyTest(); // Contract: spm reset diff --git a/test/integration-tests/debugger/lldb.test.ts b/test/integration-tests/debugger/lldb.test.ts index 6731e67a5..aa070721c 100644 --- a/test/integration-tests/debugger/lldb.test.ts +++ b/test/integration-tests/debugger/lldb.test.ts @@ -15,13 +15,23 @@ import { expect } from "chai"; import { getLLDBLibPath, getLldbProcess } from "../../../src/debugger/lldb"; import { WorkspaceContext } from "../../../src/WorkspaceContext"; -import { activateExtensionForSuite } from "../utilities/testutilities"; +import { activateExtensionForTest } from "../utilities/testutilities"; +import { Version } from "../../../src/utilities/version"; suite("lldb contract test suite", () => { let workspaceContext: WorkspaceContext; - activateExtensionForSuite({ + activateExtensionForTest({ async setup(ctx) { + // lldb.exe on Windows is not launching correctly, but only in Docker. + if ( + process.env["CI"] && + process.platform === "win32" && + ctx.swiftVersion.isGreaterThanOrEqual(new Version(6, 0, 0)) && + ctx.swiftVersion.isLessThan(new Version(6, 0, 2)) + ) { + this.skip(); + } workspaceContext = ctx; }, }); diff --git a/test/integration-tests/tasks/SwiftPluginTaskProvider.test.ts b/test/integration-tests/tasks/SwiftPluginTaskProvider.test.ts index cbb99142e..9f91ab740 100644 --- a/test/integration-tests/tasks/SwiftPluginTaskProvider.test.ts +++ b/test/integration-tests/tasks/SwiftPluginTaskProvider.test.ts @@ -14,12 +14,17 @@ import * as vscode from "vscode"; import * as assert from "assert"; +import { expect } from "chai"; import { WorkspaceContext } from "../../../src/WorkspaceContext"; import { SwiftPluginTaskProvider } from "../../../src/tasks/SwiftPluginTaskProvider"; import { FolderContext } from "../../../src/FolderContext"; import { activateExtensionForSuite, folderInRootWorkspace } from "../utilities/testutilities"; -import { executeTaskAndWaitForResult, mutable, waitForEndTaskProcess } from "../../utilities"; -import { expect } from "chai"; +import { + cleanOutput, + executeTaskAndWaitForResult, + mutable, + waitForEndTaskProcess, +} from "../../utilities"; suite("SwiftPluginTaskProvider Test Suite", () => { let workspaceContext: WorkspaceContext; @@ -29,7 +34,6 @@ suite("SwiftPluginTaskProvider Test Suite", () => { async setup(ctx) { workspaceContext = ctx; folderContext = await folderInRootWorkspace("command-plugin", workspaceContext); - expect(workspaceContext.folders).to.not.have.lengthOf(0); await folderContext.loadSwiftPlugins(); expect(workspaceContext.folders).to.not.have.lengthOf(0); }, @@ -48,13 +52,9 @@ suite("SwiftPluginTaskProvider Test Suite", () => { scope: folderContext.workspaceFolder, }); const { exitCode, output } = await executeTaskAndWaitForResult(task); - assert.equal(exitCode, 0); - assert.equal( - output.trim().endsWith("Hello, World!"), - true, - "Expceted output to end with 'Hello, World!'" - ); - }).timeout(10000); + expect(exitCode).to.equal(0); + expect(cleanOutput(output)).to.include("Hello, World!"); + }).timeout(60000); test("Exit code on failure", async () => { const task = taskProvider.createSwiftPluginTask( @@ -70,7 +70,7 @@ suite("SwiftPluginTaskProvider Test Suite", () => { ); mutable(task.execution).command = "/definitely/not/swift"; const { exitCode } = await executeTaskAndWaitForResult(task); - assert.notEqual(exitCode, 0); + expect(exitCode).to.not.equal(0); }).timeout(10000); }); @@ -84,7 +84,7 @@ suite("SwiftPluginTaskProvider Test Suite", () => { }); test("provides", () => { - assert.equal(task?.detail, "swift package command_plugin"); + expect(task?.detail).to.equal("swift package command_plugin"); }); test("executes", async () => { @@ -92,7 +92,7 @@ suite("SwiftPluginTaskProvider Test Suite", () => { const exitPromise = waitForEndTaskProcess(task); await vscode.tasks.executeTask(task); const exitCode = await exitPromise; - assert.equal(exitCode, 0); + expect(exitCode).to.equal(0); }).timeout(30000); // 30 seconds to run }); @@ -105,7 +105,7 @@ suite("SwiftPluginTaskProvider Test Suite", () => { }); test("provides", () => { - assert.equal(task?.detail, "swift package command_plugin --foo"); + expect(task?.detail).to.equal("swift package command_plugin --foo"); }); test("executes", async () => { @@ -113,7 +113,7 @@ suite("SwiftPluginTaskProvider Test Suite", () => { const exitPromise = waitForEndTaskProcess(task); await vscode.tasks.executeTask(task); const exitCode = await exitPromise; - assert.equal(exitCode, 0); + expect(exitCode).to.equal(0); }).timeout(30000); // 30 seconds to run }); }); diff --git a/test/integration-tests/tasks/SwiftTaskProvider.test.ts b/test/integration-tests/tasks/SwiftTaskProvider.test.ts index 31531e4bc..291ebbefe 100644 --- a/test/integration-tests/tasks/SwiftTaskProvider.test.ts +++ b/test/integration-tests/tasks/SwiftTaskProvider.test.ts @@ -12,6 +12,7 @@ // //===----------------------------------------------------------------------===// +import { expect } from "chai"; import * as vscode from "vscode"; import * as assert from "assert"; import { WorkspaceContext } from "../../../src/WorkspaceContext"; @@ -31,7 +32,6 @@ import { Version } from "../../../src/utilities/version"; import { FolderContext } from "../../../src/FolderContext"; import { mockGlobalObject } from "../../MockUtils"; import { activateExtensionForSuite, folderInRootWorkspace } from "../utilities/testutilities"; -import { expect } from "chai"; suite("SwiftTaskProvider Test Suite", () => { let workspaceContext: WorkspaceContext; @@ -64,10 +64,21 @@ suite("SwiftTaskProvider Test Suite", () => { toolchain ); const { exitCode } = await executeTaskAndWaitForResult(task); - assert.equal(exitCode, 0); - }).timeout(10000); + expect(exitCode).to.equal(0); + }); test("Exit code on failure", async () => { + const task = createSwiftTask( + ["invalid_swift_command"], + "invalid", + { cwd: workspaceFolder.uri, scope: vscode.TaskScope.Workspace }, + toolchain + ); + const { exitCode } = await executeTaskAndWaitForResult(task); + expect(exitCode).to.not.equal(0); + }); + + test("Exit code on failure to launch", async () => { const task = createSwiftTask( ["--help"], "help", @@ -85,8 +96,8 @@ suite("SwiftTaskProvider Test Suite", () => { ) ); const { exitCode } = await executeTaskAndWaitForResult(task); - assert.equal(exitCode, 1); - }).timeout(10000); + expect(exitCode).to.not.equal(0); + }); }); suite("provideTasks", () => { @@ -99,10 +110,9 @@ suite("SwiftTaskProvider Test Suite", () => { }); test("provided", async () => { - assert.equal( - task?.detail, - "swift build --build-tests -Xswiftc -diagnostic-style=llvm" - ); + expect(task?.detail) + .to.include("swift build --build-tests") + .and.to.include("-Xswiftc -diagnostic-style=llvm"); }); test("executes @slow", async () => { @@ -110,7 +120,7 @@ suite("SwiftTaskProvider Test Suite", () => { const exitPromise = waitForEndTaskProcess(task); await vscode.tasks.executeTask(task); const exitCode = await exitPromise; - assert.equal(exitCode, 0); + expect(exitCode).to.equal(0); }).timeout(180000); // 3 minutes to build }); @@ -123,7 +133,7 @@ suite("SwiftTaskProvider Test Suite", () => { }); test("provided", async () => { - assert.equal(task?.detail, "swift build --show-bin-path"); + expect(task?.detail).to.include("swift build --show-bin-path"); }); test("executes", async () => { @@ -131,17 +141,16 @@ suite("SwiftTaskProvider Test Suite", () => { const exitPromise = waitForEndTaskProcess(task); await vscode.tasks.executeTask(task); const exitCode = await exitPromise; - assert.equal(exitCode, 0); + expect(exitCode).to.equal(0); }); }); test("includes product debug task", async () => { const tasks = await vscode.tasks.fetchTasks({ type: "swift" }); const task = tasks.find(t => t.name === "Build Debug PackageExe (defaultPackage)"); - assert.equal( - task?.detail, - "swift build --product PackageExe -Xswiftc -diagnostic-style=llvm" - ); + expect(task?.detail) + .to.include("swift build --product PackageExe") + .and.to.include("-Xswiftc -diagnostic-style=llvm"); }); test("includes product release task", async () => { @@ -150,30 +159,23 @@ suite("SwiftTaskProvider Test Suite", () => { new vscode.CancellationTokenSource().token ); const task = tasks.find(t => t.name === "Build Release PackageExe (defaultPackage)"); - assert.equal( - task?.detail, - "swift build -c release --product PackageExe -Xswiftc -diagnostic-style=llvm" - ); + expect(task?.detail).to.include("swift build -c release --product PackageExe"); }); test("includes additional folders", async () => { const tasks = await vscode.tasks.fetchTasks({ type: "swift" }); const diagnosticTasks = tasks.filter(t => t.name.endsWith("(diagnostics)")); - assert.equal(diagnosticTasks.length, 3); + expect(diagnosticTasks).to.have.lengthOf(3); }); }); suite("createBuildAllTask", () => { test("should return same task instance", async () => { - assert.strictEqual( - createBuildAllTask(folderContext), - createBuildAllTask(folderContext) - ); + expect(createBuildAllTask(folderContext)).to.equal(createBuildAllTask(folderContext)); }); test("different task returned for release mode", async () => { - assert.notEqual( - createBuildAllTask(folderContext), + expect(createBuildAllTask(folderContext)).to.not.equal( createBuildAllTask(folderContext, true) ); }); @@ -184,8 +186,7 @@ suite("SwiftTaskProvider Test Suite", () => { test("creates build all task when it cannot find one", async () => { tasksMock.fetchTasks.resolves([]); - assert.strictEqual( - await getBuildAllTask(folderContext), + await expect(getBuildAllTask(folderContext)).to.eventually.equal( createBuildAllTask(folderContext) ); }); diff --git a/test/integration-tests/testexplorer/LSPTestDiscovery.test.ts b/test/integration-tests/testexplorer/LSPTestDiscovery.test.ts index 48a60ccc4..7e109d6d9 100644 --- a/test/integration-tests/testexplorer/LSPTestDiscovery.test.ts +++ b/test/integration-tests/testexplorer/LSPTestDiscovery.test.ts @@ -83,7 +83,7 @@ suite("LSPTestDiscovery Suite", () => { let client: TestLanguageClient; let discoverer: LSPTestDiscovery; let pkg: SwiftPackage; - const file = vscode.Uri.file("file:///some/file.swift"); + const file = vscode.Uri.file("/some/file.swift"); beforeEach(async function () { this.timeout(10000000); diff --git a/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts b/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts index 6f3c9be4c..a646c1c64 100644 --- a/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts +++ b/test/integration-tests/testexplorer/TestExplorerIntegration.test.ts @@ -14,7 +14,7 @@ import * as assert from "assert"; import * as vscode from "vscode"; -import { beforeEach } from "mocha"; +import { beforeEach, afterEach } from "mocha"; import { testAssetUri } from "../../fixtures"; import { TestExplorer } from "../../../src/TestExplorer/TestExplorer"; import { @@ -24,7 +24,6 @@ import { eventPromise, gatherTests, runTest, - testExplorerFor, waitForTestExplorerReady, } from "./utilities"; import { WorkspaceContext } from "../../../src/WorkspaceContext"; @@ -40,11 +39,7 @@ import { reduceTestItemChildren, } from "../../../src/TestExplorer/TestUtils"; import { runnableTag } from "../../../src/TestExplorer/TestDiscovery"; -import { - activateExtensionForSuite, - activateExtensionForTest, - updateSettings, -} from "../utilities/testutilities"; +import { activateExtensionForSuite, updateSettings } from "../utilities/testutilities"; import { Commands } from "../../../src/commands"; import { SwiftToolchain } from "../../../src/toolchain/toolchain"; @@ -56,6 +51,26 @@ suite("Test Explorer Suite", function () { let workspaceContext: WorkspaceContext; let testExplorer: TestExplorer; + activateExtensionForSuite({ + async setup(ctx) { + workspaceContext = ctx; + const packageFolder = testAssetUri("defaultPackage"); + const targetFolder = workspaceContext.folders.find( + folder => folder.folder.path === packageFolder.path + ); + + if (!targetFolder) { + throw new Error("Unable to find test explorer"); + } + + testExplorer = targetFolder.addTestExplorer(); + + // Set up the listener before bringing the text explorer in to focus, + // which starts searching the workspace for tests. + await waitForTestExplorerReady(testExplorer); + }, + }); + suite("Debugging", function () { async function runXCTest() { const suiteId = "PackageTests.PassingXCTestSuite"; @@ -67,7 +82,15 @@ suite("Test Explorer Suite", function () { }); } - async function runSwiftTesting() { + async function runSwiftTesting(this: Mocha.Context) { + if ( + // swift-testing was not able to produce JSON events until 6.0.2 on Windows. + process.platform === "win32" && + workspaceContext.swiftVersion.isLessThan(new Version(6, 0, 2)) + ) { + this.skip(); + } + const testId = "PackageTests.topLevelTestPassing()"; const testRun = await runTest(testExplorer, TestKind.debug, testId); @@ -77,31 +100,28 @@ suite("Test Explorer Suite", function () { } suite("lldb-dap", () => { - activateExtensionForTest({ - async setup(ctx) { - // lldb-dap is only present in the toolchain in 6.0 and up. - if (ctx.swiftVersion.isLessThan(new Version(6, 0, 0))) { - this.skip(); - } - - workspaceContext = ctx; - testExplorer = testExplorerFor( - workspaceContext, - testAssetUri("defaultPackage") - ); + let resetSettings: (() => Promise) | undefined; + beforeEach(async function () { + // lldb-dap is only present/functional in the toolchain in 6.0.2 and up. + if (workspaceContext.swiftVersion.isLessThan(new Version(6, 0, 2))) { + this.skip(); + } - // Set up the listener before bringing the text explorer in to focus, - // which starts searching the workspace for tests. - await waitForTestExplorerReady(testExplorer); + resetSettings = await updateSettings({ + "swift.debugger.useDebugAdapterFromToolchain": true, + }); + }); - return await updateSettings({ - "swift.debugger.useDebugAdapterFromToolchain": true, - }); - }, + afterEach(async () => { + if (resetSettings) { + await resetSettings(); + } }); test("Debugs specified XCTest test", runXCTest); - test("Debugs specified swift-testing test", runSwiftTesting); + test("Debugs specified swift-testing test", async function () { + await runSwiftTesting.call(this); + }); }); suite("CodeLLDB", () => { @@ -116,34 +136,28 @@ suite("Test Explorer Suite", function () { } } - activateExtensionForTest({ - async setup(ctx) { - // CodeLLDB on windows doesn't print output and so cannot be parsed - if (process.platform === "win32") { - this.skip(); - return; - } - - workspaceContext = ctx; - testExplorer = testExplorerFor( - workspaceContext, - testAssetUri("defaultPackage") - ); + let resetSettings: (() => Promise) | undefined; + beforeEach(async function () { + // CodeLLDB on windows doesn't print output and so cannot be parsed + if (process.platform === "win32") { + this.skip(); + } - // Set up the listener before bringing the text explorer in to focus, - // which starts searching the workspace for tests. - await waitForTestExplorerReady(testExplorer); + const lldbPath = + process.env["CI"] === "1" + ? { "lldb.library": await getLLDBDebugAdapterPath() } + : {}; - const lldbPath = - process.env["CI"] === "1" - ? { "lldb.library": await getLLDBDebugAdapterPath() } - : {}; + resetSettings = await updateSettings({ + "swift.debugger.useDebugAdapterFromToolchain": false, + ...lldbPath, + }); + }); - return await updateSettings({ - "swift.debugger.useDebugAdapterFromToolchain": false, - ...lldbPath, - }); - }, + afterEach(async () => { + if (resetSettings) { + await resetSettings(); + } }); test("Debugs specified XCTest test", async function () { @@ -158,33 +172,12 @@ suite("Test Explorer Suite", function () { if (workspaceContext.swiftVersion.isLessThan(new Version(6, 0, 0))) { this.skip(); } - await runSwiftTesting(); + await runSwiftTesting.call(this); }); }); }); suite("Standard", () => { - activateExtensionForSuite({ - async setup(ctx) { - workspaceContext = ctx; - }, - }); - - beforeEach(async () => { - const packageFolder = testAssetUri("defaultPackage"); - const targetFolder = workspaceContext.folders.find( - folder => folder.folder.path === packageFolder.path - ); - if (!targetFolder || !targetFolder.testExplorer) { - throw new Error("Unable to find test explorer"); - } - testExplorer = targetFolder.testExplorer; - - // Set up the listener before bringing the text explorer in to focus, - // which starts searching the workspace for tests. - await waitForTestExplorerReady(testExplorer); - }); - test("Finds Tests", async function () { if (workspaceContext.swiftVersion.isGreaterThanOrEqual(new Version(6, 0, 0))) { // 6.0 uses the LSP which returns tests in the order they're declared. @@ -240,7 +233,12 @@ suite("Test Explorer Suite", function () { suite("swift-testing", () => { suiteSetup(function () { - if (workspaceContext.swiftVersion.isLessThan(new Version(6, 0, 0))) { + if ( + workspaceContext.swiftVersion.isLessThan(new Version(6, 0, 0)) || + // swift-testing was not able to produce JSON events until 6.0.2 on Windows. + (process.platform === "win32" && + workspaceContext.swiftVersion.isLessThan(new Version(6, 0, 2))) + ) { this.skip(); } }); @@ -487,13 +485,17 @@ suite("Test Explorer Suite", function () { runProfile === TestKind.parallel && !workspaceContext.toolchain.hasMultiLineParallelTestOutput ? "failed" - : "failed - oh no"; + : `failed - oh no`; }); suite(runProfile, () => { suite(`swift-testing (${runProfile})`, function () { suiteSetup(function () { - if (workspaceContext.swiftVersion.isLessThan(new Version(6, 0, 0))) { + if ( + workspaceContext.swiftVersion.isLessThan(new Version(6, 0, 0)) || + (process.platform === "win32" && + workspaceContext.swiftVersion.isLessThan(new Version(6, 0, 2))) + ) { this.skip(); } }); diff --git a/test/integration-tests/testexplorer/utilities.ts b/test/integration-tests/testexplorer/utilities.ts index 6a86b9a26..af64c6731 100644 --- a/test/integration-tests/testexplorer/utilities.ts +++ b/test/integration-tests/testexplorer/utilities.ts @@ -26,6 +26,8 @@ import { SettingsMap, updateSettings, } from "../utilities/testutilities"; +// eslint-disable-next-line @typescript-eslint/no-require-imports +import stripAnsi = require("strip-ansi"); /** * Sets up a test that leverages the TestExplorer, returning the TestExplorer, @@ -34,12 +36,12 @@ import { * @returns Object containing the TestExplorer, WorkspaceContext and a callback to revert * the settings back to their original values. */ -export async function setupTestExplorerTest(settings: SettingsMap = {}) { +export async function setupTestExplorerTest(currentTest?: Mocha.Test, settings: SettingsMap = {}) { const settingsTeardown = await updateSettings(settings); const testProject = testAssetUri("defaultPackage"); - const workspaceContext = await activateExtension(); + const workspaceContext = await activateExtension(currentTest); const testExplorer = testExplorerFor(workspaceContext, testProject); // Set up the listener before bringing the text explorer in to focus, @@ -139,8 +141,8 @@ export function assertTestResults( .map(({ test, message }) => ({ test: test.id, issues: Array.isArray(message) - ? message.map(({ message }) => message) - : [(message as vscode.TestMessage).message], + ? message.map(({ message }) => stripAnsi(message.toString())) + : [stripAnsi((message as vscode.TestMessage).message.toString())], })) .sort(), skipped: testRun.runState.skipped.map(({ id }) => id).sort(), @@ -149,7 +151,12 @@ export function assertTestResults( }, { passed: (state.passed ?? []).sort(), - failed: (state.failed ?? []).sort(), + failed: (state.failed ?? []) + .map(({ test, issues }) => ({ + test, + issues: issues.map(message => stripAnsi(message)), + })) + .sort(), skipped: (state.skipped ?? []).sort(), errored: (state.errored ?? []).sort(), unknown: 0, @@ -232,12 +239,16 @@ export async function gatherTests( const testItems = tests.map(test => { const testItem = getTestItem(controller, test); if (!testItem) { - const testsInController: string[] = []; - controller.items.forEach(item => { - testsInController.push( - `${item.id}: ${item.label} ${item.error ? `(error: ${item.error})` : ""}` - ); - }); + const testsInController = reduceTestItemChildren( + controller.items, + (acc, item) => { + acc.push( + `${item.id}: ${item.label} ${item.error ? `(error: ${item.error})` : ""}` + ); + return acc; + }, + [] as string[] + ); assert.fail( `Unable to find ${test} in Test Controller. Items in test controller are: ${testsInController.join(", ")}` diff --git a/test/integration-tests/ui/PackageDependencyProvider.test.ts b/test/integration-tests/ui/PackageDependencyProvider.test.ts index 686eb5614..85d62556b 100644 --- a/test/integration-tests/ui/PackageDependencyProvider.test.ts +++ b/test/integration-tests/ui/PackageDependencyProvider.test.ts @@ -13,6 +13,8 @@ //===----------------------------------------------------------------------===// import { expect } from "chai"; +import * as vscode from "vscode"; +import * as path from "path"; import { PackageDependenciesProvider, PackageNode, @@ -38,6 +40,7 @@ suite("PackageDependencyProvider Test Suite", function () { async teardown() { treeProvider.dispose(); }, + testAssets: ["dependencies"], }); test("Includes remote dependency", async () => { @@ -46,8 +49,9 @@ suite("PackageDependencyProvider Test Suite", function () { const dep = items.find(n => n.name === "swift-markdown") as PackageNode; expect(dep).to.not.be.undefined; expect(dep?.location).to.equal("https://github.com/swiftlang/swift-markdown.git"); - expect(dep?.path).to.equal( - `${testAssetPath("dependencies")}/.build/checkouts/swift-markdown` + assertPathsEqual( + dep?.path, + path.join(testAssetPath("dependencies"), ".build/checkouts/swift-markdown") ); }); @@ -55,35 +59,45 @@ suite("PackageDependencyProvider Test Suite", function () { const items = await treeProvider.getChildren(); const dep = items.find(n => n.name === "defaultpackage") as PackageNode; - expect(dep).to.not.be.undefined; - expect(dep?.location).to.equal(testAssetPath("defaultPackage")); - expect(dep?.path).to.equal(testAssetPath("defaultPackage")); + expect( + dep, + `Expected to find defaultPackage, but instead items were ${items.map(n => n.name)}` + ).to.not.be.undefined; + assertPathsEqual(dep?.location, testAssetPath("defaultPackage")); + assertPathsEqual(dep?.path, testAssetPath("defaultPackage")); }); test("Lists local dependency file structure", async () => { const items = await treeProvider.getChildren(); const dep = items.find(n => n.name === "defaultpackage") as PackageNode; - expect(dep).to.not.be.undefined; + expect( + dep, + `Expected to find defaultPackage, but instead items were ${items.map(n => n.name)}` + ).to.not.be.undefined; const folders = await treeProvider.getChildren(dep); const folder = folders.find(n => n.name === "Sources"); expect(folder).to.not.be.undefined; - expect(folder?.path).to.equal(`${testAssetPath("defaultPackage")}/Sources`); + assertPathsEqual(folder?.path, path.join(testAssetPath("defaultPackage"), "Sources")); const childFolders = await treeProvider.getChildren(folder); const childFolder = childFolders.find(n => n.name === "PackageExe"); expect(childFolder).to.not.be.undefined; - expect(childFolder?.path).to.equal(`${testAssetPath("defaultPackage")}/Sources/PackageExe`); + assertPathsEqual( + childFolder?.path, + path.join(testAssetPath("defaultPackage"), "Sources/PackageExe") + ); const files = await treeProvider.getChildren(childFolder); const file = files.find(n => n.name === "main.swift"); expect(file).to.not.be.undefined; - expect(file?.path).to.equal( - `${testAssetPath("defaultPackage")}/Sources/PackageExe/main.swift` + assertPathsEqual( + file?.path, + path.join(testAssetPath("defaultPackage"), "Sources/PackageExe/main.swift") ); }); @@ -97,19 +111,26 @@ suite("PackageDependencyProvider Test Suite", function () { const folder = folders.find(n => n.name === "Sources"); expect(folder).to.not.be.undefined; - const path = `${testAssetPath("dependencies")}/.build/checkouts/swift-markdown`; - expect(folder?.path).to.equal(`${path}/Sources`); + const depPath = path.join(testAssetPath("dependencies"), ".build/checkouts/swift-markdown"); + assertPathsEqual(folder?.path, path.join(depPath, "Sources")); const childFolders = await treeProvider.getChildren(folder); const childFolder = childFolders.find(n => n.name === "CAtomic"); expect(childFolder).to.not.be.undefined; - expect(childFolder?.path).to.equal(`${path}/Sources/CAtomic`); + assertPathsEqual(childFolder?.path, path.join(depPath, "Sources/CAtomic")); const files = await treeProvider.getChildren(childFolder); const file = files.find(n => n.name === "CAtomic.c"); expect(file).to.not.be.undefined; - expect(file?.path).to.equal(`${path}/Sources/CAtomic/CAtomic.c`); + assertPathsEqual(file?.path, path.join(depPath, "Sources/CAtomic/CAtomic.c")); }); + + function assertPathsEqual(path1: string | undefined, path2: string | undefined) { + expect(path1).to.not.be.undefined; + expect(path2).to.not.be.undefined; + // Convert to vscode.Uri to normalize paths, including drive letter capitalization on Windows. + expect(vscode.Uri.file(path1!).fsPath).to.equal(vscode.Uri.file(path2!).fsPath); + } }); diff --git a/test/integration-tests/utilities/testutilities.ts b/test/integration-tests/utilities/testutilities.ts index a5f6b101b..7dbd044f1 100644 --- a/test/integration-tests/utilities/testutilities.ts +++ b/test/integration-tests/utilities/testutilities.ts @@ -82,9 +82,14 @@ const extensionBootstrapper = (() => { // Typically this is the promise returned from `updateSettings`, which will // undo any settings changed during setup. autoTeardown = await setup.call(this, workspaceContext); - } catch (error) { - console.error(`Error during test/suite setup, captured logs are:`); - workspaceContext.outputChannel.logs.map(log => console.log(log)); + } catch (error: any) { + // Mocha will throw an error to break out of a test if `.skip` is used. + if (error.message?.indexOf("sync skip;") === -1) { + console.error(`Error during test/suite setup: ${JSON.stringify(error)}`); + console.error("Captured logs are:"); + workspaceContext.outputChannel.logs.map(log => console.error(log)); + console.error("================ end test logs ================"); + } throw error; } }); @@ -135,10 +140,14 @@ const extensionBootstrapper = (() => { // Subsequent activations must be done through the returned API object. if (!activator) { activatedAPI = await ext.activate(); + // Save the test name so if the test doesn't clean up by deactivating properly the next + // test that tries to activate can throw an error with the name of the test that needs to clean up. + lastTestName = currentTest?.titlePath().join(" → "); activator = activatedAPI.activate; workspaceContext = activatedAPI.workspaceContext; } else { activatedAPI = await activator(); + lastTestName = currentTest?.titlePath().join(" → "); workspaceContext = activatedAPI.workspaceContext; } @@ -153,10 +162,6 @@ const extensionBootstrapper = (() => { await workspaceContext.addPackageFolder(packageFolder, workspaceFolder); } - // Save the test name so if the test doesn't clean up by deactivating properly the next - // test that tries to activate can throw an error with the name of the test that needs to clean up. - lastTestName = currentTest?.fullTitle(); - return workspaceContext; }, deactivateExtension: async () => { diff --git a/test/unit-tests/debugger/lldb.test.ts b/test/unit-tests/debugger/lldb.test.ts index e2f59aadc..0bc5c1d6d 100644 --- a/test/unit-tests/debugger/lldb.test.ts +++ b/test/unit-tests/debugger/lldb.test.ts @@ -30,6 +30,7 @@ import { } from "../../MockUtils"; import { SwiftToolchain } from "../../../src/toolchain/toolchain"; import { WorkspaceContext } from "../../../src/WorkspaceContext"; +import { SwiftOutputChannel } from "../../../src/ui/SwiftOutputChannel"; suite("debugger.lldb Tests", () => { suite("getLLDBLibPath Tests", () => { @@ -144,11 +145,16 @@ suite("debugger.lldb Tests", () => { let mockToolchain: MockedObject; setup(() => { + windowMock.createOutputChannel.returns({ + appendLine() {}, + } as unknown as vscode.LogOutputChannel); + mockToolchain = mockObject({ getLLDB: mockFn(s => s.resolves("/path/to/lldb")), }); mockContext = mockObject({ toolchain: instance(mockToolchain), + outputChannel: new SwiftOutputChannel("mockChannel", false), }); }); diff --git a/test/utilities.ts b/test/utilities.ts index e851f5c05..92ade71a4 100644 --- a/test/utilities.ts +++ b/test/utilities.ts @@ -13,6 +13,8 @@ //===----------------------------------------------------------------------===// import * as vscode from "vscode"; +// eslint-disable-next-line @typescript-eslint/no-require-imports +import stripAnsi = require("strip-ansi"); import { SwiftTaskFixture } from "./fixtures"; import { SwiftTask } from "../src/tasks/SwiftTaskProvider"; @@ -141,3 +143,14 @@ export function waitForEndTaskProcess(task: vscode.Task): Promise Date: Wed, 4 Dec 2024 16:27:23 -0500 Subject: [PATCH 16/22] Make dependency integration test not flaky (#1245) --- .../commands/dependency.test.ts | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/test/integration-tests/commands/dependency.test.ts b/test/integration-tests/commands/dependency.test.ts index cc4f9e264..b8e8d0304 100644 --- a/test/integration-tests/commands/dependency.test.ts +++ b/test/integration-tests/commands/dependency.test.ts @@ -25,11 +25,7 @@ import { FolderContext } from "../../../src/FolderContext"; import { WorkspaceContext } from "../../../src/WorkspaceContext"; import * as sinon from "sinon"; import { Commands } from "../../../src/commands"; -import { - activateExtensionForSuite, - activateExtensionForTest, - folderInRootWorkspace, -} from "../utilities/testutilities"; +import { activateExtensionForSuite, folderInRootWorkspace } from "../utilities/testutilities"; suite("Dependency Commmands Test Suite", function () { // full workflow's interaction with spm is longer than the default timeout @@ -82,7 +78,7 @@ suite("Dependency Commmands Test Suite", function () { let treeProvider: PackageDependenciesProvider; let item: PackageNode; - activateExtensionForTest({ + activateExtensionForSuite({ async setup(ctx) { // Check before each test case start: // Expect to fail without setting up local version @@ -111,13 +107,14 @@ suite("Dependency Commmands Test Suite", function () { // Contract: spm edit with user supplied local version of dependency const windowMock = sinon.stub(vscode.window, "showOpenDialog"); windowMock.resolves([testAssetUri("Swift-Markdown")]); - const result = await vscode.commands.executeCommand( - Commands.USE_LOCAL_DEPENDENCY, - item - ); + let result = await vscode.commands.executeCommand(Commands.USE_LOCAL_DEPENDENCY, item); expect(result).to.be.true; windowMock.restore(); + // Make sure new dependencies resolve before building + result = await vscode.commands.executeCommand(Commands.RESOLVE_DEPENDENCIES); + expect(result).to.be.true; + // This will now pass as we have the required library const { exitCode, output } = await executeTaskAndWaitForResult(tasks); expect(exitCode, `${output}`).to.equal(0); From cecfaedb8cf07b3f741c482cb39ad90dda208334 Mon Sep 17 00:00:00 2001 From: Michael Weng Date: Thu, 5 Dec 2024 15:02:54 -0500 Subject: [PATCH 17/22] Improve the debugging on expect statement in test (#1239) * Improve the debugging on expect statement in test - This is true especially on flaky test, sometimes we check for the exit code but not print out the output from the command on failure. - Fix it for a bunch of asserts where sporadic failures were observed to better understand what went wrong. * Address some expected failure and added comment where applicable * Rebase main and resolve conflict --- test/integration-tests/DiagnosticsManager.test.ts | 2 ++ test/integration-tests/commands/dependency.test.ts | 4 ++-- test/integration-tests/tasks/SwiftPluginTaskProvider.test.ts | 4 ++-- test/integration-tests/ui/PackageDependencyProvider.test.ts | 4 ++-- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/test/integration-tests/DiagnosticsManager.test.ts b/test/integration-tests/DiagnosticsManager.test.ts index 8e0938e9c..03f1e82f6 100644 --- a/test/integration-tests/DiagnosticsManager.test.ts +++ b/test/integration-tests/DiagnosticsManager.test.ts @@ -211,6 +211,8 @@ suite("DiagnosticsManager Test Suite", async function () { suiteSetup(async function () { this.timeout(2 * 60 * 1000); // Allow 2 minutes to build const task = createBuildAllTask(folderContext); + // This return exit code and output for the task but we will omit it here + // because the failures are expected and we just want the task to build await executeTaskAndWaitForResult(task); }); diff --git a/test/integration-tests/commands/dependency.test.ts b/test/integration-tests/commands/dependency.test.ts index b8e8d0304..b8a885a1c 100644 --- a/test/integration-tests/commands/dependency.test.ts +++ b/test/integration-tests/commands/dependency.test.ts @@ -89,7 +89,7 @@ suite("Dependency Commmands Test Suite", function () { tasks = (await getBuildAllTask(folderContext)) as SwiftTask; const { exitCode, output } = await executeTaskAndWaitForResult(tasks); - expect(exitCode).to.not.equal(0); + expect(exitCode, `${output}`).to.not.equal(0); expect(output).to.include("PackageLib"); expect(output).to.include("required"); @@ -125,7 +125,7 @@ suite("Dependency Commmands Test Suite", function () { async function assertDependencyNoLongerExists() { // Expect to fail again now dependency is missing const { exitCode, output } = await executeTaskAndWaitForResult(tasks); - expect(exitCode).to.not.equal(0); + expect(exitCode, `${output}`).to.not.equal(0); expect(output).to.include("PackageLib"); expect(output).to.include("required"); } diff --git a/test/integration-tests/tasks/SwiftPluginTaskProvider.test.ts b/test/integration-tests/tasks/SwiftPluginTaskProvider.test.ts index 9f91ab740..23dc46d67 100644 --- a/test/integration-tests/tasks/SwiftPluginTaskProvider.test.ts +++ b/test/integration-tests/tasks/SwiftPluginTaskProvider.test.ts @@ -69,8 +69,8 @@ suite("SwiftPluginTaskProvider Test Suite", () => { } ); mutable(task.execution).command = "/definitely/not/swift"; - const { exitCode } = await executeTaskAndWaitForResult(task); - expect(exitCode).to.not.equal(0); + const { exitCode, output } = await executeTaskAndWaitForResult(task); + expect(exitCode, `${output}`).to.not.equal(0); }).timeout(10000); }); diff --git a/test/integration-tests/ui/PackageDependencyProvider.test.ts b/test/integration-tests/ui/PackageDependencyProvider.test.ts index 85d62556b..bec1e9903 100644 --- a/test/integration-tests/ui/PackageDependencyProvider.test.ts +++ b/test/integration-tests/ui/PackageDependencyProvider.test.ts @@ -47,7 +47,7 @@ suite("PackageDependencyProvider Test Suite", function () { const items = await treeProvider.getChildren(); const dep = items.find(n => n.name === "swift-markdown") as PackageNode; - expect(dep).to.not.be.undefined; + expect(dep, `${JSON.stringify(items, null, 2)}`).to.not.be.undefined; expect(dep?.location).to.equal("https://github.com/swiftlang/swift-markdown.git"); assertPathsEqual( dep?.path, @@ -105,7 +105,7 @@ suite("PackageDependencyProvider Test Suite", function () { const items = await treeProvider.getChildren(); const dep = items.find(n => n.name === "swift-markdown") as PackageNode; - expect(dep).to.not.be.undefined; + expect(dep, `${JSON.stringify(items, null, 2)}`).to.not.be.undefined; const folders = await treeProvider.getChildren(dep); const folder = folders.find(n => n.name === "Sources"); From d51edfc45c0dcd5a41aedca28caac74cdbaa5b2b Mon Sep 17 00:00:00 2001 From: Michael Weng Date: Fri, 6 Dec 2024 09:10:36 -0500 Subject: [PATCH 18/22] Add integration tests for Expand Macro actions (#1206) * Add integration tests for Inline/Expand Macro actions - Validate the workflow of user calling Inline/Expand Macro actions on a swift project with macro - Add test fixture for swift macro - Verify inline macro by asserting on inlined value after calling the action - Verify expand macro by asserting expanded macro document contain the right macro Issue: #1205 --- assets/test/swift-macro/Package.swift | 42 ++++++ .../Sources/swift-macro/swift_macro.swift | 11 ++ .../Sources/swift-macroClient/main.swift | 8 ++ .../swift-macroMacros/swift_macroMacro.swift | 33 +++++ test/integration-tests/language/macro.test.ts | 131 ++++++++++++++++++ 5 files changed, 225 insertions(+) create mode 100644 assets/test/swift-macro/Package.swift create mode 100644 assets/test/swift-macro/Sources/swift-macro/swift_macro.swift create mode 100644 assets/test/swift-macro/Sources/swift-macroClient/main.swift create mode 100644 assets/test/swift-macro/Sources/swift-macroMacros/swift_macroMacro.swift create mode 100644 test/integration-tests/language/macro.test.ts diff --git a/assets/test/swift-macro/Package.swift b/assets/test/swift-macro/Package.swift new file mode 100644 index 000000000..24d3dbf6d --- /dev/null +++ b/assets/test/swift-macro/Package.swift @@ -0,0 +1,42 @@ +// swift-tools-version:5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription +import CompilerPluginSupport + +let package = Package( + name: "swift-macro", + platforms: [.macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v6), .macCatalyst(.v13)], + products: [ + // Products define the executables and libraries a package produces, making them visible to other packages. + .library( + name: "swift-macro", + targets: ["swift-macro"] + ), + .executable( + name: "swift-macroClient", + targets: ["swift-macroClient"] + ), + ], + dependencies: [ + .package(url: "https://github.com/swiftlang/swift-syntax.git", from: "600.0.0-latest"), + ], + targets: [ + // Targets are the basic building blocks of a package, defining a module or a test suite. + // Targets can depend on other targets in this package and products from dependencies. + // Macro implementation that performs the source transformation of a macro. + .macro( + name: "swift-macroMacros", + dependencies: [ + .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), + .product(name: "SwiftCompilerPlugin", package: "swift-syntax") + ] + ), + + // Library that exposes a macro as part of its API, which is used in client programs. + .target(name: "swift-macro", dependencies: ["swift-macroMacros"]), + + // A client of the library, which is able to use the macro in its own code. + .executableTarget(name: "swift-macroClient", dependencies: ["swift-macro"]), + ] +) diff --git a/assets/test/swift-macro/Sources/swift-macro/swift_macro.swift b/assets/test/swift-macro/Sources/swift-macro/swift_macro.swift new file mode 100644 index 000000000..0e0da12b8 --- /dev/null +++ b/assets/test/swift-macro/Sources/swift-macro/swift_macro.swift @@ -0,0 +1,11 @@ +// The Swift Programming Language +// https://docs.swift.org/swift-book + +/// A macro that produces both a value and a string containing the +/// source code that generated the value. For example, +/// +/// #stringify(x + y) +/// +/// produces a tuple `(x + y, "x + y")`. +@freestanding(expression) +public macro stringify(_ value: T) -> (T, String) = #externalMacro(module: "swift_macroMacros", type: "StringifyMacro") diff --git a/assets/test/swift-macro/Sources/swift-macroClient/main.swift b/assets/test/swift-macro/Sources/swift-macroClient/main.swift new file mode 100644 index 000000000..f80e10e00 --- /dev/null +++ b/assets/test/swift-macro/Sources/swift-macroClient/main.swift @@ -0,0 +1,8 @@ +import swift_macro + +let a = 17 +let b = 25 + +let (result, code) = #stringify(a + b) + +print("The value \(result) was produced by the code \"\(code)\"") diff --git a/assets/test/swift-macro/Sources/swift-macroMacros/swift_macroMacro.swift b/assets/test/swift-macro/Sources/swift-macroMacros/swift_macroMacro.swift new file mode 100644 index 000000000..5f242ce91 --- /dev/null +++ b/assets/test/swift-macro/Sources/swift-macroMacros/swift_macroMacro.swift @@ -0,0 +1,33 @@ +import SwiftCompilerPlugin +import SwiftSyntax +import SwiftSyntaxBuilder +import SwiftSyntaxMacros + +/// Implementation of the `stringify` macro, which takes an expression +/// of any type and produces a tuple containing the value of that expression +/// and the source code that produced the value. For example +/// +/// #stringify(x + y) +/// +/// will expand to +/// +/// (x + y, "x + y") +public struct StringifyMacro: ExpressionMacro { + public static func expansion( + of node: some FreestandingMacroExpansionSyntax, + in context: some MacroExpansionContext + ) -> ExprSyntax { + guard let argument = node.arguments.first?.expression else { + fatalError("compiler bug: the macro does not have any arguments") + } + + return "(\(argument), \(literal: argument.description))" + } +} + +@main +struct swift_macroPlugin: CompilerPlugin { + let providingMacros: [Macro.Type] = [ + StringifyMacro.self, + ] +} diff --git a/test/integration-tests/language/macro.test.ts b/test/integration-tests/language/macro.test.ts new file mode 100644 index 000000000..ae2a47cbe --- /dev/null +++ b/test/integration-tests/language/macro.test.ts @@ -0,0 +1,131 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the VS Code Swift open source project +// +// Copyright (c) 2024 the VS Code Swift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of VS Code Swift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import * as vscode from "vscode"; +import * as langclient from "vscode-languageclient/node"; +import { expect } from "chai"; +import { LanguageClientManager } from "../../../src/sourcekit-lsp/LanguageClientManager"; +import { WorkspaceContext } from "../../../src/WorkspaceContext"; +import { testAssetUri } from "../../fixtures"; +import { FolderContext } from "../../../src/FolderContext"; +import { executeTaskAndWaitForResult, waitForNoRunningTasks } from "../../utilities"; +import { getBuildAllTask, SwiftTask } from "../../../src/tasks/SwiftTaskProvider"; +import { Version } from "../../../src/utilities/version"; +import { activateExtensionForSuite, folderInRootWorkspace } from "../utilities/testutilities"; + +async function waitForClientState( + languageClientManager: LanguageClientManager, + expectedState: langclient.State +): Promise { + let clientState = undefined; + while (clientState !== expectedState) { + clientState = await languageClientManager.useLanguageClient(async client => client.state); + console.warn("Language client is not ready yet. Retrying in 100 ms..."); + await new Promise(resolve => setTimeout(resolve, 100)); + } + return clientState; +} + +suite("Integration, Macros Functionality Support with Sourcekit-lsp", function () { + // Take around 60 seconds if running in isolation, longer than default timeout + this.timeout(2 * 60 * 1000); + + let clientManager: LanguageClientManager; + let workspaceContext: WorkspaceContext; + let folderContext: FolderContext; + + activateExtensionForSuite({ + async setup(ctx) { + workspaceContext = ctx; + // Expand Macro support in Swift started from 6.1 + if (workspaceContext.swiftVersion.isLessThan(new Version(6, 1, 0))) { + this.skip(); + } + + // Wait for a clean starting point, and build all tasks for the fixture + await waitForNoRunningTasks(); + folderContext = await folderInRootWorkspace("swift-macro", workspaceContext); + await workspaceContext.focusFolder(folderContext); + const tasks = (await getBuildAllTask(folderContext)) as SwiftTask; + const { exitCode, output } = await executeTaskAndWaitForResult(tasks); + expect(exitCode, `${output}`).to.equal(0); + + // Ensure lsp client is ready + clientManager = workspaceContext.languageClientManager; + const clientState = await waitForClientState(clientManager, langclient.State.Running); + expect(clientState).to.equals(langclient.State.Running); + }, + }); + + test("Expand Macro", async function () { + // Focus on the file of interest + const uri = testAssetUri("swift-macro/Sources/swift-macroClient/main.swift"); + await vscode.window.showTextDocument(uri); + + // Beginning of macro, # + const position = new vscode.Position(5, 21); + + // Create a range starting and ending at the specified position + const range = new vscode.Range(position, position); + + // Execute the code action provider command + const codeActions = await vscode.commands.executeCommand( + "vscode.executeCodeActionProvider", + uri, + range + ); + + const expectedMacro = '(a + b, "a + b")'; + + // Find the "expand.macro.command" action + const expandMacroAction = codeActions.find( + action => action.command?.command === "expand.macro.command" + ); + + // Assert that the expand macro command is available + expect(expandMacroAction).is.not.undefined; + + // Set up a promise that resolves when the expected document is opened + const expandedMacroUriPromise = new Promise((resolve, reject) => { + const disposable = vscode.workspace.onDidOpenTextDocument(openedDocument => { + if (openedDocument.uri.scheme === "sourcekit-lsp") { + disposable.dispose(); // Stop listening once we find the desired document + resolve(openedDocument); + } + }); + + // Set a timeout to reject the promise if the document is not found + setTimeout(() => { + disposable.dispose(); + reject(new Error("Timed out waiting for sourcekit-lsp document to be opened.")); + }, 10000); // Wait up to 10 seconds for the document + }); + + // Run expand macro action + const command = expandMacroAction!.command!; + expect(command.arguments).is.not.undefined; + const commandArgs = command.arguments!; + await vscode.commands.executeCommand(command.command, ...commandArgs); + + // Wait for the expanded macro document to be opened + const referenceDocument = await expandedMacroUriPromise; + + // Verify that the reference document was successfully opened + expect(referenceDocument).to.not.be.undefined; + + // Assert that the content contains the expected result + const content = referenceDocument.getText(); + expect(content).to.include(expectedMacro); + }); +}); From 4fc8f881b4639b0eeed61749f334ddb5567d243a Mon Sep 17 00:00:00 2001 From: Michael Weng Date: Fri, 6 Dec 2024 09:12:17 -0500 Subject: [PATCH 19/22] Disable dependency integration test (#1247) - Disable this test suite until we have a good way to set up the test to not dependent on external components that can fail. (remote git) - This will need to be revisited when project panel is introduced. --- test/integration-tests/commands/dependency.test.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/integration-tests/commands/dependency.test.ts b/test/integration-tests/commands/dependency.test.ts index b8a885a1c..b5b9ec2d3 100644 --- a/test/integration-tests/commands/dependency.test.ts +++ b/test/integration-tests/commands/dependency.test.ts @@ -80,6 +80,12 @@ suite("Dependency Commmands Test Suite", function () { activateExtensionForSuite({ async setup(ctx) { + // FIXME: Disable this test suite as this is dependent on external git dependency + // and introduces flakinesss when run in the CI setting. The spm command only + // runs if the dependency is remote, which make faking difficult. + // For enabling the test in the future, we would need to set up the environment + // into a pre-resolved state, so spm does not need to visit remote git url. + this.skip(); // Check before each test case start: // Expect to fail without setting up local version workspaceContext = ctx; From c047d4e5da2d467f0b89aef397845201bb52f58b Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Fri, 6 Dec 2024 11:23:25 -0500 Subject: [PATCH 20/22] Activate extension when compile_flags.txt or buildServer.json is present (#1240) SourceKit-LSP can handle projects configured with the paired down clang `compile_flags.txt` configuration file, as well as projects configured with the Build Server Protocol's `buildServer.json`. Activate the extension if the folder added to the workspace contains either of these files in the root. Issue: #1087 --- .../test/cmake-compile-flags/CMakeLists.txt | 5 ++++ .../cmake-compile-flags/compile_flags.txt | 8 ++++++ assets/test/cmake-compile-flags/main.cpp | 7 +++++ package.json | 2 ++ src/WorkspaceContext.ts | 8 +++--- .../ExtensionActivation.test.ts | 27 +++++++++++++++++++ 6 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 assets/test/cmake-compile-flags/CMakeLists.txt create mode 100644 assets/test/cmake-compile-flags/compile_flags.txt create mode 100644 assets/test/cmake-compile-flags/main.cpp diff --git a/assets/test/cmake-compile-flags/CMakeLists.txt b/assets/test/cmake-compile-flags/CMakeLists.txt new file mode 100644 index 000000000..a8b35d019 --- /dev/null +++ b/assets/test/cmake-compile-flags/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 2.4) + +project(hello_world) + +add_executable(app main.cpp) \ No newline at end of file diff --git a/assets/test/cmake-compile-flags/compile_flags.txt b/assets/test/cmake-compile-flags/compile_flags.txt new file mode 100644 index 000000000..af1c2d2a9 --- /dev/null +++ b/assets/test/cmake-compile-flags/compile_flags.txt @@ -0,0 +1,8 @@ +/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/c++ +-isysroot +/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.1.sdk +-mmacosx-version-min=13.0 +-o +CMakeFiles/app.dir/main.o +-c +main.cpp \ No newline at end of file diff --git a/assets/test/cmake-compile-flags/main.cpp b/assets/test/cmake-compile-flags/main.cpp new file mode 100644 index 000000000..e52a3fb1e --- /dev/null +++ b/assets/test/cmake-compile-flags/main.cpp @@ -0,0 +1,7 @@ +#include + +int main() +{ + std::cout << "Hello World!\n"; + return 0; +} \ No newline at end of file diff --git a/package.json b/package.json index 830713b71..2de3ff0f5 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,8 @@ "onLanguage:swift", "workspaceContains:Package.swift", "workspaceContains:compile_commands.json", + "workspaceContains:compile_flags.txt", + "workspaceContains:buildServer.json", "onDebugResolve:swift-lldb" ], "main": "./dist/src/extension.js", diff --git a/src/WorkspaceContext.ts b/src/WorkspaceContext.ts index fdc26f697..cba4a7c47 100644 --- a/src/WorkspaceContext.ts +++ b/src/WorkspaceContext.ts @@ -349,7 +349,7 @@ export class WorkspaceContext implements vscode.Disposable { } async searchForPackages(folder: vscode.Uri, workspaceFolder: vscode.WorkspaceFolder) { - // add folder if Package.swift/compile_commands.json exists + // add folder if Package.swift/compile_commands.json/compile_flags.txt/buildServer.json exists if (await this.isValidWorkspaceFolder(folder.fsPath)) { await this.addPackageFolder(folder, workspaceFolder); return; @@ -614,13 +614,15 @@ export class WorkspaceContext implements vscode.Disposable { /** * Return if folder is considered a valid root folder ie does it contain a SwiftPM - * Package.swift or a CMake compile_commands.json + * Package.swift or a CMake compile_commands.json, compile_flags.txt, or a BSP buildServer.json. */ async isValidWorkspaceFolder(folder: string): Promise { return ( ((await pathExists(folder, "Package.swift")) && !configuration.disableSwiftPMIntegration) || - (await pathExists(folder, "compile_commands.json")) + (await pathExists(folder, "compile_commands.json")) || + (await pathExists(folder, "compile_flags.txt")) || + (await pathExists(folder, "buildServer.json")) ); } diff --git a/test/integration-tests/ExtensionActivation.test.ts b/test/integration-tests/ExtensionActivation.test.ts index 52913d17a..a878cde5a 100644 --- a/test/integration-tests/ExtensionActivation.test.ts +++ b/test/integration-tests/ExtensionActivation.test.ts @@ -22,6 +22,8 @@ import { deactivateExtension, } from "./utilities/testutilities"; import { WorkspaceContext } from "../../src/WorkspaceContext"; +import { testAssetUri } from "../fixtures"; +import { assertContains } from "./testexplorer/utilities"; suite("Extension Activation/Deactivation Tests", () => { suite("Extension Activation", () => { @@ -97,4 +99,29 @@ suite("Extension Activation/Deactivation Tests", () => { assert.notStrictEqual(workspaceContext, capturedWorkspaceContext); }); }); + + suite("Activates for cmake projects", () => { + let workspaceContext: WorkspaceContext; + + activateExtensionForTest({ + async setup(ctx) { + workspaceContext = ctx; + }, + testAssets: ["cmake", "cmake-compile-flags"], + }); + + test("compile_commands.json", async () => { + const lspWorkspaces = workspaceContext.languageClientManager.subFolderWorkspaces.map( + ({ fsPath }) => fsPath + ); + assertContains(lspWorkspaces, testAssetUri("cmake").fsPath); + }); + + test("compile_flags.txt", async () => { + const lspWorkspaces = workspaceContext.languageClientManager.subFolderWorkspaces.map( + ({ fsPath }) => fsPath + ); + assertContains(lspWorkspaces, testAssetUri("cmake-compile-flags").fsPath); + }); + }); }); From feea97e8ec24507f13380e2841984db6f72b3ece Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Fri, 6 Dec 2024 15:07:35 -0500 Subject: [PATCH 21/22] Release activities for 1.11.4 (#1249) --- CHANGELOG.md | 14 ++++++++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bccf3a90..2549f025c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## 1.11.4 - 2024-12-06 + +### Added + +- Activate extension when `compile_flags.txt` or `buildServer.json` is present ([#1240](https://github.com/swiftlang/vscode-swift/pull/1240)) +- Add `"auto"` mode to sourcekit-lsp `backgroundIndexing` setting ([#1232](https://github.com/swiftlang/vscode-swift/pull/1232)) + +### Fixed + +- Fix location for diagnostics generated from macros via `swiftc` ([#1234](https://github.com/swiftlang/vscode-swift/pull/1234)) +- Fixed inability to `Debug Test` on test targets in Test Explorer ([#1209](https://github.com/swiftlang/vscode-swift/pull/1209)) +- Fixed bug that could cause all tests to run when only some tests were requested ([#1186](https://github.com/swiftlang/vscode-swift/pull/1186)) +- Fix test runs not being able to be cancelled in some situations ([#1153](https://github.com/swiftlang/vscode-swift/pull/1153)) + ## 1.11.3 - 2024-09-23 ### Fixed diff --git a/package-lock.json b/package-lock.json index 993d4bdcc..76a3ee88e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "swift-lang", - "version": "1.11.3", + "version": "1.11.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "swift-lang", - "version": "1.11.3", + "version": "1.11.4", "dependencies": { "lcov-parse": "^1.0.0", "plist": "^3.1.0", diff --git a/package.json b/package.json index 2de3ff0f5..54f7e7936 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "swift-lang", "displayName": "Swift", "description": "Swift Language Support for Visual Studio Code.", - "version": "1.11.3", + "version": "1.11.4", "publisher": "sswg", "icon": "icon.png", "repository": { From b229e9cbfea92323e5aee7c76b7a354c3e4f867d Mon Sep 17 00:00:00 2001 From: Paul LeMarquand Date: Tue, 10 Dec 2024 08:37:31 -0500 Subject: [PATCH 22/22] Warn when using `console` logging methods (#1241) Add an ESLint warning when using methods like console.log in the extension code. It is preferable to use the `SwiftOutputChannel` so that these logs are captured by the extension diagnostics mechanism, and so that they are all grouped together in the same place when debugging. Sources under the tests directory do not have this restriction. --- .eslintrc.json | 1 + src/TestExplorer/TestRunner.ts | 16 ++++++++++++---- src/TestExplorer/TestXUnitParser.ts | 6 ++++-- src/WorkspaceContext.ts | 2 ++ src/debugger/logTracker.ts | 9 +++++++-- src/tasks/TaskManager.ts | 6 ++++-- src/ui/SwiftOutputChannel.ts | 2 ++ test/.eslintrc.json | 1 + 8 files changed, 33 insertions(+), 10 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 81a495120..a1f488343 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -12,6 +12,7 @@ "no-throw-literal": "warn", // TODO "@typescript-eslint/semi" rule moved to https://eslint.style "semi": "error", + "no-console": "warn", // Mostly fails tests, ex. expect(...).to.be.true returns a Chai.Assertion "@typescript-eslint/no-unused-expressions": "off", "@typescript-eslint/no-non-null-assertion": "off", diff --git a/src/TestExplorer/TestRunner.ts b/src/TestExplorer/TestRunner.ts index bf4cd80a1..7753f2322 100644 --- a/src/TestExplorer/TestRunner.ts +++ b/src/TestExplorer/TestRunner.ts @@ -735,7 +735,11 @@ export class TestRunner { const xUnitParser = new TestXUnitParser( this.folderContext.workspaceContext.toolchain.hasMultiLineParallelTestOutput ); - const results = await xUnitParser.parse(buffer, runState); + const results = await xUnitParser.parse( + buffer, + runState, + this.workspaceContext.outputChannel + ); if (results) { this.testRun.appendOutput( `\r\nExecuted ${results.tests} tests, with ${results.failures} failures and ${results.errors} errors.\r\n` @@ -865,9 +869,13 @@ export class TestRunner { ); const outputHandler = this.testOutputHandler(config.testType, runState); - LoggingDebugAdapterTracker.setDebugSessionCallback(session, output => { - outputHandler(output); - }); + LoggingDebugAdapterTracker.setDebugSessionCallback( + session, + this.workspaceContext.outputChannel, + output => { + outputHandler(output); + } + ); const cancellation = this.testRun.token.onCancellationRequested(() => { this.workspaceContext.outputChannel.logDiagnostic( diff --git a/src/TestExplorer/TestXUnitParser.ts b/src/TestExplorer/TestXUnitParser.ts index 26c76fb72..8ef064d92 100644 --- a/src/TestExplorer/TestXUnitParser.ts +++ b/src/TestExplorer/TestXUnitParser.ts @@ -14,6 +14,7 @@ import * as xml2js from "xml2js"; import { TestRunnerTestRunState } from "./TestRunner"; +import { OutputChannel } from "vscode"; export interface TestResults { tests: number; @@ -48,14 +49,15 @@ export class TestXUnitParser { async parse( buffer: string, - runState: TestRunnerTestRunState + runState: TestRunnerTestRunState, + outputChannel: OutputChannel ): Promise { const xml = await xml2js.parseStringPromise(buffer); try { return await this.parseXUnit(xml, runState); } catch (error) { // ignore error - console.log(error); + outputChannel.appendLine(`Error parsing xUnit output: ${error}`); return undefined; } } diff --git a/src/WorkspaceContext.ts b/src/WorkspaceContext.ts index cba4a7c47..664f18fc1 100644 --- a/src/WorkspaceContext.ts +++ b/src/WorkspaceContext.ts @@ -253,6 +253,7 @@ export class WorkspaceContext implements vscode.Disposable { // add event listener for when a workspace folder is added/removed const onWorkspaceChange = vscode.workspace.onDidChangeWorkspaceFolders(event => { if (this === undefined) { + // eslint-disable-next-line no-console console.log("Trying to run onDidChangeWorkspaceFolders on deleted context"); return; } @@ -261,6 +262,7 @@ export class WorkspaceContext implements vscode.Disposable { // add event listener for when the active edited text document changes const onDidChangeActiveWindow = vscode.window.onDidChangeActiveTextEditor(async editor => { if (this === undefined) { + // eslint-disable-next-line no-console console.log("Trying to run onDidChangeWorkspaceFolders on deleted context"); return; } diff --git a/src/debugger/logTracker.ts b/src/debugger/logTracker.ts index 4696a7df7..b5dde65e1 100644 --- a/src/debugger/logTracker.ts +++ b/src/debugger/logTracker.ts @@ -15,6 +15,7 @@ import * as vscode from "vscode"; import { DebugAdapter } from "./debugAdapter"; import { Version } from "../utilities/version"; +import { SwiftOutputChannel } from "../ui/SwiftOutputChannel"; /** * Factory class for building LoggingDebugAdapterTracker @@ -82,12 +83,16 @@ export class LoggingDebugAdapterTracker implements vscode.DebugAdapterTracker { LoggingDebugAdapterTracker.debugSessionIdMap[id] = this; } - static setDebugSessionCallback(session: vscode.DebugSession, cb: (log: string) => void) { + static setDebugSessionCallback( + session: vscode.DebugSession, + outputChannel: SwiftOutputChannel, + cb: (log: string) => void + ) { const loggingDebugAdapter = this.debugSessionIdMap[session.id]; if (loggingDebugAdapter) { loggingDebugAdapter.cb = cb; } else { - console.error("Could not find debug adapter for session:", session.id); + outputChannel.appendLine("Could not find debug adapter for session: " + session.id); } } diff --git a/src/tasks/TaskManager.ts b/src/tasks/TaskManager.ts index 980f53093..dfa1e3434 100644 --- a/src/tasks/TaskManager.ts +++ b/src/tasks/TaskManager.ts @@ -104,7 +104,9 @@ export class TaskManager implements vscode.Disposable { }); // setup startingTaskPromise to be resolved one task has started if (this.startingTaskPromise !== undefined) { - console.warn("TaskManager: Starting promise should be undefined if we reach here."); + this.workspaceContext.outputChannel.appendLine( + "TaskManager: Starting promise should be undefined if we reach here." + ); } this.startingTaskPromise = new Promise(resolve => { this.taskStartObserver = () => { @@ -122,7 +124,7 @@ export class TaskManager implements vscode.Disposable { }); }, error => { - console.log(error); + this.workspaceContext.outputChannel.appendLine(`Error executing task: ${error}`); disposable.dispose(); this.startingTaskPromise = undefined; reject(error); diff --git a/src/ui/SwiftOutputChannel.ts b/src/ui/SwiftOutputChannel.ts index 04291b0ba..4dfe482ab 100644 --- a/src/ui/SwiftOutputChannel.ts +++ b/src/ui/SwiftOutputChannel.ts @@ -39,6 +39,7 @@ export class SwiftOutputChannel implements vscode.OutputChannel { this.logStore.append(value); if (this.logToConsole) { + // eslint-disable-next-line no-console console.log(value); } } @@ -48,6 +49,7 @@ export class SwiftOutputChannel implements vscode.OutputChannel { this.logStore.appendLine(value); if (this.logToConsole) { + // eslint-disable-next-line no-console console.log(value); } } diff --git a/test/.eslintrc.json b/test/.eslintrc.json index 8b8e4250e..ac8f7c1d5 100644 --- a/test/.eslintrc.json +++ b/test/.eslintrc.json @@ -1,5 +1,6 @@ { "rules": { + "no-console": "off", "@typescript-eslint/no-explicit-any": "off" } }