diff --git a/CHANGELOG.md b/CHANGELOG.md index c8f67ad6a79a..eb66dd0b4fc5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ # Changelog -## 2019.5.2 (4 June 2019) +## 2019.5.3 (5 June 2019) + +### Fixes + +1. Fixes to detection of the shell. + ([#5916](https://github.com/microsoft/vscode-python/issues/5916)) + + +## 2019.5.18426 (4 June 2019) ### Fixes diff --git a/package-lock.json b/package-lock.json index 9784376d2605..6ad5859de0a8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "python", - "version": "2019.5.2", + "version": "2019.5.3", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1609,6 +1609,42 @@ "integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==", "dev": true }, + "@sinonjs/commons": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.4.0.tgz", + "integrity": "sha512-9jHK3YF/8HtJ9wCAbG+j8cD0i0+ATS9A7gXFqS36TblLPNy6rEEc+SB0imo91eCboGaBYGV/MT1/br/J+EE7Tw==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/formatio": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.1.tgz", + "integrity": "sha512-tsHvOB24rvyvV2+zKMmPkZ7dXX6LSLKZ7aOtXY6Edklp0uRcgGpOsQTTGTcWViFyx4uhWc6GV8QdnALbIbIdeQ==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1", + "@sinonjs/samsam": "^3.1.0" + } + }, + "@sinonjs/samsam": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.1.tgz", + "integrity": "sha512-wRSfmyd81swH0hA1bxJZJ57xr22kC07a1N4zuIL47yTS04bDk6AoCkczcqHEjcRPmJ+FruGJ9WBQiJwMtIElFw==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.0.2", + "array-from": "^2.1.1", + "lodash": "^4.17.11" + } + }, + "@sinonjs/text-encoding": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", + "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", + "dev": true + }, "@types/anymatch": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.0.tgz", @@ -1848,13 +1884,13 @@ }, "@types/json5": { "version": "0.0.29", - "resolved": "http://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, "@types/loader-utils": { "version": "1.1.3", - "resolved": "http://registry.npmjs.org/@types/loader-utils/-/loader-utils-1.1.3.tgz", + "resolved": "https://registry.npmjs.org/@types/loader-utils/-/loader-utils-1.1.3.tgz", "integrity": "sha512-euKGFr2oCB3ASBwG39CYJMR3N9T0nanVqXdiH7Zu/Nqddt6SmFRxytq/i2w9LQYNQekEtGBz+pE3qG6fQTNvRg==", "dev": true, "requires": { @@ -1944,7 +1980,7 @@ }, "@types/relateurl": { "version": "0.2.28", - "resolved": "http://registry.npmjs.org/@types/relateurl/-/relateurl-0.2.28.tgz", + "resolved": "https://registry.npmjs.org/@types/relateurl/-/relateurl-0.2.28.tgz", "integrity": "sha1-a9p9uGU/piZD9e5p6facEaOS46Y=", "dev": true }, @@ -2631,6 +2667,12 @@ "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", "dev": true }, + "array-from": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", + "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=", + "dev": true + }, "array-initial": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz", @@ -3115,7 +3157,7 @@ }, "babel-plugin-syntax-jsx": { "version": "6.18.0", - "resolved": "http://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=", "dev": true }, @@ -3478,7 +3520,7 @@ }, "browserify-aes": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", "dev": true, "requires": { @@ -3520,7 +3562,7 @@ }, "browserify-rsa": { "version": "4.0.1", - "resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", "dev": true, "requires": { @@ -3938,7 +3980,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -3959,12 +4002,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3979,17 +4024,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -4106,7 +4154,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -4118,6 +4167,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -4132,6 +4182,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -4243,7 +4294,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -4255,6 +4307,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -4376,6 +4429,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -4395,6 +4449,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -4438,7 +4493,8 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.3", @@ -4933,7 +4989,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -5196,7 +5252,7 @@ }, "create-hash": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", "dev": true, "requires": { @@ -5209,7 +5265,7 @@ }, "create-hmac": { "version": "1.1.7", - "resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", "dev": true, "requires": { @@ -6102,7 +6158,7 @@ }, "diffie-hellman": { "version": "5.0.3", - "resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", "dev": true, "requires": { @@ -6728,7 +6784,7 @@ }, "event-stream": { "version": "3.3.4", - "resolved": "http://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", "dev": true, "requires": { @@ -6910,7 +6966,7 @@ }, "external-editor": { "version": "2.2.0", - "resolved": "http://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", "dev": true, "requires": { @@ -7446,7 +7502,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -7467,12 +7524,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -7487,17 +7546,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -7614,7 +7676,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -7626,6 +7689,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -7640,6 +7704,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -7751,7 +7816,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -7763,6 +7829,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -7884,6 +7951,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -7903,6 +7971,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -7946,7 +8015,8 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.2", @@ -8072,7 +8142,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -9026,7 +9096,7 @@ }, "html-webpack-plugin": { "version": "3.2.0", - "resolved": "http://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz", "integrity": "sha1-sBq71yOsqqeze2r0SS69oD2d03s=", "dev": true, "requires": { @@ -10744,6 +10814,12 @@ "integrity": "sha1-h/zPrv/AtozRnVX2cilD+SnqNeo=", "dev": true }, + "just-extend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.0.2.tgz", + "integrity": "sha512-FrLwOgm+iXrPV+5zDU6Jqu4gCRXbWEQg2O3SKONsWE4w7AXFRkryS53bpWdaL9cNol+AmR3AEYz6kn+o0fCPnw==", + "dev": true + }, "keyv": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.0.0.tgz", @@ -11170,6 +11246,12 @@ "object.assign": "^4.1.0" } }, + "lolex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-4.1.0.tgz", + "integrity": "sha512-BYxIEXiVq5lGIXeVHnsFzqa1TxN5acnKnPCdlZSpzm8viNEOhiigupA4vTQ9HEFQ6nLTQ9wQOgBknJgzUYQ9Aw==", + "dev": true + }, "loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -11346,7 +11428,7 @@ }, "media-typer": { "version": "0.3.0", - "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", "dev": true }, @@ -11870,7 +11952,7 @@ }, "moment": { "version": "2.21.0", - "resolved": "http://registry.npmjs.org/moment/-/moment-2.21.0.tgz", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.21.0.tgz", "integrity": "sha512-TCZ36BjURTeFTM/CwRcViQlfkMvL1/vFISuNLO5GkcVm1+QHfbSiNqZuWeMFjj1/3+uAjXswgRk30j1kkLYJBQ==" }, "monaco-editor": { @@ -12026,6 +12108,36 @@ "integrity": "sha512-2NpiFHqC87y/zFke0fC0spBXL3bBsoh/p5H1EFhshxjCR5+0g2d6BiXbUFz9v1sAcxsk2htp2eQnNIci2dIYcA==", "dev": true }, + "nise": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/nise/-/nise-1.5.0.tgz", + "integrity": "sha512-Z3sfYEkLFzFmL8KY6xnSJLRxwQwYBjOXi/24lb62ZnZiGA0JUzGGTI6TBIgfCSMIDl9Jlu8SRmHNACLTemDHww==", + "dev": true, + "requires": { + "@sinonjs/formatio": "^3.1.0", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "lolex": "^4.1.0", + "path-to-regexp": "^1.7.0" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "path-to-regexp": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", + "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", + "dev": true, + "requires": { + "isarray": "0.0.1" + } + } + } + }, "no-case": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", @@ -12120,7 +12232,7 @@ }, "buffer": { "version": "4.9.1", - "resolved": "http://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", "dev": true, "requires": { @@ -12137,7 +12249,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -12991,7 +13103,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -13681,7 +13793,7 @@ }, "queue": { "version": "3.1.0", - "resolved": "http://registry.npmjs.org/queue/-/queue-3.1.0.tgz", + "resolved": "https://registry.npmjs.org/queue/-/queue-3.1.0.tgz", "integrity": "sha1-bEnQHwCeIlZ4h4nyv/rGuLmZBYU=", "dev": true, "requires": { @@ -14967,7 +15079,7 @@ }, "sha.js": { "version": "2.4.11", - "resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", "dev": true, "requires": { @@ -15047,7 +15159,7 @@ }, "simple-html-tokenizer": { "version": "0.1.1", - "resolved": "http://registry.npmjs.org/simple-html-tokenizer/-/simple-html-tokenizer-0.1.1.tgz", + "resolved": "https://registry.npmjs.org/simple-html-tokenizer/-/simple-html-tokenizer-0.1.1.tgz", "integrity": "sha1-BcLuxXn//+FFoDCsJs/qYbmA+r4=", "dev": true }, @@ -15066,6 +15178,38 @@ } } }, + "sinon": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.3.2.tgz", + "integrity": "sha512-thErC1z64BeyGiPvF8aoSg0LEnptSaWE7YhdWWbWXgelOyThent7uKOnnEh9zBxDbKixtr5dEko+ws1sZMuFMA==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.4.0", + "@sinonjs/formatio": "^3.2.1", + "@sinonjs/samsam": "^3.3.1", + "diff": "^3.5.0", + "lolex": "^4.0.1", + "nise": "^1.4.10", + "supports-color": "^5.5.0" + }, + "dependencies": { + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, "slash": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", @@ -15482,7 +15626,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -16702,7 +16846,7 @@ "dependencies": { "json5": { "version": "1.0.1", - "resolved": "http://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", "dev": true, "requires": { @@ -16956,7 +17100,7 @@ "dependencies": { "@types/react": { "version": "0.14.57", - "resolved": "http://registry.npmjs.org/@types/react/-/react-0.14.57.tgz", + "resolved": "https://registry.npmjs.org/@types/react/-/react-0.14.57.tgz", "integrity": "sha1-GHioZU+v3R04G4RXKStkM0mMW2I=", "dev": true } @@ -17720,7 +17864,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -18596,7 +18740,7 @@ }, "webpack-node-externals": { "version": "1.7.2", - "resolved": "http://registry.npmjs.org/webpack-node-externals/-/webpack-node-externals-1.7.2.tgz", + "resolved": "https://registry.npmjs.org/webpack-node-externals/-/webpack-node-externals-1.7.2.tgz", "integrity": "sha512-ajerHZ+BJKeCLviLUUmnyd5B4RavLF76uv3cs6KNuO8W+HuQaEs0y0L7o40NQxdPy5w0pcv8Ew7yPUAQG0UdCg==", "dev": true }, @@ -18829,7 +18973,7 @@ }, "wrap-ansi": { "version": "2.1.0", - "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "dev": true, "requires": { diff --git a/package.json b/package.json index 31261bcea00e..fc92de68b9f0 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "python", "displayName": "Python", "description": "Linting, Debugging (multi-threaded, remote), Intellisense, code formatting, refactoring, unit tests, snippets, and more.", - "version": "2019.5.2", + "version": "2019.5.3", "languageServerVersion": "0.2.82", "publisher": "ms-python", "author": { @@ -2252,7 +2252,7 @@ "@types/request": "^2.47.0", "@types/semver": "^5.5.0", "@types/shortid": "^0.0.29", - "@types/sinon": "^4.3.0", + "@types/sinon": "^4.3.3", "@types/stack-trace": "0.0.29", "@types/strip-json-comments": "0.0.30", "@types/temp": "^0.8.32", @@ -2326,6 +2326,7 @@ "rewiremock": "^3.13.0", "sass-loader": "^7.1.0", "shortid": "^2.2.8", + "sinon": "^7.3.2", "style-loader": "^0.23.1", "styled-jsx": "^3.1.0", "svg-inline-loader": "^0.8.0", diff --git a/src/client/common/platform/platformService.ts b/src/client/common/platform/platformService.ts index dd092df9b68e..65c1623dd3eb 100644 --- a/src/client/common/platform/platformService.ts +++ b/src/client/common/platform/platformService.ts @@ -57,6 +57,9 @@ export class PlatformService implements IPlatformService { public get isLinux(): boolean { return this.osType === OSType.Linux; } + public get osRelease(): string { + return os.release(); + } public get is64bit(): boolean { // tslint:disable-next-line:no-require-imports const arch = require('arch'); diff --git a/src/client/common/platform/types.ts b/src/client/common/platform/types.ts index 1214a77e9fa7..d14a5001cef1 100644 --- a/src/client/common/platform/types.ts +++ b/src/client/common/platform/types.ts @@ -20,6 +20,7 @@ export interface IRegistry { export const IPlatformService = Symbol('IPlatformService'); export interface IPlatformService { readonly osType: OSType; + osRelease: string; readonly pathVariableName: 'Path' | 'PATH'; readonly virtualEnvBinName: 'bin' | 'Scripts'; diff --git a/src/client/common/terminal/helper.ts b/src/client/common/terminal/helper.ts index ff67db9a74d6..e09283e45a14 100644 --- a/src/client/common/terminal/helper.ts +++ b/src/client/common/terminal/helper.ts @@ -2,44 +2,22 @@ // Licensed under the MIT License. import { inject, injectable, named } from 'inversify'; -import * as os from 'os'; import { Terminal, Uri } from 'vscode'; import { ICondaService, IInterpreterService, InterpreterType, PythonInterpreter } from '../../interpreter/contracts'; import { sendTelemetryEvent } from '../../telemetry'; import { EventName } from '../../telemetry/constants'; -import { ITerminalManager } from '../application/types'; +import { ITerminalManager, IWorkspaceService } from '../application/types'; import '../extensions'; import { traceDecorators, traceError } from '../logger'; import { IPlatformService } from '../platform/types'; import { IConfigurationService, ICurrentProcess, Resource } from '../types'; import { OSType } from '../utils/platform'; +import { ShellDetector } from './shellDetector'; import { ITerminalActivationCommandProvider, ITerminalHelper, TerminalActivationProviders, TerminalShellType } from './types'; -// Types of shells can be found here: -// 1. https://wiki.ubuntu.com/ChangingShells -const IS_GITBASH = /(gitbash.exe$)/i; -const IS_BASH = /(bash.exe$|bash$)/i; -const IS_WSL = /(wsl.exe$)/i; -const IS_ZSH = /(zsh$)/i; -const IS_KSH = /(ksh$)/i; -const IS_COMMAND = /(cmd.exe$|cmd$)/i; -const IS_POWERSHELL = /(powershell.exe$|powershell$)/i; -const IS_POWERSHELL_CORE = /(pwsh.exe$|pwsh$)/i; -const IS_FISH = /(fish$)/i; -const IS_CSHELL = /(csh$)/i; -const IS_TCSHELL = /(tcsh$)/i; -const IS_XONSH = /(xonsh$)/i; - -const defaultOSShells = { - [OSType.Linux]: TerminalShellType.bash, - [OSType.OSX]: TerminalShellType.bash, - [OSType.Windows]: TerminalShellType.commandPrompt, - [OSType.Unknown]: undefined -}; - @injectable() export class TerminalHelper implements ITerminalHelper { - private readonly detectableShells: Map; + private readonly shellDetector: ShellDetector; constructor(@inject(IPlatformService) private readonly platform: IPlatformService, @inject(ITerminalManager) private readonly terminalManager: ITerminalManager, @inject(ICondaService) private readonly condaService: ICondaService, @@ -50,61 +28,17 @@ export class TerminalHelper implements ITerminalHelper { @inject(ITerminalActivationCommandProvider) @named(TerminalActivationProviders.commandPromptAndPowerShell) private readonly commandPromptAndPowerShell: ITerminalActivationCommandProvider, @inject(ITerminalActivationCommandProvider) @named(TerminalActivationProviders.pyenv) private readonly pyenv: ITerminalActivationCommandProvider, @inject(ITerminalActivationCommandProvider) @named(TerminalActivationProviders.pipenv) private readonly pipenv: ITerminalActivationCommandProvider, - @inject(IConfigurationService) private readonly currentProcess: ICurrentProcess + @inject(ICurrentProcess) private readonly currentProcess: ICurrentProcess, + @inject(IWorkspaceService) private readonly workspace: IWorkspaceService ) { - this.detectableShells = new Map(); - this.detectableShells.set(TerminalShellType.powershell, IS_POWERSHELL); - this.detectableShells.set(TerminalShellType.gitbash, IS_GITBASH); - this.detectableShells.set(TerminalShellType.bash, IS_BASH); - this.detectableShells.set(TerminalShellType.wsl, IS_WSL); - this.detectableShells.set(TerminalShellType.zsh, IS_ZSH); - this.detectableShells.set(TerminalShellType.ksh, IS_KSH); - this.detectableShells.set(TerminalShellType.commandPrompt, IS_COMMAND); - this.detectableShells.set(TerminalShellType.fish, IS_FISH); - this.detectableShells.set(TerminalShellType.tcshell, IS_TCSHELL); - this.detectableShells.set(TerminalShellType.cshell, IS_CSHELL); - this.detectableShells.set(TerminalShellType.powershellCore, IS_POWERSHELL_CORE); - this.detectableShells.set(TerminalShellType.xonsh, IS_XONSH); + this.shellDetector = new ShellDetector(this.platform, this.currentProcess, this.workspace); + } public createTerminal(title?: string): Terminal { return this.terminalManager.createTerminal({ name: title }); } public identifyTerminalShell(terminal?: Terminal): TerminalShellType { - let shell = TerminalShellType.other; - let usingDefaultShell = false; - const terminalProvided = !!terminal; - // Determine shell based on the name of the terminal. - // See solution here https://github.com/microsoft/vscode/issues/74233#issuecomment-497527337 - if (terminal) { - shell = this.identifyTerminalShellByName(terminal.name); - } - - // If still unable to identify, then use fall back to determine path to the default shell. - if (shell === TerminalShellType.other) { - const shellPath = getDefaultShell(this.platform.osType, this.currentProcess); - shell = Array.from(this.detectableShells.keys()) - .reduce((matchedShell, shellToDetect) => { - if (matchedShell === TerminalShellType.other && this.detectableShells.get(shellToDetect)!.test(shellPath)) { - return shellToDetect; - } - return matchedShell; - }, TerminalShellType.other); - - // We have restored to using the default shell. - usingDefaultShell = shell !== TerminalShellType.other; - } - const properties = { failed: shell === TerminalShellType.other, usingDefaultShell, terminalProvided }; - sendTelemetryEvent(EventName.TERMINAL_SHELL_IDENTIFICATION, undefined, properties); - return shell; - } - public identifyTerminalShellByName(name: string): TerminalShellType { - return Array.from(this.detectableShells.keys()) - .reduce((matchedShell, shellToDetect) => { - if (matchedShell === TerminalShellType.other && this.detectableShells.get(shellToDetect)!.test(name)) { - return shellToDetect; - } - return matchedShell; - }, TerminalShellType.other); + return this.shellDetector.identifyTerminalShell(terminal); } public buildCommandForTerminal(terminalShellType: TerminalShellType, command: string, args: string[]) { @@ -119,7 +53,10 @@ export class TerminalHelper implements ITerminalHelper { return promise; } public async getEnvironmentActivationShellCommands(resource: Resource, interpreter?: PythonInterpreter): Promise { - const shell = defaultOSShells[this.platform.osType]; + if (this.platform.osType === OSType.Unknown){ + return; + } + const shell = this.shellDetector.identifyTerminalShell(); if (!shell) { return; } @@ -181,30 +118,3 @@ export class TerminalHelper implements ITerminalHelper { } } } - -/* - The following code is based on VS Code from https://github.com/microsoft/vscode/blob/5c65d9bfa4c56538150d7f3066318e0db2c6151f/src/vs/workbench/contrib/terminal/node/terminal.ts#L12-L55 - This is only a fall back to identify the default shell used by VSC. - On Windows, determine the default shell. - On others, default to bash. -*/ -function getDefaultShell(osType: OSType, currentProcess: ICurrentProcess): string { - if (osType === OSType.Windows) { - return getTerminalDefaultShellWindows(osType, currentProcess); - } - return '/bin/bash'; -} -let _TERMINAL_DEFAULT_SHELL_WINDOWS: string | null = null; -function getTerminalDefaultShellWindows(osType: OSType, currentProcess: ICurrentProcess): string { - if (!_TERMINAL_DEFAULT_SHELL_WINDOWS) { - const isAtLeastWindows10 = osType === OSType.Windows && parseFloat(os.release()) >= 10; - const is32ProcessOn64Windows = process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432'); - const powerShellPath = `${process.env.windir}\\${is32ProcessOn64Windows ? 'Sysnative' : 'System32'}\\WindowsPowerShell\\v1.0\\powershell.exe`; - _TERMINAL_DEFAULT_SHELL_WINDOWS = isAtLeastWindows10 ? powerShellPath : getWindowsShell(currentProcess); - } - return _TERMINAL_DEFAULT_SHELL_WINDOWS; -} - -function getWindowsShell(currentProcess: ICurrentProcess): string { - return currentProcess.env.comspec || 'cmd.exe'; -} diff --git a/src/client/common/terminal/shellDetector.ts b/src/client/common/terminal/shellDetector.ts new file mode 100644 index 000000000000..5af05018632e --- /dev/null +++ b/src/client/common/terminal/shellDetector.ts @@ -0,0 +1,209 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { inject, injectable } from 'inversify'; +import { Terminal } from 'vscode'; +import { sendTelemetryEvent } from '../../telemetry'; +import { EventName } from '../../telemetry/constants'; +import { IWorkspaceService } from '../application/types'; +import '../extensions'; +import { traceVerbose } from '../logger'; +import { IPlatformService } from '../platform/types'; +import { ICurrentProcess } from '../types'; +import { OSType } from '../utils/platform'; +import { TerminalShellType } from './types'; + +// Types of shells can be found here: +// 1. https://wiki.ubuntu.com/ChangingShells +const IS_GITBASH = /(gitbash.exe$)/i; +const IS_BASH = /(bash.exe$|bash$)/i; +const IS_WSL = /(wsl.exe$)/i; +const IS_ZSH = /(zsh$)/i; +const IS_KSH = /(ksh$)/i; +const IS_COMMAND = /(cmd.exe$|cmd$)/i; +const IS_POWERSHELL = /(powershell.exe$|powershell$)/i; +const IS_POWERSHELL_CORE = /(pwsh.exe$|pwsh$)/i; +const IS_FISH = /(fish$)/i; +const IS_CSHELL = /(csh$)/i; +const IS_TCSHELL = /(tcsh$)/i; +const IS_XONSH = /(xonsh$)/i; + +const defaultOSShells = { + [OSType.Linux]: TerminalShellType.bash, + [OSType.OSX]: TerminalShellType.bash, + [OSType.Windows]: TerminalShellType.commandPrompt, + [OSType.Unknown]: undefined +}; + +type ShellIdentificationTelemetry = { + failed: boolean; + terminalProvided: boolean; + shellIdentificationSource: 'terminalName' | 'settings' | 'environment' | 'default'; + hasCustomShell: undefined | boolean; + hasShellInEnv: undefined | boolean; +}; +const detectableShells = new Map(); +detectableShells.set(TerminalShellType.powershell, IS_POWERSHELL); +detectableShells.set(TerminalShellType.gitbash, IS_GITBASH); +detectableShells.set(TerminalShellType.bash, IS_BASH); +detectableShells.set(TerminalShellType.wsl, IS_WSL); +detectableShells.set(TerminalShellType.zsh, IS_ZSH); +detectableShells.set(TerminalShellType.ksh, IS_KSH); +detectableShells.set(TerminalShellType.commandPrompt, IS_COMMAND); +detectableShells.set(TerminalShellType.fish, IS_FISH); +detectableShells.set(TerminalShellType.tcshell, IS_TCSHELL); +detectableShells.set(TerminalShellType.cshell, IS_CSHELL); +detectableShells.set(TerminalShellType.powershellCore, IS_POWERSHELL_CORE); +detectableShells.set(TerminalShellType.xonsh, IS_XONSH); + +@injectable() +export class ShellDetector { + constructor(@inject(IPlatformService) private readonly platform: IPlatformService, + @inject(ICurrentProcess) private readonly currentProcess: ICurrentProcess, + @inject(IWorkspaceService) private readonly workspace: IWorkspaceService + ) { } + /** + * Logic is as follows: + * 1. Try to identify the type of the shell based on the name of the terminal. + * 2. Try to identify the type of the shell based on the usettigs in VSC. + * 3. Try to identify the type of the shell based on the user environment (OS). + * 4. If all else fail, use defaults hardcoded (cmd for windows, bash for linux & mac). + * More information here See solution here https://github.com/microsoft/vscode/issues/74233#issuecomment-497527337 + * + * @param {Terminal} [terminal] + * @returns {TerminalShellType} + * @memberof TerminalHelper + */ + public identifyTerminalShell(terminal?: Terminal): TerminalShellType { + let shell = TerminalShellType.other; + const telemetryProperties: ShellIdentificationTelemetry = { + failed: true, + shellIdentificationSource: 'default', + terminalProvided: !!terminal, + hasCustomShell: undefined, + hasShellInEnv: undefined + }; + + // Step 1. Determine shell based on the name of the terminal. + if (terminal) { + shell = this.identifyShellByTerminalName(terminal.name, telemetryProperties); + } + + // Step 2. Detemrine shell based on user settings. + if (shell === TerminalShellType.other) { + shell = this.identifyShellFromSettings(telemetryProperties); + } + + // Step 3. Determine shell based on user environment. + if (shell === TerminalShellType.other) { + shell = this.identifyShellFromUserEnv(telemetryProperties); + } + + // This information is useful in determining how well we identify shells on users machines. + // This impacts executing code in terminals and activation of environments in terminal. + // So, the better this works, the better it is for the user. + sendTelemetryEvent(EventName.TERMINAL_SHELL_IDENTIFICATION, undefined, telemetryProperties); + traceVerbose(`Shell identified as '${shell}'`); + + // If we could not identify the shell, use the defaults. + return shell === TerminalShellType.other ? (defaultOSShells[this.platform.osType] || TerminalShellType.other) : shell; + } + public getTerminalShellPath(): string | undefined { + const shellConfig = this.workspace.getConfiguration('terminal.integrated.shell'); + let osSection = ''; + switch (this.platform.osType) { + case OSType.Windows: { + osSection = 'windows'; + break; + } + case OSType.OSX: { + osSection = 'osx'; + break; + } + case OSType.Linux: { + osSection = 'linux'; + break; + } + default: { + return ''; + } + } + return shellConfig.get(osSection)!; + } + public getDefaultPlatformShell(): string { + return getDefaultShell(this.platform, this.currentProcess); + } + public identifyShellByTerminalName(name: string, telemetryProperties: ShellIdentificationTelemetry): TerminalShellType { + const shell = Array.from(detectableShells.keys()) + .reduce((matchedShell, shellToDetect) => { + if (matchedShell === TerminalShellType.other && detectableShells.get(shellToDetect)!.test(name)) { + return shellToDetect; + } + return matchedShell; + }, TerminalShellType.other); + traceVerbose(`Terminal name '${name}' identified as shell '${shell}'`); + telemetryProperties.shellIdentificationSource = shell === TerminalShellType.other ? telemetryProperties.shellIdentificationSource : 'terminalName'; + return shell; + } + public identifyShellFromSettings(telemetryProperties: ShellIdentificationTelemetry): TerminalShellType { + const shellPath = this.getTerminalShellPath(); + telemetryProperties.hasCustomShell = !!shellPath; + const shell = shellPath ? this.identifyShellFromShellPath(shellPath) : TerminalShellType.other; + + if (shell !== TerminalShellType.other) { + telemetryProperties.shellIdentificationSource = 'environment'; + } + telemetryProperties.shellIdentificationSource = 'settings'; + traceVerbose(`Shell path from user settings '${shellPath}'`); + return shell; + } + + public identifyShellFromUserEnv(telemetryProperties: ShellIdentificationTelemetry): TerminalShellType { + const shellPath = this.getDefaultPlatformShell(); + telemetryProperties.hasShellInEnv = !!shellPath; + const shell = this.identifyShellFromShellPath(shellPath); + + if (shell !== TerminalShellType.other) { + telemetryProperties.shellIdentificationSource = 'environment'; + } + traceVerbose(`Shell path from user env '${shellPath}'`); + return shell; + } + public identifyShellFromShellPath(shellPath: string): TerminalShellType { + const shell = Array.from(detectableShells.keys()) + .reduce((matchedShell, shellToDetect) => { + if (matchedShell === TerminalShellType.other && detectableShells.get(shellToDetect)!.test(shellPath)) { + return shellToDetect; + } + return matchedShell; + }, TerminalShellType.other); + + traceVerbose(`Shell path '${shellPath}'`); + traceVerbose(`Shell path identified as shell '${shell}'`); + return shell; + } +} + +/* + The following code is based on VS Code from https://github.com/microsoft/vscode/blob/5c65d9bfa4c56538150d7f3066318e0db2c6151f/src/vs/workbench/contrib/terminal/node/terminal.ts#L12-L55 + This is only a fall back to identify the default shell used by VSC. + On Windows, determine the default shell. + On others, default to bash. +*/ +function getDefaultShell(platform: IPlatformService, currentProcess: ICurrentProcess): string { + if (platform.osType === OSType.Windows) { + return getTerminalDefaultShellWindows(platform, currentProcess); + } + + return currentProcess.env.SHELL && currentProcess.env.SHELL !== '/bin/false' ? currentProcess.env.SHELL : '/bin/bash'; +} +function getTerminalDefaultShellWindows(platform: IPlatformService, currentProcess: ICurrentProcess): string { + const isAtLeastWindows10 = parseFloat(platform.osRelease) >= 10; + const is32ProcessOn64Windows = currentProcess.env.hasOwnProperty('PROCESSOR_ARCHITEW6432'); + const powerShellPath = `${currentProcess.env.windir}\\${is32ProcessOn64Windows ? 'Sysnative' : 'System32'}\\WindowsPowerShell\\v1.0\\powershell.exe`; + return isAtLeastWindows10 ? powerShellPath : getWindowsShell(currentProcess); +} + +function getWindowsShell(currentProcess: ICurrentProcess): string { + return currentProcess.env.comspec || 'cmd.exe'; +} diff --git a/src/client/telemetry/index.ts b/src/client/telemetry/index.ts index dca4b863c90a..c0b93fdd2da4 100644 --- a/src/client/telemetry/index.ts +++ b/src/client/telemetry/index.ts @@ -370,9 +370,30 @@ export interface IEventNamePropertyMapping { [EventName.UNITTEST_EXPLORER_WORK_SPACE_COUNT]: { count: number }; /* Telemetry event sent to provide information on whether we have successfully identify the type of shell used. + This information is useful in determining how well we identify shells on users machines. + This impacts executing code in terminals and activation of environments in terminal. + So, the better this works, the better it is for the user. failed - If true, indicates we have failed to identify the shell. Note this impacts impacts ability to activate environments in the terminal & code. - usingDefaultShell - If true, this indicates we failed to identify the shell and we have reverted to using the default shell on user machine. + shellIdentificationSource - How was the shell identified. One of 'terminalName' | 'settings' | 'environment' | 'default' + If terminalName, then this means we identified the type of the shell based on the name of the terminal. + If settings, then this means we identified the type of the shell based on user settings in VS Code. + If environment, then this means we identified the type of the shell based on their environment (env variables, etc). + I.e. their default OS Shell. + If default, then we reverted to OS defaults (cmd on windows, and bash on the rest). + This is the worst case scenario. + I.e. we could not identify the shell at all. terminalProvided - If true, we used the terminal provided to detec the shell. If not provided, we use the default shell on user machine. + hasCustomShell - If undefined (not set), we didn't check. + If true, user has customzied their shell in VSC Settings. + hasShellInEnv - If undefined (not set), we didn't check. + If true, user has a shell in their environment. + If false, user does not have a shell in their environment. */ - [EventName.TERMINAL_SHELL_IDENTIFICATION]: { failed: boolean; usingDefaultShell: boolean; terminalProvided: boolean }; + [EventName.TERMINAL_SHELL_IDENTIFICATION]: { + failed: boolean; + terminalProvided: boolean; + shellIdentificationSource: 'terminalName' | 'settings' | 'environment' | 'default'; + hasCustomShell: undefined | boolean; + hasShellInEnv: undefined | boolean; + }; } diff --git a/src/test/common/platform/platformService.test.ts b/src/test/common/platform/platformService.test.ts index 34e478920325..5a8c379aa0d8 100644 --- a/src/test/common/platform/platformService.test.ts +++ b/src/test/common/platform/platformService.test.ts @@ -55,6 +55,14 @@ suite('PlatformService', () => { expect(result).to.be.equal(expected, 'invalid value'); }); + test('osRelease', async () => { + const expected = os.release(); + const svc = new PlatformService(); + const result = svc.osRelease; + + expect(result).to.be.equal(expected, 'invalid value'); + }); + test('is64bit', async () => { // tslint:disable-next-line:no-require-imports const arch = require('arch'); diff --git a/src/test/common/terminals/activation.conda.unit.test.ts b/src/test/common/terminals/activation.conda.unit.test.ts index 974f1997bf59..10858eb65d80 100644 --- a/src/test/common/terminals/activation.conda.unit.test.ts +++ b/src/test/common/terminals/activation.conda.unit.test.ts @@ -10,6 +10,7 @@ import { anything, instance, mock, when } from 'ts-mockito'; import * as TypeMoq from 'typemoq'; import { Disposable } from 'vscode'; import { TerminalManager } from '../../../client/common/application/terminalManager'; +import { WorkspaceService } from '../../../client/common/application/workspace'; import '../../../client/common/extensions'; import { IFileSystem, IPlatformService @@ -93,7 +94,8 @@ suite('Terminal Environment Activation conda', () => { mock(CommandPromptAndPowerShell), mock(PyEnvActivationCommandProvider), mock(PipEnvActivationCommandProvider), - instance(mock(CurrentProcess))); + instance(mock(CurrentProcess)), + instance(mock(WorkspaceService))); }); teardown(() => { diff --git a/src/test/common/terminals/helper.unit.test.ts b/src/test/common/terminals/helper.unit.test.ts index 28f255278d8a..3d53c41a9ecf 100644 --- a/src/test/common/terminals/helper.unit.test.ts +++ b/src/test/common/terminals/helper.unit.test.ts @@ -2,10 +2,12 @@ // Licensed under the MIT License. import { expect } from 'chai'; import { SemVer } from 'semver'; +import * as sinon from 'sinon'; import { anything, capture, instance, mock, verify, when } from 'ts-mockito'; import { Uri } from 'vscode'; import { TerminalManager } from '../../../client/common/application/terminalManager'; import { ITerminalManager } from '../../../client/common/application/types'; +import { WorkspaceService } from '../../../client/common/application/workspace'; import { PythonSettings } from '../../../client/common/configSettings'; import { ConfigurationService } from '../../../client/common/configuration/service'; import { PlatformService } from '../../../client/common/platform/platformService'; @@ -23,6 +25,7 @@ import { PyEnvActivationCommandProvider } from '../../../client/common/terminal/environmentActivationProviders/pyenvActivationProvider'; import { TerminalHelper } from '../../../client/common/terminal/helper'; +import { ShellDetector } from '../../../client/common/terminal/shellDetector'; import { ITerminalActivationCommandProvider, TerminalShellType @@ -48,6 +51,8 @@ suite('Terminal Service helpers', () => { let pyenvActivationProvider: ITerminalActivationCommandProvider; let pipenvActivationProvider: ITerminalActivationCommandProvider; let pythonSettings: PythonSettings; + let currentProcess: CurrentProcess; + let shellDetectorIdentifyTerminalShell: sinon.SinonStub; const pythonInterpreter: PythonInterpreter = { path: '/foo/bar/python.exe', @@ -69,7 +74,8 @@ suite('Terminal Service helpers', () => { pyenvActivationProvider = mock(PyEnvActivationCommandProvider); pipenvActivationProvider = mock(PipEnvActivationCommandProvider); pythonSettings = mock(PythonSettings); - + currentProcess = mock(CurrentProcess); + shellDetectorIdentifyTerminalShell = sinon.stub(ShellDetector.prototype, 'identifyTerminalShell'); helper = new TerminalHelper(instance(platformService), instance(terminalManager), instance(condaService), instance(mock(InterpreterService)), @@ -79,8 +85,10 @@ suite('Terminal Service helpers', () => { instance(cmdActivationProvider), instance(pyenvActivationProvider), instance(pipenvActivationProvider), - instance(mock(CurrentProcess))); + instance(currentProcess), + instance(mock(WorkspaceService))); } + teardown(() => shellDetectorIdentifyTerminalShell.restore()); suite('Misc', () => { setup(doSetup); @@ -107,44 +115,6 @@ suite('Terminal Service helpers', () => { expect(term).to.be.deep.equal(terminal); expect(args.name).to.be.deep.equal(theTitle); }); - test('Revert to default shell', async () => { - when(platformService.osType).thenReturn(OSType.OSX); - - const shellType = helper.identifyTerminalShell(); - - expect(shellType).to.be.equal(TerminalShellType.bash); - }); - test('Test identification of Terminal Shells', async () => { - const shellPathsAndIdentification = new Map(); - shellPathsAndIdentification.set('c:\\windows\\system32\\cmd.exe', TerminalShellType.commandPrompt); - - shellPathsAndIdentification.set('c:\\windows\\system32\\bash.exe', TerminalShellType.bash); - shellPathsAndIdentification.set('c:\\windows\\system32\\wsl.exe', TerminalShellType.wsl); - shellPathsAndIdentification.set('c:\\windows\\system32\\gitbash.exe', TerminalShellType.gitbash); - shellPathsAndIdentification.set('/usr/bin/bash', TerminalShellType.bash); - shellPathsAndIdentification.set('/usr/bin/zsh', TerminalShellType.zsh); - shellPathsAndIdentification.set('/usr/bin/ksh', TerminalShellType.ksh); - - shellPathsAndIdentification.set('c:\\windows\\system32\\powershell.exe', TerminalShellType.powershell); - shellPathsAndIdentification.set('c:\\windows\\system32\\pwsh.exe', TerminalShellType.powershellCore); - shellPathsAndIdentification.set('/usr/microsoft/xxx/powershell/powershell', TerminalShellType.powershell); - shellPathsAndIdentification.set('/usr/microsoft/xxx/powershell/pwsh', TerminalShellType.powershellCore); - - shellPathsAndIdentification.set('/usr/bin/fish', TerminalShellType.fish); - - shellPathsAndIdentification.set('c:\\windows\\system32\\shell.exe', TerminalShellType.other); - shellPathsAndIdentification.set('/usr/bin/shell', TerminalShellType.other); - - shellPathsAndIdentification.set('/usr/bin/csh', TerminalShellType.cshell); - shellPathsAndIdentification.set('/usr/bin/tcsh', TerminalShellType.tcshell); - - shellPathsAndIdentification.set('/usr/bin/xonsh', TerminalShellType.xonsh); - shellPathsAndIdentification.set('/usr/bin/xonshx', TerminalShellType.other); - - shellPathsAndIdentification.forEach((shellType, shellPath) => { - expect(helper.identifyTerminalShellByName(shellPath)).to.equal(shellType, `Incorrect Shell Type for path '${shellPath}'`); - }); - }); test('Ensure spaces in command is quoted', async () => { getNamesAndValues(TerminalShellType).forEach(item => { const command = 'c:\\python 3.7.exe'; @@ -182,6 +152,7 @@ suite('Terminal Service helpers', () => { }); }); + function title(resource?: Uri, interpreter?: PythonInterpreter) { return `${resource ? 'With a resource' : 'Without a resource'}${interpreter ? ' and an interpreter' : ''}`; } @@ -342,23 +313,11 @@ suite('Terminal Service helpers', () => { }); [undefined, pythonInterpreter].forEach(interpreter => { test('Activation command for Shell must be empty for unknown os', async () => { - const pythonPath = 'some python Path value'; - ensureCondaIsSupported(false, pythonPath, []); - when(platformService.osType).thenReturn(OSType.Unknown); - when(bashActivationProvider.isShellSupported(anything())).thenReturn(false); - when(cmdActivationProvider.isShellSupported(anything())).thenReturn(false); const cmd = await helper.getEnvironmentActivationShellCommands(resource, interpreter); expect(cmd).to.equal(undefined, 'Command must be undefined'); - verify(pythonSettings.terminal).never(); - verify(pythonSettings.pythonPath).never(); - verify(condaService.isCondaEnvironment(pythonPath)).never(); - verify(bashActivationProvider.isShellSupported(anything())).never(); - verify(pyenvActivationProvider.isShellSupported(anything())).never(); - verify(pipenvActivationProvider.isShellSupported(anything())).never(); - verify(cmdActivationProvider.isShellSupported(anything())).never(); }); }); [undefined, pythonInterpreter].forEach(interpreter => { @@ -368,6 +327,7 @@ suite('Terminal Service helpers', () => { const shellToExpect = osType === OSType.Windows ? TerminalShellType.commandPrompt : TerminalShellType.bash; ensureCondaIsSupported(false, pythonPath, []); + shellDetectorIdentifyTerminalShell.returns(shellToExpect); when(platformService.osType).thenReturn(osType); when(bashActivationProvider.isShellSupported(shellToExpect)).thenReturn(false); when(cmdActivationProvider.isShellSupported(shellToExpect)).thenReturn(false); diff --git a/src/test/common/terminals/shellDetector.unit.test.ts b/src/test/common/terminals/shellDetector.unit.test.ts new file mode 100644 index 000000000000..c3084c500dd7 --- /dev/null +++ b/src/test/common/terminals/shellDetector.unit.test.ts @@ -0,0 +1,221 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +import { expect } from 'chai'; +import * as sinon from 'sinon'; +import { instance, mock, when } from 'ts-mockito'; +import { Terminal } from 'vscode'; +import { WorkspaceService } from '../../../client/common/application/workspace'; +import { PlatformService } from '../../../client/common/platform/platformService'; +import { IPlatformService } from '../../../client/common/platform/types'; +import { CurrentProcess } from '../../../client/common/process/currentProcess'; +import { ShellDetector } from '../../../client/common/terminal/shellDetector'; +import { TerminalShellType } from '../../../client/common/terminal/types'; +import { OSType } from '../../common'; + +// tslint:disable:max-func-body-length no-any + +suite('Shell Detector', () => { + let shellDetector: ShellDetector; + let platformService: IPlatformService; + let currentProcess: CurrentProcess; + + // Dummy data for testing. + const shellPathsAndIdentification = new Map(); + shellPathsAndIdentification.set('c:\\windows\\system32\\cmd.exe', TerminalShellType.commandPrompt); + shellPathsAndIdentification.set('c:\\windows\\system32\\bash.exe', TerminalShellType.bash); + shellPathsAndIdentification.set('c:\\windows\\system32\\wsl.exe', TerminalShellType.wsl); + shellPathsAndIdentification.set('c:\\windows\\system32\\gitbash.exe', TerminalShellType.gitbash); + shellPathsAndIdentification.set('/usr/bin/bash', TerminalShellType.bash); + shellPathsAndIdentification.set('/usr/bin/zsh', TerminalShellType.zsh); + shellPathsAndIdentification.set('/usr/bin/ksh', TerminalShellType.ksh); + shellPathsAndIdentification.set('c:\\windows\\system32\\powershell.exe', TerminalShellType.powershell); + shellPathsAndIdentification.set('c:\\windows\\system32\\pwsh.exe', TerminalShellType.powershellCore); + shellPathsAndIdentification.set('/usr/microsoft/xxx/powershell/powershell', TerminalShellType.powershell); + shellPathsAndIdentification.set('/usr/microsoft/xxx/powershell/pwsh', TerminalShellType.powershellCore); + shellPathsAndIdentification.set('/usr/bin/fish', TerminalShellType.fish); + shellPathsAndIdentification.set('c:\\windows\\system32\\shell.exe', TerminalShellType.other); + shellPathsAndIdentification.set('/usr/bin/shell', TerminalShellType.other); + shellPathsAndIdentification.set('/usr/bin/csh', TerminalShellType.cshell); + shellPathsAndIdentification.set('/usr/bin/tcsh', TerminalShellType.tcshell); + shellPathsAndIdentification.set('/usr/bin/xonsh', TerminalShellType.xonsh); + shellPathsAndIdentification.set('/usr/bin/xonshx', TerminalShellType.other); + + + setup(() => { + platformService = mock(PlatformService); + currentProcess = mock(CurrentProcess); + shellDetector = new ShellDetector(instance(platformService), + instance(currentProcess), + instance(mock(WorkspaceService))); + }); + test('Test identification of Terminal Shells', async () => { + shellPathsAndIdentification.forEach((shellType, shellPath) => { + expect(shellDetector.identifyShellByTerminalName(shellPath, {} as any)).to.equal(shellType, `Incorrect Shell Type from identifyShellByTerminalName, for path '${shellPath}'`); + expect(shellDetector.identifyShellFromShellPath(shellPath)).to.equal(shellType, `Incorrect Shell Type for path from identifyTerminalFromShellPath, '${shellPath}'`); + + // Assume the same paths are stored in user settings, we should still be able to identify the shell. + shellDetector.getTerminalShellPath = () => shellPath; + expect(shellDetector.identifyShellFromSettings({} as any)).to.equal(shellType, `Incorrect Shell Type from identifyTerminalFromSettings, for path '${shellPath}'`); + + // Assume the same paths are defined in user environment variables, we should still be able to identify the shell. + shellDetector.getDefaultPlatformShell = () => shellPath; + expect(shellDetector.identifyShellFromUserEnv({} as any)).to.equal(shellType, `Incorrect Shell Type from identifyTerminalFromEnv, for path '${shellPath}'`); + }); + }); + test('Default shell on Windows < 10 is cmd.exe', () => { + when(platformService.osType).thenReturn(OSType.Windows); + when(platformService.osRelease).thenReturn('7'); + when(currentProcess.env).thenReturn({}); + + const shellPath = shellDetector.getDefaultPlatformShell(); + + expect(shellPath).to.equal('cmd.exe'); + }); + test('Default shell on Windows >= 10 32bit is powershell.exe', () => { + when(platformService.osType).thenReturn(OSType.Windows); + when(platformService.osRelease).thenReturn('10'); + when(currentProcess.env).thenReturn({ windir: 'WindowsDir', PROCESSOR_ARCHITEW6432: '', comspec: 'hello.exe' }); + + const shellPath = shellDetector.getDefaultPlatformShell(); + + expect(shellPath).to.equal('WindowsDir\\Sysnative\\WindowsPowerShell\\v1.0\\powershell.exe'); + }); + test('Default shell on Windows >= 10 64bit is powershell.exe', () => { + when(platformService.osType).thenReturn(OSType.Windows); + when(platformService.osRelease).thenReturn('10'); + when(currentProcess.env).thenReturn({ windir: 'WindowsDir', comspec: 'hello.exe' }); + + const shellPath = shellDetector.getDefaultPlatformShell(); + + expect(shellPath).to.equal('WindowsDir\\System32\\WindowsPowerShell\\v1.0\\powershell.exe'); + }); + test('Default shell on Windows < 10 is what ever is defined in env.comspec', () => { + when(platformService.osType).thenReturn(OSType.Windows); + when(platformService.osRelease).thenReturn('7'); + when(currentProcess.env).thenReturn({ comspec: 'hello.exe' }); + + const shellPath = shellDetector.getDefaultPlatformShell(); + + expect(shellPath).to.equal('hello.exe'); + }); + [OSType.OSX, OSType.Linux].forEach((osType) => { + test(`Default shell on ${osType} is /bin/bash`, () => { + when(platformService.osType).thenReturn(OSType.OSX); + when(currentProcess.env).thenReturn({}); + + const shellPath = shellDetector.getDefaultPlatformShell(); + + expect(shellPath).to.equal('/bin/bash'); + }); + test(`Default shell on ${osType} is what ever is in env.SHELL`, () => { + when(platformService.osType).thenReturn(OSType.OSX); + when(currentProcess.env).thenReturn({ SHELL: 'hello terminal.app' }); + + const shellPath = shellDetector.getDefaultPlatformShell(); + + expect(shellPath).to.equal('hello terminal.app'); + }); + test(`Default shell on ${osType} is what ever is /bin/bash if env.SHELL == /bin/false`, () => { + when(platformService.osType).thenReturn(OSType.OSX); + when(currentProcess.env).thenReturn({ SHELL: '/bin/false' }); + + const shellPath = shellDetector.getDefaultPlatformShell(); + + expect(shellPath).to.equal('/bin/bash'); + }); + }); + shellPathsAndIdentification.forEach((expectedShell, shellPath) => { + if (expectedShell === TerminalShellType.other) { + return; + } + const testSuffix = `(${shellPath})`; + test(`Try identifying the shell based on the terminal name ${testSuffix}`, () => { + const terminal: Terminal = { name: shellPath } as any; + + const identifyShellByTerminalName = sinon.stub(shellDetector, 'identifyShellByTerminalName'); + const getTerminalShellPath = sinon.stub(shellDetector, 'getTerminalShellPath'); + const getDefaultPlatformShell = sinon.stub(shellDetector, 'getDefaultPlatformShell'); + + identifyShellByTerminalName.withArgs(terminal.name).callsFake(() => expectedShell); + + const shell = shellDetector.identifyTerminalShell(terminal); + + expect(identifyShellByTerminalName.calledOnce).to.equal(true, 'identifyShellByTerminalName should be invoked to identify the shell'); + expect(getTerminalShellPath.notCalled).to.equal(true, 'We should not be checking the shell path'); + expect(getDefaultPlatformShell.notCalled).to.equal(true, 'We should not be identifying the default OS shell'); + expect(shell).to.equal(expectedShell); + }); + test(`Try identifying the shell based on VSC Settings ${testSuffix}`, () => { + // As the terminal is 'some unknown value' we don't know the shell. + // We should identify the shell based on VSC settings. + // We should not check user environment for shell. + const terminal: Terminal = { name: 'some unknown name' } as any; + + const identifyShellByTerminalName = sinon.stub(shellDetector, 'identifyShellByTerminalName'); + const getTerminalShellPath = sinon.stub(shellDetector, 'getTerminalShellPath'); + const getDefaultPlatformShell = sinon.stub(shellDetector, 'getDefaultPlatformShell'); + + // We cannot identify shell by the name of the terminal, hence other will be returned. + identifyShellByTerminalName.withArgs(terminal.name).callsFake(() => TerminalShellType.other); + getTerminalShellPath.returns(shellPath); + + const shell = shellDetector.identifyTerminalShell(terminal); + + expect(getTerminalShellPath.calledOnce).to.equal(true, 'We should be checking the shell path'); + expect(getTerminalShellPath.calledAfter(identifyShellByTerminalName)).to.equal(true, 'We should be checking the shell path after checking terminal name'); + expect(getDefaultPlatformShell.calledOnce).to.equal(false, 'We should not be identifying the default OS shell'); + expect(identifyShellByTerminalName.calledOnce).to.equal(true, 'identifyShellByTerminalName should be invoked'); + expect(shell).to.equal(expectedShell); + }); + test(`Try identifying the shell based on user environment ${testSuffix}`, () => { + // As the terminal is 'some unknown value' we don't know the shell. + // We should try try identify the shell based on VSC settings. + // We should check user environment for shell. + const terminal: Terminal = { name: 'some unknown name' } as any; + + const identifyShellByTerminalName = sinon.stub(shellDetector, 'identifyShellByTerminalName'); + const getTerminalShellPath = sinon.stub(shellDetector, 'getTerminalShellPath'); + const getDefaultPlatformShell = sinon.stub(shellDetector, 'getDefaultPlatformShell'); + + // We cannot identify shell by the name of the terminal, hence other will be returned. + identifyShellByTerminalName.withArgs(terminal.name).callsFake(() => TerminalShellType.other); + getTerminalShellPath.returns('some bogus terminal app.app'); + getDefaultPlatformShell.returns(shellPath); + + const shell = shellDetector.identifyTerminalShell(terminal); + + expect(getTerminalShellPath.calledOnce).to.equal(true, 'We should be checking the shell path'); + expect(getTerminalShellPath.calledAfter(identifyShellByTerminalName)).to.equal(true, 'We should be checking the shell path after checking terminal name'); + expect(getDefaultPlatformShell.calledOnce).to.equal(true, 'We should be identifying the default OS shell'); + expect(getDefaultPlatformShell.calledAfter(getTerminalShellPath)).to.equal(true, 'We should be checking the platform shell path after checking settings'); + expect(identifyShellByTerminalName.calledOnce).to.equal(true, 'identifyShellByTerminalName should be invoked'); + expect(shell).to.equal(expectedShell); + }); + }); + [OSType.Windows, OSType.Linux, OSType.OSX].forEach(osType => { + test(`Use os defaults if all 3 stratergies fail (${osType})`, () => { + // All three approaches should fail. + // We should try try identify the shell based on VSC settings. + // We should check user environment for shell. + const terminal: Terminal = { name: 'some unknown name' } as any; + const expectedDefault = osType === OSType.Windows ? TerminalShellType.commandPrompt : TerminalShellType.bash; + + const identifyShellByTerminalName = sinon.stub(shellDetector, 'identifyShellByTerminalName'); + const getTerminalShellPath = sinon.stub(shellDetector, 'getTerminalShellPath'); + const getDefaultPlatformShell = sinon.stub(shellDetector, 'getDefaultPlatformShell'); + + // Remember, none of the methods should return a valid terminal. + when(platformService.osType).thenReturn(osType); + identifyShellByTerminalName.withArgs(terminal.name).callsFake(() => TerminalShellType.other); + getTerminalShellPath.returns('some bogus terminal app.app'); + getDefaultPlatformShell.returns('nothing here as well'); + + const shell = shellDetector.identifyTerminalShell(terminal); + + expect(getTerminalShellPath.calledOnce).to.equal(true, 'We should be checking the shell path'); + expect(getDefaultPlatformShell.calledOnce).to.equal(true, 'We should be identifying the default OS shell'); + expect(identifyShellByTerminalName.calledOnce).to.equal(true, 'identifyShellByTerminalName should be invoked'); + expect(shell).to.equal(expectedDefault); + }); + }); +});