diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 045d61d31678..5ade8dec4885 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -6,6 +6,7 @@ "esbenp.prettier-vscode", "dbaeumer.vscode-eslint", "ms-python.python", + "ms-python.black-formatter", "ms-python.vscode-pylance" ] } diff --git a/.vscode/launch.json b/.vscode/launch.json index 7b654703dbd8..82981a93305d 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -253,6 +253,15 @@ "request": "attach", "listen": { "host": "localhost", "port": 5678 }, "justMyCode": true + }, + { + "name": "Debug pytest plugin tests", + + "type": "python", + "request": "launch", + "module": "pytest", + "args": ["${workspaceFolder}/pythonFiles/tests/pytestadapter"], + "justMyCode": true } ], "compounds": [ diff --git a/build/webpack/webpack.extension.config.js b/build/webpack/webpack.extension.config.js index b1b3922126d6..f496aa32ee26 100644 --- a/build/webpack/webpack.extension.config.js +++ b/build/webpack/webpack.extension.config.js @@ -61,6 +61,9 @@ const config = { // See: https://github.com/microsoft/vscode-extension-telemetry/issues/41#issuecomment-598852991 'applicationinsights-native-metrics', '@opentelemetry/tracing', + '@azure/opentelemetry-instrumentation-azure-sdk', + '@opentelemetry/instrumentation', + '@azure/functions-core', ], plugins: [...common.getDefaultPlugins('extension')], resolve: { diff --git a/package-lock.json b/package-lock.json index 9b537745c9bd..eb01e68b0929 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "@iarna/toml": "^2.2.5", - "@vscode/extension-telemetry": "^0.7.4-preview", + "@vscode/extension-telemetry": "^0.7.7", "@vscode/jupyter-lsp-middleware": "^0.2.50", "arch": "^2.1.0", "diff-match-patch": "^1.0.0", @@ -74,6 +74,7 @@ "@typescript-eslint/eslint-plugin": "^3.7.0", "@typescript-eslint/parser": "^3.7.0", "@vscode/test-electron": "^2.1.3", + "@vscode/vsce": "^2.18.0", "bent": "^7.3.12", "chai": "^4.1.2", "chai-arrays": "^2.0.0", @@ -116,7 +117,6 @@ "typemoq": "^2.1.0", "typescript": "4.5.5", "uuid": "^8.3.2", - "vsce": "^2.6.6", "vscode-debugadapter-testsupport": "^1.27.0", "webpack": "^5.76.0", "webpack-bundle-analyzer": "^4.5.0", @@ -143,9 +143,9 @@ } }, "node_modules/@azure/abort-controller/node_modules/tslib": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", - "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" }, "node_modules/@azure/core-auth": { "version": "1.4.0", @@ -160,85 +160,76 @@ } }, "node_modules/@azure/core-auth/node_modules/tslib": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", - "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" }, - "node_modules/@azure/core-http": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@azure/core-http/-/core-http-2.3.1.tgz", - "integrity": "sha512-cur03BUwV0Tbv81bQBOLafFB02B6G++K6F2O3IMl8pSE2QlXm3cu11bfyBNlDUKi5U+xnB3GC63ae3athhkx6Q==", + "node_modules/@azure/core-rest-pipeline": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.10.3.tgz", + "integrity": "sha512-AMQb0ttiGJ0MIV/r+4TVra6U4+90mPeOveehFnrqKlo7dknPJYdJ61wOzYJXJjDxF8LcCtSogfRelkq+fCGFTw==", "dependencies": { "@azure/abort-controller": "^1.0.0", - "@azure/core-auth": "^1.3.0", - "@azure/core-tracing": "1.0.0-preview.13", - "@azure/core-util": "^1.1.1", + "@azure/core-auth": "^1.4.0", + "@azure/core-tracing": "^1.0.1", + "@azure/core-util": "^1.3.0", "@azure/logger": "^1.0.0", - "@types/node-fetch": "^2.5.0", - "@types/tunnel": "^0.0.3", "form-data": "^4.0.0", - "node-fetch": "^2.6.7", - "process": "^0.11.10", - "tough-cookie": "^4.0.0", - "tslib": "^2.2.0", - "tunnel": "^0.0.6", - "uuid": "^8.3.0", - "xml2js": "^0.4.19" + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "tslib": "^2.2.0" }, "engines": { "node": ">=14.0.0" } }, - "node_modules/@azure/core-http/node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, + "node_modules/@azure/core-rest-pipeline/node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", "engines": { - "node": ">= 6" + "node": ">= 10" } }, - "node_modules/@azure/core-http/node_modules/tough-cookie": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", - "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", + "node_modules/@azure/core-rest-pipeline/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" + "ms": "2.1.2" }, "engines": { - "node": ">=6" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@azure/core-http/node_modules/tslib": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", - "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" - }, - "node_modules/@azure/core-http/node_modules/xml2js": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", - "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "node_modules/@azure/core-rest-pipeline/node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", "dependencies": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" }, "engines": { - "node": ">=4.0.0" + "node": ">= 6" } }, + "node_modules/@azure/core-rest-pipeline/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, "node_modules/@azure/core-tracing": { - "version": "1.0.0-preview.13", - "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.0-preview.13.tgz", - "integrity": "sha512-KxDlhXyMlh2Jhj2ykX6vNEU0Vou4nHr025KoSEiz7cS3BNiHNaZcdECk/DmLkEB0as5T7b/TpRcehJ5yV6NeXQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.1.tgz", + "integrity": "sha512-I5CGMoLtX+pI17ZdiFJZgxMJApsK6jjfm85hpgp3oazCdq5Wxgh4wMr7ge/TTWW1B5WBuvIOI1fMU/FrOAMKrw==", "dependencies": { - "@opentelemetry/api": "^1.0.1", "tslib": "^2.2.0" }, "engines": { @@ -246,42 +237,42 @@ } }, "node_modules/@azure/core-tracing/node_modules/tslib": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", - "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" }, "node_modules/@azure/core-util": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.1.1.tgz", - "integrity": "sha512-A4TBYVQCtHOigFb2ETiiKFDocBoI1Zk2Ui1KpI42aJSIDexF7DHQFpnjonltXAIU/ceH+1fsZAWWgvX6/AKzog==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.3.0.tgz", + "integrity": "sha512-ANP0Er7R2KHHHjwmKzPF9wbd0gXvOX7yRRHeYL1eNd/OaNrMLyfZH/FQasHRVAf6rMXX+EAUpvYwLMFDHDI5Gw==", "dependencies": { "@azure/abort-controller": "^1.0.0", "tslib": "^2.2.0" }, "engines": { - "node": ">=12.0.0" + "node": ">=14.0.0" } }, "node_modules/@azure/core-util/node_modules/tslib": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", - "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" }, "node_modules/@azure/logger": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.0.3.tgz", - "integrity": "sha512-aK4s3Xxjrx3daZr3VylxejK3vG5ExXck5WOHDJ8in/k9AqlfIyFMMT1uG7u8mNjX+QRILTIn0/Xgschfh/dQ9g==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.0.4.tgz", + "integrity": "sha512-ustrPY8MryhloQj7OWGe+HrYx+aoiOxzbXTtgblbV3xwCqpzUK36phH3XNHQKj3EPonyFUuDTfR3qFhTEAuZEg==", "dependencies": { "tslib": "^2.2.0" }, "engines": { - "node": ">=12.0.0" + "node": ">=14.0.0" } }, "node_modules/@azure/logger/node_modules/tslib": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", - "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" }, "node_modules/@babel/code-frame": { "version": "7.12.11", @@ -604,32 +595,44 @@ } }, "node_modules/@microsoft/1ds-core-js": { - "version": "3.2.8", - "resolved": "https://registry.npmjs.org/@microsoft/1ds-core-js/-/1ds-core-js-3.2.8.tgz", - "integrity": "sha512-9o9SUAamJiTXIYwpkQDuueYt83uZfXp8zp8YFix1IwVPwC9RmE36T2CX9gXOeq1nDckOuOduYpA8qHvdh5BGfQ==", + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/@microsoft/1ds-core-js/-/1ds-core-js-3.2.9.tgz", + "integrity": "sha512-3pCfM2TzHn3gU9pxHztduKcVRdb/nzruvPFfHPZD0IM0mb0h6TGo2isELF3CTMahTx50RAC51ojNIw2/7VRkOg==", "dependencies": { - "@microsoft/applicationinsights-core-js": "2.8.9", + "@microsoft/applicationinsights-core-js": "2.8.10", "@microsoft/applicationinsights-shims": "^2.0.2", "@microsoft/dynamicproto-js": "^1.1.7" } }, "node_modules/@microsoft/1ds-post-js": { - "version": "3.2.8", - "resolved": "https://registry.npmjs.org/@microsoft/1ds-post-js/-/1ds-post-js-3.2.8.tgz", - "integrity": "sha512-SjlRoNcXcXBH6WQD/5SkkaCHIVqldH3gDu+bI7YagrOVJ5APxwT1Duw9gm3L1FjFa9S2i81fvJ3EVSKpp9wULA==", + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/@microsoft/1ds-post-js/-/1ds-post-js-3.2.9.tgz", + "integrity": "sha512-D/RtqkQ2Nr4cuoGqmhi5QTmi3cBlxehIThJ1u3BaH9H/YkLNTKEcHZRWTXy14bXheCefNHciLuadg37G2Kekcg==", "dependencies": { - "@microsoft/1ds-core-js": "3.2.8", + "@microsoft/1ds-core-js": "3.2.9", "@microsoft/applicationinsights-shims": "^2.0.2", "@microsoft/dynamicproto-js": "^1.1.7" } }, "node_modules/@microsoft/applicationinsights-channel-js": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-2.8.9.tgz", - "integrity": "sha512-fMBsAEB7pWtPn43y72q9Xy5E5y55r6gMuDQqRRccccVoQDPXyS57VCj5IdATblctru0C6A8XpL2vRyNmEsu0Vg==", + "version": "2.8.11", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-2.8.11.tgz", + "integrity": "sha512-DGDNzT4DMlSvUzWjA4y3tDg47+QYOPV+W07vlfdPwGgLwrl4n6Q4crrW8Y/IOpthHAKDU8rolSAUvP3NqxPi4Q==", + "dependencies": { + "@microsoft/applicationinsights-common": "2.8.11", + "@microsoft/applicationinsights-core-js": "2.8.11", + "@microsoft/applicationinsights-shims": "2.0.2", + "@microsoft/dynamicproto-js": "^1.1.7" + }, + "peerDependencies": { + "tslib": "*" + } + }, + "node_modules/@microsoft/applicationinsights-channel-js/node_modules/@microsoft/applicationinsights-core-js": { + "version": "2.8.11", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.11.tgz", + "integrity": "sha512-6ScXplyb9Zb0K6TQRfqStm20j5lIe/Dslf65ozows6ibDcKkWl2ZdqzFhymVJZz1WRNpSyD4aA8qnqmslIER6g==", "dependencies": { - "@microsoft/applicationinsights-common": "2.8.9", - "@microsoft/applicationinsights-core-js": "2.8.9", "@microsoft/applicationinsights-shims": "2.0.2", "@microsoft/dynamicproto-js": "^1.1.7" }, @@ -638,11 +641,23 @@ } }, "node_modules/@microsoft/applicationinsights-common": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-2.8.9.tgz", - "integrity": "sha512-mObn1moElyxZaGIRF/IU3cOaeKMgxghXnYEoHNUCA2e+rNwBIgxjyKkblFIpmGuHf4X7Oz3o3yBWpaC6AoMpig==", + "version": "2.8.11", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-2.8.11.tgz", + "integrity": "sha512-Cxu4gRajkYv9buEtrcLGHK97AqGK62feN9jH9/JSjUSiSFhbnWtYvEg1EMqMI/P4pneu53yLJloITB+TKwmK7A==", + "dependencies": { + "@microsoft/applicationinsights-core-js": "2.8.11", + "@microsoft/applicationinsights-shims": "2.0.2", + "@microsoft/dynamicproto-js": "^1.1.7" + }, + "peerDependencies": { + "tslib": "*" + } + }, + "node_modules/@microsoft/applicationinsights-common/node_modules/@microsoft/applicationinsights-core-js": { + "version": "2.8.11", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.11.tgz", + "integrity": "sha512-6ScXplyb9Zb0K6TQRfqStm20j5lIe/Dslf65ozows6ibDcKkWl2ZdqzFhymVJZz1WRNpSyD4aA8qnqmslIER6g==", "dependencies": { - "@microsoft/applicationinsights-core-js": "2.8.9", "@microsoft/applicationinsights-shims": "2.0.2", "@microsoft/dynamicproto-js": "^1.1.7" }, @@ -651,9 +666,9 @@ } }, "node_modules/@microsoft/applicationinsights-core-js": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.9.tgz", - "integrity": "sha512-HRuIuZ6aOWezcg/G5VyFDDWGL8hDNe/ljPP01J7ImH2kRPEgbtcfPSUMjkamGMefgdq81GZsSoC/NNGTP4pp2w==", + "version": "2.8.10", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.10.tgz", + "integrity": "sha512-jQrufDW0+sV8fBhRvzIPNGiCC6dELH+Ug0DM5CfN9757TBqZJz8CSWyDjex39as8+jD0F/8HRU9QdmrVgq5vFg==", "dependencies": { "@microsoft/applicationinsights-shims": "2.0.2", "@microsoft/dynamicproto-js": "^1.1.7" @@ -668,13 +683,25 @@ "integrity": "sha512-PoHEgsnmcqruLNHZ/amACqdJ6YYQpED0KSRe6J7gIJTtpZC1FfFU9b1fmDKDKtFoUSrPzEh1qzO3kmRZP0betg==" }, "node_modules/@microsoft/applicationinsights-web-basic": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-2.8.9.tgz", - "integrity": "sha512-CH0J8JFOy7MjK8JO4pXXU+EML+Ilix+94PMZTX5EJlBU1in+mrik74/8qSg3UC4ekPi12KwrXaHCQSVC3WseXQ==", + "version": "2.8.11", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-2.8.11.tgz", + "integrity": "sha512-11T7bbP4ifIBg95E9mYZv1g/vcWvw/KaWKRcGMREP3+vBTLBwMB8r2e9Zd583bOVx+9/gRvfIg+Z/lInQqAfbA==", + "dependencies": { + "@microsoft/applicationinsights-channel-js": "2.8.11", + "@microsoft/applicationinsights-common": "2.8.11", + "@microsoft/applicationinsights-core-js": "2.8.11", + "@microsoft/applicationinsights-shims": "2.0.2", + "@microsoft/dynamicproto-js": "^1.1.7" + }, + "peerDependencies": { + "tslib": "*" + } + }, + "node_modules/@microsoft/applicationinsights-web-basic/node_modules/@microsoft/applicationinsights-core-js": { + "version": "2.8.11", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.11.tgz", + "integrity": "sha512-6ScXplyb9Zb0K6TQRfqStm20j5lIe/Dslf65ozows6ibDcKkWl2ZdqzFhymVJZz1WRNpSyD4aA8qnqmslIER6g==", "dependencies": { - "@microsoft/applicationinsights-channel-js": "2.8.9", - "@microsoft/applicationinsights-common": "2.8.9", - "@microsoft/applicationinsights-core-js": "2.8.9", "@microsoft/applicationinsights-shims": "2.0.2", "@microsoft/dynamicproto-js": "^1.1.7" }, @@ -688,9 +715,9 @@ "integrity": "sha512-2IHAOaLauc8qaAitvWS+U931T+ze+7MNWrDHY47IENP5y2UA0vqJDu67kWZDdpCN1fFC77sfgfB+HV7SrKshnQ==" }, "node_modules/@microsoft/dynamicproto-js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.7.tgz", - "integrity": "sha512-SK3D3aVt+5vOOccKPnGaJWB5gQ8FuKfjboUJHedMP7gu54HqSCXX5iFXhktGD8nfJb0Go30eDvs/UDoTnR2kOA==" + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.9.tgz", + "integrity": "sha512-n1VPsljTSkthsAFYdiWfC+DKzK2WwcRp83Y1YAqdX552BstvsDjft9YXppjUzp11BPsapDoO1LDgrDB0XVsfNQ==" }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", @@ -728,62 +755,62 @@ } }, "node_modules/@opentelemetry/api": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.3.0.tgz", - "integrity": "sha512-YveTnGNsFFixTKJz09Oi4zYkiLT5af3WpZDu4aIUM7xX+2bHAkOJayFTVQd6zB8kkWPpbua4Ha6Ql00grdLlJQ==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.4.1.tgz", + "integrity": "sha512-O2yRJce1GOc6PAy3QxFM4NzFiWzvScDC1/5ihYBL6BUEVdq0XMWN01sppE+H6bBXbaFYipjwFLEWLg5PaSOThA==", "engines": { "node": ">=8.0.0" } }, "node_modules/@opentelemetry/core": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.8.0.tgz", - "integrity": "sha512-6SDjwBML4Am0AQmy7z1j6HGrWDgeK8awBRUvl1PGw6HayViMk4QpnUXvv4HTHisecgVBy43NE/cstWprm8tIfw==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.11.0.tgz", + "integrity": "sha512-aP1wHSb+YfU0pM63UAkizYPuS4lZxzavHHw5KJfFNN2oWQ79HSm6JR3CzwFKHwKhSzHN8RE9fgP1IdVJ8zmo1w==", "dependencies": { - "@opentelemetry/semantic-conventions": "1.8.0" + "@opentelemetry/semantic-conventions": "1.11.0" }, "engines": { "node": ">=14" }, "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.4.0" + "@opentelemetry/api": ">=1.0.0 <1.5.0" } }, "node_modules/@opentelemetry/resources": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.8.0.tgz", - "integrity": "sha512-KSyMH6Jvss/PFDy16z5qkCK0ERlpyqixb1xwb73wLMvVq+j7i89lobDjw3JkpCcd1Ws0J6jAI4fw28Zufj2ssg==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.11.0.tgz", + "integrity": "sha512-y0z2YJTqk0ag+hGT4EXbxH/qPhDe8PfwltYb4tXIEsozgEFfut/bqW7H7pDvylmCjBRMG4NjtLp57V1Ev++brA==", "dependencies": { - "@opentelemetry/core": "1.8.0", - "@opentelemetry/semantic-conventions": "1.8.0" + "@opentelemetry/core": "1.11.0", + "@opentelemetry/semantic-conventions": "1.11.0" }, "engines": { "node": ">=14" }, "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.4.0" + "@opentelemetry/api": ">=1.0.0 <1.5.0" } }, "node_modules/@opentelemetry/sdk-trace-base": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.8.0.tgz", - "integrity": "sha512-iH41m0UTddnCKJzZx3M85vlhKzRcmT48pUeBbnzsGrq4nIay1oWVHKM5nhB5r8qRDGvd/n7f/YLCXClxwM0tvA==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.11.0.tgz", + "integrity": "sha512-DV8e5/Qo42V8FMBlQ0Y0Liv6Hl/Pp5bAZ73s7r1euX8w4bpRes1B7ACiA4yujADbWMJxBgSo4fGbi4yjmTMG2A==", "dependencies": { - "@opentelemetry/core": "1.8.0", - "@opentelemetry/resources": "1.8.0", - "@opentelemetry/semantic-conventions": "1.8.0" + "@opentelemetry/core": "1.11.0", + "@opentelemetry/resources": "1.11.0", + "@opentelemetry/semantic-conventions": "1.11.0" }, "engines": { "node": ">=14" }, "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.4.0" + "@opentelemetry/api": ">=1.0.0 <1.5.0" } }, "node_modules/@opentelemetry/semantic-conventions": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.8.0.tgz", - "integrity": "sha512-TYh1MRcm4JnvpqtqOwT9WYaBYY4KERHdToxs/suDTLviGRsQkIjS5yYROTYTSJQUnYLOn/TuOh5GoMwfLSU+Ew==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.11.0.tgz", + "integrity": "sha512-fG4D0AktoHyHwGhFGv+PzKrZjxbKJfckJauTJdq2A+ej5cTazmNYjJVAODXXkYyrsI10muMl+B1iO2q1R6Lp+w==", "engines": { "node": ">=14" } @@ -1044,29 +1071,8 @@ "node_modules/@types/node": { "version": "14.18.12", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.12.tgz", - "integrity": "sha512-q4jlIR71hUpWTnGhXWcakgkZeHa3CCjcQcnuzU8M891BAWA2jHiziiWEPEkdS5pFsz7H9HJiy8BrK7tBRNrY7A==" - }, - "node_modules/@types/node-fetch": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.2.tgz", - "integrity": "sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==", - "dependencies": { - "@types/node": "*", - "form-data": "^3.0.0" - } - }, - "node_modules/@types/node-fetch/node_modules/form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } + "integrity": "sha512-q4jlIR71hUpWTnGhXWcakgkZeHa3CCjcQcnuzU8M891BAWA2jHiziiWEPEkdS5pFsz7H9HJiy8BrK7tBRNrY7A==", + "dev": true }, "node_modules/@types/semver": { "version": "5.5.0", @@ -1107,14 +1113,6 @@ "integrity": "sha1-EHPEvIJHVK49EM+riKsCN7qWTk0=", "dev": true }, - "node_modules/@types/tunnel": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/@types/tunnel/-/tunnel-0.0.3.tgz", - "integrity": "sha512-sOUTGn6h1SfQ+gbgqC364jLFBw2lnFqkgF3q0WovEHRLMrVD1sd5aufqi/aJObLekJO+Aq5z646U4Oxy6shXMA==", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/uuid": { "version": "8.3.4", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", @@ -1357,14 +1355,14 @@ "dev": true }, "node_modules/@vscode/extension-telemetry": { - "version": "0.7.4-preview", - "resolved": "https://registry.npmjs.org/@vscode/extension-telemetry/-/extension-telemetry-0.7.4-preview.tgz", - "integrity": "sha512-6OkvjCc+DaC9B26t3hj7vuAxf1ONm/p4LrVvFrapa+jBCKxXXUaV1Asz6+QxYaPfd4Ws/MlnFfCvlgvv3uYRwQ==", + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/@vscode/extension-telemetry/-/extension-telemetry-0.7.7.tgz", + "integrity": "sha512-uW508BPjkWDBOKvvvSym3ZmGb7kHIiWaAfB/1PHzLz2x9TrC33CfjmFEI+CywIL/jBv4bqZxxjN4tfefB61F+g==", "dependencies": { - "@microsoft/1ds-core-js": "^3.2.8", - "@microsoft/1ds-post-js": "^3.2.8", - "@microsoft/applicationinsights-web-basic": "^2.8.9", - "applicationinsights": "2.3.6" + "@microsoft/1ds-core-js": "^3.2.9", + "@microsoft/1ds-post-js": "^3.2.9", + "@microsoft/applicationinsights-web-basic": "^2.8.11", + "applicationinsights": "2.5.0" }, "engines": { "vscode": "^1.75.0" @@ -1411,6 +1409,101 @@ "node": ">=8.9.3" } }, + "node_modules/@vscode/vsce": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-2.18.0.tgz", + "integrity": "sha512-tUA3XoKx5xjoi3EDcngk0VUYMhvfXLhS4s7CntpLPh1qtLYtgSCexTIMUHkCy6MqyozRW98bdW3a2yHPEADRnQ==", + "dev": true, + "dependencies": { + "azure-devops-node-api": "^11.0.1", + "chalk": "^2.4.2", + "cheerio": "^1.0.0-rc.9", + "commander": "^6.1.0", + "glob": "^7.0.6", + "hosted-git-info": "^4.0.2", + "jsonc-parser": "^3.2.0", + "leven": "^3.1.0", + "markdown-it": "^12.3.2", + "mime": "^1.3.4", + "minimatch": "^3.0.3", + "parse-semver": "^1.1.1", + "read": "^1.0.7", + "semver": "^5.1.0", + "tmp": "^0.2.1", + "typed-rest-client": "^1.8.4", + "url-join": "^4.0.1", + "xml2js": "^0.4.23", + "yauzl": "^2.3.1", + "yazl": "^2.2.2" + }, + "bin": { + "vsce": "vsce" + }, + "engines": { + "node": ">= 14" + }, + "optionalDependencies": { + "keytar": "^7.7.0" + } + }, + "node_modules/@vscode/vsce/node_modules/commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@vscode/vsce/node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@vscode/vsce/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@vscode/vsce/node_modules/tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "dependencies": { + "rimraf": "^3.0.0" + }, + "engines": { + "node": ">=8.17.0" + } + }, + "node_modules/@vscode/vsce/node_modules/xml2js": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "dev": true, + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/@webassemblyjs/ast": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", @@ -1644,6 +1737,33 @@ "node": ">=0.4.0" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agent-base/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/aggregate-error": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.1.tgz", @@ -1792,11 +1912,12 @@ } }, "node_modules/applicationinsights": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/applicationinsights/-/applicationinsights-2.3.6.tgz", - "integrity": "sha512-ZzXXpZpDRGcy6Pp5V319nDF9/+Ey7jNknEXZyaBajtC5onN0dcBem6ng5jcb3MPH2AjYWRI8XgyNEuzP/6Y5/A==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/applicationinsights/-/applicationinsights-2.5.0.tgz", + "integrity": "sha512-6kIFmpANRok+6FhCOmO7ZZ/mh7fdNKn17BaT13cg/RV5roLPJlA6q8srWexayHd3MPcwMb9072e8Zp0P47s/pw==", "dependencies": { - "@azure/core-http": "^2.2.3", + "@azure/core-auth": "^1.4.0", + "@azure/core-rest-pipeline": "^1.10.0", "@microsoft/applicationinsights-web-snippet": "^1.0.1", "@opentelemetry/api": "^1.0.4", "@opentelemetry/core": "^1.0.1", @@ -1823,7 +1944,8 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "dev": true + "dev": true, + "optional": true }, "node_modules/arch": { "version": "2.2.0", @@ -1876,6 +1998,7 @@ "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==", "dev": true, + "optional": true, "dependencies": { "delegates": "^1.0.0", "readable-stream": "^2.0.6" @@ -2236,7 +2359,7 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/atob": { "version": "2.1.2", @@ -3294,7 +3417,8 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.2.tgz", "integrity": "sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A==", - "dev": true + "dev": true, + "optional": true }, "node_modules/chrome-trace-event": { "version": "1.0.2", @@ -3613,7 +3737,8 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "dev": true + "dev": true, + "optional": true }, "node_modules/constants-browserify": { "version": "1.0.0", @@ -4090,6 +4215,7 @@ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "dev": true, + "optional": true, "engines": { "node": ">=4.0.0" } @@ -4239,7 +4365,7 @@ "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "engines": { "node": ">=0.4.0" } @@ -4248,7 +4374,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", - "dev": true + "dev": true, + "optional": true }, "node_modules/des.js": { "version": "1.0.0", @@ -4274,6 +4401,7 @@ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", "dev": true, + "optional": true, "bin": { "detect-libc": "bin/detect-libc.js" }, @@ -5681,6 +5809,7 @@ "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", "dev": true, + "optional": true, "engines": { "node": ">=6" } @@ -6291,6 +6420,19 @@ "node": ">=8" } }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fragment-cache": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", @@ -6442,6 +6584,7 @@ "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "dev": true, + "optional": true, "dependencies": { "aproba": "^1.0.3", "console-control-strings": "^1.0.0", @@ -6458,6 +6601,7 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true, + "optional": true, "engines": { "node": ">=0.10.0" } @@ -6467,6 +6611,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, + "optional": true, "dependencies": { "ansi-regex": "^2.0.0" }, @@ -6562,7 +6707,8 @@ "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", "integrity": "sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4=", - "dev": true + "dev": true, + "optional": true }, "node_modules/glob": { "version": "7.2.0", @@ -7143,7 +7289,8 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", - "dev": true + "dev": true, + "optional": true }, "node_modules/has-value": { "version": "1.0.0", @@ -7292,18 +7439,6 @@ "node": ">= 6" } }, - "node_modules/http-proxy-agent/node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, "node_modules/http-proxy-agent/node_modules/debug": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", @@ -7326,37 +7461,23 @@ "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", "dev": true - }, - "node_modules/https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "dev": true, - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/https-proxy-agent/node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, + }, + "node_modules/https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", "dependencies": { + "agent-base": "6", "debug": "4" }, "engines": { - "node": ">= 6.0.0" + "node": ">= 6" } }, "node_modules/https-proxy-agent/node_modules/debug": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -8585,9 +8706,9 @@ } }, "node_modules/jsonc-parser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.0.0.tgz", - "integrity": "sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==" + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==" }, "node_modules/jsx-ast-utils": { "version": "3.2.1", @@ -8620,6 +8741,7 @@ "integrity": "sha512-YEY9HWqThQc5q5xbXbRwsZTh2PJ36OSYRjSv3NN2xf5s5dpLTjEZnC2YikR29OaVybf9nQ0dJ/80i40RS97t/A==", "dev": true, "hasInstallScript": true, + "optional": true, "dependencies": { "node-addon-api": "^3.0.0", "prebuild-install": "^6.0.0" @@ -9338,7 +9460,8 @@ "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "dev": true + "dev": true, + "optional": true }, "node_modules/mocha": { "version": "9.2.2", @@ -9759,8 +9882,7 @@ "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/mute-stdout": { "version": "1.0.1", @@ -9821,7 +9943,8 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==", - "dev": true + "dev": true, + "optional": true }, "node_modules/natural-compare": { "version": "1.4.0", @@ -9889,6 +10012,7 @@ "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.30.1.tgz", "integrity": "sha512-/2D0wOQPgaUWzVSVgRMx+trKJRC2UG4SUc4oCJoXx9Uxjtp0Vy3/kt7zcbxHF8+Z/pK3UloLWzBISg72brfy1w==", "dev": true, + "optional": true, "dependencies": { "semver": "^5.4.1" } @@ -9897,26 +10021,8 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", - "dev": true - }, - "node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } + "dev": true, + "optional": true }, "node_modules/node-has-native-dependencies": { "version": "1.0.2", @@ -10262,6 +10368,7 @@ "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "dev": true, + "optional": true, "dependencies": { "are-we-there-yet": "~1.1.2", "console-control-strings": "~1.1.0", @@ -11141,6 +11248,7 @@ "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-6.1.4.tgz", "integrity": "sha512-Z4vpywnK1lBg+zdPCVCsKq0xO66eEV9rWo2zrROGGiRS4JtueBOdlB1FnY8lcy7JsUud/Q3ijUxyWN26Ika0vQ==", "dev": true, + "optional": true, "dependencies": { "detect-libc": "^1.0.3", "expand-template": "^2.0.3", @@ -11168,6 +11276,7 @@ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", "dev": true, + "optional": true, "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -11207,6 +11316,7 @@ "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true, "engines": { "node": ">= 0.6.0" } @@ -11249,11 +11359,6 @@ "node >= 0.8.1" ] }, - "node_modules/psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" - }, "node_modules/public-encrypt": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", @@ -11293,6 +11398,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, "engines": { "node": ">=6" } @@ -11339,11 +11445,6 @@ "node": ">=0.4.x" } }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" - }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -11388,6 +11489,7 @@ "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "dev": true, + "optional": true, "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", @@ -11403,6 +11505,7 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "dev": true, + "optional": true, "engines": { "node": ">=0.10.0" } @@ -11710,11 +11813,6 @@ "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", "dev": true }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" - }, "node_modules/resolve": { "version": "1.22.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", @@ -12125,13 +12223,15 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "optional": true }, "node_modules/simple-get": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", "dev": true, + "optional": true, "dependencies": { "decompress-response": "^4.2.0", "once": "^1.3.1", @@ -12143,6 +12243,7 @@ "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", "dev": true, + "optional": true, "dependencies": { "mimic-response": "^2.0.0" }, @@ -12155,6 +12256,7 @@ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", "dev": true, + "optional": true, "engines": { "node": ">=8" }, @@ -12943,6 +13045,7 @@ "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", "dev": true, + "optional": true, "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", @@ -12955,6 +13058,7 @@ "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", "dev": true, + "optional": true, "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -12966,6 +13070,7 @@ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", "dev": true, + "optional": true, "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -12976,6 +13081,7 @@ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "dev": true, + "optional": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -12990,6 +13096,7 @@ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "dev": true, + "optional": true, "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", @@ -13285,11 +13392,6 @@ "node": ">=6" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, "node_modules/traverse": { "version": "0.3.9", "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", @@ -13669,6 +13771,7 @@ "version": "0.0.6", "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "dev": true, "engines": { "node": ">=0.6.11 <=0.7.0 || >=0.7.3" } @@ -13678,6 +13781,7 @@ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", "dev": true, + "optional": true, "dependencies": { "safe-buffer": "^5.0.1" }, @@ -13892,14 +13996,6 @@ "through2-filter": "^3.0.0" } }, - "node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "engines": { - "node": ">= 4.0.0" - } - }, "node_modules/unset-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", @@ -14022,15 +14118,6 @@ "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", "dev": true }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, "node_modules/url-parse-lax": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", @@ -14092,6 +14179,7 @@ "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, "bin": { "uuid": "dist/bin/uuid" } @@ -14208,98 +14296,6 @@ "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", "dev": true }, - "node_modules/vsce": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/vsce/-/vsce-2.7.0.tgz", - "integrity": "sha512-CKU34wrQlbKDeJCRBkd1a8iwF9EvNxcYMg9hAUH6AxFGR6Wo2IKWwt3cJIcusHxx6XdjDHWlfAS/fJN30uvVnA==", - "dev": true, - "dependencies": { - "azure-devops-node-api": "^11.0.1", - "chalk": "^2.4.2", - "cheerio": "^1.0.0-rc.9", - "commander": "^6.1.0", - "glob": "^7.0.6", - "hosted-git-info": "^4.0.2", - "keytar": "^7.7.0", - "leven": "^3.1.0", - "markdown-it": "^12.3.2", - "mime": "^1.3.4", - "minimatch": "^3.0.3", - "parse-semver": "^1.1.1", - "read": "^1.0.7", - "semver": "^5.1.0", - "tmp": "^0.2.1", - "typed-rest-client": "^1.8.4", - "url-join": "^4.0.1", - "xml2js": "^0.4.23", - "yauzl": "^2.3.1", - "yazl": "^2.2.2" - }, - "bin": { - "vsce": "vsce" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/vsce/node_modules/commander": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", - "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/vsce/node_modules/hosted-git-info": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", - "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/vsce/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/vsce/node_modules/tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", - "dev": true, - "dependencies": { - "rimraf": "^3.0.0" - }, - "engines": { - "node": ">=8.17.0" - } - }, - "node_modules/vsce/node_modules/xml2js": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", - "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", - "dev": true, - "dependencies": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/vscode-debugadapter": { "version": "1.35.0", "resolved": "https://registry.npmjs.org/vscode-debugadapter/-/vscode-debugadapter-1.35.0.tgz", @@ -14426,11 +14422,6 @@ "node": ">=10.13.0" } }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, "node_modules/webpack": { "version": "5.76.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.0.tgz", @@ -14699,15 +14690,6 @@ "node": ">=10.13.0" } }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -14796,6 +14778,7 @@ "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", "dev": true, + "optional": true, "dependencies": { "string-width": "^1.0.2 || 2" } @@ -15205,9 +15188,9 @@ }, "dependencies": { "tslib": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", - "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" } } }, @@ -15221,115 +15204,101 @@ }, "dependencies": { "tslib": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", - "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" } } }, - "@azure/core-http": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@azure/core-http/-/core-http-2.3.1.tgz", - "integrity": "sha512-cur03BUwV0Tbv81bQBOLafFB02B6G++K6F2O3IMl8pSE2QlXm3cu11bfyBNlDUKi5U+xnB3GC63ae3athhkx6Q==", + "@azure/core-rest-pipeline": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.10.3.tgz", + "integrity": "sha512-AMQb0ttiGJ0MIV/r+4TVra6U4+90mPeOveehFnrqKlo7dknPJYdJ61wOzYJXJjDxF8LcCtSogfRelkq+fCGFTw==", "requires": { "@azure/abort-controller": "^1.0.0", - "@azure/core-auth": "^1.3.0", - "@azure/core-tracing": "1.0.0-preview.13", - "@azure/core-util": "^1.1.1", + "@azure/core-auth": "^1.4.0", + "@azure/core-tracing": "^1.0.1", + "@azure/core-util": "^1.3.0", "@azure/logger": "^1.0.0", - "@types/node-fetch": "^2.5.0", - "@types/tunnel": "^0.0.3", "form-data": "^4.0.0", - "node-fetch": "^2.6.7", - "process": "^0.11.10", - "tough-cookie": "^4.0.0", - "tslib": "^2.2.0", - "tunnel": "^0.0.6", - "uuid": "^8.3.0", - "xml2js": "^0.4.19" + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "tslib": "^2.2.0" }, "dependencies": { - "form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==" + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" + "ms": "2.1.2" } }, - "tough-cookie": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", - "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", + "http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", "requires": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" } }, "tslib": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", - "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" - }, - "xml2js": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", - "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", - "requires": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - } + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" } } }, "@azure/core-tracing": { - "version": "1.0.0-preview.13", - "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.0-preview.13.tgz", - "integrity": "sha512-KxDlhXyMlh2Jhj2ykX6vNEU0Vou4nHr025KoSEiz7cS3BNiHNaZcdECk/DmLkEB0as5T7b/TpRcehJ5yV6NeXQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.1.tgz", + "integrity": "sha512-I5CGMoLtX+pI17ZdiFJZgxMJApsK6jjfm85hpgp3oazCdq5Wxgh4wMr7ge/TTWW1B5WBuvIOI1fMU/FrOAMKrw==", "requires": { - "@opentelemetry/api": "^1.0.1", "tslib": "^2.2.0" }, "dependencies": { "tslib": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", - "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" } } }, "@azure/core-util": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.1.1.tgz", - "integrity": "sha512-A4TBYVQCtHOigFb2ETiiKFDocBoI1Zk2Ui1KpI42aJSIDexF7DHQFpnjonltXAIU/ceH+1fsZAWWgvX6/AKzog==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.3.0.tgz", + "integrity": "sha512-ANP0Er7R2KHHHjwmKzPF9wbd0gXvOX7yRRHeYL1eNd/OaNrMLyfZH/FQasHRVAf6rMXX+EAUpvYwLMFDHDI5Gw==", "requires": { "@azure/abort-controller": "^1.0.0", "tslib": "^2.2.0" }, "dependencies": { "tslib": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", - "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" } } }, "@azure/logger": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.0.3.tgz", - "integrity": "sha512-aK4s3Xxjrx3daZr3VylxejK3vG5ExXck5WOHDJ8in/k9AqlfIyFMMT1uG7u8mNjX+QRILTIn0/Xgschfh/dQ9g==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.0.4.tgz", + "integrity": "sha512-ustrPY8MryhloQj7OWGe+HrYx+aoiOxzbXTtgblbV3xwCqpzUK36phH3XNHQKj3EPonyFUuDTfR3qFhTEAuZEg==", "requires": { "tslib": "^2.2.0" }, "dependencies": { "tslib": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", - "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" } } }, @@ -15576,50 +15545,72 @@ } }, "@microsoft/1ds-core-js": { - "version": "3.2.8", - "resolved": "https://registry.npmjs.org/@microsoft/1ds-core-js/-/1ds-core-js-3.2.8.tgz", - "integrity": "sha512-9o9SUAamJiTXIYwpkQDuueYt83uZfXp8zp8YFix1IwVPwC9RmE36T2CX9gXOeq1nDckOuOduYpA8qHvdh5BGfQ==", + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/@microsoft/1ds-core-js/-/1ds-core-js-3.2.9.tgz", + "integrity": "sha512-3pCfM2TzHn3gU9pxHztduKcVRdb/nzruvPFfHPZD0IM0mb0h6TGo2isELF3CTMahTx50RAC51ojNIw2/7VRkOg==", "requires": { - "@microsoft/applicationinsights-core-js": "2.8.9", + "@microsoft/applicationinsights-core-js": "2.8.10", "@microsoft/applicationinsights-shims": "^2.0.2", "@microsoft/dynamicproto-js": "^1.1.7" } }, "@microsoft/1ds-post-js": { - "version": "3.2.8", - "resolved": "https://registry.npmjs.org/@microsoft/1ds-post-js/-/1ds-post-js-3.2.8.tgz", - "integrity": "sha512-SjlRoNcXcXBH6WQD/5SkkaCHIVqldH3gDu+bI7YagrOVJ5APxwT1Duw9gm3L1FjFa9S2i81fvJ3EVSKpp9wULA==", + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/@microsoft/1ds-post-js/-/1ds-post-js-3.2.9.tgz", + "integrity": "sha512-D/RtqkQ2Nr4cuoGqmhi5QTmi3cBlxehIThJ1u3BaH9H/YkLNTKEcHZRWTXy14bXheCefNHciLuadg37G2Kekcg==", "requires": { - "@microsoft/1ds-core-js": "3.2.8", + "@microsoft/1ds-core-js": "3.2.9", "@microsoft/applicationinsights-shims": "^2.0.2", "@microsoft/dynamicproto-js": "^1.1.7" } }, "@microsoft/applicationinsights-channel-js": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-2.8.9.tgz", - "integrity": "sha512-fMBsAEB7pWtPn43y72q9Xy5E5y55r6gMuDQqRRccccVoQDPXyS57VCj5IdATblctru0C6A8XpL2vRyNmEsu0Vg==", + "version": "2.8.11", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-2.8.11.tgz", + "integrity": "sha512-DGDNzT4DMlSvUzWjA4y3tDg47+QYOPV+W07vlfdPwGgLwrl4n6Q4crrW8Y/IOpthHAKDU8rolSAUvP3NqxPi4Q==", "requires": { - "@microsoft/applicationinsights-common": "2.8.9", - "@microsoft/applicationinsights-core-js": "2.8.9", + "@microsoft/applicationinsights-common": "2.8.11", + "@microsoft/applicationinsights-core-js": "2.8.11", "@microsoft/applicationinsights-shims": "2.0.2", "@microsoft/dynamicproto-js": "^1.1.7" + }, + "dependencies": { + "@microsoft/applicationinsights-core-js": { + "version": "2.8.11", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.11.tgz", + "integrity": "sha512-6ScXplyb9Zb0K6TQRfqStm20j5lIe/Dslf65ozows6ibDcKkWl2ZdqzFhymVJZz1WRNpSyD4aA8qnqmslIER6g==", + "requires": { + "@microsoft/applicationinsights-shims": "2.0.2", + "@microsoft/dynamicproto-js": "^1.1.7" + } + } } }, "@microsoft/applicationinsights-common": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-2.8.9.tgz", - "integrity": "sha512-mObn1moElyxZaGIRF/IU3cOaeKMgxghXnYEoHNUCA2e+rNwBIgxjyKkblFIpmGuHf4X7Oz3o3yBWpaC6AoMpig==", + "version": "2.8.11", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-common/-/applicationinsights-common-2.8.11.tgz", + "integrity": "sha512-Cxu4gRajkYv9buEtrcLGHK97AqGK62feN9jH9/JSjUSiSFhbnWtYvEg1EMqMI/P4pneu53yLJloITB+TKwmK7A==", "requires": { - "@microsoft/applicationinsights-core-js": "2.8.9", + "@microsoft/applicationinsights-core-js": "2.8.11", "@microsoft/applicationinsights-shims": "2.0.2", "@microsoft/dynamicproto-js": "^1.1.7" + }, + "dependencies": { + "@microsoft/applicationinsights-core-js": { + "version": "2.8.11", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.11.tgz", + "integrity": "sha512-6ScXplyb9Zb0K6TQRfqStm20j5lIe/Dslf65ozows6ibDcKkWl2ZdqzFhymVJZz1WRNpSyD4aA8qnqmslIER6g==", + "requires": { + "@microsoft/applicationinsights-shims": "2.0.2", + "@microsoft/dynamicproto-js": "^1.1.7" + } + } } }, "@microsoft/applicationinsights-core-js": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.9.tgz", - "integrity": "sha512-HRuIuZ6aOWezcg/G5VyFDDWGL8hDNe/ljPP01J7ImH2kRPEgbtcfPSUMjkamGMefgdq81GZsSoC/NNGTP4pp2w==", + "version": "2.8.10", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.10.tgz", + "integrity": "sha512-jQrufDW0+sV8fBhRvzIPNGiCC6dELH+Ug0DM5CfN9757TBqZJz8CSWyDjex39as8+jD0F/8HRU9QdmrVgq5vFg==", "requires": { "@microsoft/applicationinsights-shims": "2.0.2", "@microsoft/dynamicproto-js": "^1.1.7" @@ -15631,15 +15622,26 @@ "integrity": "sha512-PoHEgsnmcqruLNHZ/amACqdJ6YYQpED0KSRe6J7gIJTtpZC1FfFU9b1fmDKDKtFoUSrPzEh1qzO3kmRZP0betg==" }, "@microsoft/applicationinsights-web-basic": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-2.8.9.tgz", - "integrity": "sha512-CH0J8JFOy7MjK8JO4pXXU+EML+Ilix+94PMZTX5EJlBU1in+mrik74/8qSg3UC4ekPi12KwrXaHCQSVC3WseXQ==", + "version": "2.8.11", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-2.8.11.tgz", + "integrity": "sha512-11T7bbP4ifIBg95E9mYZv1g/vcWvw/KaWKRcGMREP3+vBTLBwMB8r2e9Zd583bOVx+9/gRvfIg+Z/lInQqAfbA==", "requires": { - "@microsoft/applicationinsights-channel-js": "2.8.9", - "@microsoft/applicationinsights-common": "2.8.9", - "@microsoft/applicationinsights-core-js": "2.8.9", + "@microsoft/applicationinsights-channel-js": "2.8.11", + "@microsoft/applicationinsights-common": "2.8.11", + "@microsoft/applicationinsights-core-js": "2.8.11", "@microsoft/applicationinsights-shims": "2.0.2", "@microsoft/dynamicproto-js": "^1.1.7" + }, + "dependencies": { + "@microsoft/applicationinsights-core-js": { + "version": "2.8.11", + "resolved": "https://registry.npmjs.org/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.11.tgz", + "integrity": "sha512-6ScXplyb9Zb0K6TQRfqStm20j5lIe/Dslf65ozows6ibDcKkWl2ZdqzFhymVJZz1WRNpSyD4aA8qnqmslIER6g==", + "requires": { + "@microsoft/applicationinsights-shims": "2.0.2", + "@microsoft/dynamicproto-js": "^1.1.7" + } + } } }, "@microsoft/applicationinsights-web-snippet": { @@ -15648,9 +15650,9 @@ "integrity": "sha512-2IHAOaLauc8qaAitvWS+U931T+ze+7MNWrDHY47IENP5y2UA0vqJDu67kWZDdpCN1fFC77sfgfB+HV7SrKshnQ==" }, "@microsoft/dynamicproto-js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.7.tgz", - "integrity": "sha512-SK3D3aVt+5vOOccKPnGaJWB5gQ8FuKfjboUJHedMP7gu54HqSCXX5iFXhktGD8nfJb0Go30eDvs/UDoTnR2kOA==" + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.9.tgz", + "integrity": "sha512-n1VPsljTSkthsAFYdiWfC+DKzK2WwcRp83Y1YAqdX552BstvsDjft9YXppjUzp11BPsapDoO1LDgrDB0XVsfNQ==" }, "@nodelib/fs.scandir": { "version": "2.1.5", @@ -15679,41 +15681,41 @@ } }, "@opentelemetry/api": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.3.0.tgz", - "integrity": "sha512-YveTnGNsFFixTKJz09Oi4zYkiLT5af3WpZDu4aIUM7xX+2bHAkOJayFTVQd6zB8kkWPpbua4Ha6Ql00grdLlJQ==" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.4.1.tgz", + "integrity": "sha512-O2yRJce1GOc6PAy3QxFM4NzFiWzvScDC1/5ihYBL6BUEVdq0XMWN01sppE+H6bBXbaFYipjwFLEWLg5PaSOThA==" }, "@opentelemetry/core": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.8.0.tgz", - "integrity": "sha512-6SDjwBML4Am0AQmy7z1j6HGrWDgeK8awBRUvl1PGw6HayViMk4QpnUXvv4HTHisecgVBy43NE/cstWprm8tIfw==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.11.0.tgz", + "integrity": "sha512-aP1wHSb+YfU0pM63UAkizYPuS4lZxzavHHw5KJfFNN2oWQ79HSm6JR3CzwFKHwKhSzHN8RE9fgP1IdVJ8zmo1w==", "requires": { - "@opentelemetry/semantic-conventions": "1.8.0" + "@opentelemetry/semantic-conventions": "1.11.0" } }, "@opentelemetry/resources": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.8.0.tgz", - "integrity": "sha512-KSyMH6Jvss/PFDy16z5qkCK0ERlpyqixb1xwb73wLMvVq+j7i89lobDjw3JkpCcd1Ws0J6jAI4fw28Zufj2ssg==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.11.0.tgz", + "integrity": "sha512-y0z2YJTqk0ag+hGT4EXbxH/qPhDe8PfwltYb4tXIEsozgEFfut/bqW7H7pDvylmCjBRMG4NjtLp57V1Ev++brA==", "requires": { - "@opentelemetry/core": "1.8.0", - "@opentelemetry/semantic-conventions": "1.8.0" + "@opentelemetry/core": "1.11.0", + "@opentelemetry/semantic-conventions": "1.11.0" } }, "@opentelemetry/sdk-trace-base": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.8.0.tgz", - "integrity": "sha512-iH41m0UTddnCKJzZx3M85vlhKzRcmT48pUeBbnzsGrq4nIay1oWVHKM5nhB5r8qRDGvd/n7f/YLCXClxwM0tvA==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.11.0.tgz", + "integrity": "sha512-DV8e5/Qo42V8FMBlQ0Y0Liv6Hl/Pp5bAZ73s7r1euX8w4bpRes1B7ACiA4yujADbWMJxBgSo4fGbi4yjmTMG2A==", "requires": { - "@opentelemetry/core": "1.8.0", - "@opentelemetry/resources": "1.8.0", - "@opentelemetry/semantic-conventions": "1.8.0" + "@opentelemetry/core": "1.11.0", + "@opentelemetry/resources": "1.11.0", + "@opentelemetry/semantic-conventions": "1.11.0" } }, "@opentelemetry/semantic-conventions": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.8.0.tgz", - "integrity": "sha512-TYh1MRcm4JnvpqtqOwT9WYaBYY4KERHdToxs/suDTLviGRsQkIjS5yYROTYTSJQUnYLOn/TuOh5GoMwfLSU+Ew==" + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.11.0.tgz", + "integrity": "sha512-fG4D0AktoHyHwGhFGv+PzKrZjxbKJfckJauTJdq2A+ej5cTazmNYjJVAODXXkYyrsI10muMl+B1iO2q1R6Lp+w==" }, "@polka/url": { "version": "1.0.0-next.21", @@ -15965,28 +15967,8 @@ "@types/node": { "version": "14.18.12", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.12.tgz", - "integrity": "sha512-q4jlIR71hUpWTnGhXWcakgkZeHa3CCjcQcnuzU8M891BAWA2jHiziiWEPEkdS5pFsz7H9HJiy8BrK7tBRNrY7A==" - }, - "@types/node-fetch": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.2.tgz", - "integrity": "sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==", - "requires": { - "@types/node": "*", - "form-data": "^3.0.0" - }, - "dependencies": { - "form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - } - } + "integrity": "sha512-q4jlIR71hUpWTnGhXWcakgkZeHa3CCjcQcnuzU8M891BAWA2jHiziiWEPEkdS5pFsz7H9HJiy8BrK7tBRNrY7A==", + "dev": true }, "@types/semver": { "version": "5.5.0", @@ -16027,14 +16009,6 @@ "integrity": "sha1-EHPEvIJHVK49EM+riKsCN7qWTk0=", "dev": true }, - "@types/tunnel": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/@types/tunnel/-/tunnel-0.0.3.tgz", - "integrity": "sha512-sOUTGn6h1SfQ+gbgqC364jLFBw2lnFqkgF3q0WovEHRLMrVD1sd5aufqi/aJObLekJO+Aq5z646U4Oxy6shXMA==", - "requires": { - "@types/node": "*" - } - }, "@types/uuid": { "version": "8.3.4", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", @@ -16186,14 +16160,14 @@ "dev": true }, "@vscode/extension-telemetry": { - "version": "0.7.4-preview", - "resolved": "https://registry.npmjs.org/@vscode/extension-telemetry/-/extension-telemetry-0.7.4-preview.tgz", - "integrity": "sha512-6OkvjCc+DaC9B26t3hj7vuAxf1ONm/p4LrVvFrapa+jBCKxXXUaV1Asz6+QxYaPfd4Ws/MlnFfCvlgvv3uYRwQ==", + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/@vscode/extension-telemetry/-/extension-telemetry-0.7.7.tgz", + "integrity": "sha512-uW508BPjkWDBOKvvvSym3ZmGb7kHIiWaAfB/1PHzLz2x9TrC33CfjmFEI+CywIL/jBv4bqZxxjN4tfefB61F+g==", "requires": { - "@microsoft/1ds-core-js": "^3.2.8", - "@microsoft/1ds-post-js": "^3.2.8", - "@microsoft/applicationinsights-web-basic": "^2.8.9", - "applicationinsights": "2.3.6" + "@microsoft/1ds-core-js": "^3.2.9", + "@microsoft/1ds-post-js": "^3.2.9", + "@microsoft/applicationinsights-web-basic": "^2.8.11", + "applicationinsights": "2.5.0" } }, "@vscode/jupyter-lsp-middleware": { @@ -16231,6 +16205,80 @@ "unzipper": "^0.10.11" } }, + "@vscode/vsce": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-2.18.0.tgz", + "integrity": "sha512-tUA3XoKx5xjoi3EDcngk0VUYMhvfXLhS4s7CntpLPh1qtLYtgSCexTIMUHkCy6MqyozRW98bdW3a2yHPEADRnQ==", + "dev": true, + "requires": { + "azure-devops-node-api": "^11.0.1", + "chalk": "^2.4.2", + "cheerio": "^1.0.0-rc.9", + "commander": "^6.1.0", + "glob": "^7.0.6", + "hosted-git-info": "^4.0.2", + "jsonc-parser": "^3.2.0", + "keytar": "^7.7.0", + "leven": "^3.1.0", + "markdown-it": "^12.3.2", + "mime": "^1.3.4", + "minimatch": "^3.0.3", + "parse-semver": "^1.1.1", + "read": "^1.0.7", + "semver": "^5.1.0", + "tmp": "^0.2.1", + "typed-rest-client": "^1.8.4", + "url-join": "^4.0.1", + "xml2js": "^0.4.23", + "yauzl": "^2.3.1", + "yazl": "^2.2.2" + }, + "dependencies": { + "commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "dev": true + }, + "hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "requires": { + "rimraf": "^3.0.0" + } + }, + "xml2js": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "dev": true, + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + } + } + } + }, "@webassemblyjs/ast": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", @@ -16438,6 +16486,24 @@ "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", "dev": true }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "requires": { + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + } + } + }, "aggregate-error": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.1.tgz", @@ -16554,11 +16620,12 @@ } }, "applicationinsights": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/applicationinsights/-/applicationinsights-2.3.6.tgz", - "integrity": "sha512-ZzXXpZpDRGcy6Pp5V319nDF9/+Ey7jNknEXZyaBajtC5onN0dcBem6ng5jcb3MPH2AjYWRI8XgyNEuzP/6Y5/A==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/applicationinsights/-/applicationinsights-2.5.0.tgz", + "integrity": "sha512-6kIFmpANRok+6FhCOmO7ZZ/mh7fdNKn17BaT13cg/RV5roLPJlA6q8srWexayHd3MPcwMb9072e8Zp0P47s/pw==", "requires": { - "@azure/core-http": "^2.2.3", + "@azure/core-auth": "^1.4.0", + "@azure/core-rest-pipeline": "^1.10.0", "@microsoft/applicationinsights-web-snippet": "^1.0.1", "@opentelemetry/api": "^1.0.4", "@opentelemetry/core": "^1.0.1", @@ -16574,7 +16641,8 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "dev": true + "dev": true, + "optional": true }, "arch": { "version": "2.2.0", @@ -16609,6 +16677,7 @@ "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==", "dev": true, + "optional": true, "requires": { "delegates": "^1.0.0", "readable-stream": "^2.0.6" @@ -16893,7 +16962,7 @@ "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "atob": { "version": "2.1.2", @@ -17715,7 +17784,8 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.2.tgz", "integrity": "sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A==", - "dev": true + "dev": true, + "optional": true }, "chrome-trace-event": { "version": "1.0.2", @@ -17988,7 +18058,8 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "dev": true + "dev": true, + "optional": true }, "constants-browserify": { "version": "1.0.0", @@ -18392,7 +18463,8 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true + "dev": true, + "optional": true }, "deep-is": { "version": "0.1.3", @@ -18509,13 +18581,14 @@ "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" }, "delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", - "dev": true + "dev": true, + "optional": true }, "des.js": { "version": "1.0.0", @@ -18537,7 +18610,8 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", - "dev": true + "dev": true, + "optional": true }, "diagnostic-channel": { "version": "1.1.0", @@ -19645,7 +19719,8 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", - "dev": true + "dev": true, + "optional": true }, "expand-tilde": { "version": "2.0.2", @@ -20127,6 +20202,16 @@ } } }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, "fragment-cache": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", @@ -20255,6 +20340,7 @@ "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "dev": true, + "optional": true, "requires": { "aproba": "^1.0.3", "console-control-strings": "^1.0.0", @@ -20270,13 +20356,15 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true + "dev": true, + "optional": true }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -20344,7 +20432,8 @@ "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", "integrity": "sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4=", - "dev": true + "dev": true, + "optional": true }, "glob": { "version": "7.2.0", @@ -20805,7 +20894,8 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", - "dev": true + "dev": true, + "optional": true }, "has-value": { "version": "1.0.0", @@ -20931,15 +21021,6 @@ "debug": "4" }, "dependencies": { - "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "requires": { - "debug": "4" - } - }, "debug": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", @@ -20961,26 +21042,15 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "dev": true, "requires": { "agent-base": "6", "debug": "4" }, "dependencies": { - "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "requires": { - "debug": "4" - } - }, "debug": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, "requires": { "ms": "2.1.2" } @@ -21905,9 +21975,9 @@ "dev": true }, "jsonc-parser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.0.0.tgz", - "integrity": "sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==" + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==" }, "jsx-ast-utils": { "version": "3.2.1", @@ -21936,6 +22006,7 @@ "resolved": "https://registry.npmjs.org/keytar/-/keytar-7.7.0.tgz", "integrity": "sha512-YEY9HWqThQc5q5xbXbRwsZTh2PJ36OSYRjSv3NN2xf5s5dpLTjEZnC2YikR29OaVybf9nQ0dJ/80i40RS97t/A==", "dev": true, + "optional": true, "requires": { "node-addon-api": "^3.0.0", "prebuild-install": "^6.0.0" @@ -22524,7 +22595,8 @@ "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "dev": true + "dev": true, + "optional": true }, "mocha": { "version": "9.2.2", @@ -22822,8 +22894,7 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "mute-stdout": { "version": "1.0.1", @@ -22878,7 +22949,8 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==", - "dev": true + "dev": true, + "optional": true }, "natural-compare": { "version": "1.4.0", @@ -22944,6 +23016,7 @@ "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.30.1.tgz", "integrity": "sha512-/2D0wOQPgaUWzVSVgRMx+trKJRC2UG4SUc4oCJoXx9Uxjtp0Vy3/kt7zcbxHF8+Z/pK3UloLWzBISg72brfy1w==", "dev": true, + "optional": true, "requires": { "semver": "^5.4.1" } @@ -22952,15 +23025,8 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", - "dev": true - }, - "node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "requires": { - "whatwg-url": "^5.0.0" - } + "dev": true, + "optional": true }, "node-has-native-dependencies": { "version": "1.0.2", @@ -23243,6 +23309,7 @@ "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "dev": true, + "optional": true, "requires": { "are-we-there-yet": "~1.1.2", "console-control-strings": "~1.1.0", @@ -23921,6 +23988,7 @@ "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-6.1.4.tgz", "integrity": "sha512-Z4vpywnK1lBg+zdPCVCsKq0xO66eEV9rWo2zrROGGiRS4JtueBOdlB1FnY8lcy7JsUud/Q3ijUxyWN26Ika0vQ==", "dev": true, + "optional": true, "requires": { "detect-libc": "^1.0.3", "expand-template": "^2.0.3", @@ -23942,6 +24010,7 @@ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", "dev": true, + "optional": true, "requires": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -23970,7 +24039,8 @@ "process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true }, "process-nextick-args": { "version": "2.0.1", @@ -24004,11 +24074,6 @@ "integrity": "sha1-AMLa7t2iDofjeCs0Stuhzd1q1wk=", "dev": true }, - "psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" - }, "public-encrypt": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", @@ -24047,7 +24112,8 @@ "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true }, "qs": { "version": "6.5.3", @@ -24078,11 +24144,6 @@ "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", "dev": true }, - "querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" - }, "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -24113,6 +24174,7 @@ "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "dev": true, + "optional": true, "requires": { "deep-extend": "^0.6.0", "ini": "~1.3.0", @@ -24124,7 +24186,8 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true + "dev": true, + "optional": true } } }, @@ -24368,11 +24431,6 @@ "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", "dev": true }, - "requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" - }, "resolve": { "version": "1.22.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", @@ -24683,13 +24741,15 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", - "dev": true + "dev": true, + "optional": true }, "simple-get": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", "dev": true, + "optional": true, "requires": { "decompress-response": "^4.2.0", "once": "^1.3.1", @@ -24701,6 +24761,7 @@ "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", "dev": true, + "optional": true, "requires": { "mimic-response": "^2.0.0" } @@ -24709,7 +24770,8 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", - "dev": true + "dev": true, + "optional": true } } }, @@ -25335,6 +25397,7 @@ "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", "dev": true, + "optional": true, "requires": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", @@ -25347,6 +25410,7 @@ "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", "dev": true, + "optional": true, "requires": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -25358,6 +25422,7 @@ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", "dev": true, + "optional": true, "requires": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -25368,6 +25433,7 @@ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "dev": true, + "optional": true, "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -25379,6 +25445,7 @@ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "dev": true, + "optional": true, "requires": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", @@ -25605,11 +25672,6 @@ "integrity": "sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==", "dev": true }, - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, "traverse": { "version": "0.3.9", "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", @@ -25884,13 +25946,15 @@ "tunnel": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", - "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==" + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "dev": true }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.0.1" } @@ -26062,11 +26126,6 @@ "through2-filter": "^3.0.0" } }, - "universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==" - }, "unset-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", @@ -26183,15 +26242,6 @@ "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", "dev": true }, - "url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "requires": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, "url-parse-lax": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", @@ -26239,7 +26289,8 @@ "uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true }, "v8-compile-cache": { "version": "2.3.0", @@ -26340,79 +26391,6 @@ "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", "dev": true }, - "vsce": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/vsce/-/vsce-2.7.0.tgz", - "integrity": "sha512-CKU34wrQlbKDeJCRBkd1a8iwF9EvNxcYMg9hAUH6AxFGR6Wo2IKWwt3cJIcusHxx6XdjDHWlfAS/fJN30uvVnA==", - "dev": true, - "requires": { - "azure-devops-node-api": "^11.0.1", - "chalk": "^2.4.2", - "cheerio": "^1.0.0-rc.9", - "commander": "^6.1.0", - "glob": "^7.0.6", - "hosted-git-info": "^4.0.2", - "keytar": "^7.7.0", - "leven": "^3.1.0", - "markdown-it": "^12.3.2", - "mime": "^1.3.4", - "minimatch": "^3.0.3", - "parse-semver": "^1.1.1", - "read": "^1.0.7", - "semver": "^5.1.0", - "tmp": "^0.2.1", - "typed-rest-client": "^1.8.4", - "url-join": "^4.0.1", - "xml2js": "^0.4.23", - "yauzl": "^2.3.1", - "yazl": "^2.2.2" - }, - "dependencies": { - "commander": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", - "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", - "dev": true - }, - "hosted-git-info": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", - "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", - "dev": true, - "requires": { - "rimraf": "^3.0.0" - } - }, - "xml2js": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", - "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", - "dev": true, - "requires": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - } - } - } - }, "vscode-debugadapter": { "version": "1.35.0", "resolved": "https://registry.npmjs.org/vscode-debugadapter/-/vscode-debugadapter-1.35.0.tgz", @@ -26516,11 +26494,6 @@ "graceful-fs": "^4.1.2" } }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, "webpack": { "version": "5.76.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.0.tgz", @@ -26705,15 +26678,6 @@ "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", "dev": true }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -26777,6 +26741,7 @@ "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", "dev": true, + "optional": true, "requires": { "string-width": "^1.0.2 || 2" } diff --git a/package.json b/package.json index 08d56bdf3bc0..e0b418a4cd9b 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,8 @@ "contribEditorContentMenu", "quickPickSortByLabel", "envShellEvent", - "testObserver" + "testObserver", + "quickPickItemTooltip" ], "author": { "name": "Microsoft Corporation" @@ -83,24 +84,24 @@ "walkthroughs": [ { "id": "pythonWelcome", - "title": "Get Started with Python Development", - "description": "Your first steps to set up a Python project with all the powerful tools and features that the Python extension has to offer!", + "title": "%walkthrough.pythonWelcome.title%", + "description": "%walkthrough.pythonWelcome.description%", "when": "workspacePlatform != webworker", "steps": [ { "id": "python.createPythonFile", - "title": "Create a Python file", - "description": "[Open](command:toSide:workbench.action.files.openFile) or [create](command:toSide:workbench.action.files.newUntitledFile?%7B%22languageId%22%3A%22python%22%7D) a Python file - make sure to save it as \".py\".\n[Create Python File](command:toSide:workbench.action.files.newUntitledFile?%7B%22languageId%22%3A%22python%22%7D)", + "title": "%walkthrough.step.python.createPythonFile.title%", + "description": "%walkthrough.step.python.createPythonFile.description%", "media": { "svg": "resources/walkthrough/open-folder.svg", - "altText": "Open a Python file or a folder with a Python project." + "altText": "%walkthrough.step.python.createPythonFile.altText%" }, "when": "" }, { "id": "python.installPythonWin8", - "title": "Install Python", - "description": "The Python Extension requires Python to be installed. Install Python [from python.org](https://www.python.org/downloads).\n\n[Install Python](https://www.python.org/downloads)\n", + "title": "%walkthrough.step.python.installPythonWin8.title%", + "description": "%walkthrough.step.python.installPythonWin8.description%", "media": { "markdown": "resources/walkthrough/install-python-windows-8.md" }, @@ -108,8 +109,8 @@ }, { "id": "python.installPythonMac", - "title": "Install Python", - "description": "The Python Extension requires Python to be installed. Install Python 3 through the terminal.\n[Install Python via Brew](command:python.installPythonOnMac)\n", + "title": "%walkthrough.step.python.installPythonMac.title%", + "description": "%walkthrough.step.python.installPythonMac.description%", "media": { "markdown": "resources/walkthrough/install-python-macos.md" }, @@ -118,8 +119,8 @@ }, { "id": "python.installPythonLinux", - "title": "Install Python", - "description": "The Python Extension requires Python to be installed. Install Python 3 through the terminal.\n[Install Python via terminal](command:python.installPythonOnLinux)\n", + "title": "%walkthrough.step.python.installPythonLinux.title%", + "description": "%walkthrough.step.python.installPythonLinux.description%", "media": { "markdown": "resources/walkthrough/install-python-linux.md" }, @@ -128,40 +129,40 @@ }, { "id": "python.selectInterpreter", - "title": "Select a Python Interpreter", - "description": "Choose which Python interpreter/environment you want to use for your Python project.\n[Select Python Interpreter](command:python.setInterpreter)\n**Tip**: Run the ``Python: Select Interpreter`` command in the [Command Palette](command:workbench.action.showCommands).", + "title": "%walkthrough.step.python.selectInterpreter.title%", + "description": "%walkthrough.step.python.selectInterpreter.description%", "media": { "svg": "resources/walkthrough/python-interpreter.svg", - "altText": "Selecting a python interpreter from the status bar" + "altText": "%walkthrough.step.python.selectInterpreter.altText%" }, "when": "workspaceFolderCount == 0" }, { "id": "python.createEnvironment", - "title": "Create a Python Environment ", - "description": "Create an environment for your Python project.\n[Create Environment](command:python.createEnvironment)\n**Tip**: Run the ``Python: Create Environment`` command in the [Command Palette](command:workbench.action.showCommands).\n 🔍 Check out our [docs](https://aka.ms/pythonenvs) to learn more.", + "title": "%walkthrough.step.python.createEnvironment.title%", + "description": "%walkthrough.step.python.createEnvironment.description%", "media": { "svg": "resources/walkthrough/create-environment.svg", - "altText": "Creating a Python environment from the Command Palette" + "altText": "%walkthrough.step.python.createEnvironment.altText%" }, "when": "workspaceFolderCount > 0" }, { "id": "python.runAndDebug", - "title": "Run and debug your Python file", - "description": "Open your Python file and click on the play button on the top right of the editor, or press F5 when on the file and select \"Python File\" to run with the debugger. \n \n[Learn more](https://code.visualstudio.com/docs/python/python-tutorial#_run-hello-world)", + "title": "%walkthrough.step.python.runAndDebug.title%", + "description": "%walkthrough.step.python.runAndDebug.description%", "media": { "svg": "resources/walkthrough/rundebug2.svg", - "altText": "How to run and debug in VS Code with F5 or the play button on the top right." + "altText": "%walkthrough.step.python.runAndDebug.altText%" }, "when": "" }, { "id": "python.learnMoreWithDS", - "title": "Explore more resources", - "description": "🎨 Explore all the features the Python extension has to offer by looking for \"Python\" in the [Command Palette](command:workbench.action.showCommands). \n 📈 Learn more about getting started with [data science](command:workbench.action.openWalkthrough?%7B%22category%22%3A%22ms-python.python%23pythonDataScienceWelcome%22%2C%22step%22%3A%22ms-python.python%23python.createNewNotebook%22%7D) in Python. \n ✨ Take a look at our [Release Notes](https://aka.ms/AA8dxtb) to learn more about the latest features. \n \n[Learn More](https://aka.ms/AA8dqti)", + "title": "%walkthrough.step.python.learnMoreWithDS.title%", + "description": "%walkthrough.step.python.learnMoreWithDS.description%", "media": { - "altText": "Image representing our documentation page and mailing list resources.", + "altText": "%walkthrough.step.python.learnMoreWithDS.altText%", "svg": "resources/walkthrough/learnmore.svg" }, "when": "" @@ -170,26 +171,26 @@ }, { "id": "pythonDataScienceWelcome", - "title": "Get Started with Python for Data Science", - "description": "Your first steps to getting started with a Data Science project with Python!", + "title": "%walkthrough.pythonDataScienceWelcome.title%", + "description": "%walkthrough.pythonDataScienceWelcome.description%", "when": "false", "steps": [ { "id": "python.installJupyterExt", - "title": "Install Jupyter extension", - "description": "If you haven't already, install the [Jupyter extension](command:workbench.extensions.search?\"ms-toolsai.jupyter\") to take full advantage of notebooks experiences in VS Code!\n \n[Search Jupyter extension](command:workbench.extensions.search?\"ms-toolsai.jupyter\")", + "title": "%walkthrough.step.python.installJupyterExt.title%", + "description": "%walkthrough.step.python.installJupyterExt.description%", "media": { "svg": "resources/walkthrough/data-science.svg", - "altText": "Creating a new Jupyter notebook" + "altText": "%walkthrough.step.python.installJupyterExt.altText%" } }, { "id": "python.createNewNotebook", - "title": "Create or open a Jupyter Notebook", - "description": "Right click in the file explorer and create a new file with an .ipynb extension. Or, open the [Command Palette](command:workbench.action.showCommands) and run the command \n``Jupyter: Create New Blank Notebook``.\n[Create new Jupyter Notebook](command:toSide:jupyter.createnewnotebook)\n If you have an existing project, you can also [open a folder](command:workbench.action.files.openFolder) and/or clone a project from GitHub: [clone a Git repository](command:git.clone).", + "title": "%walkthrough.step.python.createNewNotebook.title%", + "description": "%walkthrough.step.python.createNewNotebook.description%", "media": { "svg": "resources/walkthrough/create-notebook.svg", - "altText": "Creating a new Jupyter notebook" + "altText": "%walkthrough.step.python.createNewNotebook.altText%" }, "completionEvents": [ "onCommand:jupyter.createnewnotebook", @@ -199,11 +200,11 @@ }, { "id": "python.openInteractiveWindow", - "title": "Open the Python Interactive Window", - "description": "The Python Interactive Window is a Python shell where you can execute and view the results of your Python code. You can create cells on a Python file by typing ``#%%``.\n \nTo open the interactive window anytime, open the [Command Palette](command:workbench.action.showCommands) and run the command \n``Jupyter: Create Interactive Window``.\n[Open Interactive Window](command:jupyter.createnewinteractive)", + "title": "%walkthrough.step.python.openInteractiveWindow.title%", + "description": "%walkthrough.step.python.openInteractiveWindow.description%", "media": { "svg": "resources/walkthrough/interactive-window.svg", - "altText": "Opening python interactive window" + "altText": "%walkthrough.step.python.openInteractiveWindow.altText%" }, "completionEvents": [ "onCommand:jupyter.createnewinteractive" @@ -211,11 +212,11 @@ }, { "id": "python.dataScienceLearnMore", - "title": "Find out more!", - "description": "📒 Take a look into the [Jupyter extension](command:workbench.extensions.search?\"ms-toolsai.jupyter\") features, by looking for \"Jupyter\" in the [Command Palette](command:workbench.action.showCommands). \n 🏃🏻 Find out more features in our [Tutorials](https://aka.ms/AAdjzpd). \n[Learn more](https://aka.ms/AAdar6q)", + "title": "%walkthrough.step.python.dataScienceLearnMore.title%", + "description": "%walkthrough.step.python.dataScienceLearnMore.description%", "media": { "svg": "resources/walkthrough/learnmore.svg", - "altText": "Image representing our documentation page and mailing list resources." + "altText": "%walkthrough.step.python.dataScienceLearnMore.altText%" } } ] @@ -374,6 +375,11 @@ "light": "resources/light/repl.svg" }, "title": "%python.command.python.viewOutput.title%" + }, + { + "category": "Python", + "command": "python.installJupyter", + "title": "%python.command.python.installJupyter.title%" } ], "configuration": { @@ -916,6 +922,7 @@ }, "python.logging.level": { "default": "error", + "deprecationMessage": "%python.logging.level.deprecation%", "description": "%python.logging.level.description%", "enum": [ "debug", @@ -1705,13 +1712,25 @@ { "submenu": "python.run", "group": "Python", - "when": "editorLangId == python && !virtualWorkspace && shellExecutionSupported" + "when": "editorLangId == python && !virtualWorkspace && shellExecutionSupported && isWorkspaceTrusted" }, { "command": "python.sortImports", "group": "Refactor", "title": "%python.command.python.sortImports.title%", "when": "editorLangId == python && !notebookEditorFocused && !virtualWorkspace && shellExecutionSupported" + }, + { + "submenu": "python.runFileInteractive", + "group": "Jupyter2", + "when": "editorLangId == python && !virtualWorkspace && shellExecutionSupported && !isJupyterInstalled && isWorkspaceTrusted" + } + ], + "python.runFileInteractive": [ + { + "command": "python.installJupyter", + "group": "Jupyter2", + "when": "resourceLangId == python && !virtualWorkspace && shellExecutionSupported" } ], "python.run": [ @@ -1779,6 +1798,10 @@ "id": "python.run", "label": "%python.editor.context.submenu.runPython%", "icon": "$(play)" + }, + { + "id": "python.runFileInteractive", + "label": "%python.editor.context.submenu.runPythonInteractive%" } ], "viewsWelcome": [ @@ -1841,7 +1864,7 @@ }, "dependencies": { "@iarna/toml": "^2.2.5", - "@vscode/extension-telemetry": "^0.7.4-preview", + "@vscode/extension-telemetry": "^0.7.7", "@vscode/jupyter-lsp-middleware": "^0.2.50", "arch": "^2.1.0", "diff-match-patch": "^1.0.0", @@ -1899,6 +1922,7 @@ "@types/tmp": "^0.0.33", "@types/uuid": "^8.3.4", "@types/vscode": "^1.75.0", + "@vscode/vsce": "^2.18.0", "@types/which": "^2.0.1", "@types/winreg": "^1.2.30", "@types/xml2js": "^0.4.2", @@ -1947,7 +1971,6 @@ "typemoq": "^2.1.0", "typescript": "4.5.5", "uuid": "^8.3.2", - "vsce": "^2.6.6", "vscode-debugadapter-testsupport": "^1.27.0", "webpack": "^5.76.0", "webpack-bundle-analyzer": "^4.5.0", @@ -1957,10 +1980,5 @@ "webpack-node-externals": "^3.0.0", "webpack-require-from": "^1.8.6", "yargs": "^15.3.1" - }, - "__metadata": { - "id": "f1f59ae4-9318-4f3c-a9b5-81b2eaa5f8a5", - "publisherDisplayName": "Microsoft", - "publisherId": "998b010b-e2af-44a5-a6cd-0b5fd3b9b6f8" } } diff --git a/package.nls.json b/package.nls.json index 95cb54a1b509..cfbbeb7d41d5 100644 --- a/package.nls.json +++ b/package.nls.json @@ -10,6 +10,7 @@ "python.command.python.setInterpreter.title": "Select Interpreter", "python.command.python.clearWorkspaceInterpreter.title": "Clear Workspace Interpreter Setting", "python.command.python.viewOutput.title": "Show Output", + "python.command.python.installJupyter.title": "Install the Jupyter extension", "python.command.python.viewLanguageServerOutput.title": "Show Language Server Output", "python.command.python.configureTests.title": "Configure Tests", "python.command.testing.rerunFailedTests.title": "Rerun Failed Tests", @@ -26,6 +27,7 @@ "python.command.python.refreshTensorBoard.title": "Refresh TensorBoard", "python.menu.createNewFile.title": "Python File", "python.editor.context.submenu.runPython": "Run Python", + "python.editor.context.submenu.runPythonInteractive": "Run in Interactive window", "python.activeStateToolPath.description": "Path to the State Tool executable for ActiveState runtimes (version 0.36+).", "python.autoComplete.extraPaths.description": "List of paths to libraries and the like that need to be imported by auto complete engine. E.g. when using Google App SDK, the paths are not in system path, hence need to be added into this list.", "python.condaPath.description": "Path to the conda executable to use for activation (version 4.4+).", @@ -98,6 +100,7 @@ "python.linting.pylintEnabled.description": "Whether to lint Python files using pylint.", "python.linting.pylintPath.description": "Path to Pylint, you can use a custom version of pylint by modifying this setting to include the full path.", "python.logging.level.description": "The logging level the extension logs at, defaults to 'error'", + "python.logging.level.deprecation": "This setting is deprecated. Please use command `Developer: Set Log Level...` to set logging level.", "python.pipenvPath.description": "Path to the pipenv executable to use for activation.", "python.poetryPath.description": "Path to the poetry executable.", "python.sortImports.args.description": "Arguments passed in. Each argument is a separate item in the array.", @@ -120,5 +123,42 @@ "python.venvFolders.description": "Folders in your home directory to look into for virtual environments (supports pyenv, direnv and virtualenvwrapper by default).", "python.venvPath.description": "Path to folder with a list of Virtual Environments (e.g. ~/.pyenv, ~/Envs, ~/.virtualenvs).", "python.sortImports.args.deprecationMessage": "This setting will be removed soon. Use 'isort.args' instead.", - "python.sortImports.path.deprecationMessage": "This setting will be removed soon. Use 'isort.path' instead." + "python.sortImports.path.deprecationMessage": "This setting will be removed soon. Use 'isort.path' instead.", + "walkthrough.pythonWelcome.title": "Get Started with Python Development", + "walkthrough.pythonWelcome.description": "Your first steps to set up a Python project with all the powerful tools and features that the Python extension has to offer!", + "walkthrough.step.python.createPythonFile.title": "Create a Python file", + "walkthrough.step.python.createPythonFile.description": "[Open](command:toSide:workbench.action.files.openFile) or [create](command:toSide:workbench.action.files.newUntitledFile?%7B%22languageId%22%3A%22python%22%7D) a Python file - make sure to save it as \".py\".\n[Create Python File](command:toSide:workbench.action.files.newUntitledFile?%7B%22languageId%22%3A%22python%22%7D)", + "walkthrough.step.python.installPythonWin8.title": "Install Python", + "walkthrough.step.python.installPythonWin8.description": "The Python Extension requires Python to be installed. Install Python [from python.org](https://www.python.org/downloads).\n\n[Install Python](https://www.python.org/downloads)\n", + "walkthrough.step.python.installPythonMac.title": "Install Python", + "walkthrough.step.python.installPythonMac.description": "The Python Extension requires Python to be installed. Install Python 3 through the terminal.\n[Install Python via Brew](command:python.installPythonOnMac)\n", + "walkthrough.step.python.installPythonLinux.title": "Install Python", + "walkthrough.step.python.installPythonLinux.description": "The Python Extension requires Python to be installed. Install Python 3 through the terminal.\n[Install Python via terminal](command:python.installPythonOnLinux)\n", + "walkthrough.step.python.selectInterpreter.title": "Select a Python Interpreter", + "walkthrough.step.python.selectInterpreter.description": "Choose which Python interpreter/environment you want to use for your Python project.\n[Select Python Interpreter](command:python.setInterpreter)\n**Tip**: Run the ``Python: Select Interpreter`` command in the [Command Palette](command:workbench.action.showCommands).", + "walkthrough.step.python.createEnvironment.title": "Create a Python Environment ", + "walkthrough.step.python.createEnvironment.description": "Create an environment for your Python project.\n[Create Environment](command:python.createEnvironment)\n**Tip**: Run the ``Python: Create Environment`` command in the [Command Palette](command:workbench.action.showCommands).\n 🔍 Check out our [docs](https://aka.ms/pythonenvs) to learn more.", + "walkthrough.step.python.runAndDebug.title": "Run and debug your Python file", + "walkthrough.step.python.runAndDebug.description": "Open your Python file and click on the play button on the top right of the editor, or press F5 when on the file and select \"Python File\" to run with the debugger. \n \n[Learn more](https://code.visualstudio.com/docs/python/python-tutorial#_run-hello-world)", + "walkthrough.step.python.learnMoreWithDS.title": "Explore more resources", + "walkthrough.step.python.learnMoreWithDS.description": "🎨 Explore all the features the Python extension has to offer by looking for \"Python\" in the [Command Palette](command:workbench.action.showCommands). \n 📈 Learn more about getting started with [data science](command:workbench.action.openWalkthrough?%7B%22category%22%3A%22ms-python.python%23pythonDataScienceWelcome%22%2C%22step%22%3A%22ms-python.python%23python.createNewNotebook%22%7D) in Python. \n ✨ Take a look at our [Release Notes](https://aka.ms/AA8dxtb) to learn more about the latest features. \n \n[Learn More](https://aka.ms/AA8dqti)", + "walkthrough.pythonDataScienceWelcome.title": "Get Started with Python for Data Science", + "walkthrough.pythonDataScienceWelcome.description": "Your first steps to getting started with a Data Science project with Python!", + "walkthrough.step.python.installJupyterExt.title": "Install Jupyter extension", + "walkthrough.step.python.installJupyterExt.description": "If you haven't already, install the [Jupyter extension](command:workbench.extensions.search?\"ms-toolsai.jupyter\") to take full advantage of notebooks experiences in VS Code!\n \n[Search Jupyter extension](command:workbench.extensions.search?\"ms-toolsai.jupyter\")", + "walkthrough.step.python.createNewNotebook.title": "Create or open a Jupyter Notebook", + "walkthrough.step.python.createNewNotebook.description": "Right click in the file explorer and create a new file with an .ipynb extension. Or, open the [Command Palette](command:workbench.action.showCommands) and run the command \n``Jupyter: Create New Blank Notebook``.\n[Create new Jupyter Notebook](command:toSide:jupyter.createnewnotebook)\n If you have an existing project, you can also [open a folder](command:workbench.action.files.openFolder) and/or clone a project from GitHub: [clone a Git repository](command:git.clone).", + "walkthrough.step.python.openInteractiveWindow.title": "Open the Python Interactive Window", + "walkthrough.step.python.openInteractiveWindow.description": "The Python Interactive Window is a Python shell where you can execute and view the results of your Python code. You can create cells on a Python file by typing ``#%%``.\n \nTo open the interactive window anytime, open the [Command Palette](command:workbench.action.showCommands) and run the command \n``Jupyter: Create Interactive Window``.\n[Open Interactive Window](command:jupyter.createnewinteractive)", + "walkthrough.step.python.dataScienceLearnMore.title": "Find out more!", + "walkthrough.step.python.dataScienceLearnMore.description": "📒 Take a look into the [Jupyter extension](command:workbench.extensions.search?\"ms-toolsai.jupyter\") features, by looking for \"Jupyter\" in the [Command Palette](command:workbench.action.showCommands). \n 🏃🏻 Find out more features in our [Tutorials](https://aka.ms/AAdjzpd). \n[Learn more](https://aka.ms/AAdar6q)", + "walkthrough.step.python.createPythonFile.altText": "Open a Python file or a folder with a Python project.", + "walkthrough.step.python.selectInterpreter.altText": "Selecting a Python interpreter from the status bar", + "walkthrough.step.python.createEnvironment.altText": "Creating a Python environment from the Command Palette", + "walkthrough.step.python.runAndDebug.altText": "How to run and debug in VS Code with F5 or the play button on the top right.", + "walkthrough.step.python.learnMoreWithDS.altText": "Image representing our documentation page and mailing list resources.", + "walkthrough.step.python.installJupyterExt.altText": "Creating a new Jupyter notebook", + "walkthrough.step.python.createNewNotebook.altText": "Creating a new Jupyter notebook", + "walkthrough.step.python.openInteractiveWindow.altText": "Opening Python interactive window", + "walkthrough.step.python.dataScienceLearnMore.altText": "Image representing our documentation page and mailing list resources." } diff --git a/pythonFiles/create_microvenv.py b/pythonFiles/create_microvenv.py index 3dfd554a3012..10eae38ab977 100644 --- a/pythonFiles/create_microvenv.py +++ b/pythonFiles/create_microvenv.py @@ -6,7 +6,6 @@ import pathlib import subprocess import sys -import urllib.request as url_lib from typing import Optional, Sequence VENV_NAME = ".venv" @@ -29,13 +28,6 @@ def run_process(args: Sequence[str], error_message: str) -> None: def parse_args(argv: Sequence[str]) -> argparse.Namespace: parser = argparse.ArgumentParser() - parser.add_argument( - "--install-pip", - action="store_true", - default=False, - help="Install pip into the virtual environment.", - ) - parser.add_argument( "--name", default=VENV_NAME, @@ -54,31 +46,6 @@ def create_microvenv(name: str): ) -def download_pip_pyz(name: str): - url = "https://bootstrap.pypa.io/pip/pip.pyz" - print("CREATE_MICROVENV.DOWNLOADING_PIP") - - try: - with url_lib.urlopen(url) as response: - pip_pyz_path = os.fspath(CWD / name / "pip.pyz") - with open(pip_pyz_path, "wb") as out_file: - data = response.read() - out_file.write(data) - out_file.flush() - except Exception: - raise MicroVenvError("CREATE_MICROVENV.DOWNLOAD_PIP_FAILED") - - -def install_pip(name: str): - pip_pyz_path = os.fspath(CWD / name / "pip.pyz") - executable = os.fspath(CWD / name / "bin" / "python") - print("CREATE_MICROVENV.INSTALLING_PIP") - run_process( - [executable, pip_pyz_path, "install", "pip"], - "CREATE_MICROVENV.INSTALL_PIP_FAILED", - ) - - def main(argv: Optional[Sequence[str]] = None) -> None: if argv is None: argv = [] @@ -88,10 +55,6 @@ def main(argv: Optional[Sequence[str]] = None) -> None: create_microvenv(args.name) print("CREATE_MICROVENV.CREATED_MICROVENV") - if args.install_pip: - download_pip_pyz(args.name) - install_pip(args.name) - if __name__ == "__main__": main(sys.argv[1:]) diff --git a/pythonFiles/create_venv.py b/pythonFiles/create_venv.py index 8ebafdfca32c..749c36f8088b 100644 --- a/pythonFiles/create_venv.py +++ b/pythonFiles/create_venv.py @@ -7,6 +7,7 @@ import pathlib import subprocess import sys +import urllib.request as url_lib from typing import List, Optional, Sequence, Union VENV_NAME = ".venv" @@ -126,51 +127,92 @@ def add_gitignore(name: str) -> None: f.write("*") +def download_pip_pyz(name: str): + url = "https://bootstrap.pypa.io/pip/pip.pyz" + print("CREATE_VENV.DOWNLOADING_PIP") + + try: + with url_lib.urlopen(url) as response: + pip_pyz_path = os.fspath(CWD / name / "pip.pyz") + with open(pip_pyz_path, "wb") as out_file: + data = response.read() + out_file.write(data) + out_file.flush() + except Exception: + raise VenvError("CREATE_VENV.DOWNLOAD_PIP_FAILED") + + +def install_pip(name: str): + pip_pyz_path = os.fspath(CWD / name / "pip.pyz") + executable = get_venv_path(name) + print("CREATE_VENV.INSTALLING_PIP") + run_process( + [executable, pip_pyz_path, "install", "pip"], + "CREATE_VENV.INSTALL_PIP_FAILED", + ) + + def main(argv: Optional[Sequence[str]] = None) -> None: if argv is None: argv = [] args = parse_args(argv) use_micro_venv = False - if not is_installed("venv"): + venv_installed = is_installed("venv") + pip_installed = is_installed("pip") + ensure_pip_installed = is_installed("ensurepip") + + if not venv_installed: if sys.platform == "win32": raise VenvError("CREATE_VENV.VENV_NOT_FOUND") else: use_micro_venv = True - pip_installed = is_installed("pip") - deps_needed = args.requirements or args.extras or args.toml - if deps_needed and not pip_installed and not use_micro_venv: - raise VenvError("CREATE_VENV.PIP_NOT_FOUND") - if venv_exists(args.name): + # A virtual environment with same name exists. + # We will use the existing virtual environment. venv_path = get_venv_path(args.name) print(f"EXISTING_VENV:{venv_path}") else: if use_micro_venv: + # `venv` was not found but on this platform we can use `microvenv` run_process( [ sys.executable, os.fspath(MICROVENV_SCRIPT_PATH), - "--install-pip", "--name", args.name, ], "CREATE_VENV.MICROVENV_FAILED_CREATION", ) - pip_installed = True + elif not pip_installed or not ensure_pip_installed: + # `venv` was found but `pip` or `ensurepip` was not found. + # We create a venv without `pip` in it. We will later install `pip`. + run_process( + [sys.executable, "-m", "venv", "--without-pip", args.name], + "CREATE_VENV.VENV_FAILED_CREATION", + ) else: + # Both `venv` and `pip` were found. So create a .venv normally run_process( [sys.executable, "-m", "venv", args.name], "CREATE_VENV.VENV_FAILED_CREATION", ) + venv_path = get_venv_path(args.name) print(f"CREATED_VENV:{venv_path}") + if args.git_ignore: add_gitignore(args.name) - if pip_installed: + # At this point we have a .venv. Now we handle installing `pip`. + if pip_installed and ensure_pip_installed: + # We upgrade pip if it is already installed. upgrade_pip(venv_path) + else: + # `pip` was not found, so we download it and install it. + download_pip_pyz(args.name) + install_pip(args.name) if args.toml: print(f"VENV_INSTALLING_PYPROJECT: {args.toml}") diff --git a/pythonFiles/tests/pytestadapter/.data/double_nested_folder/nested_folder_one/nested_folder_two/test_nest.py b/pythonFiles/tests/pytestadapter/.data/double_nested_folder/nested_folder_one/nested_folder_two/test_nest.py new file mode 100644 index 000000000000..9ac9f7017f87 --- /dev/null +++ b/pythonFiles/tests/pytestadapter/.data/double_nested_folder/nested_folder_one/nested_folder_two/test_nest.py @@ -0,0 +1,8 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + + +# This test's id is double_nested_folder/nested_folder_one/nested_folder_two/test_nest.py::test_function. +# This test passes. +def test_function(): # test_marker--test_function + assert 1 == 1 diff --git a/pythonFiles/tests/pytestadapter/.data/dual_level_nested_folder/nested_folder_one/test_bottom_folder.py b/pythonFiles/tests/pytestadapter/.data/dual_level_nested_folder/nested_folder_one/test_bottom_folder.py new file mode 100644 index 000000000000..59738aeba37f --- /dev/null +++ b/pythonFiles/tests/pytestadapter/.data/dual_level_nested_folder/nested_folder_one/test_bottom_folder.py @@ -0,0 +1,14 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + + +# This test's id is dual_level_nested_folder/nested_folder_one/test_bottom_folder.py::test_bottom_function_t. +# This test passes. +def test_bottom_function_t(): # test_marker--test_bottom_function_t + assert True + + +# This test's id is dual_level_nested_folder/nested_folder_one/test_bottom_folder.py::test_bottom_function_f. +# This test fails. +def test_bottom_function_f(): # test_marker--test_bottom_function_f + assert False diff --git a/pythonFiles/tests/pytestadapter/.data/dual_level_nested_folder/test_top_folder.py b/pythonFiles/tests/pytestadapter/.data/dual_level_nested_folder/test_top_folder.py new file mode 100644 index 000000000000..010c54cf4461 --- /dev/null +++ b/pythonFiles/tests/pytestadapter/.data/dual_level_nested_folder/test_top_folder.py @@ -0,0 +1,14 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + + +# This test's id is dual_level_nested_folder/test_top_folder.py::test_top_function_t. +# This test passes. +def test_top_function_t(): # test_marker--test_top_function_t + assert True + + +# This test's id is dual_level_nested_folder/test_top_folder.py::test_top_function_f. +# This test fails. +def test_top_function_f(): # test_marker--test_top_function_f + assert False diff --git a/pythonFiles/tests/pytestadapter/.data/empty_discovery.py b/pythonFiles/tests/pytestadapter/.data/empty_discovery.py new file mode 100644 index 000000000000..5f4ea27aec7f --- /dev/null +++ b/pythonFiles/tests/pytestadapter/.data/empty_discovery.py @@ -0,0 +1,7 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + + +# This file has no tests in it; the discovery will return an empty list of tests. +def function_function(string): + return string diff --git a/pythonFiles/tests/pytestadapter/.data/error_parametrize_discovery.py b/pythonFiles/tests/pytestadapter/.data/error_parametrize_discovery.py new file mode 100644 index 000000000000..8e48224edf3b --- /dev/null +++ b/pythonFiles/tests/pytestadapter/.data/error_parametrize_discovery.py @@ -0,0 +1,10 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +import pytest + + +# This test has an error which will appear on pytest discovery. +# This error is intentional and is meant to test pytest discovery error handling. +@pytest.mark.parametrize("actual,expected", [("3+5", 8), ("2+4", 6), ("6*9", 42)]) +def test_function(): + assert True diff --git a/pythonFiles/tests/pytestadapter/.data/error_syntax_discovery.txt b/pythonFiles/tests/pytestadapter/.data/error_syntax_discovery.txt new file mode 100644 index 000000000000..78627fffb351 --- /dev/null +++ b/pythonFiles/tests/pytestadapter/.data/error_syntax_discovery.txt @@ -0,0 +1,7 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +# This test has a syntax error. +# This error is intentional and is meant to test pytest discovery error handling. +def test_function() + assert True diff --git a/pythonFiles/tests/pytestadapter/.data/parametrize_tests.py b/pythonFiles/tests/pytestadapter/.data/parametrize_tests.py new file mode 100644 index 000000000000..9421e0cc0691 --- /dev/null +++ b/pythonFiles/tests/pytestadapter/.data/parametrize_tests.py @@ -0,0 +1,10 @@ +import pytest + + +# Testing pytest with parametrized tests. The first two pass, the third fails. +# The tests ids are parametrize_tests.py::test_adding[3+5-8] and so on. +@pytest.mark.parametrize( # test_marker--test_adding + "actual, expected", [("3+5", 8), ("2+4", 6), ("6+9", 16)] +) +def test_adding(actual, expected): + assert eval(actual) == expected diff --git a/pythonFiles/tests/pytestadapter/.data/simple_pytest.py b/pythonFiles/tests/pytestadapter/.data/simple_pytest.py new file mode 100644 index 000000000000..9f9bfb014f3d --- /dev/null +++ b/pythonFiles/tests/pytestadapter/.data/simple_pytest.py @@ -0,0 +1,7 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + + +# This test passes. +def test_function(): # test_marker--test_function + assert 1 == 1 diff --git a/pythonFiles/tests/pytestadapter/.data/text_docstring.txt b/pythonFiles/tests/pytestadapter/.data/text_docstring.txt new file mode 100644 index 000000000000..b29132c10b57 --- /dev/null +++ b/pythonFiles/tests/pytestadapter/.data/text_docstring.txt @@ -0,0 +1,4 @@ +This is a doctest test which passes #test_marker--text_docstring.txt +>>> x = 3 +>>> x +3 diff --git a/pythonFiles/tests/pytestadapter/.data/unittest_folder/test_add.py b/pythonFiles/tests/pytestadapter/.data/unittest_folder/test_add.py new file mode 100644 index 000000000000..a96c7f2fa392 --- /dev/null +++ b/pythonFiles/tests/pytestadapter/.data/unittest_folder/test_add.py @@ -0,0 +1,21 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +import unittest + + +def add(a, b): + return a + b + + +class TestAddFunction(unittest.TestCase): + # This test's id is unittest_folder/test_add.py::TestAddFunction::test_add_positive_numbers. + # This test passes. + def test_add_positive_numbers(self): # test_marker--test_add_positive_numbers + result = add(2, 3) + self.assertEqual(result, 5) + + # This test's id is unittest_folder/test_add.py::TestAddFunction::test_add_negative_numbers. + # This test passes. + def test_add_negative_numbers(self): # test_marker--test_add_negative_numbers + result = add(-2, -3) + self.assertEqual(result, -5) diff --git a/pythonFiles/tests/pytestadapter/.data/unittest_folder/test_subtract.py b/pythonFiles/tests/pytestadapter/.data/unittest_folder/test_subtract.py new file mode 100644 index 000000000000..087e5140def4 --- /dev/null +++ b/pythonFiles/tests/pytestadapter/.data/unittest_folder/test_subtract.py @@ -0,0 +1,26 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +import unittest + + +def subtract(a, b): + return a - b + + +class TestSubtractFunction(unittest.TestCase): + # This test's id is unittest_folder/test_subtract.py::TestSubtractFunction::test_subtract_positive_numbers. + # This test passes. + def test_subtract_positive_numbers( # test_marker--test_subtract_positive_numbers + self, + ): + result = subtract(5, 3) + self.assertEqual(result, 2) + + # This test's id is unittest_folder/test_subtract.py::TestSubtractFunction::test_subtract_negative_numbers. + # This test passes. + def test_subtract_negative_numbers( # test_marker--test_subtract_negative_numbers + self, + ): + result = subtract(-2, -3) + # This is intentional to test assertion failures + self.assertEqual(result, 100000) diff --git a/pythonFiles/tests/pytestadapter/.data/unittest_pytest_same_file.py b/pythonFiles/tests/pytestadapter/.data/unittest_pytest_same_file.py new file mode 100644 index 000000000000..ac66779b9cbe --- /dev/null +++ b/pythonFiles/tests/pytestadapter/.data/unittest_pytest_same_file.py @@ -0,0 +1,17 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import unittest + + +class TestExample(unittest.TestCase): + # This test's id is unittest_pytest_same_file.py::TestExample::test_true_unittest. + # Test type is unittest and this test passes. + def test_true_unittest(self): # test_marker--test_true_unittest + assert True + + +# This test's id is unittest_pytest_same_file.py::test_true_pytest. +# Test type is pytest and this test passes. +def test_true_pytest(): # test_marker--test_true_pytest + assert True diff --git a/pythonFiles/tests/pytestadapter/__init__.py b/pythonFiles/tests/pytestadapter/__init__.py new file mode 100644 index 000000000000..5b7f7a925cc0 --- /dev/null +++ b/pythonFiles/tests/pytestadapter/__init__.py @@ -0,0 +1,2 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. diff --git a/pythonFiles/tests/pytestadapter/expected_discovery_test_output.py b/pythonFiles/tests/pytestadapter/expected_discovery_test_output.py new file mode 100644 index 000000000000..8e96d109ba78 --- /dev/null +++ b/pythonFiles/tests/pytestadapter/expected_discovery_test_output.py @@ -0,0 +1,475 @@ +import os +import pathlib + +from .helpers import TEST_DATA_PATH, find_test_line_number + +# This file contains the expected output dictionaries for tests discovery and is used in test_discovery.py. + +# This is the expected output for the empty_discovery.py file. +# └── +TEST_DATA_PATH_STR = os.fspath(TEST_DATA_PATH) +empty_discovery_pytest_expected_output = { + "name": ".data", + "path": TEST_DATA_PATH_STR, + "type_": "folder", + "children": [], + "id_": TEST_DATA_PATH_STR, +} + +# This is the expected output for the simple_pytest.py file. +# └── simple_pytest.py +# └── test_function +simple_test_file_path = os.fspath(TEST_DATA_PATH / "simple_pytest.py") +simple_discovery_pytest_expected_output = { + "name": ".data", + "path": TEST_DATA_PATH_STR, + "type_": "folder", + "children": [ + { + "name": "simple_pytest.py", + "path": simple_test_file_path, + "type_": "file", + "id_": simple_test_file_path, + "children": [ + { + "name": "test_function", + "path": simple_test_file_path, + "lineno": find_test_line_number( + "test_function", + simple_test_file_path, + ), + "type_": "test", + "id_": "simple_pytest.py::test_function", + "runID": "simple_pytest.py::test_function", + } + ], + } + ], + "id_": TEST_DATA_PATH_STR, +} + +# This is the expected output for the unittest_pytest_same_file.py file. +# ├── unittest_pytest_same_file.py +# ├── TestExample +# │ └── test_true_unittest +# └── test_true_pytest +unit_pytest_same_file_path = os.fspath(TEST_DATA_PATH / "unittest_pytest_same_file.py") +unit_pytest_same_file_discovery_expected_output = { + "name": ".data", + "path": TEST_DATA_PATH_STR, + "type_": "folder", + "children": [ + { + "name": "unittest_pytest_same_file.py", + "path": unit_pytest_same_file_path, + "type_": "file", + "id_": unit_pytest_same_file_path, + "children": [ + { + "name": "TestExample", + "path": unit_pytest_same_file_path, + "type_": "class", + "children": [ + { + "name": "test_true_unittest", + "path": unit_pytest_same_file_path, + "lineno": find_test_line_number( + "test_true_unittest", + unit_pytest_same_file_path, + ), + "type_": "test", + "id_": "unittest_pytest_same_file.py::TestExample::test_true_unittest", + "runID": "unittest_pytest_same_file.py::TestExample::test_true_unittest", + } + ], + "id_": "unittest_pytest_same_file.py::TestExample", + }, + { + "name": "test_true_pytest", + "path": unit_pytest_same_file_path, + "lineno": find_test_line_number( + "test_true_pytest", + unit_pytest_same_file_path, + ), + "type_": "test", + "id_": "unittest_pytest_same_file.py::test_true_pytest", + "runID": "unittest_pytest_same_file.py::test_true_pytest", + }, + ], + } + ], + "id_": TEST_DATA_PATH_STR, +} + +# This is the expected output for the unittest_folder tests +# └── unittest_folder +# ├── test_add.py +# │ └── TestAddFunction +# │ ├── test_add_negative_numbers +# │ └── test_add_positive_numbers +# └── test_subtract.py +# └── TestSubtractFunction +# ├── test_subtract_negative_numbers +# └── test_subtract_positive_numbers +unittest_folder_path = os.fspath(TEST_DATA_PATH / "unittest_folder") +test_add_path = os.fspath(TEST_DATA_PATH / "unittest_folder" / "test_add.py") +test_subtract_path = os.fspath(TEST_DATA_PATH / "unittest_folder" / "test_subtract.py") +unittest_folder_discovery_expected_output = { + "name": ".data", + "path": TEST_DATA_PATH_STR, + "type_": "folder", + "children": [ + { + "name": "unittest_folder", + "path": unittest_folder_path, + "type_": "folder", + "id_": unittest_folder_path, + "children": [ + { + "name": "test_add.py", + "path": test_add_path, + "type_": "file", + "id_": test_add_path, + "children": [ + { + "name": "TestAddFunction", + "path": test_add_path, + "type_": "class", + "children": [ + { + "name": "test_add_negative_numbers", + "path": test_add_path, + "lineno": find_test_line_number( + "test_add_negative_numbers", + test_add_path, + ), + "type_": "test", + "id_": "unittest_folder/test_add.py::TestAddFunction::test_add_negative_numbers", + "runID": "unittest_folder/test_add.py::TestAddFunction::test_add_negative_numbers", + }, + { + "name": "test_add_positive_numbers", + "path": test_add_path, + "lineno": find_test_line_number( + "test_add_positive_numbers", + test_add_path, + ), + "type_": "test", + "id_": "unittest_folder/test_add.py::TestAddFunction::test_add_positive_numbers", + "runID": "unittest_folder/test_add.py::TestAddFunction::test_add_positive_numbers", + }, + ], + "id_": "unittest_folder/test_add.py::TestAddFunction", + } + ], + }, + { + "name": "test_subtract.py", + "path": test_subtract_path, + "type_": "file", + "id_": test_subtract_path, + "children": [ + { + "name": "TestSubtractFunction", + "path": test_subtract_path, + "type_": "class", + "children": [ + { + "name": "test_subtract_negative_numbers", + "path": test_subtract_path, + "lineno": find_test_line_number( + "test_subtract_negative_numbers", + test_subtract_path, + ), + "type_": "test", + "id_": "unittest_folder/test_subtract.py::TestSubtractFunction::test_subtract_negative_numbers", + "runID": "unittest_folder/test_subtract.py::TestSubtractFunction::test_subtract_negative_numbers", + }, + { + "name": "test_subtract_positive_numbers", + "path": test_subtract_path, + "lineno": find_test_line_number( + "test_subtract_positive_numbers", + test_subtract_path, + ), + "type_": "test", + "id_": "unittest_folder/test_subtract.py::TestSubtractFunction::test_subtract_positive_numbers", + "runID": "unittest_folder/test_subtract.py::TestSubtractFunction::test_subtract_positive_numbers", + }, + ], + "id_": "unittest_folder/test_subtract.py::TestSubtractFunction", + } + ], + }, + ], + } + ], + "id_": TEST_DATA_PATH_STR, +} + +# This is the expected output for the dual_level_nested_folder tests +# └── dual_level_nested_folder +# └── test_top_folder.py +# └── test_top_function_t +# └── test_top_function_f +# └── nested_folder_one +# └── test_bottom_folder.py +# └── test_bottom_function_t +# └── test_bottom_function_f +dual_level_nested_folder_path = os.fspath(TEST_DATA_PATH / "dual_level_nested_folder") +test_top_folder_path = os.fspath( + TEST_DATA_PATH / "dual_level_nested_folder" / "test_top_folder.py" +) +test_nested_folder_one_path = os.fspath( + TEST_DATA_PATH / "dual_level_nested_folder" / "nested_folder_one" +) +test_bottom_folder_path = os.fspath( + TEST_DATA_PATH + / "dual_level_nested_folder" + / "nested_folder_one" + / "test_bottom_folder.py" +) + +dual_level_nested_folder_expected_output = { + "name": ".data", + "path": TEST_DATA_PATH_STR, + "type_": "folder", + "children": [ + { + "name": "dual_level_nested_folder", + "path": dual_level_nested_folder_path, + "type_": "folder", + "id_": dual_level_nested_folder_path, + "children": [ + { + "name": "test_top_folder.py", + "path": test_top_folder_path, + "type_": "file", + "id_": test_top_folder_path, + "children": [ + { + "name": "test_top_function_t", + "path": test_top_folder_path, + "lineno": find_test_line_number( + "test_top_function_t", + test_top_folder_path, + ), + "type_": "test", + "id_": "dual_level_nested_folder/test_top_folder.py::test_top_function_t", + "runID": "dual_level_nested_folder/test_top_folder.py::test_top_function_t", + }, + { + "name": "test_top_function_f", + "path": test_top_folder_path, + "lineno": find_test_line_number( + "test_top_function_f", + test_top_folder_path, + ), + "type_": "test", + "id_": "dual_level_nested_folder/test_top_folder.py::test_top_function_f", + "runID": "dual_level_nested_folder/test_top_folder.py::test_top_function_f", + }, + ], + }, + { + "name": "nested_folder_one", + "path": test_nested_folder_one_path, + "type_": "folder", + "id_": test_nested_folder_one_path, + "children": [ + { + "name": "test_bottom_folder.py", + "path": test_bottom_folder_path, + "type_": "file", + "id_": test_bottom_folder_path, + "children": [ + { + "name": "test_bottom_function_t", + "path": test_bottom_folder_path, + "lineno": find_test_line_number( + "test_bottom_function_t", + test_bottom_folder_path, + ), + "type_": "test", + "id_": "dual_level_nested_folder/nested_folder_one/test_bottom_folder.py::test_bottom_function_t", + "runID": "dual_level_nested_folder/nested_folder_one/test_bottom_folder.py::test_bottom_function_t", + }, + { + "name": "test_bottom_function_f", + "path": test_bottom_folder_path, + "lineno": find_test_line_number( + "test_bottom_function_f", + test_bottom_folder_path, + ), + "type_": "test", + "id_": "dual_level_nested_folder/nested_folder_one/test_bottom_folder.py::test_bottom_function_f", + "runID": "dual_level_nested_folder/nested_folder_one/test_bottom_folder.py::test_bottom_function_f", + }, + ], + } + ], + }, + ], + } + ], + "id_": TEST_DATA_PATH_STR, +} + +# This is the expected output for the double_nested_folder tests. +# └── double_nested_folder +# └── nested_folder_one +# └── nested_folder_two +# └── test_nest.py +# └── test_function +double_nested_folder_path = os.fspath(TEST_DATA_PATH / "double_nested_folder") +double_nested_folder_one_path = os.fspath( + TEST_DATA_PATH / "double_nested_folder" / "nested_folder_one" +) +double_nested_folder_two_path = os.fspath( + TEST_DATA_PATH / "double_nested_folder" / "nested_folder_one" / "nested_folder_two" +) +double_nested_test_nest_path = os.fspath( + TEST_DATA_PATH + / "double_nested_folder" + / "nested_folder_one" + / "nested_folder_two" + / "test_nest.py" +) +double_nested_folder_expected_output = { + "name": ".data", + "path": TEST_DATA_PATH_STR, + "type_": "folder", + "children": [ + { + "name": "double_nested_folder", + "path": double_nested_folder_path, + "type_": "folder", + "id_": double_nested_folder_path, + "children": [ + { + "name": "nested_folder_one", + "path": double_nested_folder_one_path, + "type_": "folder", + "id_": double_nested_folder_one_path, + "children": [ + { + "name": "nested_folder_two", + "path": double_nested_folder_two_path, + "type_": "folder", + "id_": double_nested_folder_two_path, + "children": [ + { + "name": "test_nest.py", + "path": double_nested_test_nest_path, + "type_": "file", + "id_": double_nested_test_nest_path, + "children": [ + { + "name": "test_function", + "path": double_nested_test_nest_path, + "lineno": find_test_line_number( + "test_function", + double_nested_test_nest_path, + ), + "type_": "test", + "id_": "double_nested_folder/nested_folder_one/nested_folder_two/test_nest.py::test_function", + "runID": "double_nested_folder/nested_folder_one/nested_folder_two/test_nest.py::test_function", + } + ], + } + ], + } + ], + } + ], + } + ], + "id_": TEST_DATA_PATH_STR, +} + +# This is the expected output for the nested_folder tests. +# └── parametrize_tests.py +# └── test_adding[3+5-8] +# └── test_adding[2+4-6] +# └── test_adding[6+9-16] +parameterize_tests_path = os.fspath(TEST_DATA_PATH / "parametrize_tests.py") +parametrize_tests_expected_output = { + "name": ".data", + "path": TEST_DATA_PATH_STR, + "type_": "folder", + "children": [ + { + "name": "parametrize_tests.py", + "path": parameterize_tests_path, + "type_": "file", + "id_": parameterize_tests_path, + "children": [ + { + "name": "test_adding[3+5-8]", + "path": parameterize_tests_path, + "lineno": find_test_line_number( + "test_adding[3+5-8]", + parameterize_tests_path, + ), + "type_": "test", + "id_": "parametrize_tests.py::test_adding[3+5-8]", + "runID": "parametrize_tests.py::test_adding[3+5-8]", + }, + { + "name": "test_adding[2+4-6]", + "path": parameterize_tests_path, + "lineno": find_test_line_number( + "test_adding[2+4-6]", + parameterize_tests_path, + ), + "type_": "test", + "id_": "parametrize_tests.py::test_adding[2+4-6]", + "runID": "parametrize_tests.py::test_adding[2+4-6]", + }, + { + "name": "test_adding[6+9-16]", + "path": parameterize_tests_path, + "lineno": find_test_line_number( + "test_adding[6+9-16]", + parameterize_tests_path, + ), + "type_": "test", + "id_": "parametrize_tests.py::test_adding[6+9-16]", + "runID": "parametrize_tests.py::test_adding[6+9-16]", + }, + ], + } + ], + "id_": TEST_DATA_PATH_STR, +} + +# This is the expected output for the text_docstring.txt tests. +# └── text_docstring.txt +text_docstring_path = os.fspath(TEST_DATA_PATH / "text_docstring.txt") +doctest_pytest_expected_output = { + "name": ".data", + "path": TEST_DATA_PATH_STR, + "type_": "folder", + "children": [ + { + "name": "text_docstring.txt", + "path": text_docstring_path, + "type_": "file", + "id_": text_docstring_path, + "children": [ + { + "name": "text_docstring.txt", + "path": text_docstring_path, + "lineno": find_test_line_number( + "text_docstring.txt", + text_docstring_path, + ), + "type_": "test", + "id_": "text_docstring.txt::text_docstring.txt", + "runID": "text_docstring.txt::text_docstring.txt", + } + ], + } + ], + "id_": TEST_DATA_PATH_STR, +} diff --git a/pythonFiles/tests/pytestadapter/expected_execution_test_output.py b/pythonFiles/tests/pytestadapter/expected_execution_test_output.py new file mode 100644 index 000000000000..a894403c7d71 --- /dev/null +++ b/pythonFiles/tests/pytestadapter/expected_execution_test_output.py @@ -0,0 +1,328 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +TEST_SUBTRACT_FUNCTION = "unittest_folder/test_subtract.py::TestSubtractFunction::" +TEST_ADD_FUNCTION = "unittest_folder/test_add.py::TestAddFunction::" +SUCCESS = "success" +FAILURE = "failure" +TEST_SUBTRACT_FUNCTION_NEGATIVE_NUMBERS_ERROR = "self = \n\n def test_subtract_negative_numbers( # test_marker--test_subtract_negative_numbers\n self,\n ):\n result = subtract(-2, -3)\n> self.assertEqual(result, 100000)\nE AssertionError: 1 != 100000\n\nunittest_folder/test_subtract.py:25: AssertionError" + +# This is the expected output for the unittest_folder execute tests +# └── unittest_folder +# ├── test_add.py +# │ └── TestAddFunction +# │ ├── test_add_negative_numbers: success +# │ └── test_add_positive_numbers: success +# └── test_subtract.py +# └── TestSubtractFunction +# ├── test_subtract_negative_numbers: failure +# └── test_subtract_positive_numbers: success +uf_execution_expected_output = { + f"{TEST_ADD_FUNCTION}test_add_negative_numbers": { + "test": f"{TEST_ADD_FUNCTION}test_add_negative_numbers", + "outcome": SUCCESS, + "message": None, + "traceback": None, + "subtest": None, + }, + f"{TEST_ADD_FUNCTION}test_add_positive_numbers": { + "test": f"{TEST_ADD_FUNCTION}test_add_positive_numbers", + "outcome": SUCCESS, + "message": None, + "traceback": None, + "subtest": None, + }, + f"{TEST_SUBTRACT_FUNCTION}test_subtract_negative_numbers": { + "test": f"{TEST_SUBTRACT_FUNCTION}test_subtract_negative_numbers", + "outcome": FAILURE, + "message": "ERROR MESSAGE", + "traceback": None, + "subtest": None, + }, + f"{TEST_SUBTRACT_FUNCTION}test_subtract_positive_numbers": { + "test": f"{TEST_SUBTRACT_FUNCTION}test_subtract_positive_numbers", + "outcome": SUCCESS, + "message": None, + "traceback": None, + "subtest": None, + }, +} + + +# This is the expected output for the unittest_folder only execute add.py tests +# └── unittest_folder +# ├── test_add.py +# │ └── TestAddFunction +# │ ├── test_add_negative_numbers: success +# │ └── test_add_positive_numbers: success +uf_single_file_expected_output = { + f"{TEST_ADD_FUNCTION}test_add_negative_numbers": { + "test": f"{TEST_ADD_FUNCTION}test_add_negative_numbers", + "outcome": SUCCESS, + "message": None, + "traceback": None, + "subtest": None, + }, + f"{TEST_ADD_FUNCTION}test_add_positive_numbers": { + "test": f"{TEST_ADD_FUNCTION}test_add_positive_numbers", + "outcome": SUCCESS, + "message": None, + "traceback": None, + "subtest": None, + }, +} + +# This is the expected output for the unittest_folder execute only signle method +# └── unittest_folder +# ├── test_add.py +# │ └── TestAddFunction +# │ └── test_add_positive_numbers: success +uf_single_method_execution_expected_output = { + f"{TEST_ADD_FUNCTION}test_add_positive_numbers": { + "test": f"{TEST_ADD_FUNCTION}test_add_positive_numbers", + "outcome": SUCCESS, + "message": None, + "traceback": None, + "subtest": None, + } +} + +# This is the expected output for the unittest_folder tests run where two tests +# run are in different files. +# └── unittest_folder +# ├── test_add.py +# │ └── TestAddFunction +# │ └── test_add_positive_numbers: success +# └── test_subtract.py +# └── TestSubtractFunction +# └── test_subtract_positive_numbers: success +uf_non_adjacent_tests_execution_expected_output = { + TEST_SUBTRACT_FUNCTION + + "test_subtract_positive_numbers": { + "test": TEST_SUBTRACT_FUNCTION + "test_subtract_positive_numbers", + "outcome": SUCCESS, + "message": None, + "traceback": None, + "subtest": None, + }, + TEST_ADD_FUNCTION + + "test_add_positive_numbers": { + "test": TEST_ADD_FUNCTION + "test_add_positive_numbers", + "outcome": SUCCESS, + "message": None, + "traceback": None, + "subtest": None, + }, +} + +# This is the expected output for the simple_pytest.py file. +# └── simple_pytest.py +# └── test_function: success +simple_execution_pytest_expected_output = { + "simple_pytest.py::test_function": { + "test": "simple_pytest.py::test_function", + "outcome": "success", + "message": None, + "traceback": None, + "subtest": None, + } +} + +# This is the expected output for the unittest_pytest_same_file.py file. +# ├── unittest_pytest_same_file.py +# ├── TestExample +# │ └── test_true_unittest: success +# └── test_true_pytest: success +unit_pytest_same_file_execution_expected_output = { + "unittest_pytest_same_file.py::TestExample::test_true_unittest": { + "test": "unittest_pytest_same_file.py::TestExample::test_true_unittest", + "outcome": "success", + "message": None, + "traceback": None, + "subtest": None, + }, + "unittest_pytest_same_file.py::test_true_pytest": { + "test": "unittest_pytest_same_file.py::test_true_pytest", + "outcome": "success", + "message": None, + "traceback": None, + "subtest": None, + }, +} + +# This is the expected output for the dual_level_nested_folder.py tests +# └── dual_level_nested_folder +# └── test_top_folder.py +# └── test_top_function_t: success +# └── test_top_function_f: failure +# └── nested_folder_one +# └── test_bottom_folder.py +# └── test_bottom_function_t: success +# └── test_bottom_function_f: failure +dual_level_nested_folder_execution_expected_output = { + "dual_level_nested_folder/test_top_folder.py::test_top_function_t": { + "test": "dual_level_nested_folder/test_top_folder.py::test_top_function_t", + "outcome": "success", + "message": None, + "traceback": None, + "subtest": None, + }, + "dual_level_nested_folder/test_top_folder.py::test_top_function_f": { + "test": "dual_level_nested_folder/test_top_folder.py::test_top_function_f", + "outcome": "failure", + "message": "ERROR MESSAGE", + "traceback": None, + "subtest": None, + }, + "dual_level_nested_folder/nested_folder_one/test_bottom_folder.py::test_bottom_function_t": { + "test": "dual_level_nested_folder/nested_folder_one/test_bottom_folder.py::test_bottom_function_t", + "outcome": "success", + "message": None, + "traceback": None, + "subtest": None, + }, + "dual_level_nested_folder/nested_folder_one/test_bottom_folder.py::test_bottom_function_f": { + "test": "dual_level_nested_folder/nested_folder_one/test_bottom_folder.py::test_bottom_function_f", + "outcome": "failure", + "message": "ERROR MESSAGE", + "traceback": None, + "subtest": None, + }, +} + +# This is the expected output for the nested_folder tests. +# └── nested_folder_one +# └── nested_folder_two +# └── test_nest.py +# └── test_function: success +double_nested_folder_expected_execution_output = { + "double_nested_folder/nested_folder_one/nested_folder_two/test_nest.py::test_function": { + "test": "double_nested_folder/nested_folder_one/nested_folder_two/test_nest.py::test_function", + "outcome": "success", + "message": None, + "traceback": None, + "subtest": None, + } +} + +# This is the expected output for the nested_folder tests. +# └── parametrize_tests.py +# └── test_adding[3+5-8]: success +# └── test_adding[2+4-6]: success +# └── test_adding[6+9-16]: failure +parametrize_tests_expected_execution_output = { + "parametrize_tests.py::test_adding[3+5-8]": { + "test": "parametrize_tests.py::test_adding[3+5-8]", + "outcome": "success", + "message": None, + "traceback": None, + "subtest": None, + }, + "parametrize_tests.py::test_adding[2+4-6]": { + "test": "parametrize_tests.py::test_adding[2+4-6]", + "outcome": "success", + "message": None, + "traceback": None, + "subtest": None, + }, + "parametrize_tests.py::test_adding[6+9-16]": { + "test": "parametrize_tests.py::test_adding[6+9-16]", + "outcome": "failure", + "message": "ERROR MESSAGE", + "traceback": None, + "subtest": None, + }, +} + +# This is the expected output for the single parameterized tests. +# └── parametrize_tests.py +# └── test_adding[3+5-8]: success +single_parametrize_tests_expected_execution_output = { + "parametrize_tests.py::test_adding[3+5-8]": { + "test": "parametrize_tests.py::test_adding[3+5-8]", + "outcome": "success", + "message": None, + "traceback": None, + "subtest": None, + }, +} + +# This is the expected output for the single parameterized tests. +# └── text_docstring.txt +# └── text_docstring: success +doctest_pytest_expected_execution_output = { + "text_docstring.txt::text_docstring.txt": { + "test": "text_docstring.txt::text_docstring.txt", + "outcome": "success", + "message": None, + "traceback": None, + "subtest": None, + } +} + +# Will run all tests in the cwd that fit the test file naming pattern. +no_test_ids_pytest_execution_expected_output = { + "double_nested_folder/nested_folder_one/nested_folder_two/test_nest.py::test_function": { + "test": "double_nested_folder/nested_folder_one/nested_folder_two/test_nest.py::test_function", + "outcome": "success", + "message": None, + "traceback": None, + "subtest": None, + }, + "dual_level_nested_folder/test_top_folder.py::test_top_function_t": { + "test": "dual_level_nested_folder/test_top_folder.py::test_top_function_t", + "outcome": "success", + "message": None, + "traceback": None, + "subtest": None, + }, + "dual_level_nested_folder/test_top_folder.py::test_top_function_f": { + "test": "dual_level_nested_folder/test_top_folder.py::test_top_function_f", + "outcome": "failure", + "message": "ERROR MESSAGE", + "traceback": None, + "subtest": None, + }, + "dual_level_nested_folder/nested_folder_one/test_bottom_folder.py::test_bottom_function_t": { + "test": "dual_level_nested_folder/nested_folder_one/test_bottom_folder.py::test_bottom_function_t", + "outcome": "success", + "message": None, + "traceback": None, + "subtest": None, + }, + "dual_level_nested_folder/nested_folder_one/test_bottom_folder.py::test_bottom_function_f": { + "test": "dual_level_nested_folder/nested_folder_one/test_bottom_folder.py::test_bottom_function_f", + "outcome": "failure", + "message": "ERROR MESSAGE", + "traceback": None, + "subtest": None, + }, + "unittest_folder/test_add.py::TestAddFunction::test_add_negative_numbers": { + "test": "unittest_folder/test_add.py::TestAddFunction::test_add_negative_numbers", + "outcome": "success", + "message": None, + "traceback": None, + "subtest": None, + }, + "unittest_folder/test_add.py::TestAddFunction::test_add_positive_numbers": { + "test": "unittest_folder/test_add.py::TestAddFunction::test_add_positive_numbers", + "outcome": "success", + "message": None, + "traceback": None, + "subtest": None, + }, + "unittest_folder/test_subtract.py::TestSubtractFunction::test_subtract_negative_numbers": { + "test": "unittest_folder/test_subtract.py::TestSubtractFunction::test_subtract_negative_numbers", + "outcome": "failure", + "message": "ERROR MESSAGE", + "traceback": None, + "subtest": None, + }, + "unittest_folder/test_subtract.py::TestSubtractFunction::test_subtract_positive_numbers": { + "test": "unittest_folder/test_subtract.py::TestSubtractFunction::test_subtract_positive_numbers", + "outcome": "success", + "message": None, + "traceback": None, + "subtest": None, + }, +} diff --git a/pythonFiles/tests/pytestadapter/helpers.py b/pythonFiles/tests/pytestadapter/helpers.py new file mode 100644 index 000000000000..b078439f6eac --- /dev/null +++ b/pythonFiles/tests/pytestadapter/helpers.py @@ -0,0 +1,161 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import contextlib +import io +import json +import os +import pathlib +import random +import socket +import subprocess +import sys +import uuid +from typing import Any, Dict, List, Optional, Union + +TEST_DATA_PATH = pathlib.Path(__file__).parent / ".data" +from typing_extensions import TypedDict + + +@contextlib.contextmanager +def test_output_file(root: pathlib.Path, ext: str = ".txt"): + """Creates a temporary python file with a random name.""" + basename = ( + "".join(random.choice("abcdefghijklmnopqrstuvwxyz") for _ in range(9)) + ext + ) + fullpath = root / basename + try: + fullpath.write_text("", encoding="utf-8") + yield fullpath + finally: + os.unlink(str(fullpath)) + + +def create_server( + host: str = "127.0.0.1", + port: int = 0, + backlog: int = socket.SOMAXCONN, + timeout: int = 1000, +) -> socket.socket: + """Return a local server socket listening on the given port.""" + server: socket.socket = _new_sock() + if port: + # If binding to a specific port, make sure that the user doesn't have + # to wait until the OS times out waiting for socket in order to use + # that port again if the server or the adapter crash or are force-killed. + if sys.platform == "win32": + server.setsockopt(socket.SOL_SOCKET, socket.SO_EXCLUSIVEADDRUSE, 1) + else: + try: + server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + except (AttributeError, OSError): + pass # Not available everywhere + server.bind((host, port)) + if timeout: + server.settimeout(timeout) + server.listen(backlog) + return server + + +def _new_sock() -> socket.socket: + sock: socket.socket = socket.socket( + socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP + ) + options = [ + ("SOL_SOCKET", "SO_KEEPALIVE", 1), + ("IPPROTO_TCP", "TCP_KEEPIDLE", 1), + ("IPPROTO_TCP", "TCP_KEEPINTVL", 3), + ("IPPROTO_TCP", "TCP_KEEPCNT", 5), + ] + + for level, name, value in options: + try: + sock.setsockopt(getattr(socket, level), getattr(socket, name), value) + except (AttributeError, OSError): + pass # May not be available everywhere. + + return sock + + +CONTENT_LENGTH: str = "Content-Length:" +Env_Dict = TypedDict( + "Env_Dict", {"TEST_UUID": str, "TEST_PORT": str, "PYTHONPATH": str} +) + + +def process_rpc_json(data: str) -> Dict[str, Any]: + """Process the JSON data which comes from the server which runs the pytest discovery.""" + str_stream: io.StringIO = io.StringIO(data) + + length: int = 0 + + while True: + line: str = str_stream.readline() + if CONTENT_LENGTH.lower() in line.lower(): + length = int(line[len(CONTENT_LENGTH) :]) + break + + if not line or line.isspace(): + raise ValueError("Header does not contain Content-Length") + + while True: + line: str = str_stream.readline() + if not line or line.isspace(): + break + + raw_json: str = str_stream.read(length) + return json.loads(raw_json) + + +def runner(args: List[str]) -> Optional[Dict[str, Any]]: + """Run the pytest discovery and return the JSON data from the server.""" + process_args: List[str] = [ + sys.executable, + "-m", + "pytest", + "-p", + "vscode_pytest", + ] + args + + with test_output_file(TEST_DATA_PATH) as output_path: + env = os.environ.copy() + env.update( + { + "TEST_UUID": str(uuid.uuid4()), + "TEST_PORT": str(12345), # port is not used for tests + "PYTHONPATH": os.fspath(pathlib.Path(__file__).parent.parent.parent), + "TEST_OUTPUT_FILE": os.fspath(output_path), + } + ) + + result = subprocess.run( + process_args, + env=env, + cwd=os.fspath(TEST_DATA_PATH), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + if result.returncode != 0: + print("Subprocess Run failed with:") + print(result.stdout.decode(encoding="utf-8")) + print(result.stderr.decode(encoding="utf-8")) + + return process_rpc_json(output_path.read_text(encoding="utf-8")) + + +def find_test_line_number(test_name: str, test_file_path) -> str: + """Function which finds the correct line number for a test by looking for the "test_marker--[test_name]" string. + + The test_name is split on the "[" character to remove the parameterization information. + + Args: + test_name: The name of the test to find the line number for, will be unique per file. + test_file_path: The path to the test file where the test is located. + """ + test_file_unique_id: str = "test_marker--" + test_name.split("[")[0] + with open(test_file_path) as f: + for i, line in enumerate(f): + if test_file_unique_id in line: + return str(i + 1) + error_str: str = f"Test {test_name!r} not found on any line in {test_file_path}" + raise ValueError(error_str) diff --git a/pythonFiles/tests/pytestadapter/test_discovery.py b/pythonFiles/tests/pytestadapter/test_discovery.py new file mode 100644 index 000000000000..bb6e7255704e --- /dev/null +++ b/pythonFiles/tests/pytestadapter/test_discovery.py @@ -0,0 +1,111 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +import os +import shutil + +import pytest + +from . import expected_discovery_test_output +from .helpers import TEST_DATA_PATH, runner + + +def test_syntax_error(tmp_path): + """Test pytest discovery on a file that has a syntax error. + + Copies the contents of a .txt file to a .py file in the temporary directory + to then run pytest discovery on. + + The json should still be returned but the errors list should be present. + + Keyword arguments: + tmp_path -- pytest fixture that creates a temporary directory. + """ + # Saving some files as .txt to avoid that file displaying a syntax error for + # the extension as a whole. Instead, rename it before running this test + # in order to test the error handling. + file_path = TEST_DATA_PATH / "error_syntax_discovery.txt" + temp_dir = tmp_path / "temp_data" + temp_dir.mkdir() + p = temp_dir / "error_syntax_discovery.py" + shutil.copyfile(file_path, p) + actual = runner(["--collect-only", os.fspath(p)]) + assert actual + assert all(item in actual for item in ("status", "cwd", "error")) + assert actual["status"] == "error" + assert actual["cwd"] == os.fspath(TEST_DATA_PATH) + assert len(actual["error"]) == 2 + + +def test_parameterized_error_collect(): + """Tests pytest discovery on specific file that incorrectly uses parametrize. + + The json should still be returned but the errors list should be present. + """ + file_path_str = "error_parametrize_discovery.py" + actual = runner(["--collect-only", file_path_str]) + assert actual + assert all(item in actual for item in ("status", "cwd", "error")) + assert actual["status"] == "error" + assert actual["cwd"] == os.fspath(TEST_DATA_PATH) + assert len(actual["error"]) == 2 + + +@pytest.mark.parametrize( + "file, expected_const", + [ + ( + "parametrize_tests.py", + expected_discovery_test_output.parametrize_tests_expected_output, + ), + ( + "empty_discovery.py", + expected_discovery_test_output.empty_discovery_pytest_expected_output, + ), + ( + "simple_pytest.py", + expected_discovery_test_output.simple_discovery_pytest_expected_output, + ), + ( + "unittest_pytest_same_file.py", + expected_discovery_test_output.unit_pytest_same_file_discovery_expected_output, + ), + ( + "unittest_folder", + expected_discovery_test_output.unittest_folder_discovery_expected_output, + ), + ( + "dual_level_nested_folder", + expected_discovery_test_output.dual_level_nested_folder_expected_output, + ), + ( + "double_nested_folder", + expected_discovery_test_output.double_nested_folder_expected_output, + ), + ( + "text_docstring.txt", + expected_discovery_test_output.doctest_pytest_expected_output, + ), + ], +) +def test_pytest_collect(file, expected_const): + """ + Test to test pytest discovery on a variety of test files/ folder structures. + Uses variables from expected_discovery_test_output.py to store the expected dictionary return. + Only handles discovery and therefore already contains the arg --collect-only. + All test discovery will succeed, be in the correct cwd, and match expected test output. + + Keyword arguments: + file -- a string with the file or folder to run pytest discovery on. + expected_const -- the expected output from running pytest discovery on the file. + """ + actual = runner( + [ + "--collect-only", + os.fspath(TEST_DATA_PATH / file), + ] + ) + assert actual + assert all(item in actual for item in ("status", "cwd", "tests")) + assert actual["status"] == "success" + assert actual["cwd"] == os.fspath(TEST_DATA_PATH) + assert actual["tests"] == expected_const diff --git a/pythonFiles/tests/pytestadapter/test_execution.py b/pythonFiles/tests/pytestadapter/test_execution.py new file mode 100644 index 000000000000..8613deb96098 --- /dev/null +++ b/pythonFiles/tests/pytestadapter/test_execution.py @@ -0,0 +1,165 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +import os +import shutil + +import pytest +from tests.pytestadapter import expected_execution_test_output + +from .helpers import TEST_DATA_PATH, runner + + +def test_syntax_error_execution(tmp_path): + """Test pytest execution on a file that has a syntax error. + + Copies the contents of a .txt file to a .py file in the temporary directory + to then run pytest exeuction on. + + The json should still be returned but the errors list should be present. + + Keyword arguments: + tmp_path -- pytest fixture that creates a temporary directory. + """ + # Saving some files as .txt to avoid that file displaying a syntax error for + # the extension as a whole. Instead, rename it before running this test + # in order to test the error handling. + file_path = TEST_DATA_PATH / "error_syntax_discovery.txt" + temp_dir = tmp_path / "temp_data" + temp_dir.mkdir() + p = temp_dir / "error_syntax_discovery.py" + shutil.copyfile(file_path, p) + actual = runner(["error_syntax_discover.py::test_function"]) + assert actual + assert all(item in actual for item in ("status", "cwd", "error")) + assert actual["status"] == "error" + assert actual["cwd"] == os.fspath(TEST_DATA_PATH) + assert len(actual["error"]) == 1 + + +def test_bad_id_error_execution(): + """Test pytest discovery with a non-existent test_id. + + The json should still be returned but the errors list should be present. + """ + actual = runner(["not/a/real::test_id"]) + assert actual + assert all(item in actual for item in ("status", "cwd", "error")) + assert actual["status"] == "error" + assert actual["cwd"] == os.fspath(TEST_DATA_PATH) + assert len(actual["error"]) == 1 + + +@pytest.mark.parametrize( + "test_ids, expected_const", + [ + ( + [ + "unittest_folder/test_add.py::TestAddFunction::test_add_positive_numbers", + "unittest_folder/test_add.py::TestAddFunction::test_add_negative_numbers", + "unittest_folder/test_subtract.py::TestSubtractFunction::test_subtract_positive_numbers", + "unittest_folder/test_subtract.py::TestSubtractFunction::test_subtract_negative_numbers", + ], + expected_execution_test_output.uf_execution_expected_output, + ), + ( + [ + "unittest_folder/test_add.py::TestAddFunction::test_add_positive_numbers", + "unittest_folder/test_add.py::TestAddFunction::test_add_negative_numbers", + ], + expected_execution_test_output.uf_single_file_expected_output, + ), + ( + [ + "unittest_folder/test_add.py::TestAddFunction::test_add_positive_numbers", + ], + expected_execution_test_output.uf_single_method_execution_expected_output, + ), + ( + [ + "unittest_folder/test_add.py::TestAddFunction::test_add_positive_numbers", + "unittest_folder/test_subtract.py::TestSubtractFunction::test_subtract_positive_numbers", + ], + expected_execution_test_output.uf_non_adjacent_tests_execution_expected_output, + ), + ( + [ + "unittest_pytest_same_file.py::TestExample::test_true_unittest", + "unittest_pytest_same_file.py::test_true_pytest", + ], + expected_execution_test_output.unit_pytest_same_file_execution_expected_output, + ), + ( + [ + "dual_level_nested_folder/test_top_folder.py::test_top_function_t", + "dual_level_nested_folder/test_top_folder.py::test_top_function_f", + "dual_level_nested_folder/nested_folder_one/test_bottom_folder.py::test_bottom_function_t", + "dual_level_nested_folder/nested_folder_one/test_bottom_folder.py::test_bottom_function_f", + ], + expected_execution_test_output.dual_level_nested_folder_execution_expected_output, + ), + ( + [ + "double_nested_folder/nested_folder_one/nested_folder_two/test_nest.py::test_function" + ], + expected_execution_test_output.double_nested_folder_expected_execution_output, + ), + ( + [ + "parametrize_tests.py::test_adding[3+5-8]", + "parametrize_tests.py::test_adding[2+4-6]", + "parametrize_tests.py::test_adding[6+9-16]", + ], + expected_execution_test_output.parametrize_tests_expected_execution_output, + ), + ( + [ + "parametrize_tests.py::test_adding[3+5-8]", + ], + expected_execution_test_output.single_parametrize_tests_expected_execution_output, + ), + ( + [ + "text_docstring.txt::text_docstring.txt", + ], + expected_execution_test_output.doctest_pytest_expected_execution_output, + ), + ( + [ + "", + ], + expected_execution_test_output.no_test_ids_pytest_execution_expected_output, + ), + ], +) +def test_pytest_execution(test_ids, expected_const): + """ + Test that pytest discovery works as expected where run pytest is always successful + but the actual test results are both successes and failures.: + 1. uf_execution_expected_output: unittest tests run on multiple files. + 2. uf_single_file_expected_output: test run on a single file. + 3. uf_single_method_execution_expected_output: test run on a single method in a file. + 4. uf_non_adjacent_tests_execution_expected_output: test run on unittests in two files with single selection in test explorer. + 5. unit_pytest_same_file_execution_expected_output: test run on a file with both unittest and pytest tests. + 6. dual_level_nested_folder_execution_expected_output: test run on a file with one test file at the top level and one test file in a nested folder. + 7. double_nested_folder_expected_execution_output: test run on a double nested folder. + 8. parametrize_tests_expected_execution_output: test run on a parametrize test with 3 inputs. + 9. single_parametrize_tests_expected_execution_output: test run on single parametrize test. + 10. doctest_pytest_expected_execution_output: test run on doctest file. + 11. no_test_ids_pytest_execution_expected_output: test run with no inputted test ids. + + + Keyword arguments: + test_ids -- an array of test_ids to run. + expected_const -- a dictionary of the expected output from running pytest discovery on the files. + """ + args = test_ids + actual = runner(args) + assert actual + assert all(item in actual for item in ("status", "cwd", "result")) + assert actual["status"] == "success" + assert actual["cwd"] == os.fspath(TEST_DATA_PATH) + result_data = actual["result"] + for key in result_data: + if result_data[key]["outcome"] == "failure": + result_data[key]["message"] = "ERROR MESSAGE" + assert result_data == expected_const diff --git a/pythonFiles/tests/test_create_microvenv.py b/pythonFiles/tests/test_create_microvenv.py index 26a57dda26a1..f123052c491c 100644 --- a/pythonFiles/tests/test_create_microvenv.py +++ b/pythonFiles/tests/test_create_microvenv.py @@ -27,34 +27,3 @@ def run_process(args, error_message): create_microvenv.main() assert run_process_called == True - - -def test_create_microvenv_with_pip(): - importlib.reload(create_microvenv) - - download_pip_pyz_called = False - - def download_pip_pyz(name): - nonlocal download_pip_pyz_called - download_pip_pyz_called = True - assert name == create_microvenv.VENV_NAME - - create_microvenv.download_pip_pyz = download_pip_pyz - - run_process_called = False - - def run_process(args, error_message): - if "install" in args and "pip" in args: - nonlocal run_process_called - run_process_called = True - pip_pyz_path = os.fspath( - create_microvenv.CWD / create_microvenv.VENV_NAME / "pip.pyz" - ) - executable = os.fspath( - create_microvenv.CWD / create_microvenv.VENV_NAME / "bin" / "python" - ) - assert args == [executable, pip_pyz_path, "install", "pip"] - assert error_message == "CREATE_MICROVENV.INSTALL_PIP_FAILED" - - create_microvenv.run_process = run_process - create_microvenv.main(["--install-pip"]) diff --git a/pythonFiles/tests/test_create_venv.py b/pythonFiles/tests/test_create_venv.py index 2949a18ebd40..bebe304c13c3 100644 --- a/pythonFiles/tests/test_create_venv.py +++ b/pythonFiles/tests/test_create_venv.py @@ -19,12 +19,12 @@ def test_venv_not_installed_unix(): def run_process(args, error_message): nonlocal run_process_called - if "--install-pip" in args: + microvenv_path = os.fspath(create_venv.MICROVENV_SCRIPT_PATH) + if microvenv_path in args: run_process_called = True assert args == [ sys.executable, - os.fspath(create_venv.MICROVENV_SCRIPT_PATH), - "--install-pip", + microvenv_path, "--name", ".test_venv", ] @@ -49,20 +49,6 @@ def test_venv_not_installed_windows(): assert str(e.value) == "CREATE_VENV.VENV_NOT_FOUND" -@pytest.mark.parametrize("install", ["requirements", "toml"]) -def test_pip_not_installed(install): - importlib.reload(create_venv) - create_venv.venv_exists = lambda _n: True - create_venv.is_installed = lambda module: module != "pip" - create_venv.run_process = lambda _args, _error_message: None - with pytest.raises(create_venv.VenvError) as e: - if install == "requirements": - create_venv.main(["--requirements", "requirements-for-test.txt"]) - elif install == "toml": - create_venv.main(["--toml", "pyproject.toml", "--extras", "test"]) - assert str(e.value) == "CREATE_VENV.PIP_NOT_FOUND" - - @pytest.mark.parametrize("env_exists", ["hasEnv", "noEnv"]) @pytest.mark.parametrize("git_ignore", ["useGitIgnore", "skipGitIgnore"]) @pytest.mark.parametrize("install", ["requirements", "toml", "skipInstall"]) @@ -207,3 +193,33 @@ def run_process(args, error_message): create_venv.install_requirements(sys.executable, extras) assert actual == expected + + +def test_create_venv_missing_pip(): + importlib.reload(create_venv) + create_venv.venv_exists = lambda _n: True + create_venv.is_installed = lambda module: module != "pip" + + download_pip_pyz_called = False + + def download_pip_pyz(name): + nonlocal download_pip_pyz_called + download_pip_pyz_called = True + assert name == create_venv.VENV_NAME + + create_venv.download_pip_pyz = download_pip_pyz + + run_process_called = False + + def run_process(args, error_message): + if "install" in args and "pip" in args: + nonlocal run_process_called + run_process_called = True + pip_pyz_path = os.fspath( + create_venv.CWD / create_venv.VENV_NAME / "pip.pyz" + ) + assert args[1:] == [pip_pyz_path, "install", "pip"] + assert error_message == "CREATE_VENV.INSTALL_PIP_FAILED" + + create_venv.run_process = run_process + create_venv.main([]) diff --git a/pythonFiles/tests/unittestadapter/.data/test_fail_simple.py b/pythonFiles/tests/unittestadapter/.data/test_fail_simple.py new file mode 100644 index 000000000000..e329c3fd7003 --- /dev/null +++ b/pythonFiles/tests/unittestadapter/.data/test_fail_simple.py @@ -0,0 +1,21 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import unittest + +# Test class for the test_fail_simple test. +# The test_failed_tests function should return a dictionary with a "success" status +# and the two tests with their outcome as "failed". + +class RunFailSimple(unittest.TestCase): + """Test class for the test_fail_simple test. + + The test_failed_tests function should return a dictionary with a "success" status + and the two tests with their outcome as "failed". + """ + + def test_one_fail(self) -> None: + self.assertGreater(2, 3) + + def test_two_fail(self) -> None: + self.assertNotEqual(1, 1) diff --git a/pythonFiles/tests/unittestadapter/.data/test_subtest.py b/pythonFiles/tests/unittestadapter/.data/test_subtest.py new file mode 100644 index 000000000000..b913b8773701 --- /dev/null +++ b/pythonFiles/tests/unittestadapter/.data/test_subtest.py @@ -0,0 +1,18 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import unittest + +# Test class for the test_subtest_run test. +# The test_failed_tests function should return a dictionary that has a "success" status +# and the "result" value is a dict with 6 entries, one for each subtest. + + +class NumbersTest(unittest.TestCase): + def test_even(self): + """ + Test that numbers between 0 and 5 are all even. + """ + for i in range(0, 6): + with self.subTest(i=i): + self.assertEqual(i % 2, 0) diff --git a/pythonFiles/tests/unittestadapter/.data/test_two_classes.py b/pythonFiles/tests/unittestadapter/.data/test_two_classes.py new file mode 100644 index 000000000000..60b26706ad42 --- /dev/null +++ b/pythonFiles/tests/unittestadapter/.data/test_two_classes.py @@ -0,0 +1,20 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import unittest + +# Test class which runs for the test_multiple_ids_run test with the two class parameters. +# Both test functions will be returned in a dictionary with a "success" status, +# and the two tests with their outcome as "success". + + +class ClassOne(unittest.TestCase): + + def test_one(self) -> None: + self.assertGreater(2, 1) + +class ClassTwo(unittest.TestCase): + + def test_two(self) -> None: + self.assertGreater(2, 1) + diff --git a/pythonFiles/tests/unittestadapter/.data/two_patterns/pattern_a_test.py b/pythonFiles/tests/unittestadapter/.data/two_patterns/pattern_a_test.py new file mode 100644 index 000000000000..4f3f77e1056e --- /dev/null +++ b/pythonFiles/tests/unittestadapter/.data/two_patterns/pattern_a_test.py @@ -0,0 +1,22 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +import unittest + +# Test class for the two file pattern test. It is pattern *test.py. +# The test_ids_multiple_runs function should return a dictionary with a "success" status, +# and the two tests with their outcome as "success". + + + +class DiscoveryA(unittest.TestCase): + """Test class for the two file pattern test. It is pattern *test.py + + The test_ids_multiple_runs function should return a dictionary with a "success" status, + and the two tests with their outcome as "success". + """ + + def test_one_a(self) -> None: + self.assertGreater(2, 1) + + def test_two_a(self) -> None: + self.assertNotEqual(2, 1) \ No newline at end of file diff --git a/pythonFiles/tests/unittestadapter/.data/two_patterns/test_pattern_b.py b/pythonFiles/tests/unittestadapter/.data/two_patterns/test_pattern_b.py new file mode 100644 index 000000000000..a912699383ca --- /dev/null +++ b/pythonFiles/tests/unittestadapter/.data/two_patterns/test_pattern_b.py @@ -0,0 +1,15 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +import unittest + +# Test class for the two file pattern test. This file is pattern test*.py. +# The test_ids_multiple_runs function should return a dictionary with a "success" status, +# and the two tests with their outcome as "success". + +class DiscoveryB(unittest.TestCase): + + def test_one_b(self) -> None: + self.assertGreater(2, 1) + + def test_two_b(self) -> None: + self.assertNotEqual(2, 1) \ No newline at end of file diff --git a/pythonFiles/tests/unittestadapter/.data/unittest_folder/test_add.py b/pythonFiles/tests/unittestadapter/.data/unittest_folder/test_add.py new file mode 100644 index 000000000000..2e616077ec40 --- /dev/null +++ b/pythonFiles/tests/unittestadapter/.data/unittest_folder/test_add.py @@ -0,0 +1,22 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +import unittest + +# Test class which runs for the test_multiple_ids_run test with the two test +# files in the same folder. The cwd is set to the parent folder. This should return +# a dictionary with a "success" status and the two tests with their outcome as "success". + +def add(a, b): + return a + b + + +class TestAddFunction(unittest.TestCase): + + def test_add_positive_numbers(self): + result = add(2, 3) + self.assertEqual(result, 5) + + + def test_add_negative_numbers(self): + result = add(-2, -3) + self.assertEqual(result, -5) \ No newline at end of file diff --git a/pythonFiles/tests/unittestadapter/.data/unittest_folder/test_subtract.py b/pythonFiles/tests/unittestadapter/.data/unittest_folder/test_subtract.py new file mode 100644 index 000000000000..4028e25825d1 --- /dev/null +++ b/pythonFiles/tests/unittestadapter/.data/unittest_folder/test_subtract.py @@ -0,0 +1,21 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +import unittest + +# Test class which runs for the test_multiple_ids_run test with the two test +# files in the same folder. The cwd is set to the parent folder. This should return +# a dictionary with a "success" status and the two tests with their outcome as "success". + +def subtract(a, b): + return a - b + + +class TestSubtractFunction(unittest.TestCase): + def test_subtract_positive_numbers(self): + result = subtract(5, 3) + self.assertEqual(result, 2) + + + def test_subtract_negative_numbers(self): + result = subtract(-2, -3) + self.assertEqual(result, 1) \ No newline at end of file diff --git a/pythonFiles/tests/unittestadapter/test_execution.py b/pythonFiles/tests/unittestadapter/test_execution.py index 14ddefa48d52..7f58049a56b7 100644 --- a/pythonFiles/tests/unittestadapter/test_execution.py +++ b/pythonFiles/tests/unittestadapter/test_execution.py @@ -92,3 +92,192 @@ def test_single_ids_run() -> None: assert id_result is not None assert "outcome" in id_result assert id_result["outcome"] == "success" + + +def test_subtest_run() -> None: + """This test runs on a the test_subtest which has a single method, test_even, + that uses unittest subtest. + + The actual result of run should return a dict payload with 6 entry for the 6 subtests. + """ + id = "test_subtest.NumbersTest.test_even" + actual = run_tests( + os.fspath(TEST_DATA_PATH), [id], "test_subtest.py", None, "fake-uuid" + ) + subtests_ids = [ + "test_subtest.NumbersTest.test_even (i=0)", + "test_subtest.NumbersTest.test_even (i=1)", + "test_subtest.NumbersTest.test_even (i=2)", + "test_subtest.NumbersTest.test_even (i=3)", + "test_subtest.NumbersTest.test_even (i=4)", + "test_subtest.NumbersTest.test_even (i=5)", + ] + assert actual + assert all(item in actual for item in ("cwd", "status")) + assert actual["status"] == "success" + assert actual["cwd"] == os.fspath(TEST_DATA_PATH) + assert "result" in actual + result = actual["result"] + assert len(result) == 6 + for id in subtests_ids: + assert id in result + + +@pytest.mark.parametrize( + "test_ids, pattern, cwd, expected_outcome", + [ + ( + [ + "test_add.TestAddFunction.test_add_negative_numbers", + "test_add.TestAddFunction.test_add_positive_numbers", + ], + "test_add.py", + os.fspath(TEST_DATA_PATH / "unittest_folder"), + "success", + ), + ( + [ + "test_add.TestAddFunction.test_add_negative_numbers", + "test_add.TestAddFunction.test_add_positive_numbers", + "test_subtract.TestSubtractFunction.test_subtract_negative_numbers", + "test_subtract.TestSubtractFunction.test_subtract_positive_numbers", + ], + "test*", + os.fspath(TEST_DATA_PATH / "unittest_folder"), + "success", + ), + ( + [ + "pattern_a_test.DiscoveryA.test_one_a", + "pattern_a_test.DiscoveryA.test_two_a", + ], + "*test", + os.fspath(TEST_DATA_PATH / "two_patterns"), + "success", + ), + ( + [ + "test_pattern_b.DiscoveryB.test_one_b", + "test_pattern_b.DiscoveryB.test_two_b", + ], + "test_*", + os.fspath(TEST_DATA_PATH / "two_patterns"), + "success", + ), + ( + [ + "file_one.CaseTwoFileOne.test_one", + "file_one.CaseTwoFileOne.test_two", + "folder.file_two.CaseTwoFileTwo.test_one", + "folder.file_two.CaseTwoFileTwo.test_two", + ], + "*", + os.fspath(TEST_DATA_PATH / "utils_nested_cases"), + "success", + ), + ( + [ + "test_two_classes.ClassOne.test_one", + "test_two_classes.ClassTwo.test_two", + ], + "test_two_classes.py", + os.fspath(TEST_DATA_PATH), + "success", + ), + ], +) +def test_multiple_ids_run(test_ids, pattern, cwd, expected_outcome) -> None: + """ + The following are all successful tests of different formats. + + # 1. Two tests with the `pattern` specified as a file + # 2. Two test files in the same folder called `unittest_folder` + # 3. A folder with two different test file patterns, this test gathers pattern `*test` + # 4. A folder with two different test file patterns, this test gathers pattern `test_*` + # 5. A nested structure where a test file is on the same level as a folder containing a test file + # 6. Test file with two test classes + + All tests should have the outcome of `success`. + """ + actual = run_tests(cwd, test_ids, pattern, None, "fake-uuid") + assert actual + assert all(item in actual for item in ("cwd", "status")) + assert actual["status"] == "success" + assert actual["cwd"] == cwd + assert "result" in actual + result = actual["result"] + assert len(result) == len(test_ids) + for test_id in test_ids: + assert test_id in result + id_result = result[test_id] + assert id_result is not None + assert "outcome" in id_result + assert id_result["outcome"] == expected_outcome + assert True + + +def test_failed_tests(): + """This test runs on a single file `test_fail` with two tests that fail.""" + test_ids = [ + "test_fail_simple.RunFailSimple.test_one_fail", + "test_fail_simple.RunFailSimple.test_two_fail", + ] + actual = run_tests( + os.fspath(TEST_DATA_PATH), test_ids, "test_fail_simple*", None, "fake-uuid" + ) + assert actual + assert all(item in actual for item in ("cwd", "status")) + assert actual["status"] == "success" + assert actual["cwd"] == os.fspath(TEST_DATA_PATH) + assert "result" in actual + result = actual["result"] + assert len(result) == len(test_ids) + for test_id in test_ids: + assert test_id in result + id_result = result[test_id] + assert id_result is not None + assert "outcome" in id_result + assert id_result["outcome"] == "failure" + assert "message" and "traceback" in id_result + assert True + + +def test_unknown_id(): + """This test runs on a unknown test_id, therefore it should return + an error as the outcome as it attempts to find the given test. + """ + test_ids = ["unknown_id"] + actual = run_tests( + os.fspath(TEST_DATA_PATH), test_ids, "test_fail_simple*", None, "fake-uuid" + ) + assert actual + assert all(item in actual for item in ("cwd", "status")) + assert actual["status"] == "success" + assert actual["cwd"] == os.fspath(TEST_DATA_PATH) + assert "result" in actual + result = actual["result"] + assert len(result) == len(test_ids) + assert "unittest.loader._FailedTest.unknown_id" in result + id_result = result["unittest.loader._FailedTest.unknown_id"] + assert id_result is not None + assert "outcome" in id_result + assert id_result["outcome"] == "error" + assert "message" and "traceback" in id_result + + +def test_incorrect_path(): + """This test runs on a non existent path, therefore it should return + an error as the outcome as it attempts to find the given folder. + """ + test_ids = ["unknown_id"] + actual = run_tests( + os.fspath(TEST_DATA_PATH / "unknown_folder"), + test_ids, + "test_fail_simple*", + None, + "fake-uuid", + ) + assert actual + assert all(item in actual for item in ("cwd", "status", "error")) + assert actual["status"] == "error" + assert actual["cwd"] == os.fspath(TEST_DATA_PATH / "unknown_folder") diff --git a/pythonFiles/unittestadapter/execution.py b/pythonFiles/unittestadapter/execution.py index 569ad7fdf298..37288651f531 100644 --- a/pythonFiles/unittestadapter/execution.py +++ b/pythonFiles/unittestadapter/execution.py @@ -62,7 +62,9 @@ class TestOutcomeEnum(str, enum.Enum): class UnittestTestResult(unittest.TextTestResult): - formatted: Dict[str, Dict[str, Union[str, None]]] = dict() + def __init__(self, *args, **kwargs): + self.formatted: Dict[str, Dict[str, Union[str, None]]] = dict() + super(UnittestTestResult, self).__init__(*args, **kwargs) def startTest(self, test: unittest.TestCase): super(UnittestTestResult, self).startTest(test) @@ -128,7 +130,10 @@ def formatResult( formatted = formatted[1:] tb = "".join(formatted) - test_id = test.id() + if subtest: + test_id = subtest.id() + else: + test_id = test.id() result = { "test": test.id(), diff --git a/pythonFiles/vscode_pytest/__init__.py b/pythonFiles/vscode_pytest/__init__.py new file mode 100644 index 000000000000..6063e4113d55 --- /dev/null +++ b/pythonFiles/vscode_pytest/__init__.py @@ -0,0 +1,492 @@ +import json +import os +import pathlib +import sys +import traceback + +import pytest + +script_dir = pathlib.Path(__file__).parent.parent +sys.path.append(os.fspath(script_dir)) +sys.path.append(os.fspath(script_dir / "lib" / "python")) + +from typing import Any, Dict, List, Optional, Union + +from testing_tools import socket_manager +from typing_extensions import Literal, TypedDict + + +class TestData(TypedDict): + """A general class that all test objects inherit from.""" + + name: str + path: str + type_: Literal["class", "file", "folder", "test", "error"] + id_: str + + +class TestItem(TestData): + """A class defining test items.""" + + lineno: str + runID: str + + +class TestNode(TestData): + """A general class that handles all test data which contains children.""" + + children: "list[Union[TestNode, TestItem, None]]" + + +class VSCodePytestError(Exception): + """A custom exception class for pytest errors.""" + + def __init__(self, message): + super().__init__(message) + + +ERRORS = [] + + +def pytest_internalerror(excrepr, excinfo): + """A pytest hook that is called when an internal error occurs. + + Keyword arguments: + excrepr -- the exception representation. + excinfo -- the exception information of type ExceptionInfo. + """ + # call.excinfo.exconly() returns the exception as a string. + ERRORS.append(excinfo.exconly()) + + +def pytest_exception_interact(node, call, report): + """A pytest hook that is called when an exception is raised which could be handled. + + Keyword arguments: + node -- the node that raised the exception. + call -- the call object. + report -- the report object of either type CollectReport or TestReport. + """ + # call.excinfo is the captured exception of the call, if it raised as type ExceptionInfo. + # call.excinfo.exconly() returns the exception as a string. + if call.excinfo and call.excinfo.typename != "AssertionError": + ERRORS.append(call.excinfo.exconly()) + + +def pytest_keyboard_interrupt(excinfo): + """A pytest hook that is called when a keyboard interrupt is raised. + + Keyword arguments: + excinfo -- the exception information of type ExceptionInfo. + """ + # The function execonly() returns the exception as a string. + ERRORS.append(excinfo.exconly()) + + +class TestOutcome(Dict): + """A class that handles outcome for a single test. + + for pytest the outcome for a test is only 'passed', 'skipped' or 'failed' + """ + + test: str + outcome: Literal["success", "failure", "skipped"] + message: Union[str, None] + traceback: Union[str, None] + subtest: Optional[str] + + +def create_test_outcome( + test: str, + outcome: str, + message: Union[str, None], + traceback: Union[str, None], + subtype: Optional[str] = None, +) -> TestOutcome: + """A function that creates a TestOutcome object.""" + return TestOutcome( + test=test, + outcome=outcome, + message=message, + traceback=traceback, # TODO: traceback + subtest=None, + ) + + +class testRunResultDict(Dict[str, Dict[str, TestOutcome]]): + """A class that stores all test run results.""" + + outcome: str + tests: Dict[str, TestOutcome] + + +collected_tests = testRunResultDict() +IS_DISCOVERY = False + + +def pytest_load_initial_conftests(early_config, parser, args): + if "--collect-only" in args: + global IS_DISCOVERY + IS_DISCOVERY = True + + +def pytest_report_teststatus(report, config): + """ + A pytest hook that is called when a test is called. It is called 3 times per test, + during setup, call, and teardown. + Keyword arguments: + report -- the report on the test setup, call, and teardown. + config -- configuration object. + """ + + if report.when == "call": + traceback = None + message = None + report_value = "skipped" + if report.passed: + report_value = "success" + elif report.failed: + report_value = "failure" + message = report.longreprtext + item_result = create_test_outcome( + report.nodeid, + report_value, + message, + traceback, + ) + collected_tests[report.nodeid] = item_result + + +ERROR_MESSAGE_CONST = { + 2: "Pytest was unable to start or run any tests due to issues with test discovery or test collection.", + 3: "Pytest was interrupted by the user, for example by pressing Ctrl+C during test execution.", + 4: "Pytest encountered an internal error or exception during test execution.", + 5: "Pytest was unable to find any tests to run.", +} + + +def pytest_sessionfinish(session, exitstatus): + """A pytest hook that is called after pytest has fulled finished. + + Keyword arguments: + session -- the pytest session object. + exitstatus -- the status code of the session. + + 0: All tests passed successfully. + 1: One or more tests failed. + 2: Pytest was unable to start or run any tests due to issues with test discovery or test collection. + 3: Pytest was interrupted by the user, for example by pressing Ctrl+C during test execution. + 4: Pytest encountered an internal error or exception during test execution. + 5: Pytest was unable to find any tests to run. + """ + cwd = pathlib.Path.cwd() + if IS_DISCOVERY: + try: + session_node: Union[TestNode, None] = build_test_tree(session) + if not session_node: + raise VSCodePytestError( + "Something went wrong following pytest finish, \ + no session node was created" + ) + post_response(os.fsdecode(cwd), session_node) + except Exception as e: + ERRORS.append( + f"Error Occurred, traceback: {(traceback.format_exc() if e.__traceback__ else '')}" + ) + errorNode: TestNode = { + "name": "", + "path": "", + "type_": "error", + "children": [], + "id_": "", + } + post_response(os.fsdecode(cwd), errorNode) + else: + if exitstatus == 0 or exitstatus == 1: + exitstatus_bool = "success" + else: + ERRORS.append( + f"Pytest exited with error status: {exitstatus}, {ERROR_MESSAGE_CONST[exitstatus]}" + ) + exitstatus_bool = "error" + + execution_post( + os.fsdecode(cwd), + exitstatus_bool, + collected_tests if collected_tests else None, + ) + + +def build_test_tree(session: pytest.Session) -> TestNode: + """Builds a tree made up of testing nodes from the pytest session. + + Keyword arguments: + session -- the pytest session object. + """ + session_node = create_session_node(session) + session_children_dict: Dict[str, TestNode] = {} + file_nodes_dict: Dict[Any, TestNode] = {} + class_nodes_dict: Dict[str, TestNode] = {} + + for test_case in session.items: + test_node = create_test_node(test_case) + if isinstance(test_case.parent, pytest.Class): + try: + test_class_node = class_nodes_dict[test_case.parent.name] + except KeyError: + test_class_node = create_class_node(test_case.parent) + class_nodes_dict[test_case.parent.name] = test_class_node + test_class_node["children"].append(test_node) + if test_case.parent.parent: + parent_module = test_case.parent.parent + else: + ERRORS.append(f"Test class {test_case.parent} has no parent") + break + # Create a file node that has the class as a child. + try: + test_file_node: TestNode = file_nodes_dict[parent_module] + except KeyError: + test_file_node = create_file_node(parent_module) + file_nodes_dict[parent_module] = test_file_node + # Check if the class is already a child of the file node. + if test_class_node not in test_file_node["children"]: + test_file_node["children"].append(test_class_node) + else: # This includes test cases that are pytest functions or a doctests. + try: + parent_test_case = file_nodes_dict[test_case.parent] + except KeyError: + parent_test_case = create_file_node(test_case.parent) + file_nodes_dict[test_case.parent] = parent_test_case + parent_test_case["children"].append(test_node) + created_files_folders_dict: Dict[str, TestNode] = {} + for file_module, file_node in file_nodes_dict.items(): + # Iterate through all the files that exist and construct them into nested folders. + root_folder_node: TestNode = build_nested_folders( + file_module, file_node, created_files_folders_dict, session + ) + # The final folder we get to is the highest folder in the path + # and therefore we add this as a child to the session. + root_id = root_folder_node.get("id_") + if root_id and root_id not in session_children_dict: + session_children_dict[root_id] = root_folder_node + session_node["children"] = list(session_children_dict.values()) + return session_node + + +def build_nested_folders( + file_module: Any, + file_node: TestNode, + created_files_folders_dict: Dict[str, TestNode], + session: pytest.Session, +) -> TestNode: + """Takes a file or folder and builds the nested folder structure for it. + + Keyword arguments: + file_module -- the created module for the file we are nesting. + file_node -- the file node that we are building the nested folders for. + created_files_folders_dict -- Dictionary of all the folders and files that have been created. + session -- the pytest session object. + """ + prev_folder_node = file_node + + # Begin the iterator_path one level above the current file. + iterator_path = file_module.path.parent + while iterator_path != session.path: + curr_folder_name = iterator_path.name + try: + curr_folder_node: TestNode = created_files_folders_dict[curr_folder_name] + except KeyError: + curr_folder_node: TestNode = create_folder_node( + curr_folder_name, iterator_path + ) + created_files_folders_dict[curr_folder_name] = curr_folder_node + if prev_folder_node not in curr_folder_node["children"]: + curr_folder_node["children"].append(prev_folder_node) + iterator_path = iterator_path.parent + prev_folder_node = curr_folder_node + return prev_folder_node + + +def create_test_node( + test_case: pytest.Item, +) -> TestItem: + """Creates a test node from a pytest test case. + + Keyword arguments: + test_case -- the pytest test case. + """ + test_case_loc: str = ( + str(test_case.location[1] + 1) if (test_case.location[1] is not None) else "" + ) + return { + "name": test_case.name, + "path": os.fspath(test_case.path), + "lineno": test_case_loc, + "type_": "test", + "id_": test_case.nodeid, + "runID": test_case.nodeid, + } + + +def create_session_node(session: pytest.Session) -> TestNode: + """Creates a session node from a pytest session. + + Keyword arguments: + session -- the pytest session. + """ + return { + "name": session.name, + "path": os.fspath(session.path), + "type_": "folder", + "children": [], + "id_": os.fspath(session.path), + } + + +def create_class_node(class_module: pytest.Class) -> TestNode: + """Creates a class node from a pytest class object. + + Keyword arguments: + class_module -- the pytest object representing a class module. + """ + return { + "name": class_module.name, + "path": os.fspath(class_module.path), + "type_": "class", + "children": [], + "id_": class_module.nodeid, + } + + +def create_file_node(file_module: Any) -> TestNode: + """Creates a file node from a pytest file module. + + Keyword arguments: + file_module -- the pytest file module. + """ + return { + "name": file_module.path.name, + "path": os.fspath(file_module.path), + "type_": "file", + "id_": os.fspath(file_module.path), + "children": [], + } + + +def create_folder_node(folderName: str, path_iterator: pathlib.Path) -> TestNode: + """Creates a folder node from a pytest folder name and its path. + + Keyword arguments: + folderName -- the name of the folder. + path_iterator -- the path of the folder. + """ + return { + "name": folderName, + "path": os.fspath(path_iterator), + "type_": "folder", + "id_": os.fspath(path_iterator), + "children": [], + } + + +class DiscoveryPayloadDict(TypedDict): + """A dictionary that is used to send a post request to the server.""" + + cwd: str + status: Literal["success", "error"] + tests: Optional[TestNode] + error: Optional[List[str]] + + +class ExecutionPayloadDict(Dict): + """ + A dictionary that is used to send a execution post request to the server. + """ + + cwd: str + status: Literal["success", "error"] + result: Union[testRunResultDict, None] + not_found: Union[List[str], None] # Currently unused need to check + error: Union[str, None] # Currently unused need to check + + +def execution_post( + cwd: str, + status: Literal["success", "error"], + tests: Union[testRunResultDict, None], +): + """ + Sends a post request to the server after the tests have been executed. + Keyword arguments: + cwd -- the current working directory. + session_node -- the status of running the tests + tests -- the tests that were run and their status. + """ + testPort = os.getenv("TEST_PORT", 45454) + testuuid = os.getenv("TEST_UUID") + payload: ExecutionPayloadDict = ExecutionPayloadDict( + cwd=cwd, status=status, result=tests, not_found=None, error=None + ) + if ERRORS: + payload["error"] = ERRORS + + addr = ("localhost", int(testPort)) + data = json.dumps(payload) + request = f"""Content-Length: {len(data)} +Content-Type: application/json +Request-uuid: {testuuid} + +{data}""" + test_output_file: Optional[str] = os.getenv("TEST_OUTPUT_FILE", None) + if test_output_file == "stdout": + print(request) + elif test_output_file: + pathlib.Path(test_output_file).write_text(request, encoding="utf-8") + else: + try: + with socket_manager.SocketManager(addr) as s: + if s.socket is not None: + s.socket.sendall(request.encode("utf-8")) + except Exception as e: + print(f"Plugin error connection error[vscode-pytest]: {e}") + print(f"[vscode-pytest] data: {request}") + + +def post_response(cwd: str, session_node: TestNode) -> None: + """Sends a post request to the server. + + Keyword arguments: + cwd -- the current working directory. + session_node -- the session node, which is the top of the testing tree. + errors -- a list of errors that occurred during test collection. + """ + payload: DiscoveryPayloadDict = { + "cwd": cwd, + "status": "success" if not ERRORS else "error", + "tests": session_node, + "error": [], + } + if ERRORS is not None: + payload["error"] = ERRORS + testPort: Union[str, int] = os.getenv("TEST_PORT", 45454) + testuuid: Union[str, None] = os.getenv("TEST_UUID") + addr = "localhost", int(testPort) + data = json.dumps(payload) + request = f"""Content-Length: {len(data)} +Content-Type: application/json +Request-uuid: {testuuid} + +{data}""" + test_output_file: Optional[str] = os.getenv("TEST_OUTPUT_FILE", None) + if test_output_file == "stdout": + print(request) + elif test_output_file: + pathlib.Path(test_output_file).write_text(request, encoding="utf-8") + else: + try: + with socket_manager.SocketManager(addr) as s: + if s.socket is not None: + s.socket.sendall(request.encode("utf-8")) + except Exception as e: + print(f"Plugin error connection error[vscode-pytest]: {e}") + print(f"[vscode-pytest] data: {request}") diff --git a/src/client/activation/common/analysisOptions.ts b/src/client/activation/common/analysisOptions.ts index 18de19384fbf..75d0aabef9d2 100644 --- a/src/client/activation/common/analysisOptions.ts +++ b/src/client/activation/common/analysisOptions.ts @@ -5,7 +5,7 @@ import { DocumentFilter, LanguageClientOptions, RevealOutputChannelOn } from 'vs import { IWorkspaceService } from '../../common/application/types'; import { PYTHON, PYTHON_LANGUAGE } from '../../common/constants'; -import { IOutputChannel, Resource } from '../../common/types'; +import { ILogOutputChannel, Resource } from '../../common/types'; import { debounceSync } from '../../common/utils/decorators'; import { IEnvironmentVariablesProvider } from '../../common/variables/types'; import { traceDecoratorError } from '../../logging'; @@ -14,7 +14,7 @@ import { ILanguageServerAnalysisOptions, ILanguageServerOutputChannel } from '.. export abstract class LanguageServerAnalysisOptionsBase implements ILanguageServerAnalysisOptions { protected readonly didChange = new EventEmitter(); - private readonly output: IOutputChannel; + private readonly output: ILogOutputChannel; protected constructor( lsOutputChannel: ILanguageServerOutputChannel, diff --git a/src/client/activation/common/outputChannel.ts b/src/client/activation/common/outputChannel.ts index 830bfbfdf55b..60a99687793e 100644 --- a/src/client/activation/common/outputChannel.ts +++ b/src/client/activation/common/outputChannel.ts @@ -6,13 +6,13 @@ import { inject, injectable } from 'inversify'; import { IApplicationShell, ICommandManager } from '../../common/application/types'; import '../../common/extensions'; -import { IDisposableRegistry, IOutputChannel } from '../../common/types'; +import { IDisposableRegistry, ILogOutputChannel } from '../../common/types'; import { OutputChannelNames } from '../../common/utils/localize'; import { ILanguageServerOutputChannel } from '../types'; @injectable() export class LanguageServerOutputChannel implements ILanguageServerOutputChannel { - public output: IOutputChannel | undefined; + public output: ILogOutputChannel | undefined; private registered = false; @@ -22,7 +22,7 @@ export class LanguageServerOutputChannel implements ILanguageServerOutputChannel @inject(IDisposableRegistry) private readonly disposable: IDisposableRegistry, ) {} - public get channel(): IOutputChannel { + public get channel(): ILogOutputChannel { if (!this.output) { this.output = this.appShell.createOutputChannel(OutputChannelNames.languageServer); this.disposable.push(this.output); diff --git a/src/client/activation/types.ts b/src/client/activation/types.ts index 873d608f0bd0..2a177bb570b8 100644 --- a/src/client/activation/types.ts +++ b/src/client/activation/types.ts @@ -5,7 +5,7 @@ import { Event } from 'vscode'; import { LanguageClient, LanguageClientOptions } from 'vscode-languageclient/node'; -import type { IDisposable, IOutputChannel, Resource } from '../common/types'; +import type { IDisposable, ILogOutputChannel, Resource } from '../common/types'; import { PythonEnvironment } from '../pythonEnvironments/info'; export const IExtensionActivationManager = Symbol('IExtensionActivationManager'); @@ -110,10 +110,10 @@ export interface ILanguageServerOutputChannel { /** * Creates output channel if necessary and returns it * - * @type {IOutputChannel} + * @type {ILogOutputChannel} * @memberof ILanguageServerOutputChannel */ - readonly channel: IOutputChannel; + readonly channel: ILogOutputChannel; } export const IExtensionSingleActivationService = Symbol('IExtensionSingleActivationService'); diff --git a/src/client/application/diagnostics/applicationDiagnostics.ts b/src/client/application/diagnostics/applicationDiagnostics.ts index ba31021fc34f..493c6cfece53 100644 --- a/src/client/application/diagnostics/applicationDiagnostics.ts +++ b/src/client/application/diagnostics/applicationDiagnostics.ts @@ -7,7 +7,7 @@ import { IWorkspaceService } from '../../common/application/types'; import { isTestExecution } from '../../common/constants'; import { Resource } from '../../common/types'; import { IServiceContainer } from '../../ioc/types'; -import { traceInfo, traceLog } from '../../logging'; +import { traceLog, traceVerbose } from '../../logging'; import { IApplicationDiagnostics } from '../types'; import { IDiagnostic, IDiagnosticsService, ISourceMapSupportService } from './types'; @@ -21,7 +21,7 @@ function log(diagnostics: IDiagnostic[]): void { break; } default: { - traceInfo(message); + traceVerbose(message); } } }); diff --git a/src/client/common/application/applicationShell.ts b/src/client/common/application/applicationShell.ts index c1a5de51b7f6..454662472010 100644 --- a/src/client/common/application/applicationShell.ts +++ b/src/client/common/application/applicationShell.ts @@ -14,10 +14,10 @@ import { InputBoxOptions, languages, LanguageStatusItem, + LogOutputChannel, MessageItem, MessageOptions, OpenDialogOptions, - OutputChannel, Progress, ProgressOptions, QuickPick, @@ -166,8 +166,8 @@ export class ApplicationShell implements IApplicationShell { public createTreeView(viewId: string, options: TreeViewOptions): TreeView { return window.createTreeView(viewId, options); } - public createOutputChannel(name: string): OutputChannel { - return window.createOutputChannel(name); + public createOutputChannel(name: string): LogOutputChannel { + return window.createOutputChannel(name, { log: true }); } public createLanguageStatusItem(id: string, selector: DocumentSelector): LanguageStatusItem { return languages.createLanguageStatusItem(id, selector); diff --git a/src/client/common/application/commands.ts b/src/client/common/application/commands.ts index 277baffd19a4..2a4404440101 100644 --- a/src/client/common/application/commands.ts +++ b/src/client/common/application/commands.ts @@ -17,6 +17,7 @@ export type CommandsWithoutArgs = keyof ICommandNameWithoutArgumentTypeMapping; */ interface ICommandNameWithoutArgumentTypeMapping { [Commands.InstallPythonOnMac]: []; + [Commands.InstallJupyter]: []; [Commands.InstallPythonOnLinux]: []; [Commands.InstallPython]: []; [Commands.ClearWorkspaceInterpreter]: []; diff --git a/src/client/common/application/contextKeys.ts b/src/client/common/application/contextKeys.ts index 2f791ae66846..d6249f05eaec 100644 --- a/src/client/common/application/contextKeys.ts +++ b/src/client/common/application/contextKeys.ts @@ -5,4 +5,5 @@ export enum ExtensionContextKey { showInstallPythonTile = 'showInstallPythonTile', HasFailedTests = 'hasFailedTests', RefreshingTests = 'refreshingTests', + IsJupyterInstalled = 'isJupyterInstalled', } diff --git a/src/client/common/application/types.ts b/src/client/common/application/types.ts index 1b054eda687c..77d7b5af3279 100644 --- a/src/client/common/application/types.ts +++ b/src/client/common/application/types.ts @@ -25,10 +25,10 @@ import { InputBox, InputBoxOptions, LanguageStatusItem, + LogOutputChannel, MessageItem, MessageOptions, OpenDialogOptions, - OutputChannel, Progress, ProgressOptions, QuickPick, @@ -429,7 +429,7 @@ export interface IApplicationShell { * * @param name Human-readable string which will be used to represent the channel in the UI. */ - createOutputChannel(name: string): OutputChannel; + createOutputChannel(name: string): LogOutputChannel; createLanguageStatusItem(id: string, selector: DocumentSelector): LanguageStatusItem; } diff --git a/src/client/common/constants.ts b/src/client/common/constants.ts index da44a7bfe677..104504650aed 100644 --- a/src/client/common/constants.ts +++ b/src/client/common/constants.ts @@ -46,6 +46,7 @@ export namespace Commands { export const Exec_Selection_In_Django_Shell = 'python.execSelectionInDjangoShell'; export const Exec_Selection_In_Terminal = 'python.execSelectionInTerminal'; export const GetSelectedInterpreterPath = 'python.interpreterPath'; + export const InstallJupyter = 'python.installJupyter'; export const InstallPython = 'python.installPython'; export const InstallPythonOnLinux = 'python.installPythonOnLinux'; export const InstallPythonOnMac = 'python.installPythonOnMac'; @@ -93,8 +94,6 @@ export namespace ThemeIcons { export const DEFAULT_INTERPRETER_SETTING = 'python'; -export const STANDARD_OUTPUT_CHANNEL = 'STANDARD_OUTPUT_CHANNEL'; - export const isCI = process.env.TRAVIS === 'true' || process.env.TF_BUILD !== undefined; export function isTestExecution(): boolean { diff --git a/src/client/common/installer/moduleInstaller.ts b/src/client/common/installer/moduleInstaller.ts index 4049edb8ec0d..62160b7e25c9 100644 --- a/src/client/common/installer/moduleInstaller.ts +++ b/src/client/common/installer/moduleInstaller.ts @@ -12,12 +12,11 @@ import { sendTelemetryEvent } from '../../telemetry'; import { EventName } from '../../telemetry/constants'; import { IApplicationShell } from '../application/types'; import { wrapCancellationTokens } from '../cancellation'; -import { STANDARD_OUTPUT_CHANNEL } from '../constants'; import { IFileSystem } from '../platform/types'; import * as internalPython from '../process/internal/python'; import { IProcessServiceFactory } from '../process/types'; import { ITerminalServiceFactory, TerminalCreationOptions } from '../terminal/types'; -import { ExecutionInfo, IConfigurationService, IOutputChannel, Product } from '../types'; +import { ExecutionInfo, IConfigurationService, ILogOutputChannel, Product } from '../types'; import { isResource } from '../utils/misc'; import { ProductNames } from './productNames'; import { IModuleInstaller, InstallOptions, InterpreterUri, ModuleInstallFlags } from './types'; @@ -152,7 +151,7 @@ export abstract class ModuleInstaller implements IModuleInstaller { const options = { name: 'VS Code Python', }; - const outputChannel = this.serviceContainer.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); + const outputChannel = this.serviceContainer.get(ILogOutputChannel); const command = `"${execPath.replace(/\\/g, '/')}" ${args.join(' ')}`; traceLog(`[Elevated] ${command}`); diff --git a/src/client/common/process/pythonEnvironment.ts b/src/client/common/process/pythonEnvironment.ts index f4c6f8954126..9566f373aa91 100644 --- a/src/client/common/process/pythonEnvironment.ts +++ b/src/client/common/process/pythonEnvironment.ts @@ -2,7 +2,7 @@ // Licensed under the MIT License. import * as path from 'path'; -import { traceError, traceInfo } from '../../logging'; +import { traceError, traceVerbose } from '../../logging'; import { Conda, CondaEnvironmentInfo } from '../../pythonEnvironments/common/environmentManagers/conda'; import { buildPythonExecInfo, PythonExecInfo } from '../../pythonEnvironments/exec'; import { InterpreterInformation } from '../../pythonEnvironments/info'; @@ -71,7 +71,7 @@ class PythonEnvironment implements IPythonEnvironment { try { data = await this.deps.exec(info.command, info.args); } catch (ex) { - traceInfo(`Error when getting version of module ${moduleName}`, ex); + traceVerbose(`Error when getting version of module ${moduleName}`, ex); return undefined; } return parse(data.stdout); @@ -84,7 +84,7 @@ class PythonEnvironment implements IPythonEnvironment { try { await this.deps.exec(info.command, info.args); } catch (ex) { - traceInfo(`Error when checking if module is installed ${moduleName}`, ex); + traceVerbose(`Error when checking if module is installed ${moduleName}`, ex); return false; } return true; @@ -93,7 +93,7 @@ class PythonEnvironment implements IPythonEnvironment { private async getInterpreterInformationImpl(): Promise { try { const python = this.getExecutionInfo(); - return await getInterpreterInfo(python, this.deps.shellExec, { info: traceInfo, error: traceError }); + return await getInterpreterInfo(python, this.deps.shellExec, { verbose: traceVerbose, error: traceError }); } catch (ex) { traceError(`Failed to get interpreter information for '${this.pythonPath}'`, ex); } diff --git a/src/client/common/process/rawProcessApis.ts b/src/client/common/process/rawProcessApis.ts index 59b5fe69c9cd..fe54b3a8be87 100644 --- a/src/client/common/process/rawProcessApis.ts +++ b/src/client/common/process/rawProcessApis.ts @@ -121,7 +121,10 @@ export function plainExec( } const stdoutBuffers: Buffer[] = []; - on(proc.stdout, 'data', (data: Buffer) => stdoutBuffers.push(data)); + on(proc.stdout, 'data', (data: Buffer) => { + stdoutBuffers.push(data); + options.outputChannel?.append(data.toString()); + }); const stderrBuffers: Buffer[] = []; on(proc.stderr, 'data', (data: Buffer) => { if (options.mergeStdOutErr) { @@ -130,6 +133,7 @@ export function plainExec( } else { stderrBuffers.push(data); } + options.outputChannel?.append(data.toString()); }); proc.once('close', () => { diff --git a/src/client/common/process/types.ts b/src/client/common/process/types.ts index bcab76e66b09..8298957285e8 100644 --- a/src/client/common/process/types.ts +++ b/src/client/common/process/types.ts @@ -3,7 +3,7 @@ import { ChildProcess, ExecOptions, SpawnOptions as ChildProcessSpawnOptions } from 'child_process'; import { Observable } from 'rxjs/Observable'; -import { CancellationToken, Uri } from 'vscode'; +import { CancellationToken, OutputChannel, Uri } from 'vscode'; import { PythonExecInfo } from '../../pythonEnvironments/exec'; import { InterpreterInformation, PythonEnvironment } from '../../pythonEnvironments/info'; import { ExecutionInfo, IDisposable } from '../types'; @@ -24,6 +24,7 @@ export type SpawnOptions = ChildProcessSpawnOptions & { mergeStdOutErr?: boolean; throwOnStdErr?: boolean; extraVariables?: NodeJS.ProcessEnv; + outputChannel?: OutputChannel; }; export type ShellOptions = ExecOptions & { throwOnStdErr?: boolean }; diff --git a/src/client/common/serviceRegistry.ts b/src/client/common/serviceRegistry.ts index 8e2c4d4d3ebe..5b527499460a 100644 --- a/src/client/common/serviceRegistry.ts +++ b/src/client/common/serviceRegistry.ts @@ -90,6 +90,7 @@ import { IMultiStepInputFactory, MultiStepInputFactory } from './utils/multiStep import { Random } from './utils/random'; import { ContextKeyManager } from './application/contextKeyManager'; import { CreatePythonFileCommandHandler } from './application/commands/createPythonFile'; +import { RequireJupyterPrompt } from '../jupyter/requireJupyterPrompt'; export function registerTypes(serviceManager: IServiceManager): void { serviceManager.addSingletonInstance(IsWindows, IS_WINDOWS); @@ -110,6 +111,10 @@ export function registerTypes(serviceManager: IServiceManager): void { IJupyterExtensionDependencyManager, JupyterExtensionDependencyManager, ); + serviceManager.addSingleton( + IExtensionSingleActivationService, + RequireJupyterPrompt, + ); serviceManager.addSingleton( IExtensionSingleActivationService, CreatePythonFileCommandHandler, diff --git a/src/client/common/terminal/activator/index.ts b/src/client/common/terminal/activator/index.ts index 5bc76c0cb0f8..1c2cf4041585 100644 --- a/src/client/common/terminal/activator/index.ts +++ b/src/client/common/terminal/activator/index.ts @@ -5,9 +5,10 @@ import { inject, injectable, multiInject } from 'inversify'; import { Terminal } from 'vscode'; -import { IConfigurationService } from '../../types'; +import { IConfigurationService, IExperimentService } from '../../types'; import { ITerminalActivationHandler, ITerminalActivator, ITerminalHelper, TerminalActivationOptions } from '../types'; import { BaseTerminalActivator } from './base'; +import { inTerminalEnvVarExperiment } from '../../experiments/helpers'; @injectable() export class TerminalActivator implements ITerminalActivator { @@ -17,6 +18,7 @@ export class TerminalActivator implements ITerminalActivator { @inject(ITerminalHelper) readonly helper: ITerminalHelper, @multiInject(ITerminalActivationHandler) private readonly handlers: ITerminalActivationHandler[], @inject(IConfigurationService) private readonly configurationService: IConfigurationService, + @inject(IExperimentService) private readonly experimentService: IExperimentService, ) { this.initialize(); } @@ -37,7 +39,8 @@ export class TerminalActivator implements ITerminalActivator { options?: TerminalActivationOptions, ): Promise { const settings = this.configurationService.getSettings(options?.resource); - const activateEnvironment = settings.terminal.activateEnvironment; + const activateEnvironment = + settings.terminal.activateEnvironment && !inTerminalEnvVarExperiment(this.experimentService); if (!activateEnvironment || options?.hideFromUser) { return false; } diff --git a/src/client/common/types.ts b/src/client/common/types.ts index 3fac5e7e0044..3359854f89b7 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -15,9 +15,10 @@ import { Extension, ExtensionContext, Memento, - OutputChannel, + LogOutputChannel, Uri, WorkspaceEdit, + OutputChannel, } from 'vscode'; import { LanguageServerType } from '../activation/types'; import type { InstallOptions, InterpreterUri, ModuleInstallFlags } from './installer/types'; @@ -29,8 +30,10 @@ export interface IDisposable { dispose(): void | undefined | Promise; } -export const IOutputChannel = Symbol('IOutputChannel'); -export interface IOutputChannel extends OutputChannel {} +export const ILogOutputChannel = Symbol('ILogOutputChannel'); +export interface ILogOutputChannel extends LogOutputChannel {} +export const ITestOutputChannel = Symbol('ITestOutputChannel'); +export interface ITestOutputChannel extends OutputChannel {} export const IDocumentSymbolProvider = Symbol('IDocumentSymbolProvider'); export interface IDocumentSymbolProvider extends DocumentSymbolProvider {} export const IsWindows = Symbol('IS_WINDOWS'); diff --git a/src/client/common/utils/localize.ts b/src/client/common/utils/localize.ts index 10c70c8c6cd0..4c3cd7a45d0d 100644 --- a/src/client/common/utils/localize.ts +++ b/src/client/common/utils/localize.ts @@ -188,6 +188,9 @@ export namespace LanguageService { ); } export namespace Interpreters { + export const requireJupyter = l10n.t( + 'Running in Interactive window requires Jupyter Extension. Would you like to install it? [Learn more](https://aka.ms/pythonJupyterSupport).', + ); export const installingPython = l10n.t('Installing Python into Environment...'); export const discovering = l10n.t('Discovering Python Interpreters'); export const refreshing = l10n.t('Refreshing Python Interpreters'); @@ -218,6 +221,9 @@ export namespace Interpreters { } export namespace InterpreterQuickPickList { + export const condaEnvWithoutPythonTooltip = l10n.t( + 'Python is not available in this environment, it will automatically be installed upon selecting it', + ); export const noPythonInstalled = l10n.t('Python is not installed, please download and install it'); export const clickForInstructions = l10n.t('Click for instructions...'); export const globalGroupName = l10n.t('Global'); diff --git a/src/client/common/variables/environment.ts b/src/client/common/variables/environment.ts index 4b3652e5021f..81e6b8b2cfc9 100644 --- a/src/client/common/variables/environment.ts +++ b/src/client/common/variables/environment.ts @@ -57,7 +57,7 @@ export class EnvironmentVariablesService implements IEnvironmentVariablesService public mergeVariables( source: EnvironmentVariables, target: EnvironmentVariables, - options?: { overwrite?: boolean }, + options?: { overwrite?: boolean; mergeAll?: boolean }, ) { if (!target) { return; @@ -67,7 +67,7 @@ export class EnvironmentVariablesService implements IEnvironmentVariablesService source = normCaseKeys(source); const settingsNotToMerge = ['PYTHONPATH', this.pathVariable]; Object.keys(source).forEach((setting) => { - if (settingsNotToMerge.indexOf(setting) >= 0) { + if (!options?.mergeAll && settingsNotToMerge.indexOf(setting) >= 0) { return; } if (target[setting] === undefined || options?.overwrite) { diff --git a/src/client/common/variables/types.ts b/src/client/common/variables/types.ts index e4c301db7dd7..252a0d48038f 100644 --- a/src/client/common/variables/types.ts +++ b/src/client/common/variables/types.ts @@ -10,7 +10,11 @@ export const IEnvironmentVariablesService = Symbol('IEnvironmentVariablesService export interface IEnvironmentVariablesService { parseFile(filePath?: string, baseVars?: EnvironmentVariables): Promise; parseFileSync(filePath?: string, baseVars?: EnvironmentVariables): EnvironmentVariables | undefined; - mergeVariables(source: EnvironmentVariables, target: EnvironmentVariables, options?: { overwrite?: boolean }): void; + mergeVariables( + source: EnvironmentVariables, + target: EnvironmentVariables, + options?: { overwrite?: boolean; mergeAll?: boolean }, + ): void; appendPythonPath(vars: EnvironmentVariables, ...pythonPaths: string[]): void; appendPath(vars: EnvironmentVariables, ...paths: string[]): void; } diff --git a/src/client/debugger/extension/configuration/resolvers/helper.ts b/src/client/debugger/extension/configuration/resolvers/helper.ts index 96cdf65bfac3..15be5f97538e 100644 --- a/src/client/debugger/extension/configuration/resolvers/helper.ts +++ b/src/client/debugger/extension/configuration/resolvers/helper.ts @@ -44,7 +44,7 @@ export class DebugEnvironmentVariablesHelper implements IDebugEnvironmentVariabl // take precedence over env file. this.envParser.mergeVariables(debugLaunchEnvVars, env, { overwrite: true }); if (baseVars) { - this.envParser.mergeVariables(baseVars, env); + this.envParser.mergeVariables(baseVars, env, { mergeAll: true }); } // Append the PYTHONPATH and PATH variables. diff --git a/src/client/debugger/extension/configuration/resolvers/launch.ts b/src/client/debugger/extension/configuration/resolvers/launch.ts index 6a28075d4353..f48b2c19aaff 100644 --- a/src/client/debugger/extension/configuration/resolvers/launch.ts +++ b/src/client/debugger/extension/configuration/resolvers/launch.ts @@ -19,6 +19,8 @@ import { getProgram, IDebugEnvironmentVariablesService } from './helper'; @injectable() export class LaunchConfigurationResolver extends BaseConfigurationResolver { + private isPythonSet = false; + constructor( @inject(IDiagnosticsService) @named(InvalidPythonPathInDebuggerServiceId) @@ -36,6 +38,7 @@ export class LaunchConfigurationResolver extends BaseConfigurationResolver { + this.isPythonSet = debugConfiguration.python !== undefined; if ( debugConfiguration.name === undefined && debugConfiguration.type === undefined && @@ -84,7 +87,6 @@ export class LaunchConfigurationResolver extends BaseConfigurationResolver { - const isPythonSet = debugConfiguration.python !== undefined; if (debugConfiguration.python === undefined) { debugConfiguration.python = debugConfiguration.pythonPath; } @@ -104,7 +106,7 @@ export class LaunchConfigurationResolver extends BaseConfigurationResolver { const extensions = serviceContainer.get(IExtensions); await setDefaultLanguageServer(extensions, serviceManager); - // Note we should not trigger any extension related code which logs, until we have set logging level. So we cannot - // use configurations service to get level setting. Instead, we use Workspace service to query for setting as it - // directly queries VSCode API. - setLoggingLevel(getLoggingLevel()); - const configuration = serviceManager.get(IConfigurationService); // Settings are dependent on Experiment service, so we need to initialize it after experiments are activated. serviceContainer.get(IConfigurationService).getSettings().register(); @@ -173,7 +159,7 @@ async function activateLegacy(ext: ExtensionState): Promise { const dispatcher = new DebugSessionEventDispatcher(handlers, DebugService.instance, disposables); dispatcher.registerEventHandlers(); - const outputChannel = serviceManager.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); + const outputChannel = serviceManager.get(ILogOutputChannel); disposables.push(cmdManager.registerCommand(Commands.ViewOutput, () => outputChannel.show())); cmdManager.executeCommand('setContext', 'python.vscode.channel', applicationEnv.channel).then(noop, noop); diff --git a/src/client/extensionInit.ts b/src/client/extensionInit.ts index 4ee5f099f15d..851bc943cb8d 100644 --- a/src/client/extensionInit.ts +++ b/src/client/extensionInit.ts @@ -4,9 +4,8 @@ 'use strict'; import { Container } from 'inversify'; -import { Disposable, Memento, OutputChannel, window } from 'vscode'; +import { Disposable, Memento, window } from 'vscode'; import { instance, mock } from 'ts-mockito'; -import { STANDARD_OUTPUT_CHANNEL } from './common/constants'; import { registerTypes as platformRegisterTypes } from './common/platform/serviceRegistry'; import { registerTypes as processRegisterTypes } from './common/process/serviceRegistry'; import { registerTypes as commonRegisterTypes } from './common/serviceRegistry'; @@ -16,7 +15,8 @@ import { IDisposableRegistry, IExtensionContext, IMemento, - IOutputChannel, + ILogOutputChannel, + ITestOutputChannel, WORKSPACE_MEMENTO, } from './common/types'; import { registerTypes as variableRegisterTypes } from './common/variables/serviceRegistry'; @@ -26,7 +26,6 @@ import { ServiceContainer } from './ioc/container'; import { ServiceManager } from './ioc/serviceManager'; import { IServiceContainer, IServiceManager } from './ioc/types'; import * as pythonEnvironments from './pythonEnvironments'; -import { TEST_OUTPUT_CHANNEL } from './testing/constants'; import { IDiscoveryAPI } from './pythonEnvironments/base/locator'; import { registerLogger } from './logging'; import { OutputChannelLogger } from './logging/outputChannelLogger'; @@ -54,7 +53,7 @@ export function initializeGlobals( serviceManager.addSingletonInstance(IMemento, context.workspaceState, WORKSPACE_MEMENTO); serviceManager.addSingletonInstance(IExtensionContext, context); - const standardOutputChannel = window.createOutputChannel(OutputChannelNames.python); + const standardOutputChannel = window.createOutputChannel(OutputChannelNames.python, { log: true }); disposables.push(standardOutputChannel); disposables.push(registerLogger(new OutputChannelLogger(standardOutputChannel))); @@ -62,12 +61,12 @@ export function initializeGlobals( const unitTestOutChannel = workspaceService.isVirtualWorkspace || !workspaceService.isTrusted ? // Do not create any test related output UI when using virtual workspaces. - instance(mock()) + instance(mock()) : window.createOutputChannel(OutputChannelNames.pythonTest); disposables.push(unitTestOutChannel); - serviceManager.addSingletonInstance(IOutputChannel, standardOutputChannel, STANDARD_OUTPUT_CHANNEL); - serviceManager.addSingletonInstance(IOutputChannel, unitTestOutChannel, TEST_OUTPUT_CHANNEL); + serviceManager.addSingletonInstance(ILogOutputChannel, standardOutputChannel); + serviceManager.addSingletonInstance(ITestOutputChannel, unitTestOutChannel); return { context, diff --git a/src/client/interpreter/activation/service.ts b/src/client/interpreter/activation/service.ts index 897dad9cb75d..48eccfe7bfca 100644 --- a/src/client/interpreter/activation/service.ts +++ b/src/client/interpreter/activation/service.ts @@ -25,14 +25,7 @@ import { EventName } from '../../telemetry/constants'; import { IInterpreterService } from '../contracts'; import { IEnvironmentActivationService } from './types'; import { TraceOptions } from '../../logging/types'; -import { - traceDecoratorError, - traceDecoratorVerbose, - traceError, - traceInfo, - traceVerbose, - traceWarn, -} from '../../logging'; +import { traceDecoratorError, traceDecoratorVerbose, traceError, traceVerbose, traceWarn } from '../../logging'; import { Conda } from '../../pythonEnvironments/common/environmentManagers/conda'; import { StopWatch } from '../../common/utils/stopWatch'; import { identifyShellFromShellPath } from '../../common/terminal/shellDetectors/baseShellDetector'; @@ -290,7 +283,7 @@ export class EnvironmentActivationService implements IEnvironmentActivationServi // that's the case, wait and try again. This happens especially on AzDo const excString = (exc as Error).toString(); if (condaRetryMessages.find((m) => excString.includes(m)) && tryCount < 10) { - traceInfo(`Conda is busy, attempting to retry ...`); + traceVerbose(`Conda is busy, attempting to retry ...`); result = undefined; tryCount += 1; await sleep(500); diff --git a/src/client/interpreter/activation/terminalEnvVarCollectionService.ts b/src/client/interpreter/activation/terminalEnvVarCollectionService.ts index f5af71b3f2ca..7010a87a203c 100644 --- a/src/client/interpreter/activation/terminalEnvVarCollectionService.ts +++ b/src/client/interpreter/activation/terminalEnvVarCollectionService.ts @@ -8,7 +8,13 @@ import { IApplicationShell, IApplicationEnvironment } from '../../common/applica import { inTerminalEnvVarExperiment } from '../../common/experiments/helpers'; import { IPlatformService } from '../../common/platform/types'; import { identifyShellFromShellPath } from '../../common/terminal/shellDetectors/baseShellDetector'; -import { IExtensionContext, IExperimentService, Resource, IDisposableRegistry } from '../../common/types'; +import { + IExtensionContext, + IExperimentService, + Resource, + IDisposableRegistry, + IConfigurationService, +} from '../../common/types'; import { Deferred, createDeferred } from '../../common/utils/async'; import { Interpreters } from '../../common/utils/localize'; import { traceDecoratorVerbose, traceVerbose } from '../../logging'; @@ -36,6 +42,7 @@ export class TerminalEnvVarCollectionService implements IExtensionSingleActivati @inject(IApplicationEnvironment) private applicationEnvironment: IApplicationEnvironment, @inject(IDisposableRegistry) private disposables: IDisposableRegistry, @inject(IEnvironmentActivationService) private environmentActivationService: IEnvironmentActivationService, + @inject(IConfigurationService) private readonly configurationService: IConfigurationService, ) {} public async activate(): Promise { @@ -68,6 +75,11 @@ export class TerminalEnvVarCollectionService implements IExtensionSingleActivati } public async _applyCollection(resource: Resource, shell = this.applicationEnvironment.shell): Promise { + const settings = this.configurationService.getSettings(resource); + if (!settings.terminal.activateEnvironment) { + traceVerbose('Activating environments in terminal is disabled for', resource?.fsPath); + return; + } const env = await this.environmentActivationService.getActivatedEnvironmentVariables( resource, undefined, diff --git a/src/client/interpreter/configuration/interpreterSelector/commands/setInterpreter.ts b/src/client/interpreter/configuration/interpreterSelector/commands/setInterpreter.ts index 9a1d643269ef..c0876ff518dd 100644 --- a/src/client/interpreter/configuration/interpreterSelector/commands/setInterpreter.ts +++ b/src/client/interpreter/configuration/interpreterSelector/commands/setInterpreter.ts @@ -415,6 +415,7 @@ export class SetInterpreterCommand extends BaseInterpreterSelectorCommand implem if (isInterpreterQuickPickItem(item) && isProblematicCondaEnvironment(item.interpreter)) { if (!items[i].label.includes(Octicons.Warning)) { items[i].label = `${Octicons.Warning} ${items[i].label}`; + items[i].tooltip = InterpreterQuickPickList.condaEnvWithoutPythonTooltip; } } }); diff --git a/src/client/interpreter/configuration/types.ts b/src/client/interpreter/configuration/types.ts index 90facb7fe640..2f3882e1246e 100644 --- a/src/client/interpreter/configuration/types.ts +++ b/src/client/interpreter/configuration/types.ts @@ -52,11 +52,7 @@ export interface IInterpreterQuickPickItem extends QuickPickItem { interpreter: PythonEnvironment; } -export interface ISpecialQuickPickItem { - label: string; - description?: string; - detail?: string; - alwaysShow: boolean; +export interface ISpecialQuickPickItem extends QuickPickItem { path?: string; } diff --git a/src/client/jupyter/jupyterIntegration.ts b/src/client/jupyter/jupyterIntegration.ts index 16da174f3178..a0fa0fedb63f 100644 --- a/src/client/jupyter/jupyterIntegration.ts +++ b/src/client/jupyter/jupyterIntegration.ts @@ -8,7 +8,7 @@ import { inject, injectable, named } from 'inversify'; import { dirname } from 'path'; import { CancellationToken, Event, Extension, Memento, Uri } from 'vscode'; import type { SemVer } from 'semver'; -import { IWorkspaceService } from '../common/application/types'; +import { IContextKeyManager, IWorkspaceService } from '../common/application/types'; import { JUPYTER_EXTENSION_ID, PYLANCE_EXTENSION_ID } from '../common/constants'; import { InterpreterUri, ModuleInstallFlags } from '../common/installer/types'; import { @@ -35,6 +35,7 @@ import { import { PythonEnvironment } from '../pythonEnvironments/info'; import { IDataViewerDataProvider, IJupyterUriProvider } from './types'; import { PylanceApi } from '../activation/node/pylanceApi'; +import { ExtensionContextKey } from '../common/application/contextKeys'; /** * This allows Python extension to update Product enum without breaking Jupyter. * I.e. we have a strict contract, else using numbers (in enums) is bound to break across products. @@ -201,9 +202,11 @@ export class JupyterExtensionIntegration { @inject(IComponentAdapter) private pyenvs: IComponentAdapter, @inject(IWorkspaceService) private workspaceService: IWorkspaceService, @inject(ICondaService) private readonly condaService: ICondaService, + @inject(IContextKeyManager) private readonly contextManager: IContextKeyManager, ) {} public registerApi(jupyterExtensionApi: JupyterExtensionApi): JupyterExtensionApi | undefined { + this.contextManager.setContext(ExtensionContextKey.IsJupyterInstalled, true); if (!this.workspaceService.isTrusted) { this.workspaceService.onDidGrantWorkspaceTrust(() => this.registerApi(jupyterExtensionApi)); return undefined; diff --git a/src/client/jupyter/requireJupyterPrompt.ts b/src/client/jupyter/requireJupyterPrompt.ts new file mode 100644 index 000000000000..3e6878ba4269 --- /dev/null +++ b/src/client/jupyter/requireJupyterPrompt.ts @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { inject, injectable } from 'inversify'; +import { IExtensionSingleActivationService } from '../activation/types'; +import { IApplicationShell, ICommandManager } from '../common/application/types'; +import { Common, Interpreters } from '../common/utils/localize'; +import { Commands, JUPYTER_EXTENSION_ID } from '../common/constants'; +import { IDisposable, IDisposableRegistry } from '../common/types'; +import { sendTelemetryEvent } from '../telemetry'; +import { EventName } from '../telemetry/constants'; + +@injectable() +export class RequireJupyterPrompt implements IExtensionSingleActivationService { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: true }; + + constructor( + @inject(IApplicationShell) private readonly appShell: IApplicationShell, + @inject(ICommandManager) private readonly commandManager: ICommandManager, + @inject(IDisposableRegistry) private readonly disposables: IDisposable[], + ) {} + + public async activate(): Promise { + this.disposables.push(this.commandManager.registerCommand(Commands.InstallJupyter, () => this._showPrompt())); + } + + public async _showPrompt(): Promise { + const prompts = [Common.bannerLabelYes, Common.bannerLabelNo]; + const telemetrySelections: ['Yes', 'No'] = ['Yes', 'No']; + const selection = await this.appShell.showInformationMessage(Interpreters.requireJupyter, ...prompts); + sendTelemetryEvent(EventName.REQUIRE_JUPYTER_PROMPT, undefined, { + selection: selection ? telemetrySelections[prompts.indexOf(selection)] : undefined, + }); + if (!selection) { + return; + } + if (selection === prompts[0]) { + await this.commandManager.executeCommand( + 'workbench.extensions.installExtension', + JUPYTER_EXTENSION_ID, + undefined, + ); + } + } +} diff --git a/src/client/linters/errorHandlers/standard.ts b/src/client/linters/errorHandlers/standard.ts index 6bd2d3c8e115..f6e04b50ff19 100644 --- a/src/client/linters/errorHandlers/standard.ts +++ b/src/client/linters/errorHandlers/standard.ts @@ -1,7 +1,6 @@ import { l10n, Uri } from 'vscode'; import { IApplicationShell } from '../../common/application/types'; -import { STANDARD_OUTPUT_CHANNEL } from '../../common/constants'; -import { ExecutionInfo, IOutputChannel } from '../../common/types'; +import { ExecutionInfo, ILogOutputChannel } from '../../common/types'; import { traceError, traceLog } from '../../logging'; import { ILinterManager, LinterId } from '../types'; import { BaseErrorHandler } from './baseErrorHandler'; @@ -29,7 +28,7 @@ export class StandardErrorHandler extends BaseErrorHandler { private async displayLinterError(linterId: LinterId) { const message = l10n.t("There was an error in running the linter '{0}'", linterId); const appShell = this.serviceContainer.get(IApplicationShell); - const outputChannel = this.serviceContainer.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); + const outputChannel = this.serviceContainer.get(ILogOutputChannel); const action = await appShell.showErrorMessage(message, 'View Errors'); if (action === 'View Errors') { outputChannel.show(); diff --git a/src/client/logging/index.ts b/src/client/logging/index.ts index b28cadc74682..39d5652e100a 100644 --- a/src/client/logging/index.ts +++ b/src/client/logging/index.ts @@ -11,7 +11,7 @@ import { StopWatch } from '../common/utils/stopWatch'; import { sendTelemetryEvent } from '../telemetry'; import { EventName } from '../telemetry/constants'; import { FileLogger } from './fileLogger'; -import { Arguments, ILogging, LoggingLevelSettingType, LogLevel, TraceDecoratorType, TraceOptions } from './types'; +import { Arguments, ILogging, LogLevel, TraceDecoratorType, TraceOptions } from './types'; import { argsToLogString, returnValueToLogString } from './util'; const DEFAULT_OPTS: TraceOptions = TraceOptions.Arguments | TraceOptions.ReturnValue; @@ -26,21 +26,6 @@ export function registerLogger(logger: ILogging): Disposable { }; } -const logLevelMap: Map = new Map([ - ['error', LogLevel.Error], - ['warn', LogLevel.Warn], - ['info', LogLevel.Info], - ['debug', LogLevel.Debug], - ['none', LogLevel.Off], - ['off', LogLevel.Off], - [undefined, LogLevel.Error], -]); - -let globalLoggingLevel: LogLevel; -export function setLoggingLevel(level?: LoggingLevelSettingType): void { - globalLoggingLevel = logLevelMap.get(level) ?? LogLevel.Error; -} - export function initializeFileLogging(disposables: Disposable[]): void { if (process.env.VSC_PYTHON_LOG_FILE) { const fileLogger = new FileLogger(createWriteStream(process.env.VSC_PYTHON_LOG_FILE)); @@ -54,27 +39,19 @@ export function traceLog(...args: Arguments): void { } export function traceError(...args: Arguments): void { - if (globalLoggingLevel >= LogLevel.Error) { - loggers.forEach((l) => l.traceError(...args)); - } + loggers.forEach((l) => l.traceError(...args)); } export function traceWarn(...args: Arguments): void { - if (globalLoggingLevel >= LogLevel.Warn) { - loggers.forEach((l) => l.traceWarn(...args)); - } + loggers.forEach((l) => l.traceWarn(...args)); } export function traceInfo(...args: Arguments): void { - if (globalLoggingLevel >= LogLevel.Info) { - loggers.forEach((l) => l.traceInfo(...args)); - } + loggers.forEach((l) => l.traceInfo(...args)); } export function traceVerbose(...args: Arguments): void { - if (globalLoggingLevel >= LogLevel.Debug) { - loggers.forEach((l) => l.traceVerbose(...args)); - } + loggers.forEach((l) => l.traceVerbose(...args)); } /** Logging Decorators go here */ @@ -89,7 +66,7 @@ export function traceDecoratorInfo(message: string): TraceDecoratorType { return createTracingDecorator({ message, opts: DEFAULT_OPTS, level: LogLevel.Info }); } export function traceDecoratorWarn(message: string): TraceDecoratorType { - return createTracingDecorator({ message, opts: DEFAULT_OPTS, level: LogLevel.Warn }); + return createTracingDecorator({ message, opts: DEFAULT_OPTS, level: LogLevel.Warning }); } // Information about a function/method call. @@ -223,7 +200,7 @@ export function logTo(logLevel: LogLevel, ...args: Arguments): void { case LogLevel.Error: traceError(...args); break; - case LogLevel.Warn: + case LogLevel.Warning: traceWarn(...args); break; case LogLevel.Info: diff --git a/src/client/logging/outputChannelLogger.ts b/src/client/logging/outputChannelLogger.ts index 27ea0031c017..40505d33a735 100644 --- a/src/client/logging/outputChannelLogger.ts +++ b/src/client/logging/outputChannelLogger.ts @@ -2,34 +2,29 @@ // Licensed under the MIT License. import * as util from 'util'; -import { OutputChannel } from 'vscode'; +import { LogOutputChannel } from 'vscode'; import { Arguments, ILogging } from './types'; -import { getTimeForLogging } from './util'; - -function formatMessage(level?: string, ...data: Arguments): string { - return level ? `[${level.toUpperCase()} ${getTimeForLogging()}]: ${util.format(...data)}` : util.format(...data); -} export class OutputChannelLogger implements ILogging { - constructor(private readonly channel: OutputChannel) {} + constructor(private readonly channel: LogOutputChannel) {} public traceLog(...data: Arguments): void { this.channel.appendLine(util.format(...data)); } public traceError(...data: Arguments): void { - this.channel.appendLine(formatMessage('error', ...data)); + this.channel.error(util.format(...data)); } public traceWarn(...data: Arguments): void { - this.channel.appendLine(formatMessage('warn', ...data)); + this.channel.warn(util.format(...data)); } public traceInfo(...data: Arguments): void { - this.channel.appendLine(formatMessage('info', ...data)); + this.channel.info(util.format(...data)); } public traceVerbose(...data: Arguments): void { - this.channel.appendLine(formatMessage('debug', ...data)); + this.channel.debug(util.format(...data)); } } diff --git a/src/client/logging/settings.ts b/src/client/logging/settings.ts deleted file mode 100644 index 97dce79700d2..000000000000 --- a/src/client/logging/settings.ts +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -import { LoggingLevelSettingType } from './types'; -import { WorkspaceService } from '../common/application/workspace'; - -/** - * Uses Workspace service to query for `python.logging.level` setting and returns it. - */ -export function getLoggingLevel(): LoggingLevelSettingType | 'off' { - const workspace = new WorkspaceService(); - return workspace.getConfiguration('python').get('logging.level') ?? 'error'; -} diff --git a/src/client/logging/types.ts b/src/client/logging/types.ts index 92b514d218f9..c05800868512 100644 --- a/src/client/logging/types.ts +++ b/src/client/logging/types.ts @@ -4,17 +4,17 @@ /* eslint-disable @typescript-eslint/ban-types */ /* eslint-disable @typescript-eslint/no-explicit-any */ -export type LoggingLevelSettingType = 'off' | 'error' | 'warn' | 'info' | 'debug'; +export type Arguments = unknown[]; + export enum LogLevel { Off = 0, - Error = 10, - Warn = 20, - Info = 30, - Debug = 40, + Trace = 1, + Debug = 2, + Info = 3, + Warning = 4, + Error = 5, } -export type Arguments = unknown[]; - export interface ILogging { traceLog(...data: Arguments): void; traceError(...data: Arguments): void; diff --git a/src/client/proposedApiTypes.ts b/src/client/proposedApiTypes.ts index 1b772b406644..13ad5af543ec 100644 --- a/src/client/proposedApiTypes.ts +++ b/src/client/proposedApiTypes.ts @@ -1,4 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -export interface ProposedExtensionAPI {} +export interface ProposedExtensionAPI { + /** + * Top level proposed APIs should go here. + */ +} diff --git a/src/client/pythonEnvironments/base/info/environmentInfoService.ts b/src/client/pythonEnvironments/base/info/environmentInfoService.ts index d3d3a252c99f..6a981d21b6df 100644 --- a/src/client/pythonEnvironments/base/info/environmentInfoService.ts +++ b/src/client/pythonEnvironments/base/info/environmentInfoService.ts @@ -7,7 +7,7 @@ import { createDeferred, Deferred, sleep } from '../../../common/utils/async'; import { createRunningWorkerPool, IWorkerPool, QueuePosition } from '../../../common/utils/workerPool'; import { getInterpreterInfo, InterpreterInformation } from './interpreter'; import { buildPythonExecInfo } from '../../exec'; -import { traceError, traceInfo, traceWarn } from '../../../logging'; +import { traceError, traceVerbose, traceWarn } from '../../../logging'; import { Conda, CONDA_ACTIVATION_TIMEOUT, isCondaEnvironment } from '../../common/environmentManagers/conda'; import { PythonEnvInfo, PythonEnvKind } from '.'; import { normCasePath } from '../../common/externalDependencies'; @@ -153,7 +153,7 @@ class EnvironmentInfoService implements IEnvironmentInfoService { // as complete env info may not be available at this time. const isCondaEnv = env.kind === PythonEnvKind.Conda || (await isCondaEnvironment(env.executable.filename)); if (isCondaEnv) { - traceInfo( + traceVerbose( `Validating ${env.executable.filename} normally failed with error, falling back to using conda run: (${reason})`, ); if (this.condaRunWorkerPool === undefined) { diff --git a/src/client/pythonEnvironments/base/info/interpreter.ts b/src/client/pythonEnvironments/base/info/interpreter.ts index 5341a8f561ae..d0cb1f13f8f8 100644 --- a/src/client/pythonEnvironments/base/info/interpreter.ts +++ b/src/client/pythonEnvironments/base/info/interpreter.ts @@ -8,7 +8,7 @@ import { InterpreterInfoJson, } from '../../../common/process/internal/scripts'; import { Architecture } from '../../../common/utils/platform'; -import { traceError, traceInfo } from '../../../logging'; +import { traceError, traceVerbose } from '../../../logging'; import { shellExecute } from '../../common/externalDependencies'; import { copyPythonExecInfo, PythonExecInfo } from '../../exec'; import { parseVersion } from './pythonVersion'; @@ -102,6 +102,6 @@ export async function getInterpreterInfo( traceError(`Failed to parse interpreter information for >> ${quoted} << with ${ex}`); return undefined; } - traceInfo(`Found interpreter for >> ${quoted} <<: ${JSON.stringify(json)}`); + traceVerbose(`Found interpreter for >> ${quoted} <<: ${JSON.stringify(json)}`); return extractInterpreterInfo(python.pythonExecutable, json); } diff --git a/src/client/pythonEnvironments/base/locators/composite/envsCollectionCache.ts b/src/client/pythonEnvironments/base/locators/composite/envsCollectionCache.ts index d3ece41f163d..dadab2512e16 100644 --- a/src/client/pythonEnvironments/base/locators/composite/envsCollectionCache.ts +++ b/src/client/pythonEnvironments/base/locators/composite/envsCollectionCache.ts @@ -3,7 +3,7 @@ import { Event } from 'vscode'; import { isTestExecution } from '../../../../common/constants'; -import { traceInfo, traceVerbose } from '../../../../logging'; +import { traceVerbose } from '../../../../logging'; import { arePathsSame, getFileInfo, pathExists } from '../../../common/externalDependencies'; import { PythonEnvInfo, PythonEnvKind } from '../../info'; import { areEnvsDeepEqual, areSameEnv, getEnvPath } from '../../info/env'; @@ -225,7 +225,7 @@ export class PythonEnvInfoCache extends PythonEnvsWatcher p.id === provider.id).length > 0) { + throw new Error(`Create Environment provider with id ${provider.id} already registered`); + } this._createEnvProviders.push(provider); } @@ -63,15 +67,36 @@ export function registerCreateEnvironmentFeatures( return handleCreateEnvironmentCommand(providers, options); }, ), - ); - disposables.push(registerCreateEnvironmentProvider(new VenvCreationProvider(interpreterQuickPick))); - disposables.push(registerCreateEnvironmentProvider(condaCreationProvider())); - disposables.push( - onCreateEnvironmentExited(async (e: CreateEnvironmentExitedEventArgs) => { - if (e.result?.path && e.options?.selectEnvironment) { - await interpreterPathService.update(e.result.uri, ConfigurationTarget.WorkspaceFolder, e.result.path); - showInformationMessage(`${CreateEnv.informEnvCreation} ${pathUtils.getDisplayName(e.result.path)}`); + registerCreateEnvironmentProvider(new VenvCreationProvider(interpreterQuickPick)), + registerCreateEnvironmentProvider(condaCreationProvider()), + onCreateEnvironmentExited(async (e: EnvironmentDidCreateEvent) => { + if (e.path && e.options?.selectEnvironment) { + await interpreterPathService.update( + e.workspaceFolder?.uri, + ConfigurationTarget.WorkspaceFolder, + e.path, + ); + showInformationMessage(`${CreateEnv.informEnvCreation} ${pathUtils.getDisplayName(e.path)}`); } }), ); } + +export function buildEnvironmentCreationApi(): ProposedCreateEnvironmentAPI { + return { + onWillCreateEnvironment: onCreateEnvironmentStarted, + onDidCreateEnvironment: onCreateEnvironmentExited, + createEnvironment: async ( + options?: CreateEnvironmentOptions | undefined, + ): Promise => { + const providers = _createEnvironmentProviders.getAll(); + try { + return await handleCreateEnvironmentCommand(providers, options); + } catch (err) { + return { path: undefined, workspaceFolder: undefined, action: undefined, error: err as Error }; + } + }, + registerCreateEnvironmentProvider: (provider: CreateEnvironmentProvider) => + registerCreateEnvironmentProvider(provider), + }; +} diff --git a/src/client/pythonEnvironments/creation/createEnvironment.ts b/src/client/pythonEnvironments/creation/createEnvironment.ts index bdeaf89ba82d..4593ff1abf92 100644 --- a/src/client/pythonEnvironments/creation/createEnvironment.ts +++ b/src/client/pythonEnvironments/creation/createEnvironment.ts @@ -11,15 +11,15 @@ import { } from '../../common/vscodeApis/windowApis'; import { traceError, traceVerbose } from '../../logging'; import { - CreateEnvironmentExitedEventArgs, CreateEnvironmentOptions, - CreateEnvironmentProvider, CreateEnvironmentResult, - CreateEnvironmentStartedEventArgs, -} from './types'; + CreateEnvironmentProvider, + EnvironmentWillCreateEvent, + EnvironmentDidCreateEvent, +} from './proposed.createEnvApis'; -const onCreateEnvironmentStartedEvent = new EventEmitter(); -const onCreateEnvironmentExitedEvent = new EventEmitter(); +const onCreateEnvironmentStartedEvent = new EventEmitter(); +const onCreateEnvironmentExitedEvent = new EventEmitter(); let startedEventCount = 0; @@ -32,14 +32,20 @@ function fireStartedEvent(options?: CreateEnvironmentOptions): void { startedEventCount += 1; } -function fireExitedEvent(result?: CreateEnvironmentResult, options?: CreateEnvironmentOptions, error?: unknown): void { - onCreateEnvironmentExitedEvent.fire({ result, options, error }); +function fireExitedEvent(result?: CreateEnvironmentResult, options?: CreateEnvironmentOptions, error?: Error): void { + onCreateEnvironmentExitedEvent.fire({ + options, + workspaceFolder: result?.workspaceFolder, + path: result?.path, + action: result?.action, + error: error || result?.error, + }); startedEventCount -= 1; } export function getCreationEvents(): { - onCreateEnvironmentStarted: Event; - onCreateEnvironmentExited: Event; + onCreateEnvironmentStarted: Event; + onCreateEnvironmentExited: Event; isCreatingEnvironment: () => boolean; } { return { @@ -54,7 +60,7 @@ async function createEnvironment( options: CreateEnvironmentOptions, ): Promise { let result: CreateEnvironmentResult | undefined; - let err: unknown | undefined; + let err: Error | undefined; try { fireStartedEvent(options); result = await provider.createEnvironment(options); @@ -65,7 +71,7 @@ async function createEnvironment( return undefined; } } - err = ex; + err = ex as Error; throw err; } finally { fireExitedEvent(result, options, err); @@ -185,11 +191,7 @@ export async function handleCreateEnvironmentCommand( const action = await MultiStepNode.run(envTypeStep); if (options?.showBackButton) { if (action === MultiStepAction.Back || action === MultiStepAction.Cancel) { - result = { - path: result?.path, - uri: result?.uri, - action: action === MultiStepAction.Back ? 'Back' : 'Cancel', - }; + result = { action, workspaceFolder: undefined, path: undefined, error: undefined }; } } diff --git a/src/client/pythonEnvironments/creation/proposed.createEnvApis.ts b/src/client/pythonEnvironments/creation/proposed.createEnvApis.ts new file mode 100644 index 000000000000..52209a5a31d0 --- /dev/null +++ b/src/client/pythonEnvironments/creation/proposed.createEnvApis.ts @@ -0,0 +1,143 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License + +import { Event, Disposable, WorkspaceFolder } from 'vscode'; +import { EnvironmentTools } from '../../apiTypes'; + +export type CreateEnvironmentUserActions = 'Back' | 'Cancel'; +export type EnvironmentProviderId = string; + +/** + * Options used when creating a Python environment. + */ +export interface CreateEnvironmentOptions { + /** + * Default `true`. If `true`, the environment creation handler is expected to install packages. + */ + installPackages?: boolean; + + /** + * Default `true`. If `true`, the environment creation provider is expected to add the environment to ignore list + * for the source control. + */ + ignoreSourceControl?: boolean; + + /** + * Default `false`. If `true` the creation provider should show back button when showing QuickPick or QuickInput. + */ + showBackButton?: boolean; + + /** + * Default `true`. If `true`, the environment after creation will be selected. + */ + selectEnvironment?: boolean; +} + +/** + * Params passed on `onWillCreateEnvironment` event handler. + */ +export interface EnvironmentWillCreateEvent { + /** + * Options used to create a Python environment. + */ + options: CreateEnvironmentOptions | undefined; +} + +/** + * Params passed on `onDidCreateEnvironment` event handler. + */ +export interface EnvironmentDidCreateEvent extends CreateEnvironmentResult { + /** + * Options used to create the Python environment. + */ + options: CreateEnvironmentOptions | undefined; +} + +export interface CreateEnvironmentResult { + /** + * Workspace folder associated with the environment. + */ + workspaceFolder: WorkspaceFolder | undefined; + + /** + * Path to the executable python in the environment + */ + path: string | undefined; + + /** + * User action that resulted in exit from the create environment flow. + */ + action: CreateEnvironmentUserActions | undefined; + + /** + * Error if any occurred during environment creation. + */ + error: Error | undefined; +} + +/** + * Extensions that want to contribute their own environment creation can do that by registering an object + * that implements this interface. + */ +export interface CreateEnvironmentProvider { + /** + * This API is called when user selects this provider from a QuickPick to select the type of environment + * user wants. This API is expected to show a QuickPick or QuickInput to get the user input and return + * the path to the Python executable in the environment. + * + * @param {CreateEnvironmentOptions} [options] Options used to create a Python environment. + * + * @returns a promise that resolves to the path to the + * Python executable in the environment. Or any action taken by the user, such as back or cancel. + */ + createEnvironment(options?: CreateEnvironmentOptions): Promise; + + /** + * Unique ID for the creation provider, typically : + */ + id: EnvironmentProviderId; + + /** + * Display name for the creation provider. + */ + name: string; + + /** + * Description displayed to the user in the QuickPick to select environment provider. + */ + description: string; + + /** + * Tools used to manage this environment. e.g., ['conda']. In the most to least priority order + * for resolving and working with the environment. + */ + tools: EnvironmentTools[]; +} + +export interface ProposedCreateEnvironmentAPI { + /** + * This API can be used to detect when the environment creation starts for any registered + * provider (including internal providers). This will also receive any options passed in + * or defaults used to create environment. + */ + onWillCreateEnvironment: Event; + + /** + * This API can be used to detect when the environment provider exits for any registered + * provider (including internal providers). This will also receive created environment path, + * any errors, or user actions taken from the provider. + */ + onDidCreateEnvironment: Event; + + /** + * This API will show a QuickPick to select an environment provider from available list of + * providers. Based on the selection the `createEnvironment` will be called on the provider. + */ + createEnvironment(options?: CreateEnvironmentOptions): Promise; + + /** + * This API should be called to register an environment creation provider. It returns + * a (@link Disposable} which can be used to remove the registration. + */ + registerCreateEnvironmentProvider(provider: CreateEnvironmentProvider): Disposable; +} diff --git a/src/client/pythonEnvironments/creation/provider/condaCreationProvider.ts b/src/client/pythonEnvironments/creation/provider/condaCreationProvider.ts index 5bf032f9f65f..39cd40afd41a 100644 --- a/src/client/pythonEnvironments/creation/provider/condaCreationProvider.ts +++ b/src/client/pythonEnvironments/creation/provider/condaCreationProvider.ts @@ -5,12 +5,7 @@ import { CancellationToken, ProgressLocation, WorkspaceFolder } from 'vscode'; import * as path from 'path'; import { Commands, PVSC_EXTENSION_ID } from '../../../common/constants'; import { traceError, traceLog } from '../../../logging'; -import { - CreateEnvironmentOptions, - CreateEnvironmentProgress, - CreateEnvironmentProvider, - CreateEnvironmentResult, -} from '../types'; +import { CreateEnvironmentProgress } from '../types'; import { pickWorkspaceFolder } from '../common/workspaceSelection'; import { execObservable } from '../../../common/process/rawProcessApis'; import { createDeferred } from '../../../common/utils/async'; @@ -28,6 +23,11 @@ import { CONDA_ENV_EXISTING_MARKER, } from './condaProgressAndTelemetry'; import { splitLines } from '../../../common/stringUtils'; +import { + CreateEnvironmentOptions, + CreateEnvironmentResult, + CreateEnvironmentProvider, +} from '../proposed.createEnvApis'; function generateCommandArgs(version?: string, options?: CreateEnvironmentOptions): string[] { let addGitIgnore = true; @@ -247,7 +247,7 @@ async function createEnvironment(options?: CreateEnvironmentOptions): Promise(); @@ -266,7 +266,7 @@ export class VenvProgressAndTelemetry { (progress: CreateEnvironmentProgress) => { progress.report({ message: CreateEnv.Venv.upgradingPip }); sendTelemetryEvent(EventName.ENVIRONMENT_INSTALLING_PACKAGES, undefined, { - environmentType: 'microvenv', + environmentType: 'venv', using: 'pipUpgrade', }); return undefined; diff --git a/src/client/pythonEnvironments/creation/provider/venvUtils.ts b/src/client/pythonEnvironments/creation/provider/venvUtils.ts index 4c88deac5b45..7c6505082fbc 100644 --- a/src/client/pythonEnvironments/creation/provider/venvUtils.ts +++ b/src/client/pythonEnvironments/creation/provider/venvUtils.ts @@ -9,7 +9,7 @@ import { CancellationToken, QuickPickItem, RelativePattern, WorkspaceFolder } fr import { CreateEnv } from '../../../common/utils/localize'; import { MultiStepAction, MultiStepNode, showQuickPickWithBack } from '../../../common/vscodeApis/windowApis'; import { findFiles } from '../../../common/vscodeApis/workspaceApis'; -import { traceError, traceInfo, traceVerbose } from '../../../logging'; +import { traceError, traceVerbose } from '../../../logging'; const exclude = '**/{.venv*,.git,.nox,.tox,.conda,site-packages,__pypackages__}/**'; async function getPipRequirementsFiles( @@ -133,10 +133,10 @@ export async function pickPackagesToInstall( hasBuildSystem = tomlHasBuildSystem(toml); if (!hasBuildSystem) { - traceInfo('Create env: Found toml without build system. So we will not use editable install.'); + traceVerbose('Create env: Found toml without build system. So we will not use editable install.'); } if (extras.length === 0) { - traceInfo('Create env: Found toml without optional dependencies.'); + traceVerbose('Create env: Found toml without optional dependencies.'); } } else if (context === MultiStepAction.Back) { // This step is not really used so just go back diff --git a/src/client/pythonEnvironments/creation/types.ts b/src/client/pythonEnvironments/creation/types.ts index 6dbd8adfe1f4..611af5bf7841 100644 --- a/src/client/pythonEnvironments/creation/types.ts +++ b/src/client/pythonEnvironments/creation/types.ts @@ -1,52 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License -import { Progress, Uri } from 'vscode'; +import { Progress } from 'vscode'; export interface CreateEnvironmentProgress extends Progress<{ message?: string; increment?: number }> {} - -export interface CreateEnvironmentOptions { - /** - * Default `true`. If `true`, the environment creation handler is expected to install packages. - */ - installPackages?: boolean; - - /** - * Default `true`. If `true`, the environment creation provider is expected to add the environment to ignore list - * for the source control. - */ - ignoreSourceControl?: boolean; - - /** - * Default `false`. If `true` the creation provider should show back button when showing QuickPick or QuickInput. - */ - showBackButton?: boolean; - - /** - * Default `true`. If `true`, the environment will be selected as the environment to be used for the workspace. - */ - selectEnvironment?: boolean; -} - -export interface CreateEnvironmentResult { - path: string | undefined; - uri: Uri | undefined; - action?: 'Back' | 'Cancel'; -} - -export interface CreateEnvironmentStartedEventArgs { - options: CreateEnvironmentOptions | undefined; -} - -export interface CreateEnvironmentExitedEventArgs { - result: CreateEnvironmentResult | undefined; - error?: unknown; - options: CreateEnvironmentOptions | undefined; -} - -export interface CreateEnvironmentProvider { - createEnvironment(options?: CreateEnvironmentOptions): Promise; - name: string; - description: string; - id: string; -} diff --git a/src/client/pythonEnvironments/info/interpreter.ts b/src/client/pythonEnvironments/info/interpreter.ts index 8925c7a6feb6..8fe9bc7d49a8 100644 --- a/src/client/pythonEnvironments/info/interpreter.ts +++ b/src/client/pythonEnvironments/info/interpreter.ts @@ -48,7 +48,7 @@ function extractInterpreterInfo(python: string, raw: InterpreterInfoJson): Inter } type Logger = { - info(msg: string): void; + verbose(msg: string): void; error(msg: string): void; }; @@ -85,7 +85,7 @@ export async function getInterpreterInfo( } const json = parse(result.stdout); if (logger) { - logger.info(`Found interpreter for ${argv}`); + logger.verbose(`Found interpreter for ${argv}`); } if (!json) { return undefined; diff --git a/src/client/telemetry/constants.ts b/src/client/telemetry/constants.ts index d30a2683562c..778eb7cc39a0 100644 --- a/src/client/telemetry/constants.ts +++ b/src/client/telemetry/constants.ts @@ -30,6 +30,7 @@ export enum EventName { PYTHON_INTERPRETER_ACTIVATE_ENVIRONMENT_PROMPT = 'PYTHON_INTERPRETER_ACTIVATE_ENVIRONMENT_PROMPT', PYTHON_NOT_INSTALLED_PROMPT = 'PYTHON_NOT_INSTALLED_PROMPT', CONDA_INHERIT_ENV_PROMPT = 'CONDA_INHERIT_ENV_PROMPT', + REQUIRE_JUPYTER_PROMPT = 'REQUIRE_JUPYTER_PROMPT', ACTIVATED_CONDA_ENV_LAUNCH = 'ACTIVATED_CONDA_ENV_LAUNCH', ENVFILE_VARIABLE_SUBSTITUTION = 'ENVFILE_VARIABLE_SUBSTITUTION', ENVFILE_WORKSPACE = 'ENVFILE_WORKSPACE', diff --git a/src/client/telemetry/index.ts b/src/client/telemetry/index.ts index df2b454cbe27..9e86f29201ee 100644 --- a/src/client/telemetry/index.ts +++ b/src/client/telemetry/index.ts @@ -1315,6 +1315,22 @@ export interface IEventNamePropertyMapping { */ selection: 'Allow' | 'Close' | undefined; }; + /** + * Telemetry event sent with details when user attempts to run in interactive window when Jupyter is not installed. + */ + /* __GDPR__ + "conda_inherit_env_prompt" : { + "selection" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "karrtikr" } + } + */ + [EventName.REQUIRE_JUPYTER_PROMPT]: { + /** + * `Yes` When 'Yes' option is selected + * `No` When 'No' option is selected + * `undefined` When 'x' is selected + */ + selection: 'Yes' | 'No' | undefined; + }; /** * Telemetry event sent with details when user clicks the prompt with the following message: * diff --git a/src/client/tensorBoard/tensorBoardSession.ts b/src/client/tensorBoard/tensorBoardSession.ts index 5d5b9cd1bb35..1d24e8c313f7 100644 --- a/src/client/tensorBoard/tensorBoardSession.ts +++ b/src/client/tensorBoard/tensorBoardSession.ts @@ -47,7 +47,7 @@ import { ImportTracker } from '../telemetry/importTracker'; import { TensorBoardPromptSelection, TensorBoardSessionStartResult } from './constants'; import { IMultiStepInputFactory } from '../common/utils/multiStepInput'; import { ModuleInstallFlags } from '../common/installer/types'; -import { traceError, traceInfo } from '../logging'; +import { traceError, traceVerbose } from '../logging'; enum Messages { JumpToSource = 'jump_to_source', @@ -190,7 +190,7 @@ export class TensorBoardSession { // any of their open documents, also try to install the torch-tb-plugin // package, but don't block if installing that fails. private async ensurePrerequisitesAreInstalled() { - traceInfo('Ensuring TensorBoard package is installed into active interpreter'); + traceVerbose('Ensuring TensorBoard package is installed into active interpreter'); const interpreter = (await this.interpreterService.getActiveInterpreter()) || (await this.commandManager.executeCommand('python.setInterpreter')); @@ -344,7 +344,7 @@ export class TensorBoardSession { const settings = this.configurationService.getSettings(); const settingValue = settings.tensorBoard?.logDirectory; if (settingValue) { - traceInfo(`Using log directory resolved by python.tensorBoard.logDirectory setting: ${settingValue}`); + traceVerbose(`Using log directory resolved by python.tensorBoard.logDirectory setting: ${settingValue}`); return settingValue; } // No log directory in settings. Ask the user which directory to use @@ -406,7 +406,7 @@ export class TensorBoardSession { const result = await this.applicationShell.withProgress( progressOptions, (_progress: Progress, token: CancellationToken) => { - traceInfo(`Starting TensorBoard with log directory ${logDir}...`); + traceVerbose(`Starting TensorBoard with log directory ${logDir}...`); const spawnTensorBoard = this.waitForTensorBoardStart(observable); const userCancellation = createPromiseFromCancellation({ @@ -421,7 +421,7 @@ export class TensorBoardSession { switch (result) { case 'canceled': - traceInfo('Canceled starting TensorBoard session.'); + traceVerbose('Canceled starting TensorBoard session.'); sendTelemetryEvent( EventName.TENSORBOARD_SESSION_DAEMON_STARTUP_DURATION, sessionStartStopwatch.elapsedTime, @@ -468,7 +468,7 @@ export class TensorBoardSession { this.url = match[1]; urlThatTensorBoardIsRunningAt.resolve('success'); } - traceInfo(output.out); + traceVerbose(output.out); } else if (output.source === 'stderr') { traceError(output.out); } @@ -482,7 +482,7 @@ export class TensorBoardSession { } private async showPanel() { - traceInfo('Showing TensorBoard panel'); + traceVerbose('Showing TensorBoard panel'); const panel = this.webviewPanel || (await this.createPanel()); panel.reveal(); this._active = true; diff --git a/src/client/tensorBoard/tensorBoardSessionProvider.ts b/src/client/tensorBoard/tensorBoardSessionProvider.ts index f689dab6a6ba..53878bd543c2 100644 --- a/src/client/tensorBoard/tensorBoardSessionProvider.ts +++ b/src/client/tensorBoard/tensorBoardSessionProvider.ts @@ -17,7 +17,7 @@ import { } from '../common/types'; import { IMultiStepInputFactory } from '../common/utils/multiStepInput'; import { IInterpreterService } from '../interpreter/contracts'; -import { traceError, traceInfo } from '../logging'; +import { traceError, traceVerbose } from '../logging'; import { sendTelemetryEvent } from '../telemetry'; import { EventName } from '../telemetry/constants'; import { TensorBoardEntrypoint, TensorBoardEntrypointTrigger } from './constants'; @@ -94,7 +94,7 @@ export class TensorBoardSessionProvider implements IExtensionSingleActivationSer } private async createNewSession(): Promise { - traceInfo('Starting new TensorBoard session...'); + traceVerbose('Starting new TensorBoard session...'); try { const newSession = new TensorBoardSession( this.installer, diff --git a/src/client/testing/common/testConfigurationManager.ts b/src/client/testing/common/testConfigurationManager.ts index c2b050cb524a..be3f0109da02 100644 --- a/src/client/testing/common/testConfigurationManager.ts +++ b/src/client/testing/common/testConfigurationManager.ts @@ -5,12 +5,12 @@ import { IFileSystem } from '../../common/platform/types'; import { IInstaller } from '../../common/types'; import { createDeferred } from '../../common/utils/async'; import { IServiceContainer } from '../../ioc/types'; -import { traceInfo } from '../../logging'; +import { traceVerbose } from '../../logging'; import { UNIT_TEST_PRODUCTS } from './constants'; import { ITestConfigSettingsService, ITestConfigurationManager, UnitTestProduct } from './types'; function handleCancelled(): void { - traceInfo('testing configuration (in UI) cancelled'); + traceVerbose('testing configuration (in UI) cancelled'); throw Error('cancelled'); } diff --git a/src/client/testing/constants.ts b/src/client/testing/constants.ts deleted file mode 100644 index f8c4bb749498..000000000000 --- a/src/client/testing/constants.ts +++ /dev/null @@ -1,4 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -export const TEST_OUTPUT_CHANNEL = 'TEST_OUTPUT_CHANNEL'; diff --git a/src/client/testing/testController/common/server.ts b/src/client/testing/testController/common/server.ts index 1b7d46c995e7..6849f0f8969a 100644 --- a/src/client/testing/testController/common/server.ts +++ b/src/client/testing/testController/common/server.ts @@ -99,6 +99,7 @@ export class PythonTestServer implements ITestServer, Disposable { token: options.token, cwd: options.cwd, throwOnStdErr: true, + outputChannel: options.outChannel, }; // Create the Python environment in which to execute the command. diff --git a/src/client/testing/testController/controller.ts b/src/client/testing/testController/controller.ts index b2be2d9c3054..e745adb019b2 100644 --- a/src/client/testing/testController/controller.ts +++ b/src/client/testing/testController/controller.ts @@ -20,7 +20,7 @@ import { IExtensionSingleActivationService } from '../../activation/types'; import { ICommandManager, IWorkspaceService } from '../../common/application/types'; import * as constants from '../../common/constants'; import { IPythonExecutionFactory } from '../../common/process/types'; -import { IConfigurationService, IDisposableRegistry, Resource } from '../../common/types'; +import { IConfigurationService, IDisposableRegistry, ITestOutputChannel, Resource } from '../../common/types'; import { DelayedTrigger, IDelayedTrigger } from '../../common/utils/delayTrigger'; import { noop } from '../../common/utils/misc'; import { IInterpreterService } from '../../interpreter/contracts'; @@ -92,6 +92,7 @@ export class PythonTestController implements ITestController, IExtensionSingleAc @inject(ICommandManager) private readonly commandManager: ICommandManager, @inject(IPythonExecutionFactory) private readonly pythonExecFactory: IPythonExecutionFactory, @inject(ITestDebugLauncher) private readonly debugLauncher: ITestDebugLauncher, + @inject(ITestOutputChannel) private readonly testOutputChannel: ITestOutputChannel, ) { this.refreshCancellation = new CancellationTokenSource(); @@ -158,12 +159,28 @@ export class PythonTestController implements ITestController, IExtensionSingleAc let executionAdapter: ITestExecutionAdapter; let testProvider: TestProvider; if (settings.testing.unittestEnabled) { - discoveryAdapter = new UnittestTestDiscoveryAdapter(this.pythonTestServer, this.configSettings); - executionAdapter = new UnittestTestExecutionAdapter(this.pythonTestServer, this.configSettings); + discoveryAdapter = new UnittestTestDiscoveryAdapter( + this.pythonTestServer, + this.configSettings, + this.testOutputChannel, + ); + executionAdapter = new UnittestTestExecutionAdapter( + this.pythonTestServer, + this.configSettings, + this.testOutputChannel, + ); testProvider = UNITTEST_PROVIDER; } else { - discoveryAdapter = new PytestTestDiscoveryAdapter(this.pythonTestServer, this.configSettings); - executionAdapter = new PytestTestExecutionAdapter(this.pythonTestServer, this.configSettings); + discoveryAdapter = new PytestTestDiscoveryAdapter( + this.pythonTestServer, + this.configSettings, + this.testOutputChannel, + ); + executionAdapter = new PytestTestExecutionAdapter( + this.pythonTestServer, + this.configSettings, + this.testOutputChannel, + ); testProvider = PYTEST_PROVIDER; } diff --git a/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts b/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts index 2787ea180bdb..d2f6f6448777 100644 --- a/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts +++ b/src/client/testing/testController/pytest/pytestDiscoveryAdapter.ts @@ -7,7 +7,7 @@ import { IPythonExecutionFactory, SpawnOptions, } from '../../../common/process/types'; -import { IConfigurationService } from '../../../common/types'; +import { IConfigurationService, ITestOutputChannel } from '../../../common/types'; import { createDeferred, Deferred } from '../../../common/utils/async'; import { EXTENSION_ROOT_DIR } from '../../../constants'; import { traceVerbose } from '../../../logging'; @@ -21,7 +21,11 @@ export class PytestTestDiscoveryAdapter implements ITestDiscoveryAdapter { private deferred: Deferred | undefined; - constructor(public testServer: ITestServer, public configSettings: IConfigurationService) { + constructor( + public testServer: ITestServer, + public configSettings: IConfigurationService, + private readonly outputChannel: ITestOutputChannel, + ) { testServer.onDataReceived(this.onDataReceivedHandler, this); } @@ -45,16 +49,15 @@ export class PytestTestDiscoveryAdapter implements ITestDiscoveryAdapter { // const { pytestArgs } = settings.testing; // traceVerbose(pytestArgs); - // this.cwd = uri.fsPath; // return this.runPytestDiscovery(uri, executionFactory); // } async runPytestDiscovery(uri: Uri, executionFactory: IPythonExecutionFactory): Promise { const deferred = createDeferred(); - this.deferred = createDeferred(); const relativePathToPytest = 'pythonFiles'; const fullPluginPath = path.join(EXTENSION_ROOT_DIR, relativePathToPytest); const uuid = this.testServer.createUUID(uri.fsPath); + this.promiseMap.set(uuid, deferred); const settings = this.configSettings.getSettings(uri); const { pytestArgs } = settings.testing; @@ -69,6 +72,7 @@ export class PytestTestDiscoveryAdapter implements ITestDiscoveryAdapter { TEST_UUID: uuid.toString(), TEST_PORT: this.testServer.getPort().toString(), }, + outputChannel: this.outputChannel, }; // Create the Python environment in which to execute the command. @@ -86,7 +90,6 @@ export class PytestTestDiscoveryAdapter implements ITestDiscoveryAdapter { } catch (ex) { console.error(ex); } - return deferred.promise; } } diff --git a/src/client/testing/testController/pytest/pytestExecutionAdapter.ts b/src/client/testing/testController/pytest/pytestExecutionAdapter.ts index 25b7d92b767b..1f6b504074cd 100644 --- a/src/client/testing/testController/pytest/pytestExecutionAdapter.ts +++ b/src/client/testing/testController/pytest/pytestExecutionAdapter.ts @@ -2,7 +2,7 @@ // Licensed under the MIT License. import { Uri } from 'vscode'; -import { IConfigurationService } from '../../../common/types'; +import { IConfigurationService, ITestOutputChannel } from '../../../common/types'; import { createDeferred, Deferred } from '../../../common/utils/async'; import { traceVerbose } from '../../../logging'; import { DataReceivedEvent, ExecutionTestPayload, ITestExecutionAdapter, ITestServer } from '../common/types'; @@ -16,7 +16,11 @@ export class PytestTestExecutionAdapter implements ITestExecutionAdapter { private deferred: Deferred | undefined; - constructor(public testServer: ITestServer, public configSettings: IConfigurationService) { + constructor( + public testServer: ITestServer, + public configSettings: IConfigurationService, + private readonly outputChannel: ITestOutputChannel, + ) { testServer.onDataReceived(this.onDataReceivedHandler, this); } @@ -31,63 +35,67 @@ export class PytestTestExecutionAdapter implements ITestExecutionAdapter { // ** Old version of discover tests. async runTests(uri: Uri, testIds: string[], debugBool?: boolean): Promise { traceVerbose(uri, testIds, debugBool); + // TODO:Remove this line after enabling runs + this.outputChannel.appendLine('Running tests.'); this.deferred = createDeferred(); return this.deferred.promise; } +} - // public async runTests( - // uri: Uri, - // testIds: string[], - // debugBool?: boolean, - // executionFactory?: IPythonExecutionFactory, - // ): Promise { - // const deferred = createDeferred(); - // const relativePathToPytest = 'pythonFiles'; - // const fullPluginPath = path.join(EXTENSION_ROOT_DIR, relativePathToPytest); - // this.configSettings.isTestExecution(); - // const uuid = this.testServer.createUUID(uri.fsPath); - // this.promiseMap.set(uuid, deferred); - // const settings = this.configSettings.getSettings(uri); - // const { pytestArgs } = settings.testing; +// public async runTests( +// uri: Uri, +// testIds: string[], +// debugBool?: boolean, +// executionFactory?: IPythonExecutionFactory, +// ): Promise { +// const deferred = createDeferred(); +// const relativePathToPytest = 'pythonFiles'; +// const fullPluginPath = path.join(EXTENSION_ROOT_DIR, relativePathToPytest); +// this.configSettings.isTestExecution(); +// const uuid = this.testServer.createUUID(uri.fsPath); +// this.promiseMap.set(uuid, deferred); +// const settings = this.configSettings.getSettings(uri); +// const { pytestArgs } = settings.testing; - // const pythonPathParts: string[] = process.env.PYTHONPATH?.split(path.delimiter) ?? []; - // const pythonPathCommand = [fullPluginPath, ...pythonPathParts].join(path.delimiter); +// const pythonPathParts: string[] = process.env.PYTHONPATH?.split(path.delimiter) ?? []; +// const pythonPathCommand = [fullPluginPath, ...pythonPathParts].join(path.delimiter); - // const spawnOptions: SpawnOptions = { - // cwd: uri.fsPath, - // throwOnStdErr: true, - // extraVariables: { - // PYTHONPATH: pythonPathCommand, - // TEST_UUID: uuid.toString(), - // TEST_PORT: this.testServer.getPort().toString(), - // }, - // }; +// const spawnOptions: SpawnOptions = { +// cwd: uri.fsPath, +// throwOnStdErr: true, +// extraVariables: { +// PYTHONPATH: pythonPathCommand, +// TEST_UUID: uuid.toString(), +// TEST_PORT: this.testServer.getPort().toString(), +// }, +// outputChannel: this.outputChannel, +// }; - // // Create the Python environment in which to execute the command. - // const creationOptions: ExecutionFactoryCreateWithEnvironmentOptions = { - // allowEnvironmentFetchExceptions: false, - // resource: uri, - // }; - // // need to check what will happen in the exec service is NOT defined and is null - // const execService = await executionFactory?.createActivatedEnvironment(creationOptions); +// // Create the Python environment in which to execute the command. +// const creationOptions: ExecutionFactoryCreateWithEnvironmentOptions = { +// allowEnvironmentFetchExceptions: false, +// resource: uri, +// }; +// // need to check what will happen in the exec service is NOT defined and is null +// const execService = await executionFactory?.createActivatedEnvironment(creationOptions); - // const testIdsString = testIds.join(' '); - // console.debug('what to do with debug bool?', debugBool); - // try { - // execService?.exec(['-m', 'pytest', '-p', 'vscode_pytest', testIdsString].concat(pytestArgs), spawnOptions); - // } catch (ex) { - // console.error(ex); - // } +// const testIdsString = testIds.join(' '); +// console.debug('what to do with debug bool?', debugBool); +// try { +// execService?.exec(['-m', 'pytest', '-p', 'vscode_pytest', testIdsString].concat(pytestArgs), spawnOptions); +// } catch (ex) { +// console.error(ex); +// } - // return deferred.promise; - // } - // } +// return deferred.promise; +// } +// } - // function buildExecutionCommand(args: string[]): TestExecutionCommand { - // const executionScript = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'unittestadapter', 'execution.py'); +// function buildExecutionCommand(args: string[]): TestExecutionCommand { +// const executionScript = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'unittestadapter', 'execution.py'); - // return { - // script: executionScript, - // args: ['--udiscovery', ...args], - // }; -} +// return { +// script: executionScript, +// args: ['--udiscovery', ...args], +// }; +// } diff --git a/src/client/testing/testController/pytest/runner.ts b/src/client/testing/testController/pytest/runner.ts index 7bc045b34687..83da819b861a 100644 --- a/src/client/testing/testController/pytest/runner.ts +++ b/src/client/testing/testController/pytest/runner.ts @@ -1,12 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { inject, injectable, named } from 'inversify'; +import { inject, injectable } from 'inversify'; import { Disposable, TestItem, TestRun, TestRunProfileKind } from 'vscode'; -import { IOutputChannel } from '../../../common/types'; +import { ITestOutputChannel } from '../../../common/types'; import { PYTEST_PROVIDER } from '../../common/constants'; import { ITestDebugLauncher, ITestRunner, LaunchOptions, Options } from '../../common/types'; -import { TEST_OUTPUT_CHANNEL } from '../../constants'; import { filterArguments, getOptionValues } from '../common/argumentsHelper'; import { createTemporaryFile } from '../common/externalDependencies'; import { updateResultFromJunitXml } from '../common/resultsHelper'; @@ -32,7 +31,7 @@ export class PytestRunner implements ITestsRunner { constructor( @inject(ITestRunner) private readonly runner: ITestRunner, @inject(ITestDebugLauncher) private readonly debugLauncher: ITestDebugLauncher, - @inject(IOutputChannel) @named(TEST_OUTPUT_CHANNEL) private readonly outputChannel: IOutputChannel, + @inject(ITestOutputChannel) private readonly outputChannel: ITestOutputChannel, ) {} public async runTests( @@ -96,6 +95,10 @@ export class PytestRunner implements ITestsRunner { testArgs.push('--capture', 'no'); } + if (options.debug && !testArgs.some((a) => a.startsWith('--no-cov'))) { + testArgs.push('--no-cov'); + } + // Positional arguments control the tests to be run. const rawData = idToRawData.get(testNode.id); if (!rawData) { diff --git a/src/client/testing/testController/unittest/runner.ts b/src/client/testing/testController/unittest/runner.ts index d6bbb59ee640..d558f051eccb 100644 --- a/src/client/testing/testController/unittest/runner.ts +++ b/src/client/testing/testController/unittest/runner.ts @@ -1,16 +1,15 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { injectable, inject, named } from 'inversify'; +import { injectable, inject } from 'inversify'; import { Location, TestController, TestItem, TestMessage, TestRun, TestRunProfileKind } from 'vscode'; import * as internalScripts from '../../../common/process/internal/scripts'; import { splitLines } from '../../../common/stringUtils'; -import { IOutputChannel } from '../../../common/types'; +import { ITestOutputChannel } from '../../../common/types'; import { noop } from '../../../common/utils/misc'; -import { traceError, traceInfo } from '../../../logging'; +import { traceError, traceVerbose } from '../../../logging'; import { UNITTEST_PROVIDER } from '../../common/constants'; import { ITestRunner, ITestDebugLauncher, IUnitTestSocketServer, LaunchOptions, Options } from '../../common/types'; -import { TEST_OUTPUT_CHANNEL } from '../../constants'; import { clearAllChildren, getTestCaseNodes } from '../common/testItemUtilities'; import { ITestRun, ITestsRunner, TestData, TestRunInstanceOptions, TestRunOptions } from '../common/types'; import { fixLogLines } from '../common/utils'; @@ -33,7 +32,7 @@ export class UnittestRunner implements ITestsRunner { constructor( @inject(ITestRunner) private readonly runner: ITestRunner, @inject(ITestDebugLauncher) private readonly debugLauncher: ITestDebugLauncher, - @inject(IOutputChannel) @named(TEST_OUTPUT_CHANNEL) private readonly outputChannel: IOutputChannel, + @inject(ITestOutputChannel) private readonly outputChannel: ITestOutputChannel, @inject(IUnitTestSocketServer) private readonly server: IUnitTestSocketServer, ) {} @@ -99,7 +98,7 @@ export class UnittestRunner implements ITestsRunner { traceError(`${message} ${data.join(' ')}`); }); this.server.on('log', (message: string, ...data: string[]) => { - traceInfo(`${message} ${data.join(' ')}`); + traceVerbose(`${message} ${data.join(' ')}`); }); this.server.on('connect', noop); this.server.on('start', noop); diff --git a/src/client/testing/testController/unittest/testDiscoveryAdapter.ts b/src/client/testing/testController/unittest/testDiscoveryAdapter.ts index fa411b50216f..3f8ecb5797d3 100644 --- a/src/client/testing/testController/unittest/testDiscoveryAdapter.ts +++ b/src/client/testing/testController/unittest/testDiscoveryAdapter.ts @@ -3,7 +3,7 @@ import * as path from 'path'; import { Uri } from 'vscode'; -import { IConfigurationService } from '../../../common/types'; +import { IConfigurationService, ITestOutputChannel } from '../../../common/types'; import { createDeferred, Deferred } from '../../../common/utils/async'; import { EXTENSION_ROOT_DIR } from '../../../constants'; import { @@ -23,7 +23,11 @@ export class UnittestTestDiscoveryAdapter implements ITestDiscoveryAdapter { private cwd: string | undefined; - constructor(public testServer: ITestServer, public configSettings: IConfigurationService) { + constructor( + public testServer: ITestServer, + public configSettings: IConfigurationService, + private readonly outputChannel: ITestOutputChannel, + ) { testServer.onDataReceived(this.onDataReceivedHandler, this); } @@ -50,6 +54,7 @@ export class UnittestTestDiscoveryAdapter implements ITestDiscoveryAdapter { command, cwd: this.cwd, uuid, + outChannel: this.outputChannel, }; this.promiseMap.set(uuid, deferred); diff --git a/src/client/testing/testController/unittest/testExecutionAdapter.ts b/src/client/testing/testController/unittest/testExecutionAdapter.ts index 98ba8bfeb937..b39e0cd29560 100644 --- a/src/client/testing/testController/unittest/testExecutionAdapter.ts +++ b/src/client/testing/testController/unittest/testExecutionAdapter.ts @@ -3,7 +3,7 @@ import * as path from 'path'; import { Uri } from 'vscode'; -import { IConfigurationService } from '../../../common/types'; +import { IConfigurationService, ITestOutputChannel } from '../../../common/types'; import { createDeferred, Deferred } from '../../../common/utils/async'; import { EXTENSION_ROOT_DIR } from '../../../constants'; import { @@ -24,7 +24,11 @@ export class UnittestTestExecutionAdapter implements ITestExecutionAdapter { private cwd: string | undefined; - constructor(public testServer: ITestServer, public configSettings: IConfigurationService) { + constructor( + public testServer: ITestServer, + public configSettings: IConfigurationService, + private readonly outputChannel: ITestOutputChannel, + ) { testServer.onDataReceived(this.onDataReceivedHandler, this); } @@ -37,7 +41,6 @@ export class UnittestTestExecutionAdapter implements ITestExecutionAdapter { } public async runTests(uri: Uri, testIds: string[], debugBool?: boolean): Promise { - const deferred = createDeferred(); const settings = this.configSettings.getSettings(uri); const { unittestArgs } = settings.testing; @@ -52,12 +55,14 @@ export class UnittestTestExecutionAdapter implements ITestExecutionAdapter { uuid, debugBool, testIds, + outChannel: this.outputChannel, }; + const deferred = createDeferred(); this.promiseMap.set(uuid, deferred); - // send test command to server - // server fire onDataReceived event once it gets response + // Send test command to server. + // Server fire onDataReceived event once it gets response. this.testServer.sendCommand(options); return deferred.promise; diff --git a/src/client/testing/testController/workspaceTestAdapter.ts b/src/client/testing/testController/workspaceTestAdapter.ts index dc9c65c431fd..8ce51e5dab56 100644 --- a/src/client/testing/testController/workspaceTestAdapter.ts +++ b/src/client/testing/testController/workspaceTestAdapter.ts @@ -22,6 +22,7 @@ import { sendTelemetryEvent } from '../../telemetry'; import { EventName } from '../../telemetry/constants'; import { TestProvider } from '../types'; import { + clearAllChildren, createErrorTestItem, DebugTestTag, ErrorTestItemOptions, @@ -135,8 +136,11 @@ export class WorkspaceTestAdapter { } if (rawTestExecData !== undefined && rawTestExecData.result !== undefined) { + // Map which holds the subtest information for each test item. + const subTestStats: Map = new Map(); + + // iterate through payload and update the UI accordingly. for (const keyTemp of Object.keys(rawTestExecData.result)) { - // check for result and update the UI accordingly. const testCases: TestItem[] = []; // grab leaf level test items @@ -147,7 +151,6 @@ export class WorkspaceTestAdapter { if ( rawTestExecData.result[keyTemp].outcome === 'failure' || - rawTestExecData.result[keyTemp].outcome === 'subtest-failure' || rawTestExecData.result[keyTemp].outcome === 'passed-unexpected' ) { const rawTraceback = rawTestExecData.result[keyTemp].traceback ?? ''; @@ -175,8 +178,7 @@ export class WorkspaceTestAdapter { }); } else if ( rawTestExecData.result[keyTemp].outcome === 'success' || - rawTestExecData.result[keyTemp].outcome === 'expected-failure' || - rawTestExecData.result[keyTemp].outcome === 'subtest-passed' + rawTestExecData.result[keyTemp].outcome === 'expected-failure' ) { const grabTestItem = this.runIdToTestItem.get(keyTemp); const grabVSid = this.runIdToVSid.get(keyTemp); @@ -203,6 +205,73 @@ export class WorkspaceTestAdapter { } }); } + } else if (rawTestExecData.result[keyTemp].outcome === 'subtest-failure') { + // split on " " since the subtest ID has the parent test ID in the first part of the ID. + const parentTestCaseId = keyTemp.split(' ')[0]; + const parentTestItem = this.runIdToTestItem.get(parentTestCaseId); + const data = rawTestExecData.result[keyTemp]; + // find the subtest's parent test item + if (parentTestItem) { + const subtestStats = subTestStats.get(parentTestCaseId); + if (subtestStats) { + subtestStats.failed += 1; + } else { + subTestStats.set(parentTestCaseId, { failed: 1, passed: 0 }); + runInstance.appendOutput(fixLogLines(`${parentTestCaseId} [subtests]:\r\n`)); + // clear since subtest items don't persist between runs + clearAllChildren(parentTestItem); + } + const subtestId = keyTemp; + const subTestItem = testController?.createTestItem(subtestId, subtestId); + runInstance.appendOutput(fixLogLines(`${subtestId} Failed\r\n`)); + // create a new test item for the subtest + if (subTestItem) { + const traceback = data.traceback ?? ''; + const text = `${data.subtest} Failed: ${data.message ?? data.outcome}\r\n${traceback}\r\n`; + runInstance.appendOutput(fixLogLines(text)); + parentTestItem.children.add(subTestItem); + runInstance.started(subTestItem); + const message = new TestMessage(rawTestExecData?.result[keyTemp].message ?? ''); + if (parentTestItem.uri && parentTestItem.range) { + message.location = new Location(parentTestItem.uri, parentTestItem.range); + } + runInstance.failed(subTestItem, message); + } else { + throw new Error('Unable to create new child node for subtest'); + } + } else { + throw new Error('Parent test item not found'); + } + } else if (rawTestExecData.result[keyTemp].outcome === 'subtest-success') { + // split on " " since the subtest ID has the parent test ID in the first part of the ID. + const parentTestCaseId = keyTemp.split(' ')[0]; + const parentTestItem = this.runIdToTestItem.get(parentTestCaseId); + + // find the subtest's parent test item + if (parentTestItem) { + const subtestStats = subTestStats.get(parentTestCaseId); + if (subtestStats) { + subtestStats.passed += 1; + } else { + subTestStats.set(parentTestCaseId, { failed: 0, passed: 1 }); + runInstance.appendOutput(fixLogLines(`${parentTestCaseId} [subtests]:\r\n`)); + // clear since subtest items don't persist between runs + clearAllChildren(parentTestItem); + } + const subtestId = keyTemp; + const subTestItem = testController?.createTestItem(subtestId, subtestId); + // create a new test item for the subtest + if (subTestItem) { + parentTestItem.children.add(subTestItem); + runInstance.started(subTestItem); + runInstance.passed(subTestItem); + runInstance.appendOutput(fixLogLines(`${subtestId} Passed\r\n`)); + } else { + throw new Error('Unable to create new child node for subtest'); + } + } else { + throw new Error('Parent test item not found'); + } } } } diff --git a/src/test/activation/node/analysisOptions.unit.test.ts b/src/test/activation/node/analysisOptions.unit.test.ts index d4781f7e03e5..47c0cb0cc838 100644 --- a/src/test/activation/node/analysisOptions.unit.test.ts +++ b/src/test/activation/node/analysisOptions.unit.test.ts @@ -9,7 +9,7 @@ import { NodeLanguageServerAnalysisOptions } from '../../../client/activation/no import { ILanguageServerOutputChannel } from '../../../client/activation/types'; import { IWorkspaceService } from '../../../client/common/application/types'; import { PYTHON, PYTHON_LANGUAGE } from '../../../client/common/constants'; -import { IExperimentService, IOutputChannel } from '../../../client/common/types'; +import { IExperimentService, ILogOutputChannel } from '../../../client/common/types'; suite('Pylance Language Server - Analysis Options', () => { class TestClass extends NodeLanguageServerAnalysisOptions { @@ -28,13 +28,13 @@ suite('Pylance Language Server - Analysis Options', () => { } let analysisOptions: TestClass; - let outputChannel: IOutputChannel; + let outputChannel: ILogOutputChannel; let lsOutputChannel: typemoq.IMock; let workspace: typemoq.IMock; let experimentService: IExperimentService; setup(() => { - outputChannel = typemoq.Mock.ofType().object; + outputChannel = typemoq.Mock.ofType().object; workspace = typemoq.Mock.ofType(); workspace.setup((w) => w.isVirtualWorkspace).returns(() => false); const workspaceConfig = typemoq.Mock.ofType(); diff --git a/src/test/activation/node/lspInteractiveWindowMiddlewareAddon.unit.test.ts b/src/test/activation/node/lspInteractiveWindowMiddlewareAddon.unit.test.ts index 472dea147503..256e57a5d724 100644 --- a/src/test/activation/node/lspInteractiveWindowMiddlewareAddon.unit.test.ts +++ b/src/test/activation/node/lspInteractiveWindowMiddlewareAddon.unit.test.ts @@ -19,7 +19,7 @@ import { } from '../../../client/interpreter/contracts'; import { IInterpreterSelector } from '../../../client/interpreter/configuration/types'; import { IEnvironmentActivationService } from '../../../client/interpreter/activation/types'; -import { IWorkspaceService } from '../../../client/common/application/types'; +import { IContextKeyManager, IWorkspaceService } from '../../../client/common/application/types'; import { MockMemento } from '../../mocks/mementos'; suite('Pylance Language Server - Interactive Window LSP Notebooks', () => { @@ -41,6 +41,7 @@ suite('Pylance Language Server - Interactive Window LSP Notebooks', () => { mock(), mock(), mock(), + mock(), ); jupyterApi.registerGetNotebookUriForTextDocumentUriFunction(getNotebookUriFunction); }); diff --git a/src/test/activation/outputChannel.unit.test.ts b/src/test/activation/outputChannel.unit.test.ts index f7d827b4782a..f8f38783bb0e 100644 --- a/src/test/activation/outputChannel.unit.test.ts +++ b/src/test/activation/outputChannel.unit.test.ts @@ -7,7 +7,7 @@ import { expect } from 'chai'; import * as TypeMoq from 'typemoq'; import { LanguageServerOutputChannel } from '../../client/activation/common/outputChannel'; import { IApplicationShell, ICommandManager } from '../../client/common/application/types'; -import { IOutputChannel } from '../../client/common/types'; +import { ILogOutputChannel } from '../../client/common/types'; import { sleep } from '../../client/common/utils/async'; import { OutputChannelNames } from '../../client/common/utils/localize'; @@ -15,10 +15,10 @@ suite('Language Server Output Channel', () => { let appShell: TypeMoq.IMock; let languageServerOutputChannel: LanguageServerOutputChannel; let commandManager: TypeMoq.IMock; - let output: TypeMoq.IMock; + let output: TypeMoq.IMock; setup(() => { appShell = TypeMoq.Mock.ofType(); - output = TypeMoq.Mock.ofType(); + output = TypeMoq.Mock.ofType(); commandManager = TypeMoq.Mock.ofType(); languageServerOutputChannel = new LanguageServerOutputChannel(appShell.object, commandManager.object, []); }); diff --git a/src/test/common/installer/moduleInstaller.unit.test.ts b/src/test/common/installer/moduleInstaller.unit.test.ts index 5c77f8165492..01ac0e315555 100644 --- a/src/test/common/installer/moduleInstaller.unit.test.ts +++ b/src/test/common/installer/moduleInstaller.unit.test.ts @@ -13,7 +13,6 @@ import { anything, instance, mock, when } from 'ts-mockito'; import * as TypeMoq from 'typemoq'; import { CancellationTokenSource, Disposable, ProgressLocation, Uri, WorkspaceConfiguration } from 'vscode'; import { IApplicationShell, IWorkspaceService } from '../../../client/common/application/types'; -import { STANDARD_OUTPUT_CHANNEL } from '../../../client/common/constants'; import { CondaInstaller } from '../../../client/common/installer/condaInstaller'; import { ModuleInstaller } from '../../../client/common/installer/moduleInstaller'; import { PipEnvInstaller, pipenvName } from '../../../client/common/installer/pipEnvInstaller'; @@ -32,7 +31,7 @@ import { IConfigurationService, IDisposableRegistry, IInstaller, - IOutputChannel, + ILogOutputChannel, IPythonSettings, Product, } from '../../../client/common/types'; @@ -89,7 +88,7 @@ suite('Module Installer', () => { return super.elevatedInstall(execPath, args); } } - let outputChannel: TypeMoq.IMock; + let outputChannel: TypeMoq.IMock; let appShell: TypeMoq.IMock; let serviceContainer: TypeMoq.IMock; @@ -104,9 +103,9 @@ suite('Module Installer', () => { traceLogStub = sinon.stub(logging, 'traceLog'); serviceContainer = TypeMoq.Mock.ofType(); - outputChannel = TypeMoq.Mock.ofType(); + outputChannel = TypeMoq.Mock.ofType(); serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(IOutputChannel), TypeMoq.It.isValue(STANDARD_OUTPUT_CHANNEL))) + .setup((c) => c.get(TypeMoq.It.isValue(ILogOutputChannel))) .returns(() => outputChannel.object); appShell = TypeMoq.Mock.ofType(); serviceContainer.setup((c) => c.get(TypeMoq.It.isValue(IApplicationShell))).returns(() => appShell.object); diff --git a/src/test/common/terminals/activator/index.unit.test.ts b/src/test/common/terminals/activator/index.unit.test.ts index 9dff5a800cad..a50b946c391f 100644 --- a/src/test/common/terminals/activator/index.unit.test.ts +++ b/src/test/common/terminals/activator/index.unit.test.ts @@ -12,7 +12,12 @@ import { ITerminalActivator, ITerminalHelper, } from '../../../../client/common/terminal/types'; -import { IConfigurationService, IPythonSettings, ITerminalSettings } from '../../../../client/common/types'; +import { + IConfigurationService, + IExperimentService, + IPythonSettings, + ITerminalSettings, +} from '../../../../client/common/types'; suite('Terminal Activator', () => { let activator: TerminalActivator; @@ -20,9 +25,12 @@ suite('Terminal Activator', () => { let handler1: TypeMoq.IMock; let handler2: TypeMoq.IMock; let terminalSettings: TypeMoq.IMock; + let experimentService: TypeMoq.IMock; setup(() => { baseActivator = TypeMoq.Mock.ofType(); terminalSettings = TypeMoq.Mock.ofType(); + experimentService = TypeMoq.Mock.ofType(); + experimentService.setup((e) => e.inExperimentSync(TypeMoq.It.isAny())).returns(() => false); handler1 = TypeMoq.Mock.ofType(); handler2 = TypeMoq.Mock.ofType(); const configService = TypeMoq.Mock.ofType(); @@ -37,7 +45,12 @@ suite('Terminal Activator', () => { protected initialize() { this.baseActivator = baseActivator.object; } - })(TypeMoq.Mock.ofType().object, [handler1.object, handler2.object], configService.object); + })( + TypeMoq.Mock.ofType().object, + [handler1.object, handler2.object], + configService.object, + experimentService.object, + ); }); async function testActivationAndHandlers( activationSuccessful: boolean, diff --git a/src/test/format/formatter.unit.test.ts b/src/test/format/formatter.unit.test.ts index cf0297628a83..05970d0c71f6 100644 --- a/src/test/format/formatter.unit.test.ts +++ b/src/test/format/formatter.unit.test.ts @@ -13,7 +13,6 @@ import { IApplicationShell, IWorkspaceService } from '../../client/common/applic import { WorkspaceService } from '../../client/common/application/workspace'; import { PythonSettings } from '../../client/common/configSettings'; import { ConfigurationService } from '../../client/common/configuration/service'; -import { STANDARD_OUTPUT_CHANNEL } from '../../client/common/constants'; import { PythonToolExecutionService } from '../../client/common/process/pythonToolService'; import { IPythonToolExecutionService } from '../../client/common/process/types'; import { @@ -21,7 +20,7 @@ import { IConfigurationService, IDisposableRegistry, IFormattingSettings, - IOutputChannel, + ILogOutputChannel, IPythonSettings, } from '../../client/common/types'; import { AutoPep8Formatter } from '../../client/formatters/autoPep8Formatter'; @@ -37,7 +36,7 @@ import { MockOutputChannel } from '../mockClasses'; suite('Formatting - Test Arguments', () => { let container: IServiceContainer; - let outputChannel: IOutputChannel; + let outputChannel: ILogOutputChannel; let workspace: IWorkspaceService; let settings: IPythonSettings; const workspaceUri = Uri.file(__dirname); @@ -85,9 +84,7 @@ suite('Formatting - Test Arguments', () => { when(configService.getSettings(anything())).thenReturn(instance(settings)); when(workspace.getWorkspaceFolder(anything())).thenReturn({ name: '', index: 0, uri: workspaceUri }); - when(container.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL)).thenReturn( - instance(outputChannel), - ); + when(container.get(ILogOutputChannel)).thenReturn(instance(outputChannel)); when(container.get(IApplicationShell)).thenReturn(instance(appShell)); when(container.get(IFormatterHelper)).thenReturn(formatterHelper); when(container.get(IWorkspaceService)).thenReturn(instance(workspace)); diff --git a/src/test/interpreters/activation/terminalEnvVarCollectionService.unit.test.ts b/src/test/interpreters/activation/terminalEnvVarCollectionService.unit.test.ts index 4ac04cf1ee22..324de1174584 100644 --- a/src/test/interpreters/activation/terminalEnvVarCollectionService.unit.test.ts +++ b/src/test/interpreters/activation/terminalEnvVarCollectionService.unit.test.ts @@ -11,7 +11,13 @@ import { EnvironmentVariableCollection, ProgressLocation, Uri } from 'vscode'; import { IApplicationShell, IApplicationEnvironment } from '../../../client/common/application/types'; import { TerminalEnvVarActivation } from '../../../client/common/experiments/groups'; import { IPlatformService } from '../../../client/common/platform/types'; -import { IExtensionContext, IExperimentService, Resource } from '../../../client/common/types'; +import { + IExtensionContext, + IExperimentService, + Resource, + IConfigurationService, + IPythonSettings, +} from '../../../client/common/types'; import { Interpreters } from '../../../client/common/utils/localize'; import { getOSType } from '../../../client/common/utils/platform'; import { defaultShells } from '../../../client/interpreter/activation/service'; @@ -57,6 +63,10 @@ suite('Terminal Environment Variable Collection Service', () => { }) .thenResolve(); environmentActivationService = mock(); + const configService = mock(); + when(configService.getSettings(anything())).thenReturn(({ + terminal: { activateEnvironment: true }, + } as unknown) as IPythonSettings); terminalEnvVarCollectionService = new TerminalEnvVarCollectionService( instance(platform), instance(interpreterService), @@ -66,6 +76,7 @@ suite('Terminal Environment Variable Collection Service', () => { instance(applicationEnvironment), [], instance(environmentActivationService), + instance(configService), ); }); diff --git a/src/test/jupyter/requireJupyterPrompt.unit.test.ts b/src/test/jupyter/requireJupyterPrompt.unit.test.ts new file mode 100644 index 000000000000..0eb6c9e06958 --- /dev/null +++ b/src/test/jupyter/requireJupyterPrompt.unit.test.ts @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { mock, instance, verify, anything, when } from 'ts-mockito'; +import { IApplicationShell, ICommandManager } from '../../client/common/application/types'; +import { Commands, JUPYTER_EXTENSION_ID } from '../../client/common/constants'; +import { IDisposableRegistry } from '../../client/common/types'; +import { Common, Interpreters } from '../../client/common/utils/localize'; +import { RequireJupyterPrompt } from '../../client/jupyter/requireJupyterPrompt'; + +suite('RequireJupyterPrompt Unit Tests', () => { + let requireJupyterPrompt: RequireJupyterPrompt; + let appShell: IApplicationShell; + let commandManager: ICommandManager; + let disposables: IDisposableRegistry; + + setup(() => { + appShell = mock(); + commandManager = mock(); + disposables = mock(); + + requireJupyterPrompt = new RequireJupyterPrompt( + instance(appShell), + instance(commandManager), + instance(disposables), + ); + }); + + test('Activation registers command', async () => { + await requireJupyterPrompt.activate(); + + verify(commandManager.registerCommand(Commands.InstallJupyter, anything())).once(); + }); + + test('Show prompt with Yes selection installs Jupyter extension', async () => { + when( + appShell.showInformationMessage(Interpreters.requireJupyter, Common.bannerLabelYes, Common.bannerLabelNo), + ).thenReturn(Promise.resolve(Common.bannerLabelYes)); + + await requireJupyterPrompt.activate(); + await requireJupyterPrompt._showPrompt(); + + verify( + commandManager.executeCommand('workbench.extensions.installExtension', JUPYTER_EXTENSION_ID, undefined), + ).once(); + }); + + test('Show prompt with No selection does not install Jupyter extension', async () => { + when( + appShell.showInformationMessage(Interpreters.requireJupyter, Common.bannerLabelYes, Common.bannerLabelNo), + ).thenReturn(Promise.resolve(Common.bannerLabelNo)); + + await requireJupyterPrompt.activate(); + await requireJupyterPrompt._showPrompt(); + + verify( + commandManager.executeCommand('workbench.extensions.installExtension', JUPYTER_EXTENSION_ID, undefined), + ).never(); + }); +}); diff --git a/src/test/linters/common.ts b/src/test/linters/common.ts index c602492ccd67..3c8f72a8d710 100644 --- a/src/test/linters/common.ts +++ b/src/test/linters/common.ts @@ -18,7 +18,7 @@ import { IConfigurationService, IInstaller, IMypyCategorySeverity, - IOutputChannel, + ILogOutputChannel, IPycodestyleCategorySeverity, IPylintCategorySeverity, IPythonSettings, @@ -243,7 +243,7 @@ export class BaseTestFixture { public lintingSettings: LintingSettings; // data - public outputChannel: TypeMoq.IMock; + public outputChannel: TypeMoq.IMock; // artifacts public output: string; @@ -309,10 +309,10 @@ export class BaseTestFixture { // data - this.outputChannel = TypeMoq.Mock.ofType(undefined, TypeMoq.MockBehavior.Strict); + this.outputChannel = TypeMoq.Mock.ofType(undefined, TypeMoq.MockBehavior.Strict); this.serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(IOutputChannel), TypeMoq.It.isAny())) + .setup((c) => c.get(TypeMoq.It.isValue(ILogOutputChannel))) .returns(() => this.outputChannel.object); this.initData(); diff --git a/src/test/linters/lintengine.test.ts b/src/test/linters/lintengine.test.ts index 20599d7fb713..1bf77c502af5 100644 --- a/src/test/linters/lintengine.test.ts +++ b/src/test/linters/lintengine.test.ts @@ -4,12 +4,12 @@ 'use strict'; import * as TypeMoq from 'typemoq'; -import { OutputChannel, TextDocument, Uri } from 'vscode'; +import { TextDocument, Uri } from 'vscode'; import { IDocumentManager, IWorkspaceService } from '../../client/common/application/types'; -import { PYTHON_LANGUAGE, STANDARD_OUTPUT_CHANNEL } from '../../client/common/constants'; +import { PYTHON_LANGUAGE } from '../../client/common/constants'; import '../../client/common/extensions'; import { IFileSystem } from '../../client/common/platform/types'; -import { IConfigurationService, ILintingSettings, IOutputChannel, IPythonSettings } from '../../client/common/types'; +import { IConfigurationService, ILintingSettings, ILogOutputChannel, IPythonSettings } from '../../client/common/types'; import { IInterpreterService } from '../../client/interpreter/contracts'; import { IServiceContainer } from '../../client/ioc/types'; import { LintingEngine } from '../../client/linters/lintingEngine'; @@ -54,10 +54,8 @@ suite('Linting - LintingEngine', () => { .setup((c) => c.get(TypeMoq.It.isValue(IConfigurationService), TypeMoq.It.isAny())) .returns(() => configService.object); - const outputChannel = TypeMoq.Mock.ofType(); - serviceContainer - .setup((c) => c.get(TypeMoq.It.isValue(IOutputChannel), TypeMoq.It.isValue(STANDARD_OUTPUT_CHANNEL))) - .returns(() => outputChannel.object); + const outputChannel = TypeMoq.Mock.ofType(); + serviceContainer.setup((c) => c.get(TypeMoq.It.isValue(ILogOutputChannel))).returns(() => outputChannel.object); lintManager = TypeMoq.Mock.ofType(); lintManager.setup((x) => x.isLintingEnabled(TypeMoq.It.isAny())).returns(async () => true); diff --git a/src/test/mockClasses.ts b/src/test/mockClasses.ts index 0273cf27fdb9..c962c4d67ca4 100644 --- a/src/test/mockClasses.ts +++ b/src/test/mockClasses.ts @@ -1,4 +1,5 @@ import * as vscode from 'vscode'; +import * as util from 'util'; import { Flake8CategorySeverity, ILintingSettings, @@ -7,13 +8,32 @@ import { IPylintCategorySeverity, } from '../client/common/types'; -export class MockOutputChannel implements vscode.OutputChannel { +export class MockOutputChannel implements vscode.LogOutputChannel { public name: string; public output: string; public isShown!: boolean; + private _eventEmitter = new vscode.EventEmitter(); + public onDidChangeLogLevel: vscode.Event = this._eventEmitter.event; constructor(name: string) { this.name = name; this.output = ''; + this.logLevel = vscode.LogLevel.Debug; + } + public logLevel: vscode.LogLevel; + trace(message: string, ...args: any[]): void { + this.appendLine(util.format(message, ...args)); + } + debug(message: string, ...args: any[]): void { + this.appendLine(util.format(message, ...args)); + } + info(message: string, ...args: any[]): void { + this.appendLine(util.format(message, ...args)); + } + warn(message: string, ...args: any[]): void { + this.appendLine(util.format(message, ...args)); + } + error(error: string | Error, ...args: any[]): void { + this.appendLine(util.format(error, ...args)); } public append(value: string) { this.output += value; diff --git a/src/test/mocks/vsc/index.ts b/src/test/mocks/vsc/index.ts index e6ea57a88673..89f4ab1a2d07 100644 --- a/src/test/mocks/vsc/index.ts +++ b/src/test/mocks/vsc/index.ts @@ -411,3 +411,35 @@ export class InlayHint { public kind?: vscode.InlayHintKind, ) {} } + +export enum LogLevel { + /** + * No messages are logged with this level. + */ + Off = 0, + + /** + * All messages are logged with this level. + */ + Trace = 1, + + /** + * Messages with debug and higher log level are logged with this level. + */ + Debug = 2, + + /** + * Messages with info and higher log level are logged with this level. + */ + Info = 3, + + /** + * Messages with warning and higher log level are logged with this level. + */ + Warning = 4, + + /** + * Only error messages are logged with this level. + */ + Error = 5, +} diff --git a/src/test/pythonEnvironments/creation/createEnvApi.unit.test.ts b/src/test/pythonEnvironments/creation/createEnvApi.unit.test.ts index 1286ac44d58d..786bd26a881c 100644 --- a/src/test/pythonEnvironments/creation/createEnvApi.unit.test.ts +++ b/src/test/pythonEnvironments/creation/createEnvApi.unit.test.ts @@ -12,8 +12,8 @@ import * as commandApis from '../../../client/common/vscodeApis/commandApis'; import { IInterpreterQuickPick } from '../../../client/interpreter/configuration/types'; import { registerCreateEnvironmentFeatures } from '../../../client/pythonEnvironments/creation/createEnvApi'; import * as windowApis from '../../../client/common/vscodeApis/windowApis'; -import { CreateEnvironmentProvider } from '../../../client/pythonEnvironments/creation/types'; import { handleCreateEnvironmentCommand } from '../../../client/pythonEnvironments/creation/createEnvironment'; +import { CreateEnvironmentProvider } from '../../../client/pythonEnvironments/creation/proposed.createEnvApis'; chaiUse(chaiAsPromised); @@ -57,6 +57,11 @@ suite('Create Environment APIs', () => { [true, false].forEach((selectEnvironment) => { test(`Set environment selectEnvironment == ${selectEnvironment}`, async () => { + const workspace1 = { + uri: Uri.file('/path/to/env'), + name: 'workspace1', + index: 0, + }; const provider = typemoq.Mock.ofType(); provider.setup((p) => p.name).returns(() => 'test'); provider.setup((p) => p.id).returns(() => 'test-id'); @@ -66,7 +71,9 @@ suite('Create Environment APIs', () => { .returns(() => Promise.resolve({ path: '/path/to/env', - uri: Uri.file('/path/to/env'), + workspaceFolder: workspace1, + action: undefined, + error: undefined, }), ); provider.setup((p) => (p as any).then).returns(() => undefined); diff --git a/src/test/pythonEnvironments/creation/createEnvironment.unit.test.ts b/src/test/pythonEnvironments/creation/createEnvironment.unit.test.ts index 507a2aee88cd..f16f81233369 100644 --- a/src/test/pythonEnvironments/creation/createEnvironment.unit.test.ts +++ b/src/test/pythonEnvironments/creation/createEnvironment.unit.test.ts @@ -8,9 +8,9 @@ import * as typemoq from 'typemoq'; import { assert, use as chaiUse } from 'chai'; import * as windowApis from '../../../client/common/vscodeApis/windowApis'; import { handleCreateEnvironmentCommand } from '../../../client/pythonEnvironments/creation/createEnvironment'; -import { CreateEnvironmentProvider } from '../../../client/pythonEnvironments/creation/types'; import { IDisposableRegistry } from '../../../client/common/types'; import { onCreateEnvironmentStarted } from '../../../client/pythonEnvironments/creation/createEnvApi'; +import { CreateEnvironmentProvider } from '../../../client/pythonEnvironments/creation/proposed.createEnvApis'; chaiUse(chaiAsPromised); @@ -233,7 +233,12 @@ suite('Create Environments Tests', () => { showBackButton: true, }); - assert.deepStrictEqual(result, { path: undefined, uri: undefined, action: 'Back' }); + assert.deepStrictEqual(result, { + action: 'Back', + workspaceFolder: undefined, + path: undefined, + error: undefined, + }); assert.isTrue(showQuickPickStub.notCalled); assert.isTrue(showQuickPickWithBackStub.calledOnce); }); @@ -259,7 +264,12 @@ suite('Create Environments Tests', () => { showBackButton: true, }); - assert.deepStrictEqual(result, { path: undefined, uri: undefined, action: 'Cancel' }); + assert.deepStrictEqual(result, { + action: 'Cancel', + workspaceFolder: undefined, + path: undefined, + error: undefined, + }); assert.isTrue(showQuickPickStub.notCalled); assert.isTrue(showQuickPickWithBackStub.calledOnce); }); diff --git a/src/test/pythonEnvironments/creation/provider/condaCreationProvider.unit.test.ts b/src/test/pythonEnvironments/creation/provider/condaCreationProvider.unit.test.ts index db5eb351211e..cb4df95c8c1f 100644 --- a/src/test/pythonEnvironments/creation/provider/condaCreationProvider.unit.test.ts +++ b/src/test/pythonEnvironments/creation/provider/condaCreationProvider.unit.test.ts @@ -7,11 +7,7 @@ import { assert, use as chaiUse } from 'chai'; import * as sinon from 'sinon'; import * as typemoq from 'typemoq'; import { CancellationToken, ProgressOptions, Uri } from 'vscode'; -import { - CreateEnvironmentProgress, - CreateEnvironmentProvider, - CreateEnvironmentResult, -} from '../../../../client/pythonEnvironments/creation/types'; +import { CreateEnvironmentProgress } from '../../../../client/pythonEnvironments/creation/types'; import { condaCreationProvider } from '../../../../client/pythonEnvironments/creation/provider/condaCreationProvider'; import * as wsSelect from '../../../../client/pythonEnvironments/creation/common/workspaceSelection'; import * as windowApis from '../../../../client/common/vscodeApis/windowApis'; @@ -23,6 +19,10 @@ import { createDeferred } from '../../../../client/common/utils/async'; import * as commonUtils from '../../../../client/pythonEnvironments/creation/common/commonUtils'; import { CONDA_ENV_CREATED_MARKER } from '../../../../client/pythonEnvironments/creation/provider/condaProgressAndTelemetry'; import { CreateEnv } from '../../../../client/common/utils/localize'; +import { + CreateEnvironmentProvider, + CreateEnvironmentResult, +} from '../../../../client/pythonEnvironments/creation/proposed.createEnvApis'; chaiUse(chaiAsPromised); @@ -131,7 +131,12 @@ suite('Conda Creation provider tests', () => { _next!({ out: `${CONDA_ENV_CREATED_MARKER}new_environment`, source: 'stdout' }); _complete!(); - assert.deepStrictEqual(await promise, { path: 'new_environment', uri: workspace1.uri }); + assert.deepStrictEqual(await promise, { + path: 'new_environment', + workspaceFolder: workspace1, + action: undefined, + error: undefined, + }); assert.isTrue(showErrorMessageWithLogsStub.notCalled); }); diff --git a/src/test/pythonEnvironments/creation/provider/venvCreationProvider.unit.test.ts b/src/test/pythonEnvironments/creation/provider/venvCreationProvider.unit.test.ts index b56fb158d6ae..1c22264f2ada 100644 --- a/src/test/pythonEnvironments/creation/provider/venvCreationProvider.unit.test.ts +++ b/src/test/pythonEnvironments/creation/provider/venvCreationProvider.unit.test.ts @@ -6,11 +6,7 @@ import * as typemoq from 'typemoq'; import { assert, use as chaiUse } from 'chai'; import * as sinon from 'sinon'; import { CancellationToken, ProgressOptions, Uri } from 'vscode'; -import { - CreateEnvironmentProgress, - CreateEnvironmentProvider, - CreateEnvironmentResult, -} from '../../../../client/pythonEnvironments/creation/types'; +import { CreateEnvironmentProgress } from '../../../../client/pythonEnvironments/creation/types'; import { VenvCreationProvider } from '../../../../client/pythonEnvironments/creation/provider/venvCreationProvider'; import { IInterpreterQuickPick } from '../../../../client/interpreter/configuration/types'; import * as wsSelect from '../../../../client/pythonEnvironments/creation/common/workspaceSelection'; @@ -23,6 +19,10 @@ import { Output } from '../../../../client/common/process/types'; import { VENV_CREATED_MARKER } from '../../../../client/pythonEnvironments/creation/provider/venvProgressAndTelemetry'; import { CreateEnv } from '../../../../client/common/utils/localize'; import * as venvUtils from '../../../../client/pythonEnvironments/creation/provider/venvUtils'; +import { + CreateEnvironmentProvider, + CreateEnvironmentResult, +} from '../../../../client/pythonEnvironments/creation/proposed.createEnvApis'; chaiUse(chaiAsPromised); @@ -155,7 +155,12 @@ suite('venv Creation provider tests', () => { _complete!(); const actual = await promise; - assert.deepStrictEqual(actual, { path: 'new_environment', uri: workspace1.uri }); + assert.deepStrictEqual(actual, { + path: 'new_environment', + workspaceFolder: workspace1, + action: undefined, + error: undefined, + }); interpreterQuickPick.verifyAll(); progressMock.verifyAll(); assert.isTrue(showErrorMessageWithLogsStub.notCalled); diff --git a/src/test/serviceRegistry.ts b/src/test/serviceRegistry.ts index e3c6763eace7..1b8a9d78d580 100644 --- a/src/test/serviceRegistry.ts +++ b/src/test/serviceRegistry.ts @@ -4,8 +4,7 @@ import { Container } from 'inversify'; import { anything, instance, mock, when } from 'ts-mockito'; import * as TypeMoq from 'typemoq'; -import { Disposable, Memento, OutputChannel } from 'vscode'; -import { STANDARD_OUTPUT_CHANNEL } from '../client/common/constants'; +import { Disposable, Memento } from 'vscode'; import { IS_WINDOWS } from '../client/common/platform/constants'; import { FileSystem } from '../client/common/platform/fileSystem'; import { PathUtils } from '../client/common/platform/pathUtils'; @@ -28,10 +27,11 @@ import { ICurrentProcess, IDisposableRegistry, IMemento, - IOutputChannel, + ILogOutputChannel, IPathUtils, IsWindows, WORKSPACE_MEMENTO, + ITestOutputChannel, } from '../client/common/types'; import { registerTypes as variableRegisterTypes } from '../client/common/variables/serviceRegistry'; import { registerTypes as formattersRegisterTypes } from '../client/formatters/serviceRegistry'; @@ -48,7 +48,6 @@ import { ServiceContainer } from '../client/ioc/container'; import { ServiceManager } from '../client/ioc/serviceManager'; import { IServiceContainer, IServiceManager } from '../client/ioc/types'; import { registerTypes as lintersRegisterTypes } from '../client/linters/serviceRegistry'; -import { TEST_OUTPUT_CHANNEL } from '../client/testing/constants'; import { registerTypes as unittestsRegisterTypes } from '../client/testing/serviceRegistry'; import { LegacyFileSystem } from './legacyFileSystem'; import { MockOutputChannel } from './mockClasses'; @@ -83,14 +82,10 @@ export class IocContainer { const stdOutputChannel = new MockOutputChannel('Python'); this.disposables.push(stdOutputChannel); - this.serviceManager.addSingletonInstance( - IOutputChannel, - stdOutputChannel, - STANDARD_OUTPUT_CHANNEL, - ); + this.serviceManager.addSingletonInstance(ILogOutputChannel, stdOutputChannel); const testOutputChannel = new MockOutputChannel('Python Test - UnitTests'); this.disposables.push(testOutputChannel); - this.serviceManager.addSingletonInstance(IOutputChannel, testOutputChannel, TEST_OUTPUT_CHANNEL); + this.serviceManager.addSingletonInstance(ITestOutputChannel, testOutputChannel); this.serviceManager.addSingleton( IInterpreterAutoSelectionService, diff --git a/src/test/testLogger.ts b/src/test/testLogger.ts index b41f17ecafcc..26484ee119c7 100644 --- a/src/test/testLogger.ts +++ b/src/test/testLogger.ts @@ -5,6 +5,7 @@ import { initializeFileLogging, logTo } from '../client/logging'; import { LogLevel } from '../client/logging/types'; + // IMPORTANT: This file should only be importing from the '../client/logging' directory, as we // delete everything in '../client' except for '../client/logging' before running smoke tests. @@ -31,7 +32,7 @@ function monkeypatchConsole() { const streams = ['log', 'error', 'warn', 'info', 'debug', 'trace']; const levels: { [key: string]: LogLevel } = { error: LogLevel.Error, - warn: LogLevel.Warn, + warn: LogLevel.Warning, debug: LogLevel.Debug, trace: LogLevel.Debug, info: LogLevel.Info, diff --git a/src/test/testing/common/managers/testConfigurationManager.unit.test.ts b/src/test/testing/common/managers/testConfigurationManager.unit.test.ts index 02a7193172af..c8b6085e599d 100644 --- a/src/test/testing/common/managers/testConfigurationManager.unit.test.ts +++ b/src/test/testing/common/managers/testConfigurationManager.unit.test.ts @@ -5,13 +5,12 @@ import * as TypeMoq from 'typemoq'; import { OutputChannel, Uri } from 'vscode'; -import { IInstaller, IOutputChannel, Product } from '../../../../client/common/types'; +import { IInstaller, ITestOutputChannel, Product } from '../../../../client/common/types'; import { getNamesAndValues } from '../../../../client/common/utils/enum'; import { IServiceContainer } from '../../../../client/ioc/types'; import { UNIT_TEST_PRODUCTS } from '../../../../client/testing/common/constants'; import { TestConfigurationManager } from '../../../../client/testing/common/testConfigurationManager'; import { ITestConfigSettingsService, UnitTestProduct } from '../../../../client/testing/common/types'; -import { TEST_OUTPUT_CHANNEL } from '../../../../client/testing/constants'; class MockTestConfigurationManager extends TestConfigurationManager { // The workspace arg is ignored. @@ -42,7 +41,7 @@ suite('Unit Test Configuration Manager (unit)', () => { const installer = TypeMoq.Mock.ofType().object; const serviceContainer = TypeMoq.Mock.ofType(); serviceContainer - .setup((s) => s.get(TypeMoq.It.isValue(IOutputChannel), TypeMoq.It.isValue(TEST_OUTPUT_CHANNEL))) + .setup((s) => s.get(TypeMoq.It.isValue(ITestOutputChannel))) .returns(() => outputChannel); serviceContainer .setup((s) => s.get(TypeMoq.It.isValue(ITestConfigSettingsService))) diff --git a/src/test/testing/configuration.unit.test.ts b/src/test/testing/configuration.unit.test.ts index fec936a2a21a..abb57aac2309 100644 --- a/src/test/testing/configuration.unit.test.ts +++ b/src/test/testing/configuration.unit.test.ts @@ -7,7 +7,13 @@ import { expect } from 'chai'; import * as typeMoq from 'typemoq'; import { OutputChannel, Uri, WorkspaceConfiguration } from 'vscode'; import { IApplicationShell, ICommandManager, IWorkspaceService } from '../../client/common/application/types'; -import { IConfigurationService, IInstaller, IOutputChannel, IPythonSettings, Product } from '../../client/common/types'; +import { + IConfigurationService, + IInstaller, + ITestOutputChannel, + IPythonSettings, + Product, +} from '../../client/common/types'; import { getNamesAndValues } from '../../client/common/utils/enum'; import { IServiceContainer } from '../../client/ioc/types'; import { UNIT_TEST_PRODUCTS } from '../../client/testing/common/constants'; @@ -18,7 +24,6 @@ import { ITestConfigurationManagerFactory, ITestsHelper, } from '../../client/testing/common/types'; -import { TEST_OUTPUT_CHANNEL } from '../../client/testing/constants'; import { ITestingSettings } from '../../client/testing/configuration/types'; import { NONE_SELECTED, UnitTestConfigurationService } from '../../client/testing/configuration'; @@ -56,7 +61,7 @@ suite('Unit Tests - ConfigurationService', () => { configurationService.setup((c) => c.getSettings(workspaceUri)).returns(() => pythonSettings.object); serviceContainer - .setup((c) => c.get(typeMoq.It.isValue(IOutputChannel), typeMoq.It.isValue(TEST_OUTPUT_CHANNEL))) + .setup((c) => c.get(typeMoq.It.isValue(ITestOutputChannel))) .returns(() => outputChannel.object); serviceContainer.setup((c) => c.get(typeMoq.It.isValue(IInstaller))).returns(() => installer.object); serviceContainer diff --git a/src/test/testing/configurationFactory.unit.test.ts b/src/test/testing/configurationFactory.unit.test.ts index 1418147d615c..74f7dd0da19b 100644 --- a/src/test/testing/configurationFactory.unit.test.ts +++ b/src/test/testing/configurationFactory.unit.test.ts @@ -7,11 +7,10 @@ import { expect, use } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; import * as typeMoq from 'typemoq'; import { OutputChannel, Uri } from 'vscode'; -import { IInstaller, IOutputChannel, Product } from '../../client/common/types'; +import { IInstaller, ITestOutputChannel, Product } from '../../client/common/types'; import { IServiceContainer } from '../../client/ioc/types'; import { ITestConfigSettingsService, ITestConfigurationManagerFactory } from '../../client/testing/common/types'; import { TestConfigurationManagerFactory } from '../../client/testing/configurationFactory'; -import { TEST_OUTPUT_CHANNEL } from '../../client/testing/constants'; import * as pytest from '../../client/testing/configuration/pytest/testConfigurationManager'; import * as unittest from '../../client/testing/configuration/unittest/testConfigurationManager'; @@ -26,7 +25,7 @@ suite('Unit Tests - ConfigurationManagerFactory', () => { const testConfigService = typeMoq.Mock.ofType(); serviceContainer - .setup((c) => c.get(typeMoq.It.isValue(IOutputChannel), typeMoq.It.isValue(TEST_OUTPUT_CHANNEL))) + .setup((c) => c.get(typeMoq.It.isValue(ITestOutputChannel))) .returns(() => outputChannel.object); serviceContainer.setup((c) => c.get(typeMoq.It.isValue(IInstaller))).returns(() => installer.object); serviceContainer diff --git a/src/test/testing/testController/pytest/pytestDiscoveryAdapter.unit.test.ts b/src/test/testing/testController/pytest/pytestDiscoveryAdapter.unit.test.ts new file mode 100644 index 000000000000..f5a2203dbd9a --- /dev/null +++ b/src/test/testing/testController/pytest/pytestDiscoveryAdapter.unit.test.ts @@ -0,0 +1,91 @@ +// /* eslint-disable @typescript-eslint/no-explicit-any */ +// // Copyright (c) Microsoft Corporation. All rights reserved. +// // Licensed under the MIT License. +// import * as assert from 'assert'; +// import { Uri } from 'vscode'; +// import * as typeMoq from 'typemoq'; +// import { IConfigurationService } from '../../../../client/common/types'; +// import { PytestTestDiscoveryAdapter } from '../../../../client/testing/testController/pytest/pytestDiscoveryAdapter'; +// import { DataReceivedEvent, ITestServer } from '../../../../client/testing/testController/common/types'; +// import { IPythonExecutionFactory, IPythonExecutionService } from '../../../../client/common/process/types'; +// import { createDeferred, Deferred } from '../../../../client/common/utils/async'; + +// suite('pytest test discovery adapter', () => { +// let testServer: typeMoq.IMock; +// let configService: IConfigurationService; +// let execFactory = typeMoq.Mock.ofType(); +// let adapter: PytestTestDiscoveryAdapter; +// let execService: typeMoq.IMock; +// let deferred: Deferred; +// setup(() => { +// testServer = typeMoq.Mock.ofType(); +// testServer.setup((t) => t.getPort()).returns(() => 12345); +// testServer +// .setup((t) => t.onDataReceived(typeMoq.It.isAny(), typeMoq.It.isAny())) +// .returns(() => ({ +// dispose: () => { +// /* no-body */ +// }, +// })); +// configService = ({ +// getSettings: () => ({ +// testing: { pytestArgs: ['.'] }, +// }), +// } as unknown) as IConfigurationService; +// execFactory = typeMoq.Mock.ofType(); +// execService = typeMoq.Mock.ofType(); +// execFactory +// .setup((x) => x.createActivatedEnvironment(typeMoq.It.isAny())) +// .returns(() => Promise.resolve(execService.object)); +// deferred = createDeferred(); +// execService +// .setup((x) => x.exec(typeMoq.It.isAny(), typeMoq.It.isAny())) +// .returns(() => { +// deferred.resolve(); +// return Promise.resolve({ stdout: '{}' }); +// }); +// execFactory.setup((p) => ((p as unknown) as any).then).returns(() => undefined); +// execService.setup((p) => ((p as unknown) as any).then).returns(() => undefined); +// }); +// test('onDataReceivedHandler should parse only if known UUID', async () => { +// const uri = Uri.file('/my/test/path/'); +// const uuid = 'uuid123'; +// const data = { status: 'success' }; +// testServer.setup((t) => t.createUUID(typeMoq.It.isAny())).returns(() => uuid); +// const eventData: DataReceivedEvent = { +// uuid, +// data: JSON.stringify(data), +// }; + +// adapter = new PytestTestDiscoveryAdapter(testServer.object, configService); +// // ** const promise = adapter.discoverTests(uri, execFactory.object); +// const promise = adapter.discoverTests(uri); +// await deferred.promise; +// adapter.onDataReceivedHandler(eventData); +// const result = await promise; +// assert.deepStrictEqual(result, data); +// }); +// test('onDataReceivedHandler should not parse if it is unknown UUID', async () => { +// const uri = Uri.file('/my/test/path/'); +// const uuid = 'uuid456'; +// let data = { status: 'error' }; +// testServer.setup((t) => t.createUUID(typeMoq.It.isAny())).returns(() => uuid); +// const wrongUriEventData: DataReceivedEvent = { +// uuid: 'incorrect-uuid456', +// data: JSON.stringify(data), +// }; +// adapter = new PytestTestDiscoveryAdapter(testServer.object, configService); +// // ** const promise = adapter.discoverTests(uri, execFactory.object); +// const promise = adapter.discoverTests(uri); +// adapter.onDataReceivedHandler(wrongUriEventData); + +// data = { status: 'success' }; +// const correctUriEventData: DataReceivedEvent = { +// uuid, +// data: JSON.stringify(data), +// }; +// adapter.onDataReceivedHandler(correctUriEventData); +// const result = await promise; +// assert.deepStrictEqual(result, data); +// }); +// }); diff --git a/src/test/testing/testController/unittest/testDiscoveryAdapter.unit.test.ts b/src/test/testing/testController/unittest/testDiscoveryAdapter.unit.test.ts index 113e4175fe69..3d3521291f74 100644 --- a/src/test/testing/testController/unittest/testDiscoveryAdapter.unit.test.ts +++ b/src/test/testing/testController/unittest/testDiscoveryAdapter.unit.test.ts @@ -3,14 +3,16 @@ import * as assert from 'assert'; import * as path from 'path'; +import * as typemoq from 'typemoq'; import { Uri } from 'vscode'; -import { IConfigurationService } from '../../../../client/common/types'; +import { IConfigurationService, ITestOutputChannel } from '../../../../client/common/types'; import { EXTENSION_ROOT_DIR } from '../../../../client/constants'; import { ITestServer, TestCommandOptions } from '../../../../client/testing/testController/common/types'; import { UnittestTestDiscoveryAdapter } from '../../../../client/testing/testController/unittest/testDiscoveryAdapter'; suite('Unittest test discovery adapter', () => { let stubConfigSettings: IConfigurationService; + let outputChannel: typemoq.IMock; setup(() => { stubConfigSettings = ({ @@ -18,6 +20,7 @@ suite('Unittest test discovery adapter', () => { testing: { unittestArgs: ['-v', '-s', '.', '-p', 'test*'] }, }), } as unknown) as IConfigurationService; + outputChannel = typemoq.Mock.ofType(); }); test('discoverTests should send the discovery command to the test server', async () => { @@ -25,6 +28,7 @@ suite('Unittest test discovery adapter', () => { const stubTestServer = ({ sendCommand(opt: TestCommandOptions): Promise { + delete opt.outChannel; options = opt; return Promise.resolve(); }, @@ -37,7 +41,7 @@ suite('Unittest test discovery adapter', () => { const uri = Uri.file('/foo/bar'); const script = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'unittestadapter', 'discovery.py'); - const adapter = new UnittestTestDiscoveryAdapter(stubTestServer, stubConfigSettings); + const adapter = new UnittestTestDiscoveryAdapter(stubTestServer, stubConfigSettings, outputChannel.object); adapter.discoverTests(uri); assert.deepStrictEqual(options, { @@ -62,7 +66,7 @@ suite('Unittest test discovery adapter', () => { const uri = Uri.file('/foo/bar'); const data = { status: 'success' }; const uuid = '123456789'; - const adapter = new UnittestTestDiscoveryAdapter(stubTestServer, stubConfigSettings); + const adapter = new UnittestTestDiscoveryAdapter(stubTestServer, stubConfigSettings, outputChannel.object); const promise = adapter.discoverTests(uri); adapter.onDataReceivedHandler({ uuid, data: JSON.stringify(data) }); @@ -87,7 +91,7 @@ suite('Unittest test discovery adapter', () => { const uri = Uri.file('/foo/bar'); - const adapter = new UnittestTestDiscoveryAdapter(stubTestServer, stubConfigSettings); + const adapter = new UnittestTestDiscoveryAdapter(stubTestServer, stubConfigSettings, outputChannel.object); const promise = adapter.discoverTests(uri); const data = { status: 'success' }; diff --git a/src/test/testing/testController/unittest/testExecutionAdapter.unit.test.ts b/src/test/testing/testController/unittest/testExecutionAdapter.unit.test.ts index f8648c8963fa..d88f033d39a4 100644 --- a/src/test/testing/testController/unittest/testExecutionAdapter.unit.test.ts +++ b/src/test/testing/testController/unittest/testExecutionAdapter.unit.test.ts @@ -3,14 +3,16 @@ import * as assert from 'assert'; import * as path from 'path'; +import * as typemoq from 'typemoq'; import { Uri } from 'vscode'; -import { IConfigurationService } from '../../../../client/common/types'; +import { IConfigurationService, ITestOutputChannel } from '../../../../client/common/types'; import { EXTENSION_ROOT_DIR } from '../../../../client/constants'; import { ITestServer, TestCommandOptions } from '../../../../client/testing/testController/common/types'; import { UnittestTestExecutionAdapter } from '../../../../client/testing/testController/unittest/testExecutionAdapter'; suite('Unittest test execution adapter', () => { let stubConfigSettings: IConfigurationService; + let outputChannel: typemoq.IMock; setup(() => { stubConfigSettings = ({ @@ -18,6 +20,7 @@ suite('Unittest test execution adapter', () => { testing: { unittestArgs: ['-v', '-s', '.', '-p', 'test*'] }, }), } as unknown) as IConfigurationService; + outputChannel = typemoq.Mock.ofType(); }); test('runTests should send the run command to the test server', async () => { @@ -25,6 +28,7 @@ suite('Unittest test execution adapter', () => { const stubTestServer = ({ sendCommand(opt: TestCommandOptions): Promise { + delete opt.outChannel; options = opt; return Promise.resolve(); }, @@ -37,7 +41,7 @@ suite('Unittest test execution adapter', () => { const uri = Uri.file('/foo/bar'); const script = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'unittestadapter', 'execution.py'); - const adapter = new UnittestTestExecutionAdapter(stubTestServer, stubConfigSettings); + const adapter = new UnittestTestExecutionAdapter(stubTestServer, stubConfigSettings, outputChannel.object); adapter.runTests(uri, [], false); const expectedOptions: TestCommandOptions = { @@ -66,7 +70,7 @@ suite('Unittest test execution adapter', () => { const data = { status: 'success' }; const uuid = '123456789'; - const adapter = new UnittestTestExecutionAdapter(stubTestServer, stubConfigSettings); + const adapter = new UnittestTestExecutionAdapter(stubTestServer, stubConfigSettings, outputChannel.object); // triggers runTests flow which will run onDataReceivedHandler and the // promise resolves into the parsed data. @@ -93,7 +97,7 @@ suite('Unittest test execution adapter', () => { const uri = Uri.file('/foo/bar'); - const adapter = new UnittestTestExecutionAdapter(stubTestServer, stubConfigSettings); + const adapter = new UnittestTestExecutionAdapter(stubTestServer, stubConfigSettings, outputChannel.object); // triggers runTests flow which will run onDataReceivedHandler and the // promise resolves into the parsed data. diff --git a/src/test/testing/testController/workspaceTestAdapter.unit.test.ts b/src/test/testing/testController/workspaceTestAdapter.unit.test.ts index b6be8d6081de..539647aece9f 100644 --- a/src/test/testing/testController/workspaceTestAdapter.unit.test.ts +++ b/src/test/testing/testController/workspaceTestAdapter.unit.test.ts @@ -3,9 +3,10 @@ import * as assert from 'assert'; import * as sinon from 'sinon'; +import * as typemoq from 'typemoq'; import { TestController, TestItem, Uri } from 'vscode'; -import { IConfigurationService } from '../../../client/common/types'; +import { IConfigurationService, ITestOutputChannel } from '../../../client/common/types'; import { UnittestTestDiscoveryAdapter } from '../../../client/testing/testController/unittest/testDiscoveryAdapter'; import { UnittestTestExecutionAdapter } from '../../../client/testing/testController/unittest/testExecutionAdapter'; // 7/7 import { WorkspaceTestAdapter } from '../../../client/testing/testController/workspaceTestAdapter'; @@ -20,6 +21,7 @@ suite('Workspace test adapter', () => { let discoverTestsStub: sinon.SinonStub; let sendTelemetryStub: sinon.SinonStub; + let outputChannel: typemoq.IMock; let telemetryEvent: { eventName: EventName; properties: Record }[] = []; @@ -97,6 +99,7 @@ suite('Workspace test adapter', () => { discoverTestsStub = sandbox.stub(UnittestTestDiscoveryAdapter.prototype, 'discoverTests'); sendTelemetryStub = sandbox.stub(Telemetry, 'sendTelemetryEvent').callsFake(mockSendTelemetryEvent); + outputChannel = typemoq.Mock.ofType(); }); teardown(() => { @@ -109,8 +112,16 @@ suite('Workspace test adapter', () => { test("When discovering tests, the workspace test adapter should call the test discovery adapter's discoverTest method", async () => { discoverTestsStub.resolves(); - const testDiscoveryAdapter = new UnittestTestDiscoveryAdapter(stubTestServer, stubConfigSettings); - const testExecutionAdapter = new UnittestTestExecutionAdapter(stubTestServer, stubConfigSettings); // 7/7 + const testDiscoveryAdapter = new UnittestTestDiscoveryAdapter( + stubTestServer, + stubConfigSettings, + outputChannel.object, + ); + const testExecutionAdapter = new UnittestTestExecutionAdapter( + stubTestServer, + stubConfigSettings, + outputChannel.object, + ); const workspaceTestAdapter = new WorkspaceTestAdapter( 'unittest', testDiscoveryAdapter, @@ -134,8 +145,16 @@ suite('Workspace test adapter', () => { }), ); - const testDiscoveryAdapter = new UnittestTestDiscoveryAdapter(stubTestServer, stubConfigSettings); - const testExecutionAdapter = new UnittestTestExecutionAdapter(stubTestServer, stubConfigSettings); // 7/7 + const testDiscoveryAdapter = new UnittestTestDiscoveryAdapter( + stubTestServer, + stubConfigSettings, + outputChannel.object, + ); + const testExecutionAdapter = new UnittestTestExecutionAdapter( + stubTestServer, + stubConfigSettings, + outputChannel.object, + ); const workspaceTestAdapter = new WorkspaceTestAdapter( 'unittest', testDiscoveryAdapter, @@ -155,8 +174,16 @@ suite('Workspace test adapter', () => { test('If discovery succeeds, send a telemetry event with the "failed" key set to false', async () => { discoverTestsStub.resolves({ status: 'success' }); - const testDiscoveryAdapter = new UnittestTestDiscoveryAdapter(stubTestServer, stubConfigSettings); - const testExecutionAdapter = new UnittestTestExecutionAdapter(stubTestServer, stubConfigSettings); + const testDiscoveryAdapter = new UnittestTestDiscoveryAdapter( + stubTestServer, + stubConfigSettings, + outputChannel.object, + ); + const testExecutionAdapter = new UnittestTestExecutionAdapter( + stubTestServer, + stubConfigSettings, + outputChannel.object, + ); const workspaceTestAdapter = new WorkspaceTestAdapter( 'unittest', @@ -177,8 +204,16 @@ suite('Workspace test adapter', () => { test('If discovery failed, send a telemetry event with the "failed" key set to true, and add an error node to the test controller', async () => { discoverTestsStub.rejects(new Error('foo')); - const testDiscoveryAdapter = new UnittestTestDiscoveryAdapter(stubTestServer, stubConfigSettings); - const testExecutionAdapter = new UnittestTestExecutionAdapter(stubTestServer, stubConfigSettings); + const testDiscoveryAdapter = new UnittestTestDiscoveryAdapter( + stubTestServer, + stubConfigSettings, + outputChannel.object, + ); + const testExecutionAdapter = new UnittestTestExecutionAdapter( + stubTestServer, + stubConfigSettings, + outputChannel.object, + ); const workspaceTestAdapter = new WorkspaceTestAdapter( 'unittest', diff --git a/src/test/vscode-mock.ts b/src/test/vscode-mock.ts index 9fb4bbec329b..ebbe7ca59e72 100644 --- a/src/test/vscode-mock.ts +++ b/src/test/vscode-mock.ts @@ -112,6 +112,7 @@ mockedVSCode.FileSystemError = vscodeMocks.vscMockExtHostedTypes.FileSystemError mockedVSCode.LanguageStatusSeverity = vscodeMocks.LanguageStatusSeverity; mockedVSCode.QuickPickItemKind = vscodeMocks.QuickPickItemKind; mockedVSCode.InlayHint = vscodeMocks.InlayHint; +mockedVSCode.LogLevel = vscodeMocks.LogLevel; (mockedVSCode as any).NotebookCellKind = vscodeMocks.vscMockExtHostedTypes.NotebookCellKind; (mockedVSCode as any).CellOutputKind = vscodeMocks.vscMockExtHostedTypes.CellOutputKind; (mockedVSCode as any).NotebookCellRunState = vscodeMocks.vscMockExtHostedTypes.NotebookCellRunState; diff --git a/typings/vscode-proposed/vscode.proposed.quickPickItemTooltip.d.ts b/typings/vscode-proposed/vscode.proposed.quickPickItemTooltip.d.ts new file mode 100644 index 000000000000..4e7d00fa5edf --- /dev/null +++ b/typings/vscode-proposed/vscode.proposed.quickPickItemTooltip.d.ts @@ -0,0 +1,16 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/73904 + + export interface QuickPickItem { + /** + * An optional flag to sort the final results by index of first query match in label. Defaults to true. + */ + tooltip?: string | MarkdownString; + } +}