From 962a0690d688489f9b474ae9ba3a0d9838e64926 Mon Sep 17 00:00:00 2001 From: Brian Cristante Date: Mon, 23 Apr 2018 10:38:12 -0400 Subject: [PATCH 1/9] Use Sinon for mocking. Fill a test gap. --- Tasks/UsePythonVersion/Tests/L0.ts | 75 +++++------- .../UsePythonVersion/Tests/package-lock.json | 115 ++++++++++++++++++ Tasks/UsePythonVersion/Tests/package.json | 4 +- 3 files changed, 148 insertions(+), 46 deletions(-) diff --git a/Tasks/UsePythonVersion/Tests/L0.ts b/Tasks/UsePythonVersion/Tests/L0.ts index 191c004f8b04..665d9ab86a5d 100644 --- a/Tasks/UsePythonVersion/Tests/L0.ts +++ b/Tasks/UsePythonVersion/Tests/L0.ts @@ -3,6 +3,7 @@ import { EOL } from 'os'; import * as path from 'path'; import * as mockery from 'mockery'; +import * as sinon from 'sinon'; import * as mockTask from 'vsts-task-lib/mock-task'; import { Platform } from '../taskutil'; @@ -73,18 +74,10 @@ describe('UsePythonVersion L0 Suite', function () { }) it('finds version in cache', async function () { - let buildVariables: { [key: string]: string } = {}; - const mockBuildVariables = { - setVariable: (variable: string, value: string) => { - buildVariables[variable] = value; - }, - getVariable: (variable: string) => buildVariables[variable] - }; - mockery.registerMock('vsts-task-lib/task', Object.assign({}, mockTask, mockBuildVariables)); - - const toolPath = path.join('/', 'Python', '3.6.4', 'x64'); + const setVariable = sinon.spy(); + mockery.registerMock('vsts-task-lib/task', Object.assign({}, mockTask, { setVariable: setVariable })); mockery.registerMock('vsts-task-tool-lib/tool', { - findLocalTool: () => toolPath + findLocalTool: sinon.stub().returns('findLocalTool') }); const uut = reload(); @@ -94,10 +87,8 @@ describe('UsePythonVersion L0 Suite', function () { architecture: 'x64' }; - assert.strictEqual(buildVariables['pythonLocation'], undefined); - await uut.usePythonVersion(parameters, Platform.Linux); - assert.strictEqual(buildVariables['pythonLocation'], toolPath); + assert(setVariable.calledOnceWithExactly('pythonLocation', 'findLocalTool')); }); it('rejects version not in cache', async function (done: MochaDone) { @@ -133,23 +124,14 @@ describe('UsePythonVersion L0 Suite', function () { }); it('selects architecture passed as input', async function () { - let buildVariables: { [key: string]: string } = {}; - const mockBuildVariables = { - setVariable: (variable: string, value: string) => { - buildVariables[variable] = value; - }, - getVariable: (variable: string) => buildVariables[variable] - }; - mockery.registerMock('vsts-task-lib/task', Object.assign({}, mockTask, mockBuildVariables)); - - const x86ToolPath = path.join('/', 'Python', '3.6.4', 'x86'); - const x64ToolPath = path.join('/', 'Python', '3.6.4', 'x64'); + const setVariable = sinon.spy(); + mockery.registerMock('vsts-task-lib/task', Object.assign({}, mockTask, { setVariable: setVariable })); mockery.registerMock('vsts-task-tool-lib/tool', { findLocalTool: (toolName: string, versionSpec: string, arch?: string) => { if (arch === 'x86') { - return x86ToolPath; + return 'x86ToolPath'; } else { - return x64ToolPath; + return 'x64ToolPath'; } } }); @@ -161,25 +143,22 @@ describe('UsePythonVersion L0 Suite', function () { architecture: 'x86' }; - assert.strictEqual(buildVariables['pythonLocation'], undefined); - await uut.usePythonVersion(parameters, Platform.Linux); - assert.strictEqual(buildVariables['pythonLocation'], x86ToolPath); + assert(setVariable.calledOnce); + assert(setVariable.calledWith('pythonLocation', 'x86ToolPath')); }); it('sets PATH correctly on Linux', async function () { mockery.registerMock('vsts-task-lib/task', mockTask); - const toolPath = path.join('/', 'Python', '3.6.4', 'x64'); + const findLocalTool = sinon.stub().returns('findLocalTool'); mockery.registerMock('vsts-task-tool-lib/tool', { - findLocalTool: () => toolPath, + findLocalTool: findLocalTool }); - let mockPath = ''; + const prependPathSafe = sinon.spy(); mockery.registerMock('./toolutil', { - prependPathSafe: (s: string) => { - mockPath = s + ':' + mockPath; - } + prependPathSafe: prependPathSafe }); const uut = reload(); @@ -190,25 +169,28 @@ describe('UsePythonVersion L0 Suite', function () { }; await uut.usePythonVersion(parameters, Platform.Linux); - assert.strictEqual(`${path.join(toolPath, 'bin')}:${toolPath}:`, mockPath); + assert(findLocalTool.calledOnceWithExactly('Python', '3.6', 'x64')); + assert(prependPathSafe.calledTwice); + assert(prependPathSafe.calledWithExactly('findLocalTool')); + assert(prependPathSafe.calledWithExactly(path.join('findLocalTool', 'bin'))); }); it('sets PATH correctly on Windows', async function () { mockery.registerMock('vsts-task-lib/task', mockTask); + // Windows PATH logic will parse this path, so it has to be realistic const toolPath = path.join('/', 'Python', '3.6.4', 'x64'); + const findLocalTool = sinon.stub().returns(toolPath); mockery.registerMock('vsts-task-tool-lib/tool', { - findLocalTool: () => toolPath + findLocalTool: findLocalTool }); - let mockPath = ''; + const prependPathSafe = sinon.spy(); mockery.registerMock('./toolutil', { - prependPathSafe: (s: string) => { - mockPath = s + ';' + mockPath; - } + prependPathSafe: prependPathSafe }); - process.env['APPDATA'] = '/mock-appdata'; + process.env['APPDATA'] = '/mock-appdata'; // needed for running this test on Linux and macOS const uut = reload(); const parameters = { @@ -218,11 +200,14 @@ describe('UsePythonVersion L0 Suite', function () { }; await uut.usePythonVersion(parameters, Platform.Windows); + assert(findLocalTool.calledOnceWithExactly('Python', '3.6', 'x64')); + assert(prependPathSafe.calledThrice); + assert(prependPathSafe.calledWithExactly(toolPath)); // On Windows, must add the two "Scripts" directories to PATH as well const expectedScripts = path.join(toolPath, 'Scripts'); + assert(prependPathSafe.calledWithExactly(expectedScripts)); const expectedUserScripts = path.join(process.env['APPDATA'], 'Python', 'Python36', 'Scripts'); - const expectedPath = `${expectedUserScripts};${expectedScripts};${toolPath};`; - assert.strictEqual(expectedPath, mockPath); + assert(prependPathSafe.calledWithExactly(expectedUserScripts)); }); }); diff --git a/Tasks/UsePythonVersion/Tests/package-lock.json b/Tasks/UsePythonVersion/Tests/package-lock.json index e27c5ab51f54..4cd852ab67cd 100644 --- a/Tasks/UsePythonVersion/Tests/package-lock.json +++ b/Tasks/UsePythonVersion/Tests/package-lock.json @@ -4,6 +4,15 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@sinonjs/formatio": { + "version": "2.0.0", + "resolved": "http://registry.npmjs.org/@sinonjs/formatio/-/formatio-2.0.0.tgz", + "integrity": "sha512-ls6CAMA6/5gG+O/IdsBcblvnd8qcO/l1TYoNeAzp3wcISOxlPXQEus0mLcdwazEkWjaBdaJ3TaxmNgCLWwvWzg==", + "dev": true, + "requires": { + "samsam": "1.3.0" + } + }, "@types/mocha": { "version": "2.2.48", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-2.2.48.tgz", @@ -16,11 +25,117 @@ "integrity": "sha1-m6It838H43gP/4Ux0aOOYz+UV6U=", "dev": true }, + "@types/sinon": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-4.3.1.tgz", + "integrity": "sha512-DK4YtH30I67k4klURIBS4VAe1aBISfS9lgNlHFkibSmKem2tLQc5VkKoJreT3dCJAd+xRyCS8bx1o97iq3yUVg==", + "dev": true + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "just-extend": { + "version": "1.1.27", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-1.1.27.tgz", + "integrity": "sha512-mJVp13Ix6gFo3SBAy9U/kL+oeZqzlYYYLQBwXVBlVzIsZwBqGREnOro24oC/8s8aox+rJhtZ2DiQof++IrkA+g==", + "dev": true + }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "dev": true + }, + "lolex": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.3.2.tgz", + "integrity": "sha512-A5pN2tkFj7H0dGIAM6MFvHKMJcPnjZsOMvR7ujCjfgW5TbV6H9vb1PgxLtHvjqNZTHsUolz+6/WEO0N1xNx2ng==", + "dev": true + }, "mockery": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mockery/-/mockery-2.1.0.tgz", "integrity": "sha512-9VkOmxKlWXoDO/h1jDZaS4lH33aWfRiJiNT/tKj+8OGzrcFDLo8d0syGdbsc3Bc4GvRXPb+NMMvojotmuGJTvA==", "dev": true + }, + "nise": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/nise/-/nise-1.3.3.tgz", + "integrity": "sha512-v1J/FLUB9PfGqZLGDBhQqODkbLotP0WtLo9R4EJY2PPu5f5Xg4o0rA8FDlmrjFSv9vBBKcfnOSpfYYuu5RTHqg==", + "dev": true, + "requires": { + "@sinonjs/formatio": "2.0.0", + "just-extend": "1.1.27", + "lolex": "2.3.2", + "path-to-regexp": "1.7.0", + "text-encoding": "0.6.4" + } + }, + "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" + } + }, + "samsam": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.3.0.tgz", + "integrity": "sha512-1HwIYD/8UlOtFS3QO3w7ey+SdSDFE4HRNLZoZRYVQefrOY3l17epswImeB1ijgJFQJodIaHcwkp3r/myBjFVbg==", + "dev": true + }, + "sinon": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-4.5.0.tgz", + "integrity": "sha512-trdx+mB0VBBgoYucy6a9L7/jfQOmvGeaKZT4OOJ+lPAtI8623xyGr8wLiE4eojzBS8G9yXbhx42GHUOVLr4X2w==", + "dev": true, + "requires": { + "@sinonjs/formatio": "2.0.0", + "diff": "3.5.0", + "lodash.get": "4.4.2", + "lolex": "2.3.2", + "nise": "1.3.3", + "supports-color": "5.4.0", + "type-detect": "4.0.8" + } + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + }, + "text-encoding": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", + "integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk=", + "dev": true + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true } } } diff --git a/Tasks/UsePythonVersion/Tests/package.json b/Tasks/UsePythonVersion/Tests/package.json index 478348d8088c..f6a8ad3965fa 100644 --- a/Tasks/UsePythonVersion/Tests/package.json +++ b/Tasks/UsePythonVersion/Tests/package.json @@ -19,6 +19,8 @@ "devDependencies": { "@types/mocha": "^2.2.48", "@types/mockery": "^1.4.29", - "mockery": "^2.1.0" + "@types/sinon": "^4.3.1", + "mockery": "^2.1.0", + "sinon": "^4.5.0" } } From 8106e7b5a8694c2ca1825c0bcf5e6a0dc23d9866 Mon Sep 17 00:00:00 2001 From: Brian Cristante Date: Tue, 1 May 2018 10:56:56 -0400 Subject: [PATCH 2/9] Move function-level tests out to a separate file --- Tasks/UsePythonVersion/Tests/L0.ts | 190 +--------------- .../Tests/L0_usepythonversion.ts | 211 ++++++++++++++++++ 2 files changed, 218 insertions(+), 183 deletions(-) create mode 100644 Tasks/UsePythonVersion/Tests/L0_usepythonversion.ts diff --git a/Tasks/UsePythonVersion/Tests/L0.ts b/Tasks/UsePythonVersion/Tests/L0.ts index 665d9ab86a5d..7949e1aa3c05 100644 --- a/Tasks/UsePythonVersion/Tests/L0.ts +++ b/Tasks/UsePythonVersion/Tests/L0.ts @@ -1,18 +1,10 @@ import * as assert from 'assert'; -import { EOL } from 'os'; import * as path from 'path'; import * as mockery from 'mockery'; -import * as sinon from 'sinon'; -import * as mockTask from 'vsts-task-lib/mock-task'; +import { MockTestRunner } from 'vsts-task-lib/mock-test'; import { Platform } from '../taskutil'; -import * as usePythonVersion from '../usepythonversion'; - -/** Reload the unit under test to use mocks that have been registered. */ -function reload(): typeof usePythonVersion { - return require('../usepythonversion'); -} describe('UsePythonVersion L0 Suite', function () { before(function () { @@ -21,193 +13,25 @@ describe('UsePythonVersion L0 Suite', function () { warnOnUnregistered: false }); }); - + after(function () { mockery.disable(); }); - + afterEach(function () { mockery.deregisterAll(); mockery.resetCache(); - }) - - it('converts Python prerelease versions to the semantic version format', function () { - mockery.registerMock('vsts-task-lib/task', mockTask); - mockery.registerMock('vsts-task-tool-lib/tool', {}); - const uut = reload(); - - const testCases = [ - { - versionSpec: '3.x', - expected: '3.x' - }, - { - versionSpec: '3.3.6', - expected: '3.3.6' - }, - { - versionSpec: '3.7.0b2', - expected: '3.7.0-b2' - }, - { - versionSpec: '3.7.0rc', - expected: '3.7.0-rc' - }, - { - versionSpec: '14.22.100a1000', - expected: '14.22.100-a1000' - }, - { - versionSpec: '3.6.6b2 || >= 3.7.0rc', - expected: '3.6.6-b2 || >= 3.7.0-rc' - }, - { - versionSpec: '3.7rc1', // invalid - expected: '3.7rc1' - }, - ]; - - for (let tc of testCases) { // Node 5 can't handle destructuring assignment - const actual = uut.pythonVersionToSemantic(tc.versionSpec); - assert.strictEqual(actual, tc.expected); - } - }) - - it('finds version in cache', async function () { - const setVariable = sinon.spy(); - mockery.registerMock('vsts-task-lib/task', Object.assign({}, mockTask, { setVariable: setVariable })); - mockery.registerMock('vsts-task-tool-lib/tool', { - findLocalTool: sinon.stub().returns('findLocalTool') - }); - - const uut = reload(); - const parameters = { - versionSpec: '3.6', - addToPath: false, - architecture: 'x64' - }; - - await uut.usePythonVersion(parameters, Platform.Linux); - assert(setVariable.calledOnceWithExactly('pythonLocation', 'findLocalTool')); }); - it('rejects version not in cache', async function (done: MochaDone) { - mockery.registerMock('vsts-task-lib/task', mockTask); - mockery.registerMock('vsts-task-tool-lib/tool', { - findLocalTool: () => null, - findLocalToolVersions: () => ['2.6.0', '2.7.13'] - }); - - const uut = reload(); - const parameters = { - versionSpec: '3.x', - addToPath: false, - architecture: 'x64' - }; - - try { - await uut.usePythonVersion(parameters, Platform.Linux); - done(new Error('should not have succeeded')); - } catch (e) { - const expectedMessage = [ - 'loc_mock_VersionNotFound 3.x', - 'loc_mock_ListAvailableVersions', - '2.6.0 (x86)', - '2.7.13 (x86)', - '2.6.0 (x64)', - '2.7.13 (x64)' - ].join(EOL); - - assert.strictEqual(e.message, expectedMessage); - done(); - } + describe('usepythonversion.ts', function () { + require('./L0_usepythonversion'); }); - it('selects architecture passed as input', async function () { - const setVariable = sinon.spy(); - mockery.registerMock('vsts-task-lib/task', Object.assign({}, mockTask, { setVariable: setVariable })); - mockery.registerMock('vsts-task-tool-lib/tool', { - findLocalTool: (toolName: string, versionSpec: string, arch?: string) => { - if (arch === 'x86') { - return 'x86ToolPath'; - } else { - return 'x64ToolPath'; - } - } - }); - - const uut = reload(); - const parameters = { - versionSpec: '3.6', - addToPath: false, - architecture: 'x86' - }; + it('succeeds when version is found', function () { - await uut.usePythonVersion(parameters, Platform.Linux); - assert(setVariable.calledOnce); - assert(setVariable.calledWith('pythonLocation', 'x86ToolPath')); }); - it('sets PATH correctly on Linux', async function () { - mockery.registerMock('vsts-task-lib/task', mockTask); - - const findLocalTool = sinon.stub().returns('findLocalTool'); - mockery.registerMock('vsts-task-tool-lib/tool', { - findLocalTool: findLocalTool - }); - - const prependPathSafe = sinon.spy(); - mockery.registerMock('./toolutil', { - prependPathSafe: prependPathSafe - }); - - const uut = reload(); - const parameters = { - versionSpec: '3.6', - addToPath: true, - architecture: 'x64' - }; - - await uut.usePythonVersion(parameters, Platform.Linux); - assert(findLocalTool.calledOnceWithExactly('Python', '3.6', 'x64')); - assert(prependPathSafe.calledTwice); - assert(prependPathSafe.calledWithExactly('findLocalTool')); - assert(prependPathSafe.calledWithExactly(path.join('findLocalTool', 'bin'))); - }); - - it('sets PATH correctly on Windows', async function () { - mockery.registerMock('vsts-task-lib/task', mockTask); - - // Windows PATH logic will parse this path, so it has to be realistic - const toolPath = path.join('/', 'Python', '3.6.4', 'x64'); - const findLocalTool = sinon.stub().returns(toolPath); - mockery.registerMock('vsts-task-tool-lib/tool', { - findLocalTool: findLocalTool - }); - - const prependPathSafe = sinon.spy(); - mockery.registerMock('./toolutil', { - prependPathSafe: prependPathSafe - }); - - process.env['APPDATA'] = '/mock-appdata'; // needed for running this test on Linux and macOS - - const uut = reload(); - const parameters = { - versionSpec: '3.6', - addToPath: true, - architecture: 'x64' - }; - - await uut.usePythonVersion(parameters, Platform.Windows); - assert(findLocalTool.calledOnceWithExactly('Python', '3.6', 'x64')); - assert(prependPathSafe.calledThrice); - assert(prependPathSafe.calledWithExactly(toolPath)); + it('fails when version is not found', function () { - // On Windows, must add the two "Scripts" directories to PATH as well - const expectedScripts = path.join(toolPath, 'Scripts'); - assert(prependPathSafe.calledWithExactly(expectedScripts)); - const expectedUserScripts = path.join(process.env['APPDATA'], 'Python', 'Python36', 'Scripts'); - assert(prependPathSafe.calledWithExactly(expectedUserScripts)); }); }); diff --git a/Tasks/UsePythonVersion/Tests/L0_usepythonversion.ts b/Tasks/UsePythonVersion/Tests/L0_usepythonversion.ts new file mode 100644 index 000000000000..8cca30645c82 --- /dev/null +++ b/Tasks/UsePythonVersion/Tests/L0_usepythonversion.ts @@ -0,0 +1,211 @@ +import * as assert from 'assert'; +import { EOL } from 'os'; +import * as path from 'path'; + +import * as mockery from 'mockery'; +import * as sinon from 'sinon'; +import * as mockTask from 'vsts-task-lib/mock-task'; + +import { Platform } from '../taskutil'; +import * as usePythonVersion from '../usepythonversion'; + +/** Reload the unit under test to use mocks that have been registered. */ +function reload(): typeof usePythonVersion { + return require('../usepythonversion'); +} + +before(function () { + mockery.enable({ + useCleanCache: true, + warnOnUnregistered: false + }); +}); + +after(function () { + mockery.disable(); +}); + +afterEach(function () { + mockery.deregisterAll(); + mockery.resetCache(); +}); + +it('converts Python prerelease versions to the semantic version format', function () { + mockery.registerMock('vsts-task-lib/task', mockTask); + mockery.registerMock('vsts-task-tool-lib/tool', {}); + const uut = reload(); + + const testCases = [ + { + versionSpec: '3.x', + expected: '3.x' + }, + { + versionSpec: '3.3.6', + expected: '3.3.6' + }, + { + versionSpec: '3.7.0b2', + expected: '3.7.0-b2' + }, + { + versionSpec: '3.7.0rc', + expected: '3.7.0-rc' + }, + { + versionSpec: '14.22.100a1000', + expected: '14.22.100-a1000' + }, + { + versionSpec: '3.6.6b2 || >= 3.7.0rc', + expected: '3.6.6-b2 || >= 3.7.0-rc' + }, + { + versionSpec: '3.7rc1', // invalid + expected: '3.7rc1' + }, + ]; + + for (let tc of testCases) { // Node 5 can't handle destructuring assignment + const actual = uut.pythonVersionToSemantic(tc.versionSpec); + assert.strictEqual(actual, tc.expected); + } +}) + +it('finds version in cache', async function () { + const setVariable = sinon.spy(); + mockery.registerMock('vsts-task-lib/task', Object.assign({}, mockTask, { setVariable: setVariable })); + mockery.registerMock('vsts-task-tool-lib/tool', { + findLocalTool: sinon.stub().returns('findLocalTool') + }); + + const uut = reload(); + const parameters = { + versionSpec: '3.6', + addToPath: false, + architecture: 'x64' + }; + + await uut.usePythonVersion(parameters, Platform.Linux); + assert(setVariable.calledOnceWithExactly('pythonLocation', 'findLocalTool')); +}); + +it('rejects version not in cache', async function (done: MochaDone) { + mockery.registerMock('vsts-task-lib/task', mockTask); + mockery.registerMock('vsts-task-tool-lib/tool', { + findLocalTool: () => null, + findLocalToolVersions: () => ['2.6.0', '2.7.13'] + }); + + const uut = reload(); + const parameters = { + versionSpec: '3.x', + addToPath: false, + architecture: 'x64' + }; + + try { + await uut.usePythonVersion(parameters, Platform.Linux); + done(new Error('should not have succeeded')); + } catch (e) { + const expectedMessage = [ + 'loc_mock_VersionNotFound 3.x', + 'loc_mock_ListAvailableVersions', + '2.6.0 (x86)', + '2.7.13 (x86)', + '2.6.0 (x64)', + '2.7.13 (x64)' + ].join(EOL); + + assert.strictEqual(e.message, expectedMessage); + done(); + } +}); + +it('selects architecture passed as input', async function () { + const setVariable = sinon.spy(); + mockery.registerMock('vsts-task-lib/task', Object.assign({}, mockTask, { setVariable: setVariable })); + mockery.registerMock('vsts-task-tool-lib/tool', { + findLocalTool: (toolName: string, versionSpec: string, arch?: string) => { + if (arch === 'x86') { + return 'x86ToolPath'; + } else { + return 'x64ToolPath'; + } + } + }); + + const uut = reload(); + const parameters = { + versionSpec: '3.6', + addToPath: false, + architecture: 'x86' + }; + + await uut.usePythonVersion(parameters, Platform.Linux); + assert(setVariable.calledOnce); + assert(setVariable.calledWith('pythonLocation', 'x86ToolPath')); +}); + +it('sets PATH correctly on Linux', async function () { + mockery.registerMock('vsts-task-lib/task', mockTask); + + const findLocalTool = sinon.stub().returns('findLocalTool'); + mockery.registerMock('vsts-task-tool-lib/tool', { + findLocalTool: findLocalTool + }); + + const prependPathSafe = sinon.spy(); + mockery.registerMock('./toolutil', { + prependPathSafe: prependPathSafe + }); + + const uut = reload(); + const parameters = { + versionSpec: '3.6', + addToPath: true, + architecture: 'x64' + }; + + await uut.usePythonVersion(parameters, Platform.Linux); + assert(findLocalTool.calledOnceWithExactly('Python', '3.6', 'x64')); + assert(prependPathSafe.calledTwice); + assert(prependPathSafe.calledWithExactly('findLocalTool')); + assert(prependPathSafe.calledWithExactly(path.join('findLocalTool', 'bin'))); +}); + +it('sets PATH correctly on Windows', async function () { + mockery.registerMock('vsts-task-lib/task', mockTask); + + // Windows PATH logic will parse this path, so it has to be realistic + const toolPath = path.join('/', 'Python', '3.6.4', 'x64'); + const findLocalTool = sinon.stub().returns(toolPath); + mockery.registerMock('vsts-task-tool-lib/tool', { + findLocalTool: findLocalTool + }); + + const prependPathSafe = sinon.spy(); + mockery.registerMock('./toolutil', { + prependPathSafe: prependPathSafe + }); + + process.env['APPDATA'] = '/mock-appdata'; // needed for running this test on Linux and macOS + + const uut = reload(); + const parameters = { + versionSpec: '3.6', + addToPath: true, + architecture: 'x64' + }; + + await uut.usePythonVersion(parameters, Platform.Windows); + assert(findLocalTool.calledOnceWithExactly('Python', '3.6', 'x64')); + assert(prependPathSafe.calledThrice); + assert(prependPathSafe.calledWithExactly(toolPath)); + + // On Windows, must add the two "Scripts" directories to PATH as well + const expectedScripts = path.join(toolPath, 'Scripts'); + assert(prependPathSafe.calledWithExactly(expectedScripts)); + const expectedUserScripts = path.join(process.env['APPDATA'], 'Python', 'Python36', 'Scripts'); + assert(prependPathSafe.calledWithExactly(expectedUserScripts)); +}); \ No newline at end of file From 83d35563906d181d904c6bfc48e114da7cc56096 Mon Sep 17 00:00:00 2001 From: Brian Cristante Date: Tue, 1 May 2018 11:12:26 -0400 Subject: [PATCH 3/9] L0SucceedsWhenVersionIsFound --- Tasks/UsePythonVersion/Tests/L0.ts | 36 +++++++++++-------- .../Tests/L0SucceedsWhenVersionIsFound.ts | 18 ++++++++++ 2 files changed, 39 insertions(+), 15 deletions(-) create mode 100644 Tasks/UsePythonVersion/Tests/L0SucceedsWhenVersionIsFound.ts diff --git a/Tasks/UsePythonVersion/Tests/L0.ts b/Tasks/UsePythonVersion/Tests/L0.ts index 7949e1aa3c05..15f7270f9129 100644 --- a/Tasks/UsePythonVersion/Tests/L0.ts +++ b/Tasks/UsePythonVersion/Tests/L0.ts @@ -1,34 +1,40 @@ import * as assert from 'assert'; import * as path from 'path'; -import * as mockery from 'mockery'; +// import * as mockery from 'mockery'; import { MockTestRunner } from 'vsts-task-lib/mock-test'; import { Platform } from '../taskutil'; describe('UsePythonVersion L0 Suite', function () { - before(function () { - mockery.enable({ - useCleanCache: true, - warnOnUnregistered: false - }); - }); - - after(function () { - mockery.disable(); - }); + // before(function () { + // mockery.enable({ + // useCleanCache: true, + // warnOnUnregistered: false + // }); + // }); - afterEach(function () { - mockery.deregisterAll(); - mockery.resetCache(); - }); + // after(function () { + // mockery.disable(); + // }); + + // afterEach(function () { + // mockery.deregisterAll(); + // mockery.resetCache(); + // }); describe('usepythonversion.ts', function () { require('./L0_usepythonversion'); }); it('succeeds when version is found', function () { + const testFile = path.join(__dirname, 'L0SucceedsWhenVersionIsFound.js'); + const testRunner = new MockTestRunner(testFile); + + testRunner.run(); + assert.strictEqual(testRunner.stderr.length, 0, 'should not have written to stderr'); + assert(testRunner.succeeded, 'task should have succeeded'); }); it('fails when version is not found', function () { diff --git a/Tasks/UsePythonVersion/Tests/L0SucceedsWhenVersionIsFound.ts b/Tasks/UsePythonVersion/Tests/L0SucceedsWhenVersionIsFound.ts new file mode 100644 index 000000000000..3cea08dcf59a --- /dev/null +++ b/Tasks/UsePythonVersion/Tests/L0SucceedsWhenVersionIsFound.ts @@ -0,0 +1,18 @@ +import * as path from 'path'; + +import { TaskMockRunner } from 'vsts-task-lib/mock-run'; + +const taskPath = path.join(__dirname, '..', 'main.js'); +const taskRunner = new TaskMockRunner(taskPath); + +taskRunner.setInput('versionSpec', '3.x'); +taskRunner.setInput('addToPath', 'false'); +taskRunner.setInput('architecture', 'x64'); + +// Mock vsts-task-tool-lib +const toolPath = path.join('/', 'Python', '3.6.4', 'x64'); +taskRunner.registerMock('vsts-task-tool-lib/tool', { + findLocalTool: () => toolPath +}); + +taskRunner.run(); From d1ef9689b6a293c86108135339b4a389df62fcb3 Mon Sep 17 00:00:00 2001 From: Brian Cristante Date: Tue, 1 May 2018 11:23:46 -0400 Subject: [PATCH 4/9] Add L0FailsWhenVersionIsMissing --- Tasks/UsePythonVersion/Tests/L0.ts | 31 +++++++------------ .../Tests/L0FailsWhenVersionIsMissing.ts | 18 +++++++++++ 2 files changed, 30 insertions(+), 19 deletions(-) create mode 100644 Tasks/UsePythonVersion/Tests/L0FailsWhenVersionIsMissing.ts diff --git a/Tasks/UsePythonVersion/Tests/L0.ts b/Tasks/UsePythonVersion/Tests/L0.ts index 15f7270f9129..69a8a4790d34 100644 --- a/Tasks/UsePythonVersion/Tests/L0.ts +++ b/Tasks/UsePythonVersion/Tests/L0.ts @@ -1,28 +1,10 @@ import * as assert from 'assert'; +import { EOL } from 'os'; import * as path from 'path'; -// import * as mockery from 'mockery'; import { MockTestRunner } from 'vsts-task-lib/mock-test'; -import { Platform } from '../taskutil'; - describe('UsePythonVersion L0 Suite', function () { - // before(function () { - // mockery.enable({ - // useCleanCache: true, - // warnOnUnregistered: false - // }); - // }); - - // after(function () { - // mockery.disable(); - // }); - - // afterEach(function () { - // mockery.deregisterAll(); - // mockery.resetCache(); - // }); - describe('usepythonversion.ts', function () { require('./L0_usepythonversion'); }); @@ -38,6 +20,17 @@ describe('UsePythonVersion L0 Suite', function () { }); it('fails when version is not found', function () { + const testFile = path.join(__dirname, 'L0FailsWhenVersionIsMissing.js'); + const testRunner = new MockTestRunner(testFile); + + testRunner.run(); + + const errorMessage = [ + 'loc_mock_VersionNotFound 3.x', + 'loc_mock_ListAvailableVersions' + ].join(EOL); + assert(testRunner.createdErrorIssue(errorMessage)); + assert(testRunner.failed, 'task should have failed'); }); }); diff --git a/Tasks/UsePythonVersion/Tests/L0FailsWhenVersionIsMissing.ts b/Tasks/UsePythonVersion/Tests/L0FailsWhenVersionIsMissing.ts new file mode 100644 index 000000000000..a63c31eaac0b --- /dev/null +++ b/Tasks/UsePythonVersion/Tests/L0FailsWhenVersionIsMissing.ts @@ -0,0 +1,18 @@ +import * as path from 'path'; + +import { TaskMockRunner } from 'vsts-task-lib/mock-run'; + +const taskPath = path.join(__dirname, '..', 'main.js'); +const taskRunner = new TaskMockRunner(taskPath); + +taskRunner.setInput('versionSpec', '3.x'); +taskRunner.setInput('addToPath', 'false'); +taskRunner.setInput('architecture', 'x64'); + +// Mock vsts-task-tool-lib +taskRunner.registerMock('vsts-task-tool-lib/tool', { + findLocalTool: () => null, + findLocalToolVersions: () => [] +}); + +taskRunner.run(); From 558ac9e0c2988db749d9ff76b058cda284110f5b Mon Sep 17 00:00:00 2001 From: Brian Cristante Date: Tue, 1 May 2018 11:54:51 -0400 Subject: [PATCH 5/9] Remove redundant function-level test --- Tasks/UsePythonVersion/Tests/L0.ts | 6 +++- .../Tests/L0FailsWhenVersionIsMissing.ts | 2 +- .../Tests/L0_usepythonversion.ts | 32 ------------------- 3 files changed, 6 insertions(+), 34 deletions(-) diff --git a/Tasks/UsePythonVersion/Tests/L0.ts b/Tasks/UsePythonVersion/Tests/L0.ts index 69a8a4790d34..c479d163f4bc 100644 --- a/Tasks/UsePythonVersion/Tests/L0.ts +++ b/Tasks/UsePythonVersion/Tests/L0.ts @@ -27,7 +27,11 @@ describe('UsePythonVersion L0 Suite', function () { const errorMessage = [ 'loc_mock_VersionNotFound 3.x', - 'loc_mock_ListAvailableVersions' + 'loc_mock_ListAvailableVersions', + '2.6.0 (x86)', + '2.7.13 (x86)', + '2.6.0 (x64)', + '2.7.13 (x64)' ].join(EOL); assert(testRunner.createdErrorIssue(errorMessage)); diff --git a/Tasks/UsePythonVersion/Tests/L0FailsWhenVersionIsMissing.ts b/Tasks/UsePythonVersion/Tests/L0FailsWhenVersionIsMissing.ts index a63c31eaac0b..bbbd5415a80c 100644 --- a/Tasks/UsePythonVersion/Tests/L0FailsWhenVersionIsMissing.ts +++ b/Tasks/UsePythonVersion/Tests/L0FailsWhenVersionIsMissing.ts @@ -12,7 +12,7 @@ taskRunner.setInput('architecture', 'x64'); // Mock vsts-task-tool-lib taskRunner.registerMock('vsts-task-tool-lib/tool', { findLocalTool: () => null, - findLocalToolVersions: () => [] + findLocalToolVersions: () => ['2.6.0', '2.7.13'] }); taskRunner.run(); diff --git a/Tasks/UsePythonVersion/Tests/L0_usepythonversion.ts b/Tasks/UsePythonVersion/Tests/L0_usepythonversion.ts index 8cca30645c82..57b87b35d017 100644 --- a/Tasks/UsePythonVersion/Tests/L0_usepythonversion.ts +++ b/Tasks/UsePythonVersion/Tests/L0_usepythonversion.ts @@ -90,38 +90,6 @@ it('finds version in cache', async function () { assert(setVariable.calledOnceWithExactly('pythonLocation', 'findLocalTool')); }); -it('rejects version not in cache', async function (done: MochaDone) { - mockery.registerMock('vsts-task-lib/task', mockTask); - mockery.registerMock('vsts-task-tool-lib/tool', { - findLocalTool: () => null, - findLocalToolVersions: () => ['2.6.0', '2.7.13'] - }); - - const uut = reload(); - const parameters = { - versionSpec: '3.x', - addToPath: false, - architecture: 'x64' - }; - - try { - await uut.usePythonVersion(parameters, Platform.Linux); - done(new Error('should not have succeeded')); - } catch (e) { - const expectedMessage = [ - 'loc_mock_VersionNotFound 3.x', - 'loc_mock_ListAvailableVersions', - '2.6.0 (x86)', - '2.7.13 (x86)', - '2.6.0 (x64)', - '2.7.13 (x64)' - ].join(EOL); - - assert.strictEqual(e.message, expectedMessage); - done(); - } -}); - it('selects architecture passed as input', async function () { const setVariable = sinon.spy(); mockery.registerMock('vsts-task-lib/task', Object.assign({}, mockTask, { setVariable: setVariable })); From 82fab65e59132d6d2aef0fb70e65e304d00b8551 Mon Sep 17 00:00:00 2001 From: Brian Cristante Date: Tue, 1 May 2018 12:01:49 -0400 Subject: [PATCH 6/9] Use sinon for architecture test --- .../UsePythonVersion/Tests/L0_usepythonversion.ts | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/Tasks/UsePythonVersion/Tests/L0_usepythonversion.ts b/Tasks/UsePythonVersion/Tests/L0_usepythonversion.ts index 57b87b35d017..144d41551fbd 100644 --- a/Tasks/UsePythonVersion/Tests/L0_usepythonversion.ts +++ b/Tasks/UsePythonVersion/Tests/L0_usepythonversion.ts @@ -93,14 +93,12 @@ it('finds version in cache', async function () { it('selects architecture passed as input', async function () { const setVariable = sinon.spy(); mockery.registerMock('vsts-task-lib/task', Object.assign({}, mockTask, { setVariable: setVariable })); + + const findLocalTool = sinon.stub(); + findLocalTool.withArgs(sinon.match.any, sinon.match.any, 'x86').returns('x86ToolPath'); + findLocalTool.withArgs(sinon.match.any, sinon.match.any, 'x64').returns('x64ToolPath'); mockery.registerMock('vsts-task-tool-lib/tool', { - findLocalTool: (toolName: string, versionSpec: string, arch?: string) => { - if (arch === 'x86') { - return 'x86ToolPath'; - } else { - return 'x64ToolPath'; - } - } + findLocalTool: findLocalTool }); const uut = reload(); @@ -111,8 +109,7 @@ it('selects architecture passed as input', async function () { }; await uut.usePythonVersion(parameters, Platform.Linux); - assert(setVariable.calledOnce); - assert(setVariable.calledWith('pythonLocation', 'x86ToolPath')); + assert(setVariable.calledOnceWithExactly('pythonLocation', 'x86ToolPath')); }); it('sets PATH correctly on Linux', async function () { From 72d7ae74d3876c1d9decf562a63a95b3e6e0fd4f Mon Sep 17 00:00:00 2001 From: Brian Cristante Date: Tue, 1 May 2018 12:12:29 -0400 Subject: [PATCH 7/9] check that output variable was set with task runner --- Tasks/UsePythonVersion/Tests/L0.ts | 1 + .../Tests/L0_usepythonversion.ts | 18 ------------------ 2 files changed, 1 insertion(+), 18 deletions(-) diff --git a/Tasks/UsePythonVersion/Tests/L0.ts b/Tasks/UsePythonVersion/Tests/L0.ts index c479d163f4bc..8cdeeae3e5aa 100644 --- a/Tasks/UsePythonVersion/Tests/L0.ts +++ b/Tasks/UsePythonVersion/Tests/L0.ts @@ -15,6 +15,7 @@ describe('UsePythonVersion L0 Suite', function () { testRunner.run(); + assert(testRunner.stdOutContained(`##vso[task.setvariable variable=pythonLocation;issecret=false;]${path.join('/', 'Python', '3.6.4', 'x64')}`)) assert.strictEqual(testRunner.stderr.length, 0, 'should not have written to stderr'); assert(testRunner.succeeded, 'task should have succeeded'); }); diff --git a/Tasks/UsePythonVersion/Tests/L0_usepythonversion.ts b/Tasks/UsePythonVersion/Tests/L0_usepythonversion.ts index 144d41551fbd..f3ece297dbc1 100644 --- a/Tasks/UsePythonVersion/Tests/L0_usepythonversion.ts +++ b/Tasks/UsePythonVersion/Tests/L0_usepythonversion.ts @@ -72,24 +72,6 @@ it('converts Python prerelease versions to the semantic version format', functio } }) -it('finds version in cache', async function () { - const setVariable = sinon.spy(); - mockery.registerMock('vsts-task-lib/task', Object.assign({}, mockTask, { setVariable: setVariable })); - mockery.registerMock('vsts-task-tool-lib/tool', { - findLocalTool: sinon.stub().returns('findLocalTool') - }); - - const uut = reload(); - const parameters = { - versionSpec: '3.6', - addToPath: false, - architecture: 'x64' - }; - - await uut.usePythonVersion(parameters, Platform.Linux); - assert(setVariable.calledOnceWithExactly('pythonLocation', 'findLocalTool')); -}); - it('selects architecture passed as input', async function () { const setVariable = sinon.spy(); mockery.registerMock('vsts-task-lib/task', Object.assign({}, mockTask, { setVariable: setVariable })); From b19ad2bc0c80fb376482e5fef117b5c1a95b86dc Mon Sep 17 00:00:00 2001 From: Brian Cristante Date: Tue, 1 May 2018 12:16:42 -0400 Subject: [PATCH 8/9] Rewrite test to select architecture to use test runner --- Tasks/UsePythonVersion/Tests/L0.ts | 11 ++++++++++ .../Tests/L0SelectsArchitecture.ts | 22 +++++++++++++++++++ .../Tests/L0_usepythonversion.ts | 22 ------------------- 3 files changed, 33 insertions(+), 22 deletions(-) create mode 100644 Tasks/UsePythonVersion/Tests/L0SelectsArchitecture.ts diff --git a/Tasks/UsePythonVersion/Tests/L0.ts b/Tasks/UsePythonVersion/Tests/L0.ts index 8cdeeae3e5aa..9871f23296a6 100644 --- a/Tasks/UsePythonVersion/Tests/L0.ts +++ b/Tasks/UsePythonVersion/Tests/L0.ts @@ -38,4 +38,15 @@ describe('UsePythonVersion L0 Suite', function () { assert(testRunner.createdErrorIssue(errorMessage)); assert(testRunner.failed, 'task should have failed'); }); + + it('selects architecture passed as input', function () { + const testFile = path.join(__dirname, 'L0SelectsArchitecture.js'); + const testRunner = new MockTestRunner(testFile); + + testRunner.run(); + + assert(testRunner.stdOutContained(`##vso[task.setvariable variable=pythonLocation;issecret=false;]${'x86ToolPath'}`)); + assert.strictEqual(testRunner.stderr.length, 0, 'should not have written to stderr'); + assert(testRunner.succeeded, 'task should have succeeded'); + }); }); diff --git a/Tasks/UsePythonVersion/Tests/L0SelectsArchitecture.ts b/Tasks/UsePythonVersion/Tests/L0SelectsArchitecture.ts new file mode 100644 index 000000000000..96fcb48f8db7 --- /dev/null +++ b/Tasks/UsePythonVersion/Tests/L0SelectsArchitecture.ts @@ -0,0 +1,22 @@ +import * as path from 'path'; + +import * as sinon from 'sinon'; + +import { TaskMockRunner } from 'vsts-task-lib/mock-run'; + +const taskPath = path.join(__dirname, '..', 'main.js'); +const taskRunner = new TaskMockRunner(taskPath); + +taskRunner.setInput('versionSpec', '3.x'); +taskRunner.setInput('addToPath', 'false'); +taskRunner.setInput('architecture', 'x86'); + +// Mock vsts-task-tool-lib +const findLocalTool = sinon.stub(); +findLocalTool.withArgs(sinon.match.any, sinon.match.any, 'x86').returns('x86ToolPath'); +findLocalTool.withArgs(sinon.match.any, sinon.match.any, 'x64').returns('x64ToolPath'); +taskRunner.registerMock('vsts-task-tool-lib/tool', { + findLocalTool: findLocalTool +}); + +taskRunner.run(); diff --git a/Tasks/UsePythonVersion/Tests/L0_usepythonversion.ts b/Tasks/UsePythonVersion/Tests/L0_usepythonversion.ts index f3ece297dbc1..3ae6e926dd24 100644 --- a/Tasks/UsePythonVersion/Tests/L0_usepythonversion.ts +++ b/Tasks/UsePythonVersion/Tests/L0_usepythonversion.ts @@ -72,28 +72,6 @@ it('converts Python prerelease versions to the semantic version format', functio } }) -it('selects architecture passed as input', async function () { - const setVariable = sinon.spy(); - mockery.registerMock('vsts-task-lib/task', Object.assign({}, mockTask, { setVariable: setVariable })); - - const findLocalTool = sinon.stub(); - findLocalTool.withArgs(sinon.match.any, sinon.match.any, 'x86').returns('x86ToolPath'); - findLocalTool.withArgs(sinon.match.any, sinon.match.any, 'x64').returns('x64ToolPath'); - mockery.registerMock('vsts-task-tool-lib/tool', { - findLocalTool: findLocalTool - }); - - const uut = reload(); - const parameters = { - versionSpec: '3.6', - addToPath: false, - architecture: 'x86' - }; - - await uut.usePythonVersion(parameters, Platform.Linux); - assert(setVariable.calledOnceWithExactly('pythonLocation', 'x86ToolPath')); -}); - it('sets PATH correctly on Linux', async function () { mockery.registerMock('vsts-task-lib/task', mockTask); From 5c164a4d1fdc19878cc618caa83e941e64dd5e5f Mon Sep 17 00:00:00 2001 From: Brian Cristante Date: Tue, 1 May 2018 12:22:53 -0400 Subject: [PATCH 9/9] Helper function for checking variables --- Tasks/UsePythonVersion/Tests/L0.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Tasks/UsePythonVersion/Tests/L0.ts b/Tasks/UsePythonVersion/Tests/L0.ts index 9871f23296a6..8c4c8aff9c34 100644 --- a/Tasks/UsePythonVersion/Tests/L0.ts +++ b/Tasks/UsePythonVersion/Tests/L0.ts @@ -4,6 +4,10 @@ import * as path from 'path'; import { MockTestRunner } from 'vsts-task-lib/mock-test'; +function didSetVariable(testRunner: MockTestRunner, variableName: string, variableValue: string): boolean { + return testRunner.stdOutContained(`##vso[task.setvariable variable=${variableName};issecret=false;]${variableValue}`); +} + describe('UsePythonVersion L0 Suite', function () { describe('usepythonversion.ts', function () { require('./L0_usepythonversion'); @@ -15,7 +19,7 @@ describe('UsePythonVersion L0 Suite', function () { testRunner.run(); - assert(testRunner.stdOutContained(`##vso[task.setvariable variable=pythonLocation;issecret=false;]${path.join('/', 'Python', '3.6.4', 'x64')}`)) + assert(didSetVariable(testRunner, 'pythonLocation', path.join('/', 'Python', '3.6.4', 'x64'))); assert.strictEqual(testRunner.stderr.length, 0, 'should not have written to stderr'); assert(testRunner.succeeded, 'task should have succeeded'); }); @@ -45,7 +49,7 @@ describe('UsePythonVersion L0 Suite', function () { testRunner.run(); - assert(testRunner.stdOutContained(`##vso[task.setvariable variable=pythonLocation;issecret=false;]${'x86ToolPath'}`)); + assert(didSetVariable(testRunner, 'pythonLocation', 'x86ToolPath')); assert.strictEqual(testRunner.stderr.length, 0, 'should not have written to stderr'); assert(testRunner.succeeded, 'task should have succeeded'); });