From e5fdb1ffe414ad14d71bc2b4a399a8c938dbb491 Mon Sep 17 00:00:00 2001 From: Ivan Duplenskikh <115665590+ivanduplenskikh@users.noreply.github.com> Date: Tue, 24 Dec 2024 15:30:33 +0100 Subject: [PATCH 01/11] Replace all methods of shelljs module --- node/package-lock.json | 2 +- node/package.json | 2 +- node/task.ts | 351 +++++++++++++---- node/test/cd.ts | 113 ++++++ node/test/cp.ts | 125 ++++++ node/test/ls.ts | 254 ++++++++++++ node/test/mv.ts | 112 ++++++ node/test/popd.ts | 123 ++++++ node/test/pushd.ts | 301 +++++++++++++++ node/test/rm.ts | 117 ++++++ node/test/tsconfig.json | 7 + powershell/definitions/shelljs.d.ts | 577 ---------------------------- 12 files changed, 1425 insertions(+), 659 deletions(-) create mode 100644 node/test/cd.ts create mode 100644 node/test/cp.ts create mode 100644 node/test/ls.ts create mode 100644 node/test/mv.ts create mode 100644 node/test/popd.ts create mode 100644 node/test/pushd.ts create mode 100644 node/test/rm.ts delete mode 100644 powershell/definitions/shelljs.d.ts diff --git a/node/package-lock.json b/node/package-lock.json index 01c3085b1..5dd130839 100644 --- a/node/package-lock.json +++ b/node/package-lock.json @@ -1,6 +1,6 @@ { "name": "azure-pipelines-task-lib", - "version": "4.17.3", + "version": "5.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/node/package.json b/node/package.json index 3073e290d..099180776 100644 --- a/node/package.json +++ b/node/package.json @@ -1,6 +1,6 @@ { "name": "azure-pipelines-task-lib", - "version": "4.17.3", + "version": "5.0.0", "description": "Azure Pipelines Task SDK", "main": "./task.js", "typings": "./task.d.ts", diff --git a/node/task.ts b/node/task.ts index 7c11beb9a..d41f0bd7d 100644 --- a/node/task.ts +++ b/node/task.ts @@ -1,5 +1,4 @@ import Q = require('q'); -import shell = require('shelljs'); import childProcess = require('child_process'); import fs = require('fs'); import path = require('path'); @@ -681,24 +680,6 @@ export const debug = im._debug; //----------------------------------------------------- // Disk Functions //----------------------------------------------------- -function _checkShell(cmd: string, continueOnError?: boolean) { - var se = shell.error(); - - if (se) { - debug(cmd + ' failed'); - var errMsg = loc('LIB_OperationFailed', cmd, se); - debug(errMsg); - - if (!continueOnError) { - throw new Error(errMsg); - } - } -} - -export interface FsStats extends fs.Stats { - -} - /** * Get's stat on a path. * Useful for checking whether a file or directory. Also getting created, modified and accessed time. @@ -707,7 +688,7 @@ export interface FsStats extends fs.Stats { * @param path path to check * @returns fsStat */ -export function stats(path: string): FsStats { +export function stats(path: string): fs.Stats { return fs.statSync(path); } @@ -800,31 +781,112 @@ export const checkPath = im._checkPath; * @returns void */ export function cd(path: string): void { - if (path) { - shell.cd(path); - _checkShell('cd'); + if (path === '-') { + if (!process.env.OLDPWD) { + throw new Error(`Failed cd: could not find previous directory`); + } else { + path = process.env.OLDPWD; + } + } + + if (path === '~') { + path = os.homedir(); + } + + if (!fs.existsSync(path)) { + throw new Error(`Failed cd: no such file or directory: ${path}`) + } + + if (!fs.statSync(path).isDirectory()) { + throw new Error(`Failed cd: not a directory: ${path}`); + } + + try { + const currentPath = process.cwd(); + process.chdir(path); + process.env.OLDPWD = currentPath; + } catch (error) { + debug(loc('LIB_OperationFailed', 'cd', error)); } } +const dirStack: string[] = []; + +function getActualStack() { + return [process.cwd()].concat(dirStack); +} + /** * Change working directory and push it on the stack * - * @param path new working directory path + * @param dir new working directory path * @returns void */ -export function pushd(path: string): void { - shell.pushd(path); - _checkShell('pushd'); +export function pushd(dir: string = ''): string[] { + const dirs = getActualStack(); + + let maybeIndex = parseInt(dir); + + if (dir === '+0') { + return dirs; + } else if (dir.length === 0) { + if (dirs.length > 1) { + dirs.splice(0, 0, ...dirs.splice(1, 1)); + } else { + throw new Error(`Failed pushd: no other directory`); + } + } else if (!isNaN(maybeIndex)) { + if (maybeIndex < dirStack.length + 1) { + maybeIndex = dir.charAt(0) === '-' ? maybeIndex - 1 : maybeIndex; + } + dirs.splice(0, dirs.length, ...dirs.slice(maybeIndex).concat(dirs.slice(0, maybeIndex))); + } else { + dirs.unshift(dir); + } + + const _path = path.resolve(dirs.shift()!); + + try { + cd(_path); + } catch (error) { + if (!fs.existsSync(_path)) { + throw new Error(`Failed pushd: no such file or directory: ${_path}`); + } + + throw error; + } + dirStack.splice(0, dirStack.length, ...dirs); + return getActualStack(); } /** * Change working directory back to previously pushed directory * + * @param index index to remove from the stack * @returns void */ -export function popd(): void { - shell.popd(); - _checkShell('popd'); +export function popd(index: string = ''): string[] { + if (dirStack.length === 0) { + throw new Error(`Failed popd: directory stack empty`); + } + + let maybeIndex = parseInt(index); + + if (isNaN(maybeIndex)) { + maybeIndex = 0; + } else if (maybeIndex < dirStack.length + 1) { + maybeIndex = index.charAt(0) === '-' ? maybeIndex - 1 : maybeIndex; + } + + if (maybeIndex > 0 || dirStack.length + maybeIndex === 0) { + maybeIndex = maybeIndex > 0 ? maybeIndex - 1 : maybeIndex; + dirStack.splice(maybeIndex, 1); + } else { + const _path = path.resolve(dirStack.shift()!); + cd(_path); + } + + return getActualStack(); } /** @@ -917,49 +979,158 @@ export const which = im._which; * @param {string[]} paths Paths to search. * @return {string[]} An array of files in the given path(s). */ -export function ls(options: string, paths: string[]): string[] { - if (options) { - return shell.ls(options, paths); - } else { - return shell.ls(paths); +export function ls(optionsOrPaths?: string | string[], ...paths: string[]): string[] { + try { + let isRecursive = false; + let includeHidden = false; + let handleAsOptions = false; + + if (typeof optionsOrPaths == 'string' && optionsOrPaths.includes('-')) { + isRecursive = optionsOrPaths.includes('R'); + includeHidden = optionsOrPaths.includes('A'); + handleAsOptions = isRecursive || includeHidden; + } + + if (paths === undefined || paths.length === 0) { + if (Array.isArray(optionsOrPaths)) { + paths = optionsOrPaths as string[]; + } else if (optionsOrPaths && !handleAsOptions) { + paths = [optionsOrPaths]; + } else { + paths = []; + } + } + + if (paths.length === 0) { + paths.push(path.resolve('.')); + } + + const preparedPaths: string[] = []; + + while (paths.length > 0) { + const pathEntry = resolve(paths.shift()); + + if (pathEntry?.includes('*')) { + paths.push(...findMatch(path.dirname(pathEntry), [path.basename(pathEntry)])); + continue; + } + + if (fs.lstatSync(pathEntry).isDirectory()) { + preparedPaths.push(...fs.readdirSync(pathEntry).map(file => path.join(pathEntry, file))); + } else { + preparedPaths.push(pathEntry); + } + } + + const entries: string[] = []; + + while (preparedPaths.length > 0) { + const entry = preparedPaths.shift()!; + const entrybasename = path.basename(entry); + + if (entry?.includes('*')) { + preparedPaths.push(...findMatch(path.dirname(entry), [entrybasename])); + continue; + } + + if (!includeHidden && entrybasename.startsWith('.') && entrybasename !== '.' && entrybasename !== '..') { + continue; + } + + if (fs.lstatSync(entry).isDirectory() && isRecursive) { + preparedPaths.push(...fs.readdirSync(entry).map(x => path.join(entry, x))); + } else { + entries.push(entry); + } + } + + return entries; + } catch (error) { + if (error.code === 'ENOENT') { + throw new Error(`Failed ls: ${error}`); + } else { + throw new Error(loc('LIB_OperationFailed', 'ls', error)); + } + } +} + +function retryer(func: Function, retryCount: number = 0, continueOnError: boolean = false) { + while (retryCount >= 0) { + try { + return func(); + } catch (error) { + if (!continueOnError || error.code === "ENOENT") { + throw error; + } + + console.log(loc('LIB_CopyFileFailed', retryCount)); + retryCount--; + + if (retryCount < 0) { + warning(error, IssueSource.TaskInternal); + break; + } + } } } /** * Copies a file or folder. * - * @param source source path - * @param dest destination path - * @param options string -r, -f or -rf for recursive and force - * @param continueOnError optional. whether to continue on error - * @param retryCount optional. Retry count to copy the file. It might help to resolve intermittent issues e.g. with UNC target paths on a remote host. + * @param {string} sourceOrOptions - Either the source path or an option string '-r', '-f' or '-rf' for recursive and force. + * @param {string} destinationOrSource - The destination path or the source path. + * @param {string} [optionsOrDestination] - Options string or the destination path. + * @param {boolean} [continueOnError] - Optional. whether to continue on error. + * @param {number} [retryCount=0] - Optional. Retry count to copy the file. It might help to resolve intermittent issues e.g. with UNC target paths on a remote host. */ -export function cp(source: string, dest: string, options?: string, continueOnError?: boolean, retryCount: number = 0): void { - while (retryCount >= 0) { +export function cp(sourceOrOptions: string, destinationOrSource: string, optionsOrDestination?: string, continueOnError?: boolean, retryCount: number = 0): void { + retryer(() => { + const isOptions = sourceOrOptions.startsWith('-') && sourceOrOptions.length > 1; + let recursive = false; + let force = true; + let source = sourceOrOptions; + let destination = destinationOrSource; + + if (isOptions) { + sourceOrOptions = sourceOrOptions.toLowerCase(); + recursive = sourceOrOptions.includes('r'); + force = !sourceOrOptions.includes('n'); + source = destinationOrSource; + destination = optionsOrDestination!; + } + + if (!fs.existsSync(destination) && !force) { + throw new Error(`ENOENT: no such file or directory: ${destination}`); + } + + const lstatSource = fs.lstatSync(source); + + if (!force && fs.existsSync(destination)) { + return; + } + try { - if (options) { - shell.cp(options, source, dest); - } - else { - shell.cp(source, dest); - } + if (lstatSource.isFile()) { + if (fs.existsSync(destination) && fs.lstatSync(destination).isDirectory()) { + destination = path.join(destination, path.basename(source)); + } - _checkShell('cp', false); - break; - } catch (e) { - if (retryCount <= 0) { - if (continueOnError) { - warning(e, IssueSource.TaskInternal); - break; + if (force) { + fs.copyFileSync(source, destination); } else { - throw e; + fs.copyFileSync(source, destination, fs.constants.COPYFILE_EXCL); } } else { - console.log(loc('LIB_CopyFileFailed', retryCount)); - retryCount--; + fs.cpSync(source, path.join(destination, path.basename(source)), { recursive, force }); + } + } catch (error) { + if (error.code === 'ENOENT') { + throw new Error(error); + } else { + throw new Error(loc('LIB_OperationFailed', 'cp', error)); } } - } + }, retryCount, continueOnError); } /** @@ -971,14 +1142,24 @@ export function cp(source: string, dest: string, options?: string, continueOnErr * @param continueOnError optional. whether to continue on error */ export function mv(source: string, dest: string, options?: string, continueOnError?: boolean): void { - if (options) { - shell.mv(options, source, dest); - } - else { - shell.mv(source, dest); - } + try { + const isForce = !options?.toLowerCase()?.includes('-n') && options?.toLowerCase()?.includes('-f'); + const destExists = fs.existsSync(dest); + + if (!fs.existsSync(source)) { + throw new Error(loc('LIB_PathNotFound', source)); + } + + if (destExists && !isForce) { + throw new Error(loc('LIB_PathNotFound', dest)); + } - _checkShell('mv', continueOnError); + fs.renameSync(source, dest); + } catch (error) { + debug('mv failed'); + var errMsg = loc('LIB_OperationFailed', 'mv', error); + debug(errMsg); + } } /** @@ -1441,8 +1622,7 @@ export function rmRF(inputPath: string): void { debug('removing file ' + inputPath); childProcess.execFileSync("cmd.exe", ["/c", "del", "/f", "/a", inputPath]); } - } - catch (err) { + } catch (err) { // if you try to delete a file that doesn't exist, desired result is achieved // other errors are valid if (err.code != 'ENOENT') { @@ -1453,23 +1633,31 @@ export function rmRF(inputPath: string): void { // Shelling out fails to remove a symlink folder with missing source, this unlink catches that try { fs.unlinkSync(inputPath); - } - catch (err) { + } catch (err) { // if you try to delete a file that doesn't exist, desired result is achieved // other errors are valid if (err.code != 'ENOENT') { throw new Error(loc('LIB_OperationFailed', 'rmRF', err.message)); } } - } - else { + } else { // get the lstats in order to workaround a bug in shelljs@0.3.0 where symlinks // with missing targets are not handled correctly by "rm('-rf', path)" let lstats: fs.Stats; + try { - lstats = fs.lstatSync(inputPath); - } - catch (err) { + if (inputPath.includes('*')) { + const entries = findMatch(path.dirname(inputPath), [path.basename(inputPath)]); + + for (const entry of entries) { + fs.rmSync(entry, { recursive: true, force: true }); + } + + return; + } else { + lstats = fs.lstatSync(inputPath); + } + } catch (err) { // if you try to delete a file that doesn't exist, desired result is achieved // other errors are valid if (err.code == 'ENOENT') { @@ -1481,9 +1669,9 @@ export function rmRF(inputPath: string): void { if (lstats.isDirectory()) { debug('removing directory'); - shell.rm('-rf', inputPath); - let errMsg: string = shell.error(); - if (errMsg) { + try { + fs.rmSync(inputPath, { recursive: true, force: true }); + } catch (errMsg) { throw new Error(loc('LIB_OperationFailed', 'rmRF', errMsg)); } @@ -1492,7 +1680,11 @@ export function rmRF(inputPath: string): void { debug('removing file'); try { - fs.unlinkSync(inputPath); + const entries = findMatch(path.dirname(inputPath), [path.basename(inputPath)]); + + for (const entry of entries) { + fs.rmSync(entry, { recursive: true, force: true }); + } } catch (err) { throw new Error(loc('LIB_OperationFailed', 'rmRF', err.message)); @@ -1794,7 +1986,6 @@ function _getDefaultMatchOptions(): MatchOptions { * @param matchOptions defaults to { dot: true, nobrace: true, nocase: process.platform == 'win32' } */ export function findMatch(defaultRoot: string, patterns: string[] | string, findOptions?: FindOptions, matchOptions?: MatchOptions): string[] { - // apply defaults for parameters and trace defaultRoot = defaultRoot || this.getVariable('system.defaultWorkingDirectory') || process.cwd(); debug(`defaultRoot: '${defaultRoot}'`); diff --git a/node/test/cd.ts b/node/test/cd.ts new file mode 100644 index 000000000..3f5e54e36 --- /dev/null +++ b/node/test/cd.ts @@ -0,0 +1,113 @@ +import os = require('node:os'); +import fs = require('node:fs'); +import path = require('node:path'); +import assert = require('node:assert'); + +import * as tl from '../_build/task'; + +const DIRNAME = __dirname; + +import * as testutil from './testutil'; + +describe('cd cases', () => { + let TEMP_DIR_PATH: string; + const TEMP_DIR_1 = path.resolve(DIRNAME, 'temp1'); + const TEMP_DIR_2 = path.resolve(DIRNAME, 'temp2'); + const TEMP_FILE_1 = path.resolve(TEMP_DIR_1, 'file1'); + + before((done) => { + fs.mkdirSync(TEMP_DIR_1); + fs.mkdirSync(TEMP_DIR_2); + fs.writeFileSync(TEMP_FILE_1, 'file1'); + + try { + testutil.initialize(); + } catch (error) { + assert.fail(`Failed to load tl lib: ${error.message}`); + } + + done(); + }); + + beforeEach((done) => { + TEMP_DIR_PATH = fs.mkdtempSync('temp_test_'); + process.chdir(DIRNAME); + + if (!fs.existsSync(TEMP_DIR_PATH)) { + fs.mkdirSync(TEMP_DIR_PATH); + } + + done(); + }); + + afterEach((done) => { + process.chdir(DIRNAME); + if (fs.existsSync(TEMP_DIR_PATH)) { + fs.rmdirSync(TEMP_DIR_PATH, { recursive: true }); + } + + done(); + }); + + after((done) => { + fs.rmdirSync(TEMP_DIR_1, { recursive: true }); + fs.rmdirSync(TEMP_DIR_2, { recursive: true }); + done(); + }); + + it('Check change directory for a folder that does not exist', (done) => { + assert.ok(!fs.existsSync('/thisfolderdoesnotexist')); + assert.throws(() => tl.cd('/thisfolderdoesnotexist'), { message: "Failed cd: no such file or directory: /thisfolderdoesnotexist" }); + done(); + }); + + it('Change directory to a file path', (done) => { + const filePath = path.resolve(DIRNAME, 'scripts', 'match-input-exe.cs'); + assert.ok(fs.existsSync(filePath)); + assert.throws(() => tl.cd(filePath), { message: `Failed cd: not a directory: ${filePath}` }); + done(); + }); + + it('There is no previous directory', (done) => { + assert.throws(() => tl.cd('-'), { message: 'Failed cd: could not find previous directory' }); + done(); + }); + + it('Change direcotry to a relative path', (done) => { + tl.cd(TEMP_DIR_PATH); + assert.equal(path.basename(TEMP_DIR_PATH), TEMP_DIR_PATH); + done(); + }); + + it('Change directory to an absolute path', (done) => { + tl.cd('/'); + assert.equal(process.cwd(), path.resolve('/')); + done(); + }); + + it('Change directory to a previous directory -', (done) => { + tl.cd('/'); + tl.cd('-'); + assert.ok(process.cwd(), path.resolve(DIRNAME)); + done(); + }); + + it('Change directory with cp', (done) => { + assert.ok(!fs.existsSync(path.resolve(TEMP_DIR_PATH, "file1"))); + tl.cd(TEMP_DIR_1); + tl.cp(TEMP_FILE_1, path.resolve("..", TEMP_DIR_PATH)); + tl.cd(path.resolve("..", TEMP_DIR_PATH)); + assert.ok(fs.existsSync('file1')); + done(); + }); + + it('Using the tilde expansion', (done) => { + tl.cd('~'); + assert.ok(process.cwd(), os.homedir()); + tl.cd('..'); + assert.notEqual(process.cwd(), os.homedir()); + tl.cd('~'); + assert.equal(process.cwd(), os.homedir()); + done(); + }); +}); \ No newline at end of file diff --git a/node/test/cp.ts b/node/test/cp.ts new file mode 100644 index 000000000..4d122ab4f --- /dev/null +++ b/node/test/cp.ts @@ -0,0 +1,125 @@ +import fs = require('node:fs'); +import path = require('node:path'); +import assert = require('node:assert'); + +import * as tl from '../_build/task'; + +const DIRNAME = __dirname; + +import * as testutil from './testutil'; + +describe('cp cases', () => { + const TEMP_DIR_1 = path.resolve(DIRNAME, 'temp1'); + const TEMP_DIR_2 = path.resolve(DIRNAME, 'temp2'); + const TEMP_DIR_2_FILE_1 = path.resolve(TEMP_DIR_2, 'file1'); + const TESTCASE_1 = path.resolve(TEMP_DIR_1, 'testcase_1'); + const TESTCASE_2 = path.resolve(TEMP_DIR_1, 'testcase_2'); + + before((done) => { + tl.mkdirP(TEMP_DIR_1); + tl.mkdirP(TEMP_DIR_2); + tl.cd(TEMP_DIR_1); + + fs.writeFileSync(TEMP_DIR_2_FILE_1, 'file1'); + + try { + testutil.initialize(); + } catch (error) { + assert.fail(`Failed to load tl lib: ${error.message}`); + } + + done(); + }); + + beforeEach((done) => { + fs.writeFileSync(TESTCASE_1, 'testcase_1'); + fs.writeFileSync(TESTCASE_2, 'testcase_2'); + done(); + }); + + afterEach((done) => { + tl.rmRF(TESTCASE_1); + tl.rmRF(TESTCASE_2); + done(); + }); + + after((done) => { + fs.rmSync(TEMP_DIR_1, { recursive: true}); + fs.rmSync(TEMP_DIR_2, { recursive: true}); + done(); + }); + + it('Provide the source that does not exist', (done) => { + assert.throws(() => tl.cp('pathdoesnotexist', TEMP_DIR_1), { message: /^ENOENT: no such file or directory/ }); + assert.ok(!fs.existsSync(path.join(TEMP_DIR_1, 'pathdoesnotexist'))); + done(); + }); + + it('Provide the source as empty string', (done) => { + assert.throws(() => tl.cp('', 'pathdoesnotexist'), { message: /^ENOENT: no such file or directory/ }); + done(); + }); + + it('Provide the destination as empty string', (done) => { + assert.throws(() => tl.cp('pathdoesnotexist', ''), { message: /^ENOENT: no such file or directory/ }); + done(); + }); + + it('Provide -n attribute to prevent overwrite an existing file at the destination', (done) => { + assert.doesNotThrow(() => tl.cp('-n', TESTCASE_1, TESTCASE_2)); + assert.equal(fs.readFileSync(TESTCASE_2, 'utf8'), 'testcase_2'); + done(); + }); + + it('Provide two paths, check force default behavior', (done) => { + assert.doesNotThrow(() => tl.cp(TESTCASE_1, TESTCASE_2)); + assert.equal(fs.readFileSync(TESTCASE_2, 'utf8'), 'testcase_1'); + done(); + }); + + it('Provide two paths, check explicitly force attribute', (done) => { + assert.doesNotThrow(() => tl.cp('-f', TESTCASE_1, TESTCASE_2)); + assert.equal(fs.readFileSync(TESTCASE_2, 'utf8'), 'testcase_1'); + done(); + }); + + it('Check copying a file to a dir', (done) => { + assert.doesNotThrow(() => tl.cp(TESTCASE_1, TEMP_DIR_2)); + assert.ok(fs.existsSync(path.join(TEMP_DIR_2, 'testcase_1'))); + assert.equal(fs.readFileSync(path.join(TEMP_DIR_2, 'testcase_1'), 'utf8'), 'testcase_1'); + done(); + }); + + it('Check copying file to a file', (done) => { + assert.doesNotThrow(() => tl.cp(TESTCASE_2, path.join(TEMP_DIR_2, 'testcase_3'))); + assert.ok(fs.existsSync(path.join(TEMP_DIR_2, 'testcase_3'))); + assert.equal(fs.readFileSync(path.join(TEMP_DIR_2, 'testcase_3'), 'utf8'), 'testcase_2'); + done(); + }); + + it('Check copying file to an existed file with -f option', (done) => { + assert.ok(fs.existsSync(TESTCASE_1)); + assert.ok(fs.existsSync(TESTCASE_2)); + assert.equal(fs.readFileSync(TESTCASE_1, 'utf8'), 'testcase_1'); + assert.equal(fs.readFileSync(TESTCASE_2, 'utf8'), 'testcase_2'); + assert.doesNotThrow(() => tl.cp('-f', TESTCASE_1, TESTCASE_2)); + assert.ok(fs.existsSync(TESTCASE_1)); + assert.ok(fs.existsSync(TESTCASE_2)); + assert.equal(fs.readFileSync(TESTCASE_1, 'utf8'), 'testcase_1'); + assert.equal(fs.readFileSync(TESTCASE_2, 'utf8'), 'testcase_1'); + done(); + }); + + it('Check copying file to an existed file with -n option', (done) => { + assert.ok(fs.existsSync(TESTCASE_1)); + assert.ok(fs.existsSync(TESTCASE_2)); + assert.equal(fs.readFileSync(TESTCASE_1, 'utf8'), 'testcase_1'); + assert.equal(fs.readFileSync(TESTCASE_2, 'utf8'), 'testcase_2'); + tl.cp('-n', TESTCASE_1, TESTCASE_2); + assert.ok(fs.existsSync(TESTCASE_1)); + assert.ok(fs.existsSync(TESTCASE_2)); + assert.equal(fs.readFileSync(TESTCASE_1, 'utf8'), 'testcase_1'); + assert.equal(fs.readFileSync(TESTCASE_2, 'utf8'), 'testcase_2'); + done(); + }); +}); \ No newline at end of file diff --git a/node/test/ls.ts b/node/test/ls.ts new file mode 100644 index 000000000..ba7627d9d --- /dev/null +++ b/node/test/ls.ts @@ -0,0 +1,254 @@ +import os = require('node:os'); +import fs = require('node:fs'); +import path = require('node:path'); +import assert = require('node:assert'); + +import * as tl from '../_build/task'; + +const DIRNAME = __dirname; + +import * as testutil from './testutil'; + +describe('ls cases', () => { + const TEMP_DIR_1 = path.resolve(DIRNAME, 'temp1'); + const TEMP_SUBDIR_1 = path.resolve(TEMP_DIR_1, 'temp1_subdir1'); + let TEMP_FILE_1: string; + let TEMP_FILE_1_JS: string; + let TEMP_FILE_2: string; + let TEMP_FILE_2_JS: string; + let TEMP_FILE_3_ESCAPED: string; + let TEMP_HIDDEN_FILE_1: string; + let TEMP_HIDDEN_DIR_1: string; + let TEMP_SUBDIR_FILE_1: string; + let TEMP_SUBDIR_FILELINK_1: string; + + before((done) => { + try { + testutil.initialize(); + } catch (error) { + assert.fail(`Failed to load tl lib: ${error.message}`); + } + + done(); + }); + + after((done) => { + tl.cd(DIRNAME); + done(); + }); + + beforeEach((done) => { + tl.mkdirP(TEMP_SUBDIR_1); + tl.cd(TEMP_DIR_1); + + TEMP_FILE_1 = path.join(TEMP_DIR_1, 'file1'); + fs.writeFileSync(TEMP_FILE_1, 'test'); + TEMP_FILE_1_JS = path.join(TEMP_DIR_1, 'file1.js'); + fs.writeFileSync(TEMP_FILE_1_JS, 'test'); + TEMP_FILE_2 = path.join(TEMP_DIR_1, 'file2'); + fs.writeFileSync(TEMP_FILE_2, 'test'); + TEMP_FILE_2_JS = path.join(TEMP_DIR_1, 'file2.js'); + fs.writeFileSync(TEMP_FILE_2_JS, 'test'); + TEMP_FILE_3_ESCAPED = path.join(TEMP_DIR_1, '(filename)e$cap3d.[]^-%'); + fs.writeFileSync(TEMP_FILE_3_ESCAPED, 'test'); + TEMP_HIDDEN_FILE_1 = path.join(TEMP_DIR_1, '.hidden_file'); + fs.writeFileSync(TEMP_HIDDEN_FILE_1, 'test'); + TEMP_HIDDEN_DIR_1 = path.join(TEMP_DIR_1, '.hidden_dir'); + fs.mkdirSync(TEMP_HIDDEN_DIR_1); + + TEMP_SUBDIR_FILE_1 = path.join(TEMP_SUBDIR_1, 'file'); + fs.writeFileSync(TEMP_SUBDIR_FILE_1, 'test'); + + TEMP_SUBDIR_FILELINK_1 = path.join(TEMP_SUBDIR_1, 'filelink'); + fs.symlinkSync(TEMP_SUBDIR_FILE_1, TEMP_SUBDIR_FILELINK_1); + + done(); + }); + + afterEach((done) => { + tl.cd(DIRNAME); + tl.rmRF(TEMP_DIR_1); + done(); + }); + + it('Provide the folder which does not exist', (done) => { + assert.ok(!fs.existsSync('/thisfolderdoesnotexist')); + assert.throws(() => tl.ls('/thisfolderdoesnotexist'), { message: /^Failed ls: Error: ENOENT: no such file or directory, lstat/ }); + done(); + }); + + it('Without arguments', (done) => { + const result = tl.ls(); + assert.ok(result.includes(TEMP_FILE_1)); + assert.ok(result.includes(TEMP_FILE_1_JS)); + assert.ok(result.includes(TEMP_FILE_2)); + assert.ok(result.includes(TEMP_FILE_2_JS)); + assert.ok(result.includes(TEMP_FILE_3_ESCAPED)); + assert.ok(result.includes(TEMP_SUBDIR_1)); + assert.equal(result.length, 6); + done(); + }); + + it('Passed TEMP_DIR_1 as an argument', (done) => { + const result = tl.ls(TEMP_DIR_1); + assert.ok(result.includes(TEMP_FILE_1)); + assert.ok(result.includes(TEMP_FILE_1_JS)); + assert.ok(result.includes(TEMP_FILE_2)); + assert.ok(result.includes(TEMP_FILE_2_JS)); + assert.ok(result.includes(TEMP_FILE_3_ESCAPED)); + assert.ok(result.includes(TEMP_SUBDIR_1)); + assert.equal(result.length, 6); + done(); + }); + + it('Passed file as an argument', (done) => { + const result = tl.ls(TEMP_FILE_1); + assert.ok(result.includes(TEMP_FILE_1)); + assert.equal(result.length, 1); + done(); + }); + + it('Provide the -A attribute as an argument', (done) => { + tl.cd(TEMP_DIR_1); + const result = tl.ls('-A'); + assert.ok(result.includes(TEMP_FILE_1)); + assert.ok(result.includes(TEMP_FILE_1_JS)); + assert.ok(result.includes(TEMP_FILE_2)); + assert.ok(result.includes(TEMP_FILE_2_JS)); + assert.ok(result.includes(TEMP_FILE_3_ESCAPED)); + assert.ok(result.includes(TEMP_SUBDIR_1)); + assert.ok(result.includes(TEMP_HIDDEN_FILE_1)); + assert.ok(result.includes(TEMP_HIDDEN_DIR_1)); + assert.equal(result.length, 8); + done(); + }); + + it('Wildcard for TEMP_DIR_1', (done) => { + const result = tl.ls(path.join(TEMP_DIR_1, '*')); + assert.ok(result.includes(TEMP_FILE_1)); + assert.ok(result.includes(TEMP_FILE_1_JS)); + assert.ok(result.includes(TEMP_FILE_2)); + assert.ok(result.includes(TEMP_FILE_2_JS)); + assert.ok(result.includes(TEMP_FILE_3_ESCAPED)); + assert.ok(result.includes(TEMP_SUBDIR_FILE_1)); + assert.ok(result.includes(TEMP_SUBDIR_FILELINK_1)); + assert.equal(result.length, 7); + done(); + }); + + it('Wildcard for find f*l*', (done) => { + const result = tl.ls(path.join(TEMP_DIR_1, 'f*l*')); + assert.ok(result.includes(TEMP_FILE_1)); + assert.ok(result.includes(TEMP_FILE_1_JS)); + assert.ok(result.includes(TEMP_FILE_2)); + assert.ok(result.includes(TEMP_FILE_2_JS)); + assert.equal(result.length, 4); + done(); + }); + + it('Wildcard f*l*.js', (done) => { + const result = tl.ls(path.join(TEMP_DIR_1, 'f*l*.js')); + assert.ok(result.includes(TEMP_FILE_1_JS)); + assert.ok(result.includes(TEMP_FILE_2_JS)); + assert.equal(result.length, 2); + done(); + }); + + it('Wildcard that is not valid', (done) => { + const result = tl.ls(path.join(TEMP_DIR_1, '/*.j')); + assert.equal(result.length, 0); + done(); + }); + + it('Wildcard *.*', (done) => { + const result = tl.ls(path.join(TEMP_DIR_1, '*.*')); + assert.ok(result.includes(TEMP_FILE_1_JS)); + assert.ok(result.includes(TEMP_FILE_2_JS)); + assert.ok(result.includes(TEMP_FILE_3_ESCAPED)); + assert.equal(result.length, 3); + done(); + }); + + it('Two wildcards in the array', (done) => { + const result = tl.ls([path.join(TEMP_DIR_1, 'f*le*.js'), path.join(TEMP_SUBDIR_1, '*')]); + assert.ok(result.includes(TEMP_FILE_1_JS)); + assert.ok(result.includes(TEMP_FILE_2_JS)); + assert.ok(result.includes(TEMP_SUBDIR_FILE_1)); + assert.ok(result.includes(TEMP_SUBDIR_FILELINK_1)); + assert.equal(result.length, 4); + done(); + }); + + it('Recursive without path argument', (done) => { + tl.cd(TEMP_DIR_1); + const result = tl.ls('-R'); + assert.ok(result.includes(TEMP_FILE_1)); + assert.ok(result.includes(TEMP_FILE_1_JS)); + assert.ok(result.includes(TEMP_FILE_2)); + assert.ok(result.includes(TEMP_FILE_2_JS)); + assert.ok(result.includes(TEMP_FILE_3_ESCAPED)); + assert.ok(result.includes(TEMP_SUBDIR_FILE_1)); + assert.ok(result.includes(TEMP_SUBDIR_FILELINK_1)); + assert.equal(result.length, 7); + done(); + }); + + it('Provide path and recursive attribute', (done) => { + const result = tl.ls('-R', TEMP_DIR_1); + assert.ok(result.includes(TEMP_FILE_1)); + assert.ok(result.includes(TEMP_FILE_1_JS)); + assert.ok(result.includes(TEMP_FILE_2)); + assert.ok(result.includes(TEMP_FILE_2_JS)); + assert.ok(result.includes(TEMP_FILE_3_ESCAPED)); + assert.ok(result.includes(TEMP_SUBDIR_FILE_1)); + assert.ok(result.includes(TEMP_SUBDIR_FILELINK_1)); + assert.equal(result.length, 7); + done(); + }); + + it('Provide path and -RA attributes', (done) => { + const result = tl.ls('-RA', TEMP_DIR_1); + assert.ok(result.includes(TEMP_FILE_1)); + assert.ok(result.includes(TEMP_FILE_1_JS)); + assert.ok(result.includes(TEMP_FILE_2)); + assert.ok(result.includes(TEMP_FILE_2_JS)); + assert.ok(result.includes(TEMP_FILE_3_ESCAPED)); + assert.ok(result.includes(TEMP_SUBDIR_FILE_1)); + assert.ok(result.includes(TEMP_HIDDEN_FILE_1)); + assert.ok(result.includes(TEMP_SUBDIR_FILELINK_1)); + assert.equal(result.length, 8); + done(); + }); + + it('Priovide -RA attribute', (done) => { + const result = tl.ls('-RA', TEMP_SUBDIR_1); + assert.ok(result.includes(TEMP_SUBDIR_FILELINK_1)); + assert.equal(result.length, 2); + done(); + }); + + it('Provide path and the -R attribute', (done) => { + const result = tl.ls('-R', TEMP_SUBDIR_1); + assert.ok(result.includes(TEMP_SUBDIR_FILE_1)); + assert.ok(result.includes(TEMP_SUBDIR_FILELINK_1)); + assert.equal(result.length, 2); + done(); + }); + + it('Empty attributes, but several paths', (done) => { + const result = tl.ls('', TEMP_SUBDIR_1, TEMP_FILE_1); + assert.ok(result.includes(TEMP_FILE_1)); + assert.ok(result.includes(TEMP_SUBDIR_FILE_1)); + assert.ok(result.includes(TEMP_SUBDIR_FILELINK_1)); + assert.equal(result.length, 3); + done(); + }); + + it('New one folder without content', (done) => { + tl.mkdirP('foo'); + assert.doesNotThrow(() => tl.ls('foo')); + assert.equal(tl.ls('foo').length, 0); + tl.rmRF('foo'); + done(); + }); +}); \ No newline at end of file diff --git a/node/test/mv.ts b/node/test/mv.ts new file mode 100644 index 000000000..22ce7c9b5 --- /dev/null +++ b/node/test/mv.ts @@ -0,0 +1,112 @@ +import fs = require('node:fs'); +import path = require('node:path'); +import assert = require('node:assert'); + +import * as tl from '../_build/task'; + +const DIRNAME = __dirname; + +import * as testutil from './testutil'; + +describe('mv cases', () => { + const TEMP_DIR = fs.mkdtempSync(DIRNAME + '/'); + let TEMP_FILE_1: string; + let TEMP_FILE_1_JS: string; + let TEMP_FILE_2: string; + let TEMP_FILE_2_JS: string; + + before((done) => { + try { + testutil.initialize(); + } catch (error) { + assert.fail(`Failed to load tl lib: ${error.message}`); + } + + tl.cd(TEMP_DIR); + done(); + }); + + beforeEach((done) => { + TEMP_FILE_1 = path.join(TEMP_DIR, 'file1'); + fs.writeFileSync(TEMP_FILE_1, 'test'); + TEMP_FILE_1_JS = path.join(TEMP_DIR, 'file1.js'); + fs.writeFileSync(TEMP_FILE_1_JS, 'test'); + TEMP_FILE_2 = path.join(TEMP_DIR, 'file2'); + fs.writeFileSync(TEMP_FILE_2, 'test'); + TEMP_FILE_2_JS = path.join(TEMP_DIR, 'file2.js'); + fs.writeFileSync(TEMP_FILE_2_JS, 'test'); + + done(); + }); + + after((done) => { + fs.rmSync(TEMP_DIR, { recursive: true }); + done(); + }); + + it('Provide invalid arguments', (done) => { + // @ts-ignore + assert.doesNotThrow(() => tl.mv()); + // @ts-ignore + assert.doesNotThrow(() => tl.mv('file1')); + // @ts-ignore + assert.doesNotThrow(() => tl.mv('-f')); + done(); + }); + + it('Provide an unsupported option argument', (done) => { + assert.ok(fs.existsSync('file1')); + assert.doesNotThrow(() => tl.mv('file1', 'file1', '-Z')); + assert.ok(fs.existsSync('file1')); + done(); + }); + + it('Provide a source that does not exist', (done) => { + assert.doesNotThrow(() => tl.mv('pathdoesntexist1', '..')); + assert.ok(!fs.existsSync('../pathdoesntexist2')); + done(); + }); + + it('Provide a source that does not exist', (done) => { + assert.doesNotThrow(() => tl.mv('pathdoesntexist1', 'pathdoesntexist2', '..')); + assert.ok(!fs.existsSync('../pathdoesntexist1')); + assert.ok(!fs.existsSync('../pathdoesntexist2')); + done(); + }); + + it('Provide a destination that already exist', (done) => { + assert.ok(fs.existsSync('file1')); + assert.ok(fs.existsSync('file2')); + assert.doesNotThrow(() => tl.mv('file1', 'file2')); + assert.ok(fs.existsSync('file1')); + assert.ok(fs.existsSync('file2')); + done(); + }); + + it('Provide a wildcard when dest is file', (done) => { + assert.doesNotThrow(() => tl.mv('file*', 'file1')); + assert.ok(fs.existsSync('file1')); + assert.ok(fs.existsSync('file2')); + assert.ok(fs.existsSync('file1.js')); + assert.ok(fs.existsSync('file2.js')); + done(); + }); + + it('Move a file and rollback', (done) => { + assert.ok(fs.existsSync('file1')); + tl.mv('file1', 'file3'); + assert.ok(!fs.existsSync('file1')); + assert.ok(fs.existsSync('file3')); + tl.mv('file3', 'file1'); + assert.ok(fs.existsSync('file1')); + done(); + }); + + it('Move to an existed path with -f attribute', (done) => { + assert.ok(fs.existsSync('file1')); + assert.doesNotThrow(() => tl.mv('file1', 'file2', '-f')); + assert.ok(!fs.existsSync('file1')); + assert.ok(fs.existsSync('file2')); + done(); + }); +}); \ No newline at end of file diff --git a/node/test/popd.ts b/node/test/popd.ts new file mode 100644 index 000000000..51e3534fd --- /dev/null +++ b/node/test/popd.ts @@ -0,0 +1,123 @@ +import fs = require('node:fs'); +import path = require('node:path'); +import assert = require('node:assert'); + +import * as tl from '../_build/task'; + +const DIRNAME = __dirname; + +function reset() { + while (true) { + try { + const stack = tl.popd(); + + if (stack.length === 0) { + break; + } + } catch { + break; + } + } + + tl.cd(DIRNAME); +} + +describe('popd cases', () => { + const TEMP_DIR_PATH = path.resolve('test_pushd', 'nested', 'dir'); + + before(() => { + fs.mkdirSync(path.resolve(DIRNAME, TEMP_DIR_PATH, 'a'), { recursive: true }); + fs.mkdirSync(path.resolve(DIRNAME, TEMP_DIR_PATH, 'b', 'c'), { recursive: true }); + }); + + beforeEach(() => { + reset(); + }); + + + after(() => { + reset(); + fs.rmSync(path.resolve(DIRNAME, TEMP_DIR_PATH), { recursive: true }); + }); + + it('The default usage', (done) => { + tl.pushd(TEMP_DIR_PATH); + const trail = tl.popd(); + assert.ok(process.cwd(), trail[0]); + assert.deepEqual(trail, [DIRNAME]); + done(); + }); + + it('Using two directories on the stack', (done) => { + tl.pushd(TEMP_DIR_PATH); + tl.pushd('a'); + const trail = tl.popd(); + assert.ok(process.cwd(), trail[0]); + assert.deepEqual(trail, [ + path.resolve(DIRNAME, TEMP_DIR_PATH), + DIRNAME, + ]); + done(); + }); + + it('Using three directories on the stack', (done) => { + tl.pushd(TEMP_DIR_PATH); + tl.pushd('b'); + tl.pushd('c'); + const trail = tl.popd(); + assert.ok(process.cwd(), trail[0]); + assert.deepEqual(trail, [ + path.resolve(TEMP_DIR_PATH, 'b'), + TEMP_DIR_PATH, + DIRNAME, + ]); + done(); + }); + + it('Valid by index', (done) => { + tl.pushd(TEMP_DIR_PATH); + const trail = tl.popd('+0'); + assert.ok(process.cwd(), trail[0]); + assert.deepEqual(trail, [DIRNAME]); + done(); + }); + + it('Using +1 option', (done) => { + tl.pushd(TEMP_DIR_PATH); + const trail = tl.popd('+1'); + assert.ok(process.cwd(), trail[0]); + assert.deepEqual(trail, [path.resolve(DIRNAME, TEMP_DIR_PATH)]); + done(); + }); + + it('Using -0 option', (done) => { + const r = tl.pushd(TEMP_DIR_PATH); + const trail = tl.popd('-0'); + assert.ok(process.cwd(), trail[0]); + assert.deepEqual(trail, [path.resolve(DIRNAME, TEMP_DIR_PATH)]); + done(); + }); + + it('Using -1 option', (done) => { + tl.pushd(TEMP_DIR_PATH); + const trail = tl.popd('-1'); + assert.ok(process.cwd(), trail[0]); + assert.deepEqual(trail, [DIRNAME]); + done(); + }); + + it('Using when stack is empty', (done) => { + assert.throws(() => tl.popd(), { message: 'Failed popd: directory stack empty' }); + done(); + }); + + it('Using when the DIRNAME is not stored', (done) => { + tl.cd(TEMP_DIR_PATH); + tl.pushd('b'); + const trail = tl.popd(); + assert.ok(trail[0], path.resolve(DIRNAME, TEMP_DIR_PATH)); + assert.ok(process.cwd(), trail[0]); + assert.throws(() => tl.popd(), { message: 'Failed popd: directory stack empty' }); + done(); + }); +}); \ No newline at end of file diff --git a/node/test/pushd.ts b/node/test/pushd.ts new file mode 100644 index 000000000..ca40fef6f --- /dev/null +++ b/node/test/pushd.ts @@ -0,0 +1,301 @@ +import fs = require('node:fs'); +import path = require('node:path'); +import assert = require('node:assert'); + +import * as tl from '../_build/task'; + +const DIRNAME = __dirname; + +function reset() { + while (true) { + try { + const stack = tl.popd(); + + if (stack.length === 0) { + break; + } + } catch { + break; + } + } + + tl.cd(DIRNAME); +} + +describe('pushd cases', () => { + const TEMP_DIR_PATH = path.resolve(DIRNAME, 'test_pushd', 'nested', 'dir'); + + before(() => { + fs.mkdirSync(path.resolve(TEMP_DIR_PATH, 'a'), { recursive: true }); + fs.mkdirSync(path.resolve(TEMP_DIR_PATH, 'b', 'c'), { recursive: true }); + }); + + beforeEach(() => { + reset(); + }); + + after(() => { + reset(); + fs.rmSync(path.resolve(TEMP_DIR_PATH), { recursive: true }); + }); + + it('Push valid directories', (done) => { + const trail = tl.pushd(TEMP_DIR_PATH); + assert.equal(process.cwd(), trail[0]); + assert.deepEqual(trail, [ + TEMP_DIR_PATH, + DIRNAME, + ]); + done(); + }); + + it('Using two directories', (done) => { + tl.pushd(TEMP_DIR_PATH); + const trail = tl.pushd('a'); + assert.equal(process.cwd(), trail[0]); + assert.deepEqual(trail, [ + path.resolve(TEMP_DIR_PATH, 'a'), + TEMP_DIR_PATH, + DIRNAME, + ]); + done(); + }); + + it('Using three directories', (done) => { + tl.pushd(TEMP_DIR_PATH); + tl.pushd('a'); + const trail = tl.pushd('../b'); + assert.equal(process.cwd(), trail[0]); + assert.deepEqual(trail, [ + path.resolve(TEMP_DIR_PATH, 'b'), + path.resolve(TEMP_DIR_PATH, 'a'), + TEMP_DIR_PATH, + DIRNAME, + ]); + done(); + }); + + it('Using four directories', (done) => { + tl.pushd(TEMP_DIR_PATH); + tl.pushd('a'); + tl.pushd('../b'); + const trail = tl.pushd('c'); + assert.equal(process.cwd(), trail[0]); + assert.deepEqual(trail, [ + path.resolve(TEMP_DIR_PATH, 'b', 'c'), + path.resolve(TEMP_DIR_PATH, 'b'), + path.resolve(TEMP_DIR_PATH, 'a'), + TEMP_DIR_PATH, + DIRNAME, + ]); + done(); + }); + + it('Using push stuff around with positive indices', (done) => { + tl.pushd(TEMP_DIR_PATH); + tl.pushd('a'); + tl.pushd('../b'); + tl.pushd('c'); + const trail = tl.pushd('+0'); + assert.equal(process.cwd(), trail[0]); + assert.deepEqual(trail, [ + path.resolve(TEMP_DIR_PATH, 'b', 'c'), + path.resolve(TEMP_DIR_PATH, 'b'), + path.resolve(TEMP_DIR_PATH, 'a'), + TEMP_DIR_PATH, + DIRNAME, + ]); + done(); + }); + + it('Using +1 option', (done) => { + tl.pushd(TEMP_DIR_PATH); + tl.pushd('a'); + tl.pushd('../b'); + tl.pushd('c'); + const trail = tl.pushd('+1'); + assert.equal(process.cwd(), trail[0]); + assert.deepEqual(trail, [ + path.resolve(TEMP_DIR_PATH, 'b'), + path.resolve(TEMP_DIR_PATH, 'a'), + TEMP_DIR_PATH, + DIRNAME, + path.resolve(TEMP_DIR_PATH, 'b', 'c'), + ]); + done(); + }); + + it('Using +2 option', (done) => { + tl.pushd(TEMP_DIR_PATH); + tl.pushd('a'); + tl.pushd('../b'); + tl.pushd('c'); + const trail = tl.pushd('+2'); + assert.equal(process.cwd(), trail[0]); + assert.deepEqual(trail, [ + path.resolve(TEMP_DIR_PATH, 'a'), + TEMP_DIR_PATH, + DIRNAME, + path.resolve(TEMP_DIR_PATH, 'b', 'c'), + path.resolve(TEMP_DIR_PATH, 'b'), + ]); + done(); + }); + + it('Using +3 option', (done) => { + tl.pushd(TEMP_DIR_PATH); + tl.pushd('a'); + tl.pushd('../b'); + tl.pushd('c'); + const trail = tl.pushd('+3'); + assert.equal(process.cwd(), trail[0]); + assert.deepEqual(trail, [ + TEMP_DIR_PATH, + DIRNAME, + path.resolve(TEMP_DIR_PATH, 'b', 'c'), + path.resolve(TEMP_DIR_PATH, 'b'), + path.resolve(TEMP_DIR_PATH, 'a'), + ]); + done(); + }); + + it('Using +4 option', (done) => { + tl.pushd(TEMP_DIR_PATH); + tl.pushd('a'); + tl.pushd('../b'); + tl.pushd('c'); + const trail = tl.pushd('+4'); + assert.equal(process.cwd(), trail[0]); + assert.deepEqual(trail, [ + DIRNAME, + path.resolve(TEMP_DIR_PATH, 'b', 'c'), + path.resolve(TEMP_DIR_PATH, 'b'), + path.resolve(TEMP_DIR_PATH, 'a'), + TEMP_DIR_PATH, + ]); + done(); + }); + + it('Using negative index', (done) => { + tl.pushd(TEMP_DIR_PATH); + tl.pushd('a'); + tl.pushd('../b'); + tl.pushd('c'); + const trail = tl.pushd('-0'); + assert.equal(process.cwd(), trail[0]); + assert.deepEqual(trail, [ + DIRNAME, + path.resolve(TEMP_DIR_PATH, 'b', 'c'), + path.resolve(TEMP_DIR_PATH, 'b'), + path.resolve(TEMP_DIR_PATH, 'a'), + TEMP_DIR_PATH, + ]); + done(); + }); + + it('Using -1 option', (done) => { + tl.pushd(TEMP_DIR_PATH); + tl.pushd('a'); + tl.pushd('../b'); + tl.pushd('c'); + const trail = tl.pushd('-1'); + assert.equal(process.cwd(), trail[0]); + assert.deepEqual(trail, [ + TEMP_DIR_PATH, + DIRNAME, + path.resolve(TEMP_DIR_PATH, 'b', 'c'), + path.resolve(TEMP_DIR_PATH, 'b'), + path.resolve(TEMP_DIR_PATH, 'a'), + ]); + done(); + }); + + it('Using -2 option', (done) => { + tl.pushd(TEMP_DIR_PATH); + tl.pushd('a'); + tl.pushd('../b'); + tl.pushd('c'); + const trail = tl.pushd('-2'); + assert.equal(process.cwd(), trail[0]); + assert.deepEqual(trail, [ + path.resolve(TEMP_DIR_PATH, 'a'), + TEMP_DIR_PATH, + DIRNAME, + path.resolve(TEMP_DIR_PATH, 'b', 'c'), + path.resolve(TEMP_DIR_PATH, 'b'), + ]); + done(); + }); + + it('Using -3 option', (done) => { + tl.pushd(TEMP_DIR_PATH); + tl.pushd('a'); + tl.pushd('../b'); + tl.pushd('c'); + const trail = tl.pushd('-3'); + assert.equal(process.cwd(), trail[0]); + assert.deepEqual(trail, [ + path.resolve(TEMP_DIR_PATH, 'b'), + path.resolve(TEMP_DIR_PATH, 'a'), + TEMP_DIR_PATH, + DIRNAME, + path.resolve(TEMP_DIR_PATH, 'b', 'c'), + ]); + done(); + }); + + it('Using -4 option', (done) => { + tl.pushd(TEMP_DIR_PATH); + tl.pushd('a'); + tl.pushd('../b'); + tl.pushd('c'); + const trail = tl.pushd('-4'); + assert.equal(process.cwd(), trail[0]); + assert.deepEqual(trail, [ + path.resolve(TEMP_DIR_PATH, 'b', 'c'), + path.resolve(TEMP_DIR_PATH, 'b'), + path.resolve(TEMP_DIR_PATH, 'a'), + TEMP_DIR_PATH, + DIRNAME, + ]); + done(); + }); + + it('Using invalid directory', (done) => { + const oldCwd = process.cwd(); + assert.throws(() => tl.pushd('does/not/exist'), { message: /^Failed pushd: no such file or directory:/ }); + assert.equal(process.cwd(), oldCwd); + done(); + }); + + it('Using without args swaps top two directories when stack length is 2', (done) => { + let trail = tl.pushd(TEMP_DIR_PATH); + assert.equal(trail.length, 2); + assert.equal(trail[0], TEMP_DIR_PATH); + assert.equal(trail[1], DIRNAME); + assert.equal(process.cwd(), trail[0]); + trail = tl.pushd(); + assert.equal(trail.length, 2); + assert.equal(trail[0], DIRNAME); + assert.equal(trail[1], TEMP_DIR_PATH); + assert.equal(process.cwd(), trail[0]); + done(); + }); + + it('Using without args swaps top two directories for larger stacks', (done) => { + tl.pushd(TEMP_DIR_PATH); + tl.pushd(); + const trail = tl.pushd(path.join(TEMP_DIR_PATH, 'a')); + assert.equal(trail.length, 3); + assert.equal(trail[0], path.join(TEMP_DIR_PATH, 'a')); + assert.equal(trail[1], DIRNAME); + assert.equal(trail[2], TEMP_DIR_PATH); + assert.equal(process.cwd(), trail[0]); + done(); + }); + + it('Using without arguments invalid when stack is empty', (done) => { + assert.throws(() => tl.pushd(), { message: 'Failed pushd: no other directory' }); + done(); + }); +}); \ No newline at end of file diff --git a/node/test/rm.ts b/node/test/rm.ts new file mode 100644 index 000000000..997999e4a --- /dev/null +++ b/node/test/rm.ts @@ -0,0 +1,117 @@ +import os = require('node:os'); +import fs = require('node:fs'); +import path = require('node:path'); +import assert = require('node:assert'); + +import * as tl from '../_build/task'; + +const DIRNAME = __dirname; + +import * as testutil from './testutil'; + +describe('rm cases', () => { + const TEMP_DIR = fs.mkdtempSync(DIRNAME + '/'); + const TEMP_NESTED_DIR_LEVEL_1 = path.join(TEMP_DIR, 'a'); + const TEMP_NESTED_DIR_FULL_TREE = path.join(TEMP_NESTED_DIR_LEVEL_1, 'b', 'c'); + const TEMP_FILE_1 = path.join(TEMP_DIR, 'file1'); + + before((done) => { + try { + testutil.initialize(); + } catch (error) { + assert.fail(`Failed to load tl lib: ${error.message}`); + } + + fs.writeFileSync(TEMP_FILE_1, 'test'); + done(); + }); + + after((done) => { + fs.rmSync(TEMP_DIR, { recursive: true }); + done(); + }); + + it('Invalid arguments', (done) => { + // @ts-ignore + assert.throws(() => tl.rmRF(), { message: 'Failed rmRF: The "path" argument must be of type string or an instance of Buffer or URL. Received undefined' }); + assert.doesNotThrow(() => tl.rmRF('somefolderpaththatdoesnotexist')); + done(); + }); + + it('Remove TEMP_FILE_1', (done) => { + assert.ok(fs.existsSync(TEMP_FILE_1)); + assert.doesNotThrow(() => tl.rmRF(TEMP_FILE_1)); + assert.ok(!fs.existsSync(TEMP_FILE_1)); + done(); + }); + + it('Remove subdirectory recursive at TEMP_NESTED_DIR_LEVEL_1', (done) => { + tl.mkdirP(TEMP_NESTED_DIR_FULL_TREE); + assert.ok(fs.existsSync(TEMP_NESTED_DIR_FULL_TREE)); + assert.doesNotThrow(() => tl.rmRF(TEMP_NESTED_DIR_LEVEL_1)); + assert.ok(!fs.existsSync(TEMP_NESTED_DIR_LEVEL_1)); + done(); + }); + + it('Remove subdirectory recursive at TEMP_NESTED_DIR_LEVEL_1 with absolute path', (done) => { + tl.mkdirP(TEMP_NESTED_DIR_FULL_TREE); + assert.ok(fs.existsSync(TEMP_NESTED_DIR_FULL_TREE)); + assert.doesNotThrow(() => tl.rmRF(path.resolve(`./${TEMP_NESTED_DIR_LEVEL_1}`))); + assert.ok(!fs.existsSync(`./${TEMP_NESTED_DIR_LEVEL_1}`)); + done(); + }); + + it('Remove a read-only file inside temp readonly dir', (done) => { + const READONLY_DIR = path.join(TEMP_DIR, 'readonly'); + tl.mkdirP(READONLY_DIR); + fs.writeFileSync(path.join(READONLY_DIR, 'file'), 'test'); + fs.chmodSync(path.join(READONLY_DIR, 'file'), '0444'); + assert.doesNotThrow(() => tl.rmRF(path.join(READONLY_DIR, 'file'))); + assert.ok(!fs.existsSync(path.join(READONLY_DIR, 'file'))); + assert.doesNotThrow(() => tl.rmRF(READONLY_DIR)); + done(); + }); + + it('Remove of a tree containing read-only files (forced)', (done) => { + tl.mkdirP(path.join(TEMP_DIR, 'tree')); + fs.writeFileSync(path.join(TEMP_DIR, 'tree', 'file1'), 'test'); + fs.writeFileSync(path.join(TEMP_DIR, 'tree', 'file2'), 'test'); + fs.chmodSync(path.join(TEMP_DIR, 'tree', 'file1'), '0444'); + assert.doesNotThrow(() => tl.rmRF(path.join(TEMP_DIR, 'tree'))); + assert.ok(!fs.existsSync(path.join(TEMP_DIR, 'tree'))); + done(); + }); + + it('removal of a sub-tree containing read-only and hidden files - without glob', (done) => { + const TEMP_TREE4_DIR = path.join(TEMP_DIR, 'tree4'); + tl.mkdirP(path.join(TEMP_TREE4_DIR, 'subtree')); + tl.mkdirP(path.join(TEMP_TREE4_DIR, '.hidden')); + fs.writeFileSync(path.join(TEMP_TREE4_DIR, 'subtree', 'file'), 'test'); + fs.writeFileSync(path.join(TEMP_TREE4_DIR, '.hidden', 'file'), 'test'); + fs.writeFileSync(path.join(TEMP_TREE4_DIR, 'file'), 'test'); + fs.chmodSync(path.join(TEMP_TREE4_DIR, 'file'), '0444'); + fs.chmodSync(path.join(TEMP_TREE4_DIR, 'subtree', 'file'), '0444'); + fs.chmodSync(path.join(TEMP_TREE4_DIR, '.hidden', 'file'), '0444'); + assert.doesNotThrow(() => tl.rmRF(path.join(TEMP_DIR, 'tree4'))); + assert.ok(!fs.existsSync(path.join(TEMP_DIR, 'tree4'))); + done(); + }); + + it('Removing symbolic link to a directory', (done) => { + fs.mkdirSync(path.join(TEMP_DIR, 'rm', 'a_dir'), { recursive: true }); + fs.symlinkSync(path.join(TEMP_DIR, 'rm', 'a_dir'), path.join(TEMP_DIR, 'rm', 'link_to_a_dir'), 'dir'); + assert.doesNotThrow(() => tl.rmRF(path.join(TEMP_DIR, 'rm', 'link_to_a_dir'))); + assert.ok(!fs.existsSync(path.join(TEMP_DIR, 'rm', 'link_to_a_dir'))); + assert.ok(fs.existsSync(path.join(TEMP_DIR, 'rm', 'a_dir'))); + fs.rmSync(path.join(TEMP_DIR, 'rm'), { recursive: true }); + done(); + }); + + it('Remove path with relative non-normalized structure', (done) => { + tl.mkdirP(TEMP_NESTED_DIR_FULL_TREE); + assert.ok(fs.existsSync(TEMP_NESTED_DIR_FULL_TREE)); + assert.doesNotThrow(() => tl.rmRF(path.join(TEMP_DIR, 'a', '..', './a'))); + assert.ok(!fs.existsSync(path.join(TEMP_DIR, 'a'))); + done(); + }); +}); \ No newline at end of file diff --git a/node/test/tsconfig.json b/node/test/tsconfig.json index cd084a92f..31131f42e 100644 --- a/node/test/tsconfig.json +++ b/node/test/tsconfig.json @@ -7,6 +7,13 @@ "moduleResolution": "node" }, "files": [ + "mv.ts", + "ls.ts", + "cp.ts", + "rm.ts", + "cd.ts", + "popd.ts", + "pushd.ts", "dirtests.ts", "inputtests.ts", "internalhelpertests.ts", diff --git a/powershell/definitions/shelljs.d.ts b/powershell/definitions/shelljs.d.ts deleted file mode 100644 index 4a40fe541..000000000 --- a/powershell/definitions/shelljs.d.ts +++ /dev/null @@ -1,577 +0,0 @@ -// Type definitions for ShellJS v0.3.0 -// Project: http://shelljs.org -// Definitions by: Niklas Mollenhauer -// Definitions: https://github.com/borisyankov/DefinitelyTyped -// Ting update to match shelljs v0.5.3 12/7/2015 - -declare module "shelljs" -{ - import child = require("child_process"); - - /** - * Changes to directory dir for the duration of the script - * @param {string} dir Directory to change in. - */ - export function cd(dir: string): void; - - /** - * Returns the current directory. - * @return {string} The current directory. - */ - export function pwd(): string; - - /** - * Returns array of files in the given path, or in current directory if no path provided. - * @param {string[]} ...paths Paths to search. - * @return {string[]} An array of files in the given path(s). - */ - export function ls(...paths: string[]): string[]; - - /** - * Returns array of files in the given path, or in current directory if no path provided. - * @param {string} options Available options: -R (recursive), -A (all files, include files beginning with ., except for . and ..) - * @param {string[]} ...paths Paths to search. - * @return {string[]} An array of files in the given path(s). - */ - export function ls(options: string, ...paths: string[]): string[]; - - /** - * Returns array of files in the given path, or in current directory if no path provided. - * @param {string[]} paths Paths to search. - * @return {string[]} An array of files in the given path(s). - */ - export function ls(paths: string[]): string[]; - - /** - * Returns array of files in the given path, or in current directory if no path provided. - * @param {string} options Available options: -R (recursive), -A (all files, include files beginning with ., except for . and ..) - * @param {string[]} paths Paths to search. - * @return {string[]} An array of files in the given path(s). - */ - export function ls(options: string, paths: string[]): string[]; - - /** - * Returns array of all files (however deep) in the given paths. - * @param {string[]} ...path The path(s) to search. - * @return {string[]} An array of all files (however deep) in the given path(s). - */ - export function find(...path: string[]): string[]; - - /** - * Returns array of all files (however deep) in the given paths. - * @param {string[]} path The path(s) to search. - * @return {string[]} An array of all files (however deep) in the given path(s). - */ - export function find(path: string[]): string[]; - - /** - * Copies files. The wildcard * is accepted. - * @param {string} source The source. - * @param {string} dest The destination. - */ - export function cp(source: string, dest: string): void; - - /** - * Copies files. The wildcard * is accepted. - * @param {string[]} source The source. - * @param {string} dest The destination. - */ - export function cp(source: string[], dest: string): void; - - /** - * Copies files. The wildcard * is accepted. - * @param {string} options Available options: -f (force), -r, -R (recursive) - * @param {strin]} source The source. - * @param {string} dest The destination. - */ - export function cp(options: string, source: string, dest: string): void; - - /** - * Copies files. The wildcard * is accepted. - * @param {string} options Available options: -f (force), -r, -R (recursive) - * @param {string[]} source The source. - * @param {string} dest The destination. - */ - export function cp(options: string, source: string[], dest: string): void; - - /** - * Removes files. The wildcard * is accepted. - * @param {string[]} ...files Files to remove. - */ - export function rm(...files: string[]): void; - - /** - * Removes files. The wildcard * is accepted. - * @param {string[]} files Files to remove. - */ - export function rm(files: string[]): void; - - /** - * Removes files. The wildcard * is accepted. - * @param {string} options Available options: -f (force), -r, -R (recursive) - * @param {string[]} ...files Files to remove. - */ - export function rm(options: string, ...files: string[]): void; - - /** - * Removes files. The wildcard * is accepted. - * @param {string} options Available options: -f (force), -r, -R (recursive) - * @param {string[]} ...files Files to remove. - */ - export function rm(options: string, files: string[]): void; - - /** - * Moves files. The wildcard * is accepted. - * @param {string} source The source. - * @param {string} dest The destination. - */ - export function mv(source: string, dest: string): void; - - /** - * Moves files. The wildcard * is accepted. - * @param {string} options Available options: -f (force) - * @param {string} source The source. - * @param {string} dest The destination. - */ - export function mv(options: string, source: string, dest: string): void; - - - /** - * Moves files. The wildcard * is accepted. - * @param {string[]} source The source. - * @param {string} dest The destination. - */ - export function mv(source: string[], dest: string): void; - - /** - * Moves files. The wildcard * is accepted. - * @param {string} options Available options: -f (force) - * @param {string[]} source The source. - * @param {string} dest The destination. - */ - export function mv(options: string, source: string[], dest: string): void; - - /** - * Creates directories. - * @param {string[]} ...dir Directories to create. - */ - export function mkdir(...dir: string[]): void; - - /** - * Creates directories. - * @param {string[]} dir Directories to create. - */ - export function mkdir(dir: string[]): void; - - /** - * Creates directories. - * @param {string} options Available options: -p (full paths, will create intermediate dirs if necessary) - * @param {string[]} ...dir The directories to create. - */ - export function mkdir(options: string, ...dir: string[]): void; - - /** - * Creates directories. - * @param {string} options Available options: -p (full paths, will create intermediate dirs if necessary) - * @param {string[]} dir The directories to create. - */ - export function mkdir(options: string, dir: string[]): void; - - /** - * Evaluates expression using the available primaries and returns corresponding value. - * @param {string} option '-b': true if path is a block device; '-c': true if path is a character device; '-d': true if path is a directory; '-e': true if path exists; '-f': true if path is a regular file; '-L': true if path is a symboilc link; '-p': true if path is a pipe (FIFO); '-S': true if path is a socket - * @param {string} path The path. - * @return {boolean} See option parameter. - */ - export function test(option: string, path: string): boolean; - - /** - * Returns a string containing the given file, or a concatenated string containing the files if more than one file is given (a new line character is introduced between each file). Wildcard * accepted. - * @param {string[]} ...files Files to use. - * @return {string} A string containing the given file, or a concatenated string containing the files if more than one file is given (a new line character is introduced between each file). - */ - export function cat(...files: string[]): string; - - /** - * Returns a string containing the given file, or a concatenated string containing the files if more than one file is given (a new line character is introduced between each file). Wildcard * accepted. - * @param {string[]} files Files to use. - * @return {string} A string containing the given file, or a concatenated string containing the files if more than one file is given (a new line character is introduced between each file). - */ - export function cat(files: string[]): string; - - - // Does not work yet. - export interface String - { - /** - * Analogous to the redirection operator > in Unix, but works with JavaScript strings (such as those returned by cat, grep, etc). Like Unix redirections, to() will overwrite any existing file! - * @param {string} file The file to use. - */ - to(file: string): void; - - /** - * Analogous to the redirect-and-append operator >> in Unix, but works with JavaScript strings (such as those returned by cat, grep, etc). - * @param {string} file The file to append to. - */ - toEnd(file: string): void; - } - - /** - * Reads an input string from file and performs a JavaScript replace() on the input using the given search regex and replacement string or function. Returns the new string after replacement. - * @param {RegExp} searchRegex The regular expression to use for search. - * @param {string} replacement The replacement. - * @param {string} file The file to process. - * @return {string} The new string after replacement. - */ - export function sed(searchRegex: RegExp, replacement: string, file: string): string; - - /** - * Reads an input string from file and performs a JavaScript replace() on the input using the given search regex and replacement string or function. Returns the new string after replacement. - * @param {string} searchRegex The regular expression to use for search. - * @param {string} replacement The replacement. - * @param {string} file The file to process. - * @return {string} The new string after replacement. - */ - export function sed(searchRegex: string, replacement: string, file: string): string; - - /** - * Reads an input string from file and performs a JavaScript replace() on the input using the given search regex and replacement string or function. Returns the new string after replacement. - * @param {string} options Available options: -i (Replace contents of 'file' in-place. Note that no backups will be created!) - * @param {RegExp} searchRegex The regular expression to use for search. - * @param {string} replacement The replacement. - * @param {string} file The file to process. - * @return {string} The new string after replacement. - */ - export function sed(options: string, searchRegex: RegExp, replacement: string, file: string): string; - - /** - * Reads an input string from file and performs a JavaScript replace() on the input using the given search regex and replacement string or function. Returns the new string after replacement. - * @param {string} options Available options: -i (Replace contents of 'file' in-place. Note that no backups will be created!) - * @param {string} searchRegex The regular expression to use for search. - * @param {string} replacement The replacement. - * @param {string} file The file to process. - * @return {string} The new string after replacement. - */ - export function sed(options: string, searchRegex: string, replacement: string, file: string): string; - - /** - * Reads input string from given files and returns a string containing all lines of the file that match the given regex_filter. Wildcard * accepted. - * @param {RegExp} regex_filter The regular expression to use. - * @param {string[]} ...files The files to process. - * @return {string} Returns a string containing all lines of the file that match the given regex_filter. - */ - export function grep(regex_filter: RegExp, ...files: string[]): string; - - /** - * Reads input string from given files and returns a string containing all lines of the file that match the given regex_filter. Wildcard * accepted. - * @param {RegExp} regex_filter The regular expression to use. - * @param {string[]} ...files The files to process. - * @return {string} Returns a string containing all lines of the file that match the given regex_filter. - */ - export function grep(regex_filter: RegExp, files: string[]): string; - - /** - * Reads input string from given files and returns a string containing all lines of the file that match the given regex_filter. Wildcard * accepted. - * @param {string} options Available options: -v (Inverse the sense of the regex and print the lines not matching the criteria.) - * @param {string} regex_filter The regular expression to use. - * @param {string[]} ...files The files to process. - * @return {string} Returns a string containing all lines of the file that match the given regex_filter. - */ - export function grep(options: string, regex_filter: string, ...files: string[]): string; - - /** - * Reads input string from given files and returns a string containing all lines of the file that match the given regex_filter. Wildcard * accepted. - * @param {string} options Available options: -v (Inverse the sense of the regex and print the lines not matching the criteria.) - * @param {string} regex_filter The regular expression to use. - * @param {string[]} files The files to process. - * @return {string} Returns a string containing all lines of the file that match the given regex_filter. - */ - export function grep(options: string, regex_filter: string, files: string[]): string; - - /** - * Searches for command in the system's PATH. On Windows looks for .exe, .cmd, and .bat extensions. - * @param {string} command The command to search for. - * @return {string} Returns string containing the absolute path to the command. - */ - export function which(command: string): string; - - /** - * Prints string to stdout, and returns string with additional utility methods like .to(). - * @param {string[]} ...text The text to print. - * @return {string} Returns the string that was passed as argument. - */ - export function echo(...text: string[]): string; - - /** - * Save the current directory on the top of the directory stack and then cd to dir. With no arguments, pushd exchanges the top two directories. Returns an array of paths in the stack. - * @param {"+N"} dir Brings the Nth directory (counting from the left of the list printed by dirs, starting with zero) to the top of the list by rotating the stack. - * @return {string[]} Returns an array of paths in the stack. - */ - export function pushd(dir: "+N"): string[]; - - /** - * Save the current directory on the top of the directory stack and then cd to dir. With no arguments, pushd exchanges the top two directories. Returns an array of paths in the stack. - * @param {"-N"} dir Brings the Nth directory (counting from the right of the list printed by dirs, starting with zero) to the top of the list by rotating the stack. - * @return {string[]} Returns an array of paths in the stack. - */ - export function pushd(dir: "-N"): string[]; - - /** - * Save the current directory on the top of the directory stack and then cd to dir. With no arguments, pushd exchanges the top two directories. Returns an array of paths in the stack. - * @param {string} dir Makes the current working directory be the top of the stack, and then executes the equivalent of cd dir. - * @return {string[]} Returns an array of paths in the stack. - */ - export function pushd(dir: string): string[]; - - /** - * Save the current directory on the top of the directory stack and then cd to dir. With no arguments, pushd exchanges the top two directories. Returns an array of paths in the stack. - * @param {string} options Available options: -n (Suppresses the normal change of directory when adding directories to the stack, so that only the stack is manipulated) - * @param {"+N"} dir Brings the Nth directory (counting from the left of the list printed by dirs, starting with zero) to the top of the list by rotating the stack. - * @return {string[]} Returns an array of paths in the stack. - */ - export function pushd(options: string, dir: "+N"): string[]; - - /** - * Save the current directory on the top of the directory stack and then cd to dir. With no arguments, pushd exchanges the top two directories. Returns an array of paths in the stack. - * @param {string} options Available options: -n (Suppresses the normal change of directory when adding directories to the stack, so that only the stack is manipulated) - * @param {"-N"} dir Brings the Nth directory (counting from the right of the list printed by dirs, starting with zero) to the top of the list by rotating the stack. - * @return {string[]} Returns an array of paths in the stack. - */ - export function pushd(options: string, dir: "-N"): string[]; - - /** - * Save the current directory on the top of the directory stack and then cd to dir. With no arguments, pushd exchanges the top two directories. Returns an array of paths in the stack. - * @param {string} options Available options: -n (Suppresses the normal change of directory when adding directories to the stack, so that only the stack is manipulated) - * @param {string} dir Makes the current working directory be the top of the stack, and then executes the equivalent of cd dir. - * @return {string[]} Returns an array of paths in the stack. - */ - export function pushd(options: string, dir: string): string[]; - - /** - * When no arguments are given, popd removes the top directory from the stack and performs a cd to the new top directory. The elements are numbered from 0 starting at the first directory listed with dirs; i.e., popd is equivalent to popd +0. Returns an array of paths in the stack. - * @return {string[]} Returns an array of paths in the stack. - */ - export function popd(): string[]; - - /** - * When no arguments are given, popd removes the top directory from the stack and performs a cd to the new top directory. The elements are numbered from 0 starting at the first directory listed with dirs; i.e., popd is equivalent to popd +0. Returns an array of paths in the stack. - * @param {"+N"} index Removes the Nth directory (counting from the left of the list printed by dirs), starting with zero. - * @return {string[]} Returns an array of paths in the stack. - */ - export function popd(index: "+N"): string[]; - - /** - * When no arguments are given, popd removes the top directory from the stack and performs a cd to the new top directory. The elements are numbered from 0 starting at the first directory listed with dirs; i.e., popd is equivalent to popd +0. Returns an array of paths in the stack. - * @param {"-N"} index Removes the Nth directory (counting from the right of the list printed by dirs), starting with zero. - * @return {string[]} Returns an array of paths in the stack. - */ - export function popd(index: "-N"): string[]; - - /** - * When no arguments are given, popd removes the top directory from the stack and performs a cd to the new top directory. The elements are numbered from 0 starting at the first directory listed with dirs; i.e., popd is equivalent to popd +0. Returns an array of paths in the stack. - * @param {string} index You can only use -N and +N. - * @return {string[]} Returns an array of paths in the stack. - */ - export function popd(index: string): string[]; - - /** - * When no arguments are given, popd removes the top directory from the stack and performs a cd to the new top directory. The elements are numbered from 0 starting at the first directory listed with dirs; i.e., popd is equivalent to popd +0. Returns an array of paths in the stack. - * @param {string} options Available options: -n (Suppresses the normal change of directory when removing directories from the stack, so that only the stack is manipulated) - * @param {"+N"} index Removes the Nth directory (counting from the left of the list printed by dirs), starting with zero. - * @return {string[]} Returns an array of paths in the stack. - */ - export function popd(options: string, index: "+N"): string[]; - - /** - * When no arguments are given, popd removes the top directory from the stack and performs a cd to the new top directory. The elements are numbered from 0 starting at the first directory listed with dirs; i.e., popd is equivalent to popd +0. Returns an array of paths in the stack. - * @param {string} options Available options: -n (Suppresses the normal change of directory when removing directories from the stack, so that only the stack is manipulated) - * @param {"-N"} index Removes the Nth directory (counting from the right of the list printed by dirs), starting with zero. - * @return {string[]} Returns an array of paths in the stack. - */ - export function popd(options: string, index: "-N"): string[]; - - /** - * When no arguments are given, popd removes the top directory from the stack and performs a cd to the new top directory. The elements are numbered from 0 starting at the first directory listed with dirs; i.e., popd is equivalent to popd +0. Returns an array of paths in the stack. - * @param {string} options Available options: -n (Suppresses the normal change of directory when removing directories from the stack, so that only the stack is manipulated) - * @param {string} index You can only use -N and +N. - * @return {string[]} Returns an array of paths in the stack. - */ - export function popd(options: string, index: string): string[]; - - /** - * Display the list of currently remembered directories. Returns an array of paths in the stack. - * @return {string[]} Returns an array of paths in the stack. - */ - export function dirs(): string[]; - - /** - * Clears the directory stack by deleting all of the elements. - * @param {string} options Available options: -c (Clears the directory stack by deleting all of the elements). - * @return {string[]} Returns an array of paths in the stack, or a single path if +N or -N was specified. - */ - export function dirs(options: string): string[]; - - /** - * Display the list of currently remembered directories. Returns an array of paths in the stack, or a single path if +N or -N was specified. - * @param {string} Displays the Nth directory (counting from the left of the list printed by dirs when invoked without options), starting with zero. - * @return {any} Returns an array of paths in the stack, or a single path if +N or -N was specified. - */ - export function dirs(index: "+N"): string; - - /** - * Display the list of currently remembered directories. Returns an array of paths in the stack, or a single path if +N or -N was specified. - * @param {string} Displays the Nth directory (counting from the right of the list if -N printed by dirs when invoked without options), starting with zero. - * @return {any} Returns an array of paths in the stack, or a single path if +N or -N was specified. - */ - export function dirs(index: "-N"): string; - - /** - * Display the list of currently remembered directories. Returns an array of paths in the stack, or a single path if +N or -N was specified. - * @param {string} Displays the Nth directory (counting from the left of the list if +N or counting from the right of the list if -N printed by dirs when invoked without options), starting with zero. - * @return {any} Returns an array of paths in the stack, or a single path if +N or -N was specified. - */ - export function dirs(index: string): string; - - /** - * Links source to dest. Use -f to force the link, should dest already exist. - * @param {string} source The source. - * @param {string} dest The destination. - */ - export function ln(source: string, dest: string): void; - - /** - * Links source to dest. Use -f to force the link, should dest already exist. - * @param {string} options Available options: s (symlink), f (force) - * @param {string} source The source. - * @param {string} dest The destination. - */ - export function ln(options: string, source: string, dest: string): void; - - /** - * Exits the current process with the given exit code. - * @param {number} code The exit code. - */ - export function exit(code: number): void; - - /** - * Object containing environment variables (both getter and setter). Shortcut to process.env. - */ - export var env: { [key: string]: string }; - - /** - * Executes the given command synchronously. - * @param {string} command The command to execute. - * @return {ExecOutputReturnValue} Returns an object containing the return code and output as string. - */ - export function exec(command: string): ExecOutputReturnValue; - /** - * Executes the given command synchronously. - * @param {string} command The command to execute. - * @param {ExecOptions} options Silence and synchronous options. - * @return {ExecOutputReturnValue | child.ChildProcess} Returns an object containing the return code and output as string, or if {async:true} was passed, a ChildProcess. - */ - export function exec(command: string, options: ExecOptions): ExecOutputReturnValue | child.ChildProcess; - /** - * Executes the given command synchronously. - * @param {string} command The command to execute. - * @param {ExecOptions} options Silence and synchronous options. - * @param {ExecCallback} callback Receives code and output asynchronously. - */ - export function exec(command: string, options: ExecOptions, callback: ExecCallback): child.ChildProcess; - /** - * Executes the given command synchronously. - * @param {string} command The command to execute. - * @param {ExecCallback} callback Receives code and output asynchronously. - */ - export function exec(command: string, callback: ExecCallback): child.ChildProcess; - - export interface ExecCallback { - (code: number, output: string): any; - } - - export interface ExecOptions { - silent?: boolean; - async?: boolean; - } - - export interface ExecOutputReturnValue - { - code: number; - output: string; - } - - /** - * Alters the permissions of a file or directory by either specifying the absolute permissions in octal form or expressing the changes in symbols. This command tries to mimic the POSIX behavior as much as possible. Notable exceptions: - * - In symbolic modes, 'a-r' and '-r' are identical. No consideration is given to the umask. - * - There is no "quiet" option since default behavior is to run silent. - * @param {number} octalMode The access mode. Octal. - * @param {string} file The file to use. - */ - export function chmod(octalMode: number, file: string): void; - - /** - * Alters the permissions of a file or directory by either specifying the absolute permissions in octal form or expressing the changes in symbols. This command tries to mimic the POSIX behavior as much as possible. Notable exceptions: - * - In symbolic modes, 'a-r' and '-r' are identical. No consideration is given to the umask. - * - There is no "quiet" option since default behavior is to run silent. - * @param {string} mode The access mode. Can be an octal string or a symbolic mode string. - * @param {string} file The file to use. - */ - export function chmod(mode: string, file: string): void; - - /** - * Alters the permissions of a file or directory by either specifying the absolute permissions in octal form or expressing the changes in symbols. This command tries to mimic the POSIX behavior as much as possible. Notable exceptions: - * - In symbolic modes, 'a-r' and '-r' are identical. No consideration is given to the umask. - * - There is no "quiet" option since default behavior is to run silent. - * @param {string} options Available options: -v (output a diagnostic for every file processed), -c (like verbose but report only when a change is made), -R (change files and directories recursively. - * @param {number} octalMode The access mode. Octal. - * @param {string} file The file to use. - */ - export function chmod(option: string, octalMode: number, file: string): void; - - /** - * Alters the permissions of a file or directory by either specifying the absolute permissions in octal form or expressing the changes in symbols. This command tries to mimic the POSIX behavior as much as possible. Notable exceptions: - * - In symbolic modes, 'a-r' and '-r' are identical. No consideration is given to the umask. - * - There is no "quiet" option since default behavior is to run silent. - * @param {string} options Available options: -v (output a diagnostic for every file processed), -c (like verbose but report only when a change is made), -R (change files and directories recursively. - * @param {string} mode The access mode. Can be an octal string or a symbolic mode string. - * @param {string} file The file to use. - */ - export function chmod(option: string, mode: string, file: string): void; - - // Non-Unix commands - - /** - * Searches and returns string containing a writeable, platform-dependent temporary directory. Follows Python's tempfile algorithm. - * @return {string} The temp file path. - */ - export function tempdir(): string; - - /** - * Tests if error occurred in the last command. - * @return {string} Returns null if no error occurred, otherwise returns string explaining the error - */ - export function error(): string; - - // Configuration - - interface ShellConfig - { - /** - * Suppresses all command output if true, except for echo() calls. Default is false. - * @type {boolean} - */ - silent: boolean; - - /** - * If true the script will die on errors. Default is false. - * @type {boolean} - */ - fatal: boolean; - } - - /** - * The shelljs configuration. - * @type {ShellConfig} - */ - export var config: ShellConfig; -} From f7ed873452f938008d82746e49e5da047deb7950 Mon Sep 17 00:00:00 2001 From: Ivan Duplenskikh <115665590+ivanduplenskikh@users.noreply.github.com> Date: Wed, 25 Dec 2024 13:26:15 +0100 Subject: [PATCH 02/11] Fix cd, ls methods and unit tests for these methods --- node/task.ts | 82 +- node/test/cd.ts | 47 +- node/test/cp.ts | 24 +- node/test/inputtests.ts | 2474 +++++++++++++++++++-------------------- node/test/ls.ts | 36 + node/test/mv.ts | 78 +- node/test/popd.ts | 25 +- node/test/pushd.ts | 44 +- node/test/rm.ts | 26 +- 9 files changed, 1514 insertions(+), 1322 deletions(-) diff --git a/node/task.ts b/node/task.ts index d41f0bd7d..b57b5a641 100644 --- a/node/task.ts +++ b/node/task.ts @@ -980,17 +980,15 @@ export const which = im._which; * @return {string[]} An array of files in the given path(s). */ export function ls(optionsOrPaths?: string | string[], ...paths: string[]): string[] { - try { - let isRecursive = false; - let includeHidden = false; - let handleAsOptions = false; - - if (typeof optionsOrPaths == 'string' && optionsOrPaths.includes('-')) { - isRecursive = optionsOrPaths.includes('R'); - includeHidden = optionsOrPaths.includes('A'); - handleAsOptions = isRecursive || includeHidden; - } - + let isRecursive = false; + let includeHidden = false; + let handleAsOptions = false; + + if (typeof optionsOrPaths == 'string' && optionsOrPaths.startsWith('-')) { + optionsOrPaths = optionsOrPaths.toLowerCase(); + isRecursive = optionsOrPaths.includes('r'); + includeHidden = optionsOrPaths.includes('a'); + } else { if (paths === undefined || paths.length === 0) { if (Array.isArray(optionsOrPaths)) { paths = optionsOrPaths as string[]; @@ -1000,13 +998,15 @@ export function ls(optionsOrPaths?: string | string[], ...paths: string[]): stri paths = []; } } + } - if (paths.length === 0) { - paths.push(path.resolve('.')); - } + if (paths.length === 0) { + paths.push(path.resolve('.')); + } - const preparedPaths: string[] = []; + const preparedPaths: string[] = []; + try { while (paths.length > 0) { const pathEntry = resolve(paths.shift()); @@ -1085,18 +1085,24 @@ function retryer(func: Function, retryCount: number = 0, continueOnError: boolea */ export function cp(sourceOrOptions: string, destinationOrSource: string, optionsOrDestination?: string, continueOnError?: boolean, retryCount: number = 0): void { retryer(() => { - const isOptions = sourceOrOptions.startsWith('-') && sourceOrOptions.length > 1; let recursive = false; let force = true; let source = sourceOrOptions; let destination = destinationOrSource; + let options = ''; - if (isOptions) { - sourceOrOptions = sourceOrOptions.toLowerCase(); - recursive = sourceOrOptions.includes('r'); - force = !sourceOrOptions.includes('n'); + if (sourceOrOptions.startsWith('-')) { + options = sourceOrOptions.toLowerCase(); + recursive = options.includes('r'); + force = !options.includes('n'); source = destinationOrSource; destination = optionsOrDestination!; + } else if (optionsOrDestination && optionsOrDestination.startsWith('-')) { + options = optionsOrDestination.toLowerCase(); + recursive = options.includes('r'); + force = !options.includes('n'); + source = sourceOrOptions; + destination = destinationOrSource; } if (!fs.existsSync(destination) && !force) { @@ -1142,23 +1148,43 @@ export function cp(sourceOrOptions: string, destinationOrSource: string, options * @param continueOnError optional. whether to continue on error */ export function mv(source: string, dest: string, options?: string, continueOnError?: boolean): void { - try { - const isForce = !options?.toLowerCase()?.includes('-n') && options?.toLowerCase()?.includes('-f'); - const destExists = fs.existsSync(dest); + let force = false; - if (!fs.existsSync(source)) { - throw new Error(loc('LIB_PathNotFound', source)); + if (options && options.startsWith('-')) { + options = options.toLowerCase(); + force = options.includes('f') && !options.includes('n'); + } + + const sourceExists = fs.existsSync(source); + const destExists = fs.existsSync(dest); + let sources: string[] = []; + + try { + if (!sourceExists) { + if (source.includes('*')) { + sources.push(...findMatch(path.resolve(path.dirname(source)), [path.basename(source)])); + } else { + throw new Error(loc('LIB_PathNotFound', 'mv', source)); + } + } else { + sources.push(source); } - if (destExists && !isForce) { - throw new Error(loc('LIB_PathNotFound', dest)); + if (destExists && !force) { + throw new Error(`File already exists at ${dest}`); } - fs.renameSync(source, dest); + for (const source of sources) { + fs.renameSync(source, dest); + } } catch (error) { debug('mv failed'); var errMsg = loc('LIB_OperationFailed', 'mv', error); debug(errMsg); + + if (!continueOnError) { + throw new Error(errMsg); + } } } diff --git a/node/test/cd.ts b/node/test/cd.ts index 3f5e54e36..234328940 100644 --- a/node/test/cd.ts +++ b/node/test/cd.ts @@ -16,8 +16,10 @@ describe('cd cases', () => { const TEMP_FILE_1 = path.resolve(TEMP_DIR_1, 'file1'); before((done) => { - fs.mkdirSync(TEMP_DIR_1); - fs.mkdirSync(TEMP_DIR_2); + process.chdir(DIRNAME); + TEMP_DIR_PATH = fs.mkdtempSync('temp_test_'); + tl.mkdirP(TEMP_DIR_1); + tl.mkdirP(TEMP_DIR_2); fs.writeFileSync(TEMP_FILE_1, 'file1'); try { @@ -29,75 +31,71 @@ describe('cd cases', () => { done(); }); - beforeEach((done) => { - TEMP_DIR_PATH = fs.mkdtempSync('temp_test_'); - process.chdir(DIRNAME); - - if (!fs.existsSync(TEMP_DIR_PATH)) { - fs.mkdirSync(TEMP_DIR_PATH); - } - - done(); - }); - - afterEach((done) => { - process.chdir(DIRNAME); - if (fs.existsSync(TEMP_DIR_PATH)) { - fs.rmdirSync(TEMP_DIR_PATH, { recursive: true }); - } - - done(); - }); - after((done) => { - fs.rmdirSync(TEMP_DIR_1, { recursive: true }); - fs.rmdirSync(TEMP_DIR_2, { recursive: true }); + tl.cd(DIRNAME); + tl.rmRF(TEMP_DIR_1); + tl.rmRF(TEMP_DIR_2); + tl.rmRF(TEMP_DIR_PATH); + done(); }); it('Check change directory for a folder that does not exist', (done) => { assert.ok(!fs.existsSync('/thisfolderdoesnotexist')); assert.throws(() => tl.cd('/thisfolderdoesnotexist'), { message: "Failed cd: no such file or directory: /thisfolderdoesnotexist" }); + done(); }); it('Change directory to a file path', (done) => { const filePath = path.resolve(DIRNAME, 'scripts', 'match-input-exe.cs'); + assert.ok(fs.existsSync(filePath)); assert.throws(() => tl.cd(filePath), { message: `Failed cd: not a directory: ${filePath}` }); + done(); }); it('There is no previous directory', (done) => { assert.throws(() => tl.cd('-'), { message: 'Failed cd: could not find previous directory' }); + done(); }); it('Change direcotry to a relative path', (done) => { tl.cd(TEMP_DIR_PATH); + assert.equal(path.basename(TEMP_DIR_PATH), TEMP_DIR_PATH); + done(); }); it('Change directory to an absolute path', (done) => { tl.cd('/'); + assert.equal(process.cwd(), path.resolve('/')); + done(); }); it('Change directory to a previous directory -', (done) => { tl.cd('/'); tl.cd('-'); + assert.ok(process.cwd(), path.resolve(DIRNAME)); + done(); }); it('Change directory with cp', (done) => { assert.ok(!fs.existsSync(path.resolve(TEMP_DIR_PATH, "file1"))); + tl.cd(TEMP_DIR_1); tl.cp(TEMP_FILE_1, path.resolve("..", TEMP_DIR_PATH)); tl.cd(path.resolve("..", TEMP_DIR_PATH)); + assert.ok(fs.existsSync('file1')); + done(); }); @@ -108,6 +106,7 @@ describe('cd cases', () => { assert.notEqual(process.cwd(), os.homedir()); tl.cd('~'); assert.equal(process.cwd(), os.homedir()); + done(); }); }); \ No newline at end of file diff --git a/node/test/cp.ts b/node/test/cp.ts index 4d122ab4f..0c518162c 100644 --- a/node/test/cp.ts +++ b/node/test/cp.ts @@ -34,52 +34,62 @@ describe('cp cases', () => { beforeEach((done) => { fs.writeFileSync(TESTCASE_1, 'testcase_1'); fs.writeFileSync(TESTCASE_2, 'testcase_2'); + done(); }); afterEach((done) => { tl.rmRF(TESTCASE_1); tl.rmRF(TESTCASE_2); + done(); }); after((done) => { - fs.rmSync(TEMP_DIR_1, { recursive: true}); - fs.rmSync(TEMP_DIR_2, { recursive: true}); + tl.cd(DIRNAME); + tl.rmRF(TEMP_DIR_1); + tl.rmRF(TEMP_DIR_2); + done(); }); it('Provide the source that does not exist', (done) => { assert.throws(() => tl.cp('pathdoesnotexist', TEMP_DIR_1), { message: /^ENOENT: no such file or directory/ }); assert.ok(!fs.existsSync(path.join(TEMP_DIR_1, 'pathdoesnotexist'))); + done(); }); it('Provide the source as empty string', (done) => { assert.throws(() => tl.cp('', 'pathdoesnotexist'), { message: /^ENOENT: no such file or directory/ }); + done(); }); it('Provide the destination as empty string', (done) => { assert.throws(() => tl.cp('pathdoesnotexist', ''), { message: /^ENOENT: no such file or directory/ }); + done(); }); it('Provide -n attribute to prevent overwrite an existing file at the destination', (done) => { assert.doesNotThrow(() => tl.cp('-n', TESTCASE_1, TESTCASE_2)); assert.equal(fs.readFileSync(TESTCASE_2, 'utf8'), 'testcase_2'); + done(); }); it('Provide two paths, check force default behavior', (done) => { assert.doesNotThrow(() => tl.cp(TESTCASE_1, TESTCASE_2)); assert.equal(fs.readFileSync(TESTCASE_2, 'utf8'), 'testcase_1'); + done(); }); it('Provide two paths, check explicitly force attribute', (done) => { assert.doesNotThrow(() => tl.cp('-f', TESTCASE_1, TESTCASE_2)); assert.equal(fs.readFileSync(TESTCASE_2, 'utf8'), 'testcase_1'); + done(); }); @@ -87,6 +97,7 @@ describe('cp cases', () => { assert.doesNotThrow(() => tl.cp(TESTCASE_1, TEMP_DIR_2)); assert.ok(fs.existsSync(path.join(TEMP_DIR_2, 'testcase_1'))); assert.equal(fs.readFileSync(path.join(TEMP_DIR_2, 'testcase_1'), 'utf8'), 'testcase_1'); + done(); }); @@ -94,6 +105,7 @@ describe('cp cases', () => { assert.doesNotThrow(() => tl.cp(TESTCASE_2, path.join(TEMP_DIR_2, 'testcase_3'))); assert.ok(fs.existsSync(path.join(TEMP_DIR_2, 'testcase_3'))); assert.equal(fs.readFileSync(path.join(TEMP_DIR_2, 'testcase_3'), 'utf8'), 'testcase_2'); + done(); }); @@ -102,11 +114,14 @@ describe('cp cases', () => { assert.ok(fs.existsSync(TESTCASE_2)); assert.equal(fs.readFileSync(TESTCASE_1, 'utf8'), 'testcase_1'); assert.equal(fs.readFileSync(TESTCASE_2, 'utf8'), 'testcase_2'); + assert.doesNotThrow(() => tl.cp('-f', TESTCASE_1, TESTCASE_2)); + assert.ok(fs.existsSync(TESTCASE_1)); assert.ok(fs.existsSync(TESTCASE_2)); assert.equal(fs.readFileSync(TESTCASE_1, 'utf8'), 'testcase_1'); assert.equal(fs.readFileSync(TESTCASE_2, 'utf8'), 'testcase_1'); + done(); }); @@ -115,11 +130,14 @@ describe('cp cases', () => { assert.ok(fs.existsSync(TESTCASE_2)); assert.equal(fs.readFileSync(TESTCASE_1, 'utf8'), 'testcase_1'); assert.equal(fs.readFileSync(TESTCASE_2, 'utf8'), 'testcase_2'); - tl.cp('-n', TESTCASE_1, TESTCASE_2); + + assert.doesNotThrow(() => tl.cp('-n', TESTCASE_1, TESTCASE_2)); + assert.ok(fs.existsSync(TESTCASE_1)); assert.ok(fs.existsSync(TESTCASE_2)); assert.equal(fs.readFileSync(TESTCASE_1, 'utf8'), 'testcase_1'); assert.equal(fs.readFileSync(TESTCASE_2, 'utf8'), 'testcase_2'); + done(); }); }); \ No newline at end of file diff --git a/node/test/inputtests.ts b/node/test/inputtests.ts index ce1061290..5f42d496b 100644 --- a/node/test/inputtests.ts +++ b/node/test/inputtests.ts @@ -1,1237 +1,1237 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -import assert = require('assert'); -import path = require('path'); -import os = require('os'); -import * as tl from '../_build/task'; -import * as im from '../_build/internal'; -import testutil = require('./testutil'); - -describe('Input Tests', function () { - - before(function (done) { - try { - testutil.initialize(); - } - catch (err) { - assert.fail('Failed to load task lib: ' + err.message); - } - done(); - }); - - after(function () { - - }); - - it('gets input value', function (done) { - this.timeout(1000); - - process.env['INPUT_UNITTESTINPUT'] = 'test value'; - im._loadData(); - - var inval = tl.getInput('UnitTestInput', true); - assert.equal(inval, 'test value'); - - done(); - }) - it('should clear input envvar', function (done) { - this.timeout(1000); - - process.env['INPUT_UNITTESTINPUT'] = 'test value'; - im._loadData(); - var inval = tl.getInput('UnitTestInput', true); - assert.equal(inval, 'test value'); - assert(!process.env['INPUT_UNITTESTINPUT'], 'input envvar should be cleared'); - - done(); - }) - it('required input throws', function (done) { - this.timeout(1000); - - var worked: boolean = false; - try { - var inval = tl.getInput('SomeUnsuppliedRequiredInput', true); - worked = true; - } - catch (err) { } - - assert(!worked, 'req input should have not have worked'); - - done(); - }) - - it('gets input value', function (done) { - this.timeout(1000); - - process.env['INPUT_UNITTESTINPUT'] = 'test value'; - im._loadData(); - - var inval = tl.getInputRequired('UnitTestInput'); - assert.strictEqual(inval, 'test value'); - - done(); - }) - it('should clear input envvar', function (done) { - this.timeout(1000); - - process.env['INPUT_UNITTESTINPUT'] = 'test value'; - im._loadData(); - var inval = tl.getInputRequired('UnitTestInput'); - assert.strictEqual(inval, 'test value'); - assert(!process.env['INPUT_UNITTESTINPUT'], 'input envvar should be cleared'); - - done(); - }) - it('required input throws', function (done) { - this.timeout(1000); - - var worked: boolean = false; - try { - var inval = tl.getInputRequired('SomeUnsuppliedRequiredInput'); - worked = true; - } - catch (err) { } - - assert(!worked, 'req input should have not have worked'); - - done(); - }) - - // getVariable tests - it('gets a variable', function (done) { - this.timeout(1000); - - process.env['BUILD_REPOSITORY_NAME'] = 'Test Repository'; - im._loadData(); - - var varVal = tl.getVariable('Build.Repository.Name'); - assert.equal(varVal, 'Test Repository'); - - done(); - }) - it('gets a secret variable', function (done) { - this.timeout(1000); - - process.env['SECRET_BUILD_REPOSITORY_NAME'] = 'Test Repository'; - im._loadData(); - - var varVal = tl.getVariable('Build.Repository.Name'); - assert.equal(varVal, 'Test Repository'); - - done(); - }) - it('gets a secret variable while variable also exist', function (done) { - this.timeout(1000); - - process.env['BUILD_REPOSITORY_NAME'] = 'Test Repository'; - process.env['SECRET_BUILD_REPOSITORY_NAME'] = 'Secret Test Repository'; - im._loadData(); - - var varVal = tl.getVariable('Build.Repository.Name'); - assert.equal(varVal, 'Secret Test Repository'); - - done(); - }) - it('gets a variable with special characters', (done) => { - this.timeout(1000); - - let expected = 'Value of var with special chars'; - process.env['HELLO_DOT_DOT2_SPACE_SPACE2'] = expected; - im._loadData(); - - let varVal = tl.getVariable('Hello.dot.dot2 space space2'); - assert.equal(varVal, expected); - - done(); - }) - - // setVariable tests - it('sets a variable as an env var', function (done) { - this.timeout(1000); - - tl.setVariable('Build.Repository.Uri', 'test value'); - let varVal: string = process.env['BUILD_REPOSITORY_URI']; - assert.equal(varVal, 'test value'); - - done(); - }) - it('sets a variable with special chars as an env var', (done) => { - this.timeout(1000); - - let expected = 'Set value of var with special chars'; - tl.setVariable('Hello.dot.dot2 space space2', expected); - let varVal: string = process.env['HELLO_DOT_DOT2_SPACE_SPACE2']; - assert.equal(varVal, expected); - - done(); - }) - it('sets and gets a variable', function (done) { - this.timeout(1000); - - tl.setVariable('UnitTestVariable', 'test var value'); - let varVal: string = tl.getVariable('UnitTestVariable'); - assert.equal(varVal, 'test var value'); - - done(); - }) - it('sets and gets an output variable', function (done) { - this.timeout(1000); - - tl.setVariable('UnitTestVariable', 'test var value', false, true); - let varVal: string = tl.getVariable('UnitTestVariable'); - assert.equal(varVal, 'test var value'); - - done(); - }) - it('sets and gets a secret variable', function (done) { - this.timeout(1000); - - tl.setVariable('My.Secret.Var', 'test secret value', true); - let varVal: string = tl.getVariable('My.Secret.Var'); - assert.equal(varVal, 'test secret value'); - - done(); - }) - it('does not set a secret variable as an env var', function (done) { - this.timeout(1000); - - delete process.env['MY_SECRET_VAR']; - tl.setVariable('My.Secret.Var', 'test secret value', true); - let envVal: string = process.env['MY_SECRET_VAR']; - assert(!envVal, 'env var should not be set'); - - done(); - }) - it('removes env var when sets a secret variable', function (done) { - this.timeout(1000); - - process.env['MY_SECRET_VAR'] = 'test env value'; - tl.setVariable('My.Secret.Var', 'test secret value', true); - let envVal: string = process.env['MY_SECRET_VAR']; - assert(!envVal, 'env var should not be set'); - - done(); - }) - it('does not allow a secret variable to become a public variable', function (done) { - this.timeout(1000); - - im._loadData(); - tl.setVariable('My.Secret.Var', 'test secret value', true); - tl.setVariable('My.Secret.Var', 'test modified value', false); - let vars: tl.VariableInfo[] = tl.getVariables(); - assert.equal(vars.length, 1); - assert.equal(vars[0].name, 'My.Secret.Var'); - assert.equal(vars[0].value, 'test modified value'); - assert.equal(vars[0].secret, true); - - done(); - }) - it('allows a public variable to become a secret variable', function (done) { - this.timeout(1000); - - im._loadData(); - tl.setVariable('My.Var', 'test value', false); - tl.setVariable('My.Var', 'test modified value', true); - let vars: tl.VariableInfo[] = tl.getVariables(); - assert.equal(vars.length, 1); - assert.equal(vars[0].name, 'My.Var'); - assert.equal(vars[0].value, 'test modified value'); - assert.equal(vars[0].secret, true); - - done(); - }) - it('tracks known variables using env formatted name', function (done) { - this.timeout(1000); - - im._loadData(); - tl.setVariable('My.Public.Var', 'test value'); - tl.setVariable('my_public.VAR', 'test modified value'); - let vars: tl.VariableInfo[] = tl.getVariables(); - assert.equal(vars.length, 1); - assert.equal(vars[0].name, 'my_public.VAR'); - assert.equal(vars[0].value, 'test modified value'); - - done(); - }) - it('does not allow setting a multi-line secret variable', function (done) { - this.timeout(1000); - - im._loadData(); - - // test carriage return - let failed = false; - try { - tl.setVariable('my.secret', 'line1\rline2', true); - } - catch (err) { - failed = true; - } - assert(failed, 'Should have failed setting a secret variable with a carriage return'); - - // test line feed - failed = false; - try { - tl.setVariable('my.secret', 'line1\nline2', true); - } - catch (err) { - failed = true; - } - assert(failed, 'Should have failed setting a secret variable with a line feed'); - - done(); - }) - it('allows unsafe setting a multi-line secret variable', function (done) { - this.timeout(1000); - - im._loadData(); - try { - process.env['SYSTEM_UNSAFEALLOWMULTILINESECRET'] = 'true'; - tl.setVariable('my.secret', 'line1\r\nline2', true); - } - finally { - delete process.env['SYSTEM_UNSAFEALLOWMULTILINESECRET']; - } - - assert.equal(tl.getVariable('my.secret'), 'line1\r\nline2'); - - done(); - }) - - // setSecret tests - it('does not allow setting a multi-line secret', function (done) { - this.timeout(1000); - - im._loadData(); - - // test carriage return - let failed = false; - try { - tl.setSecret('line1\rline2'); - } - catch (err) { - failed = true; - } - assert(failed, 'Should have failed setting a secret with a carriage return'); - - // test line feed - failed = false; - try { - tl.setSecret('line1\nline2'); - } - catch (err) { - failed = true; - } - assert(failed, 'Should have failed setting a secret with a line feed'); - - done(); - }) - it('allows unsafe setting a multi-line secret', function (done) { - this.timeout(1000); - - im._loadData(); - try { - process.env['SYSTEM_UNSAFEALLOWMULTILINESECRET'] = 'true'; - tl.setSecret('line1\r\nline2'); - } - finally { - delete process.env['SYSTEM_UNSAFEALLOWMULTILINESECRET']; - } - - done(); - }) - - // getVariables tests - it('gets public variables from initial load', function (done) { - this.timeout(1000); - - process.env['PUBLIC_VAR_ONE'] = 'public value 1'; - process.env['PUBLIC_VAR_TWO'] = 'public value 2'; - process.env['VSTS_PUBLIC_VARIABLES'] = '[ "Public.Var.One", "Public.Var.Two" ]'; - im._loadData(); - let vars: tl.VariableInfo[] = tl.getVariables(); - assert(vars, 'variables should not be undefined or null'); - assert.equal(vars.length, 2, 'exactly 2 variables should be returned'); - vars = vars.sort((a: tl.VariableInfo, b: tl.VariableInfo) => a.name.localeCompare(b.name)); - assert.equal(vars[0].name, 'Public.Var.One'); - assert.equal(vars[0].value, 'public value 1'); - assert.equal(vars[0].secret, false); - assert.equal(vars[1].name, 'Public.Var.Two'); - assert.equal(vars[1].value, 'public value 2'); - assert.equal(vars[1].secret, false); - - done(); - }) - it('gets secret variables from initial load', function (done) { - this.timeout(1000); - - process.env['SECRET_SECRET_VAR_ONE'] = 'secret value 1'; - process.env['SECRET_SECRET_VAR_TWO'] = 'secret value 2'; - process.env['VSTS_SECRET_VARIABLES'] = '[ "Secret.Var.One", "Secret.Var.Two" ]'; - im._loadData(); - let vars: tl.VariableInfo[] = tl.getVariables(); - assert(vars, 'variables should not be undefined or null'); - assert.equal(vars.length, 2, 'exactly 2 variables should be returned'); - vars = vars.sort((a: tl.VariableInfo, b: tl.VariableInfo) => a.name.localeCompare(b.name)); - assert.equal(vars[0].name, 'Secret.Var.One'); - assert.equal(vars[0].value, 'secret value 1'); - assert.equal(vars[0].secret, true); - assert.equal(vars[1].name, 'Secret.Var.Two'); - assert.equal(vars[1].value, 'secret value 2'); - assert.equal(vars[1].secret, true); - - done(); - }) - it('gets secret variables from initial load in pre 2.104.1 agent', function (done) { - this.timeout(1000); - - process.env['SECRET_SECRET_VAR_ONE'] = 'secret value 1'; - process.env['SECRET_SECRET_VAR_TWO'] = 'secret value 2'; - im._loadData(); - let vars: tl.VariableInfo[] = tl.getVariables(); - assert(vars, 'variables should not be undefined or null'); - assert.equal(vars.length, 2, 'exactly 2 variables should be returned'); - vars = vars.sort((a: tl.VariableInfo, b: tl.VariableInfo) => a.name.localeCompare(b.name)); - assert.equal(vars[0].name, 'SECRET_VAR_ONE'); - assert.equal(vars[0].value, 'secret value 1'); - assert.equal(vars[0].secret, true); - assert.equal(vars[1].name, 'SECRET_VAR_TWO'); - assert.equal(vars[1].value, 'secret value 2'); - assert.equal(vars[1].secret, true); - - done(); - }) - it('gets public variables from setVariable', function (done) { - this.timeout(1000); - - process.env['INITIAL_PUBLIC_VAR'] = 'initial public value'; - process.env['VSTS_PUBLIC_VARIABLES'] = '[ "Initial.Public.Var" ]'; - im._loadData(); - tl.setVariable('Set.Public.Var', 'set public value'); - let vars: tl.VariableInfo[] = tl.getVariables(); - assert(vars, 'variables should not be undefined or null'); - assert.equal(vars.length, 2, 'exactly 4 variables should be returned'); - vars = vars.sort((a: tl.VariableInfo, b: tl.VariableInfo) => a.name.localeCompare(b.name)); - assert.equal(vars[0].name, 'Initial.Public.Var'); - assert.equal(vars[0].value, 'initial public value'); - assert.equal(vars[0].secret, false); - assert.equal(vars[1].name, 'Set.Public.Var'); - assert.equal(vars[1].value, 'set public value'); - assert.equal(vars[1].secret, false); - - done(); - }) - it('gets secret variables from setVariable', function (done) { - this.timeout(1000); - - process.env['SECRET_INITIAL_SECRET_VAR'] = 'initial secret value'; - process.env['VSTS_SECRET_VARIABLES'] = '[ "Initial.Secret.Var" ]'; - im._loadData(); - tl.setVariable('Set.Secret.Var', 'set secret value', true); - let vars: tl.VariableInfo[] = tl.getVariables(); - assert(vars, 'variables should not be undefined or null'); - assert.equal(vars.length, 2, 'exactly 2 variables should be returned'); - vars = vars.sort((a: tl.VariableInfo, b: tl.VariableInfo) => a.name.localeCompare(b.name)); - assert.equal(vars[0].name, 'Initial.Secret.Var'); - assert.equal(vars[0].value, 'initial secret value'); - assert.equal(vars[0].secret, true); - assert.equal(vars[1].name, 'Set.Secret.Var'); - assert.equal(vars[1].value, 'set secret value'); - assert.equal(vars[1].secret, true); - - done(); - }) - - // getEndpointUrl/getEndpointAuthorization/getEndpointData tests - it('gets an endpoint url', function (done) { - this.timeout(1000); - - process.env['ENDPOINT_URL_id1'] = 'http://url'; - im._loadData(); - - var url = tl.getEndpointUrl('id1', true); - assert.equal(url, 'http://url', 'url should match'); - - done(); - }) - it('gets a required endpoint url', function (done) { - this.timeout(1000); - - process.env['ENDPOINT_URL_id1'] = 'http://url'; - im._loadData(); - - var url = tl.getEndpointUrlRequired('id1'); - assert.equal(url, 'http://url', 'url should match'); - - done(); - }) - it('required endpoint url throws', function (done) { - this.timeout(1000); - - im._loadData(); - - var worked: boolean = false; - try { - var url = tl.getEndpointUrlRequired('SomeUnsuppliedRequiredInput'); - worked = true; - } - catch (err) { } - - assert(!worked, 'req endpoint url should have not have worked'); - - done(); - }) - it('gets an endpoint auth', function (done) { - this.timeout(1000); - - process.env['ENDPOINT_AUTH_id1'] = '{ "parameters": {"param1": "val1", "param2": "val2"}, "scheme": "UsernamePassword"}'; - im._loadData(); - - var auth = tl.getEndpointAuthorization('id1', true); - assert(auth, 'should return an auth obj'); - assert.equal(auth['parameters']['param1'], 'val1', 'should be correct object'); - - done(); - }) - it('gets null if endpoint auth not set', function (done) { - this.timeout(1000); - - // don't set - im._loadData(); - - var auth = tl.getEndpointAuthorization('id1', true); - assert.equal(auth, null, 'should not return an auth obj'); - - done(); - }) - it('should clear auth envvar', function (done) { - this.timeout(1000); - - process.env['ENDPOINT_AUTH_id1'] = '{ "parameters": {"param1": "val1", "param2": "val2"}, "scheme": "UsernamePassword"}'; - im._loadData(); - var auth = tl.getEndpointAuthorization('id1', true); - assert(auth, 'should return an auth obj'); - assert(auth['parameters']['param1'] === 'val1', 'should be correct object'); - assert(!process.env['ENDPOINT_AUTH_id1'], 'should clear auth envvar'); - - done(); - }) - it('gets endpoint auth scheme', function (done) { - this.timeout(1000); - process.env['ENDPOINT_AUTH_SCHEME_id1'] = 'scheme1'; - im._loadData(); - - var data = tl.getEndpointAuthorizationScheme('id1', true); - assert(data, 'should return a string value'); - assert.equal(data, 'scheme1', 'should be correct scheme'); - assert(!process.env['ENDPOINT_AUTH_SCHEME_id1'], 'should clear auth envvar'); - - done(); - }) - it('gets undefined if endpoint auth scheme is not set', function (done) { - this.timeout(1000); - im._loadData(); - - var data = tl.getEndpointAuthorizationScheme('id1', true); - assert(!data, 'should be undefined when auth scheme is not set'); - - done(); - }) - it('gets required endpoint auth scheme', function (done) { - this.timeout(1000); - process.env['ENDPOINT_AUTH_SCHEME_id1'] = 'scheme1'; - im._loadData(); - - var data = tl.getEndpointAuthorizationSchemeRequired('id1'); - assert(data, 'should return a string value'); - assert.equal(data, 'scheme1', 'should be correct scheme'); - assert(!process.env['ENDPOINT_AUTH_SCHEME_id1'], 'should clear auth envvar'); - - done(); - }) - it('required endpoint auth scheme throws', function (done) { - this.timeout(1000); - - im._loadData(); - - var worked: boolean = false; - try { - var data = tl.getEndpointAuthorizationSchemeRequired('SomeUnsuppliedRequiredInput'); - worked = true; - } - catch (err) { } - - assert(!worked, 'req endpoint auth scheme should have not have worked'); - - done(); - }) - it('gets endpoint auth parameters', function (done) { - this.timeout(1000); - process.env['ENDPOINT_AUTH_PARAMETER_id1_PARAM1'] = 'value1'; - im._loadData(); - - var data = tl.getEndpointAuthorizationParameter('id1', 'param1', true); - assert(data, 'should return a string value'); - assert.equal(data, 'value1', 'should be correct auth param'); - assert(!process.env['ENDPOINT_AUTH_PARAMETER_id1_PARAM1'], 'should clear auth envvar'); - - done(); - }) - it('gets undefined if endpoint auth parameter is not set', function (done) { - this.timeout(1000); - im._loadData(); - - var data = tl.getEndpointAuthorizationParameter('id1', 'noparam', true); - assert(!data, 'should be undefined when auth param is not set'); - - done(); - }) - it('gets required endpoint auth parameters', function (done) { - this.timeout(1000); - process.env['ENDPOINT_AUTH_PARAMETER_id1_PARAM1'] = 'value1'; - im._loadData(); - - var data = tl.getEndpointAuthorizationParameterRequired('id1', 'param1'); - assert(data, 'should return a string value'); - assert.equal(data, 'value1', 'should be correct auth param'); - assert(!process.env['ENDPOINT_AUTH_PARAMETER_id1_PARAM1'], 'should clear auth envvar'); - - done(); - }) - it('throws if endpoint auth parameter is not set', function (done) { - this.timeout(1000); - im._loadData(); - - var worked: boolean = false; - try { - var data = tl.getEndpointAuthorizationParameterRequired('id1', 'noparam'); - worked = true; - } - catch (err) { } - - assert(!worked, 'get endpoint authorization parameter should have not have worked'); - - done(); - }) - it('gets an endpoint data', function (done) { - this.timeout(1000); - process.env['ENDPOINT_DATA_id1_PARAM1'] = 'val1'; - im._loadData(); - - var data = tl.getEndpointDataParameter('id1', 'param1', true); - assert(data, 'should return a string value'); - assert.equal(data, 'val1', 'should be correct object'); - - done(); - }) - it('gets undefined if endpoint data is not set', function (done) { - this.timeout(1000); - im._loadData(); - - var data = tl.getEndpointDataParameter('id1', 'noparam', true); - assert.equal(data, undefined, 'Error should occur if endpoint data is not set'); - - done(); - }) - it('gets required endpoint data', function (done) { - this.timeout(1000); - process.env['ENDPOINT_DATA_id1_PARAM1'] = 'val1'; - im._loadData(); - - var data = tl.getEndpointDataParameterRequired('id1', 'param1'); - assert(data, 'should return a string value'); - assert.equal(data, 'val1', 'should be correct object'); - - done(); - }) - it('throws if endpoint data is not set', function (done) { - this.timeout(1000); - im._loadData(); - - var worked: boolean = false; - try { - var data = tl.getEndpointDataParameterRequired('id1', 'noparam'); - worked = true; - } - catch (err) { } - - assert(!worked, 'get endpoint data should have not have worked'); - - done(); - }) - // getSecureFileName/getSecureFileTicket/getSecureFiles tests - it('gets a secure file name', function (done) { - this.timeout(1000); - - process.env['SECUREFILE_NAME_10'] = 'securefile10.p12'; - im._loadData(); - - var name = tl.getSecureFileName('10'); - assert.equal(name, 'securefile10.p12', 'name should match'); - - done(); - }) - it('gets a secure file ticket', function (done) { - this.timeout(1000); - - process.env['SECUREFILE_TICKET_10'] = 'rsaticket10'; - im._loadData(); - - var ticket = tl.getSecureFileTicket('10'); - assert(ticket, 'should return a string value'); - assert.equal(ticket, 'rsaticket10', 'should be correct ticket'); - assert(!process.env['SECUREFILE_TICKET_10'], 'should clear ticket envvar'); - - done(); - }) - // getBoolInput tests - it('gets case insensitive true bool input value', function (done) { - this.timeout(1000); - - var inputValue = 'tRuE'; - process.env['INPUT_ABOOL'] = inputValue; - im._loadData(); - - var outVal = tl.getBoolInput('abool', /*required=*/true); - assert(outVal, 'should return true'); - - done(); - }) - it('gets false bool input value', function (done) { - this.timeout(1000); - - var inputValue = 'false'; - process.env['INPUT_ABOOL'] = inputValue; - im._loadData(); - - var outVal = tl.getBoolInput('abool', /*required=*/true); - assert(!outVal, 'should return false'); - - done(); - }) - it('defaults to false bool input value', function (done) { - this.timeout(1000); - - im._loadData(); - - var outVal = tl.getBoolInput('no_such_env_var', /*required=*/ false); - assert(!outVal, 'should default to false'); - - done(); - }) - - // getDelimitedInput tests - it('gets delimited input values removes empty values', function (done) { - this.timeout(1000); - - var inputValue = 'test value'; // contains two spaces - process.env['INPUT_DELIM'] = inputValue; - im._loadData(); - - var outVal = tl.getDelimitedInput('delim', ' ', /*required*/true); - assert.equal(outVal.length, 2, 'should return array with two elements'); - assert.equal(outVal[0], 'test', 'should return correct element 1'); - assert.equal(outVal[1], 'value', 'should return correct element 2'); - - done(); - }) - it('gets delimited input for a single value', function (done) { - this.timeout(1000); - - var inputValue = 'testvalue'; - process.env['INPUT_DELIM'] = inputValue; - im._loadData(); - - var outVal = tl.getDelimitedInput('delim', ' ', /*required*/true); - assert.equal(outVal.length, 1, 'should return array with one element'); - assert.equal(outVal[0], 'testvalue', 'should return correct element 1'); - - done(); - }) - it('gets delimited input for an empty value', function (done) { - this.timeout(1000); - - var inputValue = ''; - process.env['INPUT_DELIM'] = inputValue; - im._loadData(); - - var outVal = tl.getDelimitedInput('delim', ' ', /*required*/false); - assert.equal(outVal.length, 0, 'should return array with zero elements'); - - done(); - }) - it('gets delimited input with a Regexp', function (done) { - this.timeout(1000); - - var inputValue = 'a,b\nc'; - process.env['INPUT_DELIM'] = inputValue; - im._loadData(); - - var outVal = tl.getDelimitedInput('delim', /[,\n]/, /*required*/false); - assert.equal(outVal.length, 3, 'should return array with 3 elements'); - - done(); - }) - - // getPathInput tests - it('gets path input value', function (done) { - this.timeout(1000); - - var inputValue = 'test.txt' - process.env['INPUT_PATH1'] = inputValue; - im._loadData(); - - var path = tl.getPathInput('path1', /*required=*/true, /*check=*/false); - assert(path, 'should return a path'); - assert.equal(path, inputValue, 'test path value'); - - done(); - }) - it('throws if required path not supplied', function (done) { - this.timeout(1000); - - var stdStream = testutil.createStringStream(); - tl.setStdStream(stdStream); - - var worked: boolean = false; - try { - var path = tl.getPathInput(null, /*required=*/true, /*check=*/false); - worked = true; - } - catch (err) { } - - assert(!worked, 'req path should have not have worked'); - - done(); - }) - it('get invalid checked path throws', function (done) { - this.timeout(1000); - - var stdStream = testutil.createStringStream(); - tl.setStdStream(stdStream); - - var worked: boolean = false; - try { - var path = tl.getPathInput('some_missing_path', /*required=*/true, /*check=*/true); - worked = true; - } - catch (err) { } - - assert(!worked, 'invalid checked path should have not have worked'); - - done(); - }) - it('gets path invalid value not required', function (done) { - this.timeout(1000); - - var errStream = testutil.createStringStream(); - tl.setErrStream(errStream); - - var path = tl.getPathInput('some_missing_path', /*required=*/false, /*check=*/false); - assert(!path, 'should not return a path'); - - var errMsg = errStream.getContents(); - assert.equal(errMsg, "", "no err") - - done(); - }) - it('gets path input value with space', function (done) { - this.timeout(1000); - - var inputValue = 'file name.txt'; - var expectedValue = 'file name.txt'; - process.env['INPUT_PATH1'] = inputValue; - im._loadData(); - - var path = tl.getPathInput('path1', /*required=*/true, /*check=*/false); - assert(path, 'should return a path'); - assert.equal(path, expectedValue, 'returned ' + path + ', expected: ' + expectedValue); - - done(); - }) - it('gets path value with check and exist', function (done) { - this.timeout(1000); - - var errStream = testutil.createStringStream(); - tl.setErrStream(errStream); - - var inputValue = __filename; - process.env['INPUT_PATH1'] = inputValue; - im._loadData(); - - var path = tl.getPathInput('path1', /*required=*/true, /*check=*/true); - assert(path, 'should return a path'); - assert.equal(path, inputValue, 'test path value'); - - var errMsg = errStream.getContents(); - assert(errMsg === "", "no err") - - done(); - }) - it('gets path value with check and not exist', function (done) { - this.timeout(1000); - - var stdStream = testutil.createStringStream(); - tl.setStdStream(stdStream); - - var inputValue = "someRandomFile.txt"; - process.env['INPUT_PATH1'] = inputValue; - im._loadData(); - - var worked: boolean = false; - try { - var path = tl.getPathInput('path1', /*required=*/true, /*check=*/true); - worked = true; - } - catch (err) { - assert(err.message.indexOf("Not found") >= 0, "error should have said Not found"); - } - assert(!worked, 'invalid checked path should have not have worked'); - - done(); - }) - - // getPathInputRequired tests - it('gets path input required value', function (done) { - this.timeout(1000); - - var inputValue = 'test.txt' - process.env['INPUT_PATH1'] = inputValue; - im._loadData(); - - var path = tl.getPathInputRequired('path1', /*check=*/false); - assert(path, 'should return a path'); - assert.equal(path, inputValue, 'test path value'); - - done(); - }) - it('throws if required path not supplied', function (done) { - this.timeout(1000); - - var stdStream = testutil.createStringStream(); - tl.setStdStream(stdStream); - - var worked: boolean = false; - try { - var path = tl.getPathInputRequired(null, /*check=*/false); - worked = true; - } - catch (err) { } - - assert(!worked, 'req path should have not have worked'); - - done(); - }) - it('get required path invalid checked throws', function (done) { - this.timeout(1000); - - var stdStream = testutil.createStringStream(); - tl.setStdStream(stdStream); - - var worked: boolean = false; - try { - var path = tl.getPathInputRequired('some_missing_path', /*check=*/true); - worked = true; - } - catch (err) { } - - assert(!worked, 'invalid checked path should have not have worked'); - - done(); - }) - it('gets path input required value with space', function (done) { - this.timeout(1000); - - var inputValue = 'file name.txt'; - var expectedValue = 'file name.txt'; - process.env['INPUT_PATH1'] = inputValue; - im._loadData(); - - var path = tl.getPathInputRequired('path1', /*check=*/false); - assert(path, 'should return a path'); - assert.equal(path, expectedValue, 'returned ' + path + ', expected: ' + expectedValue); - - done(); - }) - it('gets path required value with check and exist', function (done) { - this.timeout(1000); - - var errStream = testutil.createStringStream(); - tl.setErrStream(errStream); - - var inputValue = __filename; - process.env['INPUT_PATH1'] = inputValue; - im._loadData(); - - var path = tl.getPathInputRequired('path1', /*check=*/true); - assert(path, 'should return a path'); - assert.equal(path, inputValue, 'test path value'); - - var errMsg = errStream.getContents(); - assert(errMsg === "", "no err") - - done(); - }) - it('gets path required value with check and not exist', function (done) { - this.timeout(1000); - - var stdStream = testutil.createStringStream(); - tl.setStdStream(stdStream); - - var inputValue = "someRandomFile.txt"; - process.env['INPUT_PATH1'] = inputValue; - im._loadData(); - - var worked: boolean = false; - try { - var path = tl.getPathInputRequired('path1', /*check=*/true); - worked = true; - } - catch (err) { - assert(err.message.indexOf("Not found") >= 0, "error should have said Not found"); - } - assert(!worked, 'invalid checked path should have not have worked'); - - done(); - }) - - // filePathSupplied tests - it('filePathSupplied checks not supplied', function (done) { - this.timeout(1000); - - var repoRoot = '/repo/root/dir'; - process.env['INPUT_PATH1'] = repoRoot; - im._loadData(); - - process.env['BUILD_SOURCESDIRECTORY'] = repoRoot; - var supplied = tl.filePathSupplied('path1'); - assert(!supplied, 'path1 should not be supplied'); - done(); - }) - it('filePathSupplied checks supplied', function (done) { - this.timeout(1000); - - var repoRoot = '/repo/root/dir'; - process.env['INPUT_PATH1'] = repoRoot + '/some/path'; - im._loadData(); - - process.env['BUILD_SOURCESDIRECTORY'] = repoRoot; - var supplied = tl.filePathSupplied('path1'); - assert(supplied, 'path1 should be supplied'); - done(); - }) - - // resolve tests - it('resolve', function (done) { - var absolutePath = tl.resolve('/repo/root', '/repo/root/some/path'); - if (os.platform() !== 'win32') { - assert(absolutePath === '/repo/root/some/path', 'absolute path not expected, got:' + absolutePath + ' expected: /repo/root/some/path'); - } else { - var winDrive = path.parse(path.resolve('')).root; - var expectedPath = winDrive.concat('repo\\root\\some\\path'); - assert.equal(absolutePath, expectedPath, 'absolute path not as expected, got: ' + absolutePath + ' expected: ' + expectedPath); - } - done(); - }) - - // getTaskVariable tests - it('gets a task variable', function (done) { - this.timeout(1000); - - let originalAgentVersion = process.env['AGENT_VERSION']; - try { - process.env['AGENT_VERSION'] = '2.115.0'; - process.env['VSTS_TASKVARIABLE_TEST1'] = 'Task variable value 1'; - im._loadData(); - var varVal = tl.getTaskVariable('test1'); - assert.equal(varVal, 'Task variable value 1'); - } - finally { - process.env['AGENT_VERSION'] = originalAgentVersion; - } - - done(); - }) - it('sets a task variable', function (done) { - this.timeout(1000); - - let originalAgentVersion = process.env['AGENT_VERSION']; - try { - process.env['AGENT_VERSION'] = '2.115.0'; - tl.setTaskVariable('test2', 'Task variable value 2'); - let varVal: string = tl.getTaskVariable('test2'); - assert.equal(varVal, 'Task variable value 2'); - } - finally { - process.env['AGENT_VERSION'] = originalAgentVersion; - } - - done(); - }) - - // assertAgent tests - it('assert agent does not fail when empty', function (done) { - this.timeout(1000); - - let originalAgentVersion = process.env['AGENT_VERSION']; - try { - process.env['AGENT_VERSION'] = ''; - let failed = false; - try { - tl.assertAgent('2.115.0'); - } - catch (err) { - failed = true; - } - - assert(!failed, 'assert should not have thrown'); - } - finally { - process.env['AGENT_VERSION'] = originalAgentVersion; - } - - done(); - }) - it('assert agent fails when less than', function (done) { - this.timeout(1000); - - let originalAgentVersion = process.env['AGENT_VERSION']; - try { - process.env['AGENT_VERSION'] = '2.114.0'; - let failed = false; - try { - tl.assertAgent('2.115.0'); - } - catch (err) { - failed = true; - } - - assert(failed, 'assert should have thrown'); - } - finally { - process.env['AGENT_VERSION'] = originalAgentVersion; - } - - done(); - }) - it('assert succeeds when greater or equal', function (done) { - this.timeout(1000); - - let originalAgentVersion = process.env['AGENT_VERSION']; - try { - process.env['AGENT_VERSION'] = '2.115.0'; - tl.assertAgent('2.115.0'); - process.env['AGENT_VERSION'] = '2.116.0'; - tl.assertAgent('2.115.0'); - } - finally { - process.env['AGENT_VERSION'] = originalAgentVersion; - } - - done(); - }) - - // _loadData tests - it('_loadData does not run twice', function (done) { - this.timeout(5000); - - // intialize an input (stored in vault) - process.env['INPUT_SOMEINPUT'] = 'some input value'; - im._loadData(); - assert.equal(tl.getInput('SomeInput'), 'some input value'); - - // copy azure-pipelines-task-lib to a different dir and load it from - // there so it will be forced to load again - let testDir = path.join(testutil.getTestTemp(), '_loadData-not-called-twice'); - tl.mkdirP(testDir); - tl.cp(path.join(__dirname, '..', '_build'), testDir, '-R'); - require(path.join(testDir, '_build', 'task')); - - assert.equal(tl.getInput('SomeInput'), 'some input value'); - done(); - }) - - describe('Feature flags tests', () => { - - ([ - ["true", true], - ["TRUE", true], - ["TruE", true], - ["false", false], - ["treu", false], - ["fasle", false], - ["On", false], - ["", false], - [undefined, false] - ] as [string, boolean][]) - .forEach( - ( - [ - input, - expectedResult - ] - ) => { - it(`Should return ${expectedResult} if feature flag env is ${input}`, () => { - const ffName = "SOME_TEST_FF" - process.env[ffName] = input - - const ffValue = tl.getBoolFeatureFlag(ffName, false); - - assert.equal(ffValue, expectedResult); - }) - } - ); - - it(`Should return default value if feature flag env is empty`, () => { - const ffName = "SOME_TEST_FF" - process.env[ffName] = "" - - const ffValue = tl.getBoolFeatureFlag(ffName, true); - - assert.equal(ffValue, true); - }) - - it(`Should return default value if feature flag env is not specified`, () => { - const ffName = "SOME_TEST_FF" - delete process.env[ffName]; - - const ffValue = tl.getBoolFeatureFlag(ffName, true); - - assert.equal(ffValue, true); - }) - }); - - describe('Pipeline features tests', () => { - it(`Should return if no feature variable present.`, () => { - const featureName = "TestFeature" - delete process.env[im._getVariableKey(`DistributedTask.Tasks.${featureName}`)]; - - const ffValue = tl.getPipelineFeature(featureName); - - assert.deepStrictEqual(ffValue, false); - }) - - const testInputs = ([ - ["true", true], - ["TRUE", true], - ["TruE", true], - ["false", false], - ["treu", false], - ["fasle", false], - ["On", false], - ["", false], - [undefined, false] - ] as [string, boolean][]) - for (const [input, expected] of testInputs) { - it(`Should return '${expected}' if feature is '${input}'`, () => { - const featureVariable = "DISTRIBUTEDTASK_TASKS_TESTFEATURE"; - const featureName = "TestFeature"; - process.env[featureVariable] = input; - - const result = tl.getPipelineFeature(featureName); - - assert.deepStrictEqual(result, expected); - }) - } - }) -}); +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +import assert = require('assert'); +import path = require('path'); +import os = require('os'); +import * as tl from '../_build/task'; +import * as im from '../_build/internal'; +import testutil = require('./testutil'); + +describe('Input Tests', function () { + + before(function (done) { + try { + testutil.initialize(); + } + catch (err) { + assert.fail('Failed to load task lib: ' + err.message); + } + done(); + }); + + after(function () { + + }); + + it('gets input value', function (done) { + this.timeout(1000); + + process.env['INPUT_UNITTESTINPUT'] = 'test value'; + im._loadData(); + + var inval = tl.getInput('UnitTestInput', true); + assert.equal(inval, 'test value'); + + done(); + }) + it('should clear input envvar', function (done) { + this.timeout(1000); + + process.env['INPUT_UNITTESTINPUT'] = 'test value'; + im._loadData(); + var inval = tl.getInput('UnitTestInput', true); + assert.equal(inval, 'test value'); + assert(!process.env['INPUT_UNITTESTINPUT'], 'input envvar should be cleared'); + + done(); + }) + it('required input throws', function (done) { + this.timeout(1000); + + var worked: boolean = false; + try { + var inval = tl.getInput('SomeUnsuppliedRequiredInput', true); + worked = true; + } + catch (err) { } + + assert(!worked, 'req input should have not have worked'); + + done(); + }) + + it('gets input value', function (done) { + this.timeout(1000); + + process.env['INPUT_UNITTESTINPUT'] = 'test value'; + im._loadData(); + + var inval = tl.getInputRequired('UnitTestInput'); + assert.strictEqual(inval, 'test value'); + + done(); + }) + it('should clear input envvar', function (done) { + this.timeout(1000); + + process.env['INPUT_UNITTESTINPUT'] = 'test value'; + im._loadData(); + var inval = tl.getInputRequired('UnitTestInput'); + assert.strictEqual(inval, 'test value'); + assert(!process.env['INPUT_UNITTESTINPUT'], 'input envvar should be cleared'); + + done(); + }) + it('required input throws', function (done) { + this.timeout(1000); + + var worked: boolean = false; + try { + var inval = tl.getInputRequired('SomeUnsuppliedRequiredInput'); + worked = true; + } + catch (err) { } + + assert(!worked, 'req input should have not have worked'); + + done(); + }) + + // getVariable tests + it('gets a variable', function (done) { + this.timeout(1000); + + process.env['BUILD_REPOSITORY_NAME'] = 'Test Repository'; + im._loadData(); + + var varVal = tl.getVariable('Build.Repository.Name'); + assert.equal(varVal, 'Test Repository'); + + done(); + }) + it('gets a secret variable', function (done) { + this.timeout(1000); + + process.env['SECRET_BUILD_REPOSITORY_NAME'] = 'Test Repository'; + im._loadData(); + + var varVal = tl.getVariable('Build.Repository.Name'); + assert.equal(varVal, 'Test Repository'); + + done(); + }) + it('gets a secret variable while variable also exist', function (done) { + this.timeout(1000); + + process.env['BUILD_REPOSITORY_NAME'] = 'Test Repository'; + process.env['SECRET_BUILD_REPOSITORY_NAME'] = 'Secret Test Repository'; + im._loadData(); + + var varVal = tl.getVariable('Build.Repository.Name'); + assert.equal(varVal, 'Secret Test Repository'); + + done(); + }) + it('gets a variable with special characters', (done) => { + this.timeout(1000); + + let expected = 'Value of var with special chars'; + process.env['HELLO_DOT_DOT2_SPACE_SPACE2'] = expected; + im._loadData(); + + let varVal = tl.getVariable('Hello.dot.dot2 space space2'); + assert.equal(varVal, expected); + + done(); + }) + + // setVariable tests + it('sets a variable as an env var', function (done) { + this.timeout(1000); + + tl.setVariable('Build.Repository.Uri', 'test value'); + let varVal: string = process.env['BUILD_REPOSITORY_URI']; + assert.equal(varVal, 'test value'); + + done(); + }) + it('sets a variable with special chars as an env var', (done) => { + this.timeout(1000); + + let expected = 'Set value of var with special chars'; + tl.setVariable('Hello.dot.dot2 space space2', expected); + let varVal: string = process.env['HELLO_DOT_DOT2_SPACE_SPACE2']; + assert.equal(varVal, expected); + + done(); + }) + it('sets and gets a variable', function (done) { + this.timeout(1000); + + tl.setVariable('UnitTestVariable', 'test var value'); + let varVal: string = tl.getVariable('UnitTestVariable'); + assert.equal(varVal, 'test var value'); + + done(); + }) + it('sets and gets an output variable', function (done) { + this.timeout(1000); + + tl.setVariable('UnitTestVariable', 'test var value', false, true); + let varVal: string = tl.getVariable('UnitTestVariable'); + assert.equal(varVal, 'test var value'); + + done(); + }) + it('sets and gets a secret variable', function (done) { + this.timeout(1000); + + tl.setVariable('My.Secret.Var', 'test secret value', true); + let varVal: string = tl.getVariable('My.Secret.Var'); + assert.equal(varVal, 'test secret value'); + + done(); + }) + it('does not set a secret variable as an env var', function (done) { + this.timeout(1000); + + delete process.env['MY_SECRET_VAR']; + tl.setVariable('My.Secret.Var', 'test secret value', true); + let envVal: string = process.env['MY_SECRET_VAR']; + assert(!envVal, 'env var should not be set'); + + done(); + }) + it('removes env var when sets a secret variable', function (done) { + this.timeout(1000); + + process.env['MY_SECRET_VAR'] = 'test env value'; + tl.setVariable('My.Secret.Var', 'test secret value', true); + let envVal: string = process.env['MY_SECRET_VAR']; + assert(!envVal, 'env var should not be set'); + + done(); + }) + it('does not allow a secret variable to become a public variable', function (done) { + this.timeout(1000); + + im._loadData(); + tl.setVariable('My.Secret.Var', 'test secret value', true); + tl.setVariable('My.Secret.Var', 'test modified value', false); + let vars: tl.VariableInfo[] = tl.getVariables(); + assert.equal(vars.length, 1); + assert.equal(vars[0].name, 'My.Secret.Var'); + assert.equal(vars[0].value, 'test modified value'); + assert.equal(vars[0].secret, true); + + done(); + }) + it('allows a public variable to become a secret variable', function (done) { + this.timeout(1000); + + im._loadData(); + tl.setVariable('My.Var', 'test value', false); + tl.setVariable('My.Var', 'test modified value', true); + let vars: tl.VariableInfo[] = tl.getVariables(); + assert.equal(vars.length, 1); + assert.equal(vars[0].name, 'My.Var'); + assert.equal(vars[0].value, 'test modified value'); + assert.equal(vars[0].secret, true); + + done(); + }) + it('tracks known variables using env formatted name', function (done) { + this.timeout(1000); + + im._loadData(); + tl.setVariable('My.Public.Var', 'test value'); + tl.setVariable('my_public.VAR', 'test modified value'); + let vars: tl.VariableInfo[] = tl.getVariables(); + assert.equal(vars.length, 1); + assert.equal(vars[0].name, 'my_public.VAR'); + assert.equal(vars[0].value, 'test modified value'); + + done(); + }) + it('does not allow setting a multi-line secret variable', function (done) { + this.timeout(1000); + + im._loadData(); + + // test carriage return + let failed = false; + try { + tl.setVariable('my.secret', 'line1\rline2', true); + } + catch (err) { + failed = true; + } + assert(failed, 'Should have failed setting a secret variable with a carriage return'); + + // test line feed + failed = false; + try { + tl.setVariable('my.secret', 'line1\nline2', true); + } + catch (err) { + failed = true; + } + assert(failed, 'Should have failed setting a secret variable with a line feed'); + + done(); + }) + it('allows unsafe setting a multi-line secret variable', function (done) { + this.timeout(1000); + + im._loadData(); + try { + process.env['SYSTEM_UNSAFEALLOWMULTILINESECRET'] = 'true'; + tl.setVariable('my.secret', 'line1\r\nline2', true); + } + finally { + delete process.env['SYSTEM_UNSAFEALLOWMULTILINESECRET']; + } + + assert.equal(tl.getVariable('my.secret'), 'line1\r\nline2'); + + done(); + }) + + // setSecret tests + it('does not allow setting a multi-line secret', function (done) { + this.timeout(1000); + + im._loadData(); + + // test carriage return + let failed = false; + try { + tl.setSecret('line1\rline2'); + } + catch (err) { + failed = true; + } + assert(failed, 'Should have failed setting a secret with a carriage return'); + + // test line feed + failed = false; + try { + tl.setSecret('line1\nline2'); + } + catch (err) { + failed = true; + } + assert(failed, 'Should have failed setting a secret with a line feed'); + + done(); + }) + it('allows unsafe setting a multi-line secret', function (done) { + this.timeout(1000); + + im._loadData(); + try { + process.env['SYSTEM_UNSAFEALLOWMULTILINESECRET'] = 'true'; + tl.setSecret('line1\r\nline2'); + } + finally { + delete process.env['SYSTEM_UNSAFEALLOWMULTILINESECRET']; + } + + done(); + }) + + // getVariables tests + it('gets public variables from initial load', function (done) { + this.timeout(1000); + + process.env['PUBLIC_VAR_ONE'] = 'public value 1'; + process.env['PUBLIC_VAR_TWO'] = 'public value 2'; + process.env['VSTS_PUBLIC_VARIABLES'] = '[ "Public.Var.One", "Public.Var.Two" ]'; + im._loadData(); + let vars: tl.VariableInfo[] = tl.getVariables(); + assert(vars, 'variables should not be undefined or null'); + assert.equal(vars.length, 2, 'exactly 2 variables should be returned'); + vars = vars.sort((a: tl.VariableInfo, b: tl.VariableInfo) => a.name.localeCompare(b.name)); + assert.equal(vars[0].name, 'Public.Var.One'); + assert.equal(vars[0].value, 'public value 1'); + assert.equal(vars[0].secret, false); + assert.equal(vars[1].name, 'Public.Var.Two'); + assert.equal(vars[1].value, 'public value 2'); + assert.equal(vars[1].secret, false); + + done(); + }) + it('gets secret variables from initial load', function (done) { + this.timeout(1000); + + process.env['SECRET_SECRET_VAR_ONE'] = 'secret value 1'; + process.env['SECRET_SECRET_VAR_TWO'] = 'secret value 2'; + process.env['VSTS_SECRET_VARIABLES'] = '[ "Secret.Var.One", "Secret.Var.Two" ]'; + im._loadData(); + let vars: tl.VariableInfo[] = tl.getVariables(); + assert(vars, 'variables should not be undefined or null'); + assert.equal(vars.length, 2, 'exactly 2 variables should be returned'); + vars = vars.sort((a: tl.VariableInfo, b: tl.VariableInfo) => a.name.localeCompare(b.name)); + assert.equal(vars[0].name, 'Secret.Var.One'); + assert.equal(vars[0].value, 'secret value 1'); + assert.equal(vars[0].secret, true); + assert.equal(vars[1].name, 'Secret.Var.Two'); + assert.equal(vars[1].value, 'secret value 2'); + assert.equal(vars[1].secret, true); + + done(); + }) + it('gets secret variables from initial load in pre 2.104.1 agent', function (done) { + this.timeout(1000); + + process.env['SECRET_SECRET_VAR_ONE'] = 'secret value 1'; + process.env['SECRET_SECRET_VAR_TWO'] = 'secret value 2'; + im._loadData(); + let vars: tl.VariableInfo[] = tl.getVariables(); + assert(vars, 'variables should not be undefined or null'); + assert.equal(vars.length, 2, 'exactly 2 variables should be returned'); + vars = vars.sort((a: tl.VariableInfo, b: tl.VariableInfo) => a.name.localeCompare(b.name)); + assert.equal(vars[0].name, 'SECRET_VAR_ONE'); + assert.equal(vars[0].value, 'secret value 1'); + assert.equal(vars[0].secret, true); + assert.equal(vars[1].name, 'SECRET_VAR_TWO'); + assert.equal(vars[1].value, 'secret value 2'); + assert.equal(vars[1].secret, true); + + done(); + }) + it('gets public variables from setVariable', function (done) { + this.timeout(1000); + + process.env['INITIAL_PUBLIC_VAR'] = 'initial public value'; + process.env['VSTS_PUBLIC_VARIABLES'] = '[ "Initial.Public.Var" ]'; + im._loadData(); + tl.setVariable('Set.Public.Var', 'set public value'); + let vars: tl.VariableInfo[] = tl.getVariables(); + assert(vars, 'variables should not be undefined or null'); + assert.equal(vars.length, 2, 'exactly 4 variables should be returned'); + vars = vars.sort((a: tl.VariableInfo, b: tl.VariableInfo) => a.name.localeCompare(b.name)); + assert.equal(vars[0].name, 'Initial.Public.Var'); + assert.equal(vars[0].value, 'initial public value'); + assert.equal(vars[0].secret, false); + assert.equal(vars[1].name, 'Set.Public.Var'); + assert.equal(vars[1].value, 'set public value'); + assert.equal(vars[1].secret, false); + + done(); + }) + it('gets secret variables from setVariable', function (done) { + this.timeout(1000); + + process.env['SECRET_INITIAL_SECRET_VAR'] = 'initial secret value'; + process.env['VSTS_SECRET_VARIABLES'] = '[ "Initial.Secret.Var" ]'; + im._loadData(); + tl.setVariable('Set.Secret.Var', 'set secret value', true); + let vars: tl.VariableInfo[] = tl.getVariables(); + assert(vars, 'variables should not be undefined or null'); + assert.equal(vars.length, 2, 'exactly 2 variables should be returned'); + vars = vars.sort((a: tl.VariableInfo, b: tl.VariableInfo) => a.name.localeCompare(b.name)); + assert.equal(vars[0].name, 'Initial.Secret.Var'); + assert.equal(vars[0].value, 'initial secret value'); + assert.equal(vars[0].secret, true); + assert.equal(vars[1].name, 'Set.Secret.Var'); + assert.equal(vars[1].value, 'set secret value'); + assert.equal(vars[1].secret, true); + + done(); + }) + + // getEndpointUrl/getEndpointAuthorization/getEndpointData tests + it('gets an endpoint url', function (done) { + this.timeout(1000); + + process.env['ENDPOINT_URL_id1'] = 'http://url'; + im._loadData(); + + var url = tl.getEndpointUrl('id1', true); + assert.equal(url, 'http://url', 'url should match'); + + done(); + }) + it('gets a required endpoint url', function (done) { + this.timeout(1000); + + process.env['ENDPOINT_URL_id1'] = 'http://url'; + im._loadData(); + + var url = tl.getEndpointUrlRequired('id1'); + assert.equal(url, 'http://url', 'url should match'); + + done(); + }) + it('required endpoint url throws', function (done) { + this.timeout(1000); + + im._loadData(); + + var worked: boolean = false; + try { + var url = tl.getEndpointUrlRequired('SomeUnsuppliedRequiredInput'); + worked = true; + } + catch (err) { } + + assert(!worked, 'req endpoint url should have not have worked'); + + done(); + }) + it('gets an endpoint auth', function (done) { + this.timeout(1000); + + process.env['ENDPOINT_AUTH_id1'] = '{ "parameters": {"param1": "val1", "param2": "val2"}, "scheme": "UsernamePassword"}'; + im._loadData(); + + var auth = tl.getEndpointAuthorization('id1', true); + assert(auth, 'should return an auth obj'); + assert.equal(auth['parameters']['param1'], 'val1', 'should be correct object'); + + done(); + }) + it('gets null if endpoint auth not set', function (done) { + this.timeout(1000); + + // don't set + im._loadData(); + + var auth = tl.getEndpointAuthorization('id1', true); + assert.equal(auth, null, 'should not return an auth obj'); + + done(); + }) + it('should clear auth envvar', function (done) { + this.timeout(1000); + + process.env['ENDPOINT_AUTH_id1'] = '{ "parameters": {"param1": "val1", "param2": "val2"}, "scheme": "UsernamePassword"}'; + im._loadData(); + var auth = tl.getEndpointAuthorization('id1', true); + assert(auth, 'should return an auth obj'); + assert(auth['parameters']['param1'] === 'val1', 'should be correct object'); + assert(!process.env['ENDPOINT_AUTH_id1'], 'should clear auth envvar'); + + done(); + }) + it('gets endpoint auth scheme', function (done) { + this.timeout(1000); + process.env['ENDPOINT_AUTH_SCHEME_id1'] = 'scheme1'; + im._loadData(); + + var data = tl.getEndpointAuthorizationScheme('id1', true); + assert(data, 'should return a string value'); + assert.equal(data, 'scheme1', 'should be correct scheme'); + assert(!process.env['ENDPOINT_AUTH_SCHEME_id1'], 'should clear auth envvar'); + + done(); + }) + it('gets undefined if endpoint auth scheme is not set', function (done) { + this.timeout(1000); + im._loadData(); + + var data = tl.getEndpointAuthorizationScheme('id1', true); + assert(!data, 'should be undefined when auth scheme is not set'); + + done(); + }) + it('gets required endpoint auth scheme', function (done) { + this.timeout(1000); + process.env['ENDPOINT_AUTH_SCHEME_id1'] = 'scheme1'; + im._loadData(); + + var data = tl.getEndpointAuthorizationSchemeRequired('id1'); + assert(data, 'should return a string value'); + assert.equal(data, 'scheme1', 'should be correct scheme'); + assert(!process.env['ENDPOINT_AUTH_SCHEME_id1'], 'should clear auth envvar'); + + done(); + }) + it('required endpoint auth scheme throws', function (done) { + this.timeout(1000); + + im._loadData(); + + var worked: boolean = false; + try { + var data = tl.getEndpointAuthorizationSchemeRequired('SomeUnsuppliedRequiredInput'); + worked = true; + } + catch (err) { } + + assert(!worked, 'req endpoint auth scheme should have not have worked'); + + done(); + }) + it('gets endpoint auth parameters', function (done) { + this.timeout(1000); + process.env['ENDPOINT_AUTH_PARAMETER_id1_PARAM1'] = 'value1'; + im._loadData(); + + var data = tl.getEndpointAuthorizationParameter('id1', 'param1', true); + assert(data, 'should return a string value'); + assert.equal(data, 'value1', 'should be correct auth param'); + assert(!process.env['ENDPOINT_AUTH_PARAMETER_id1_PARAM1'], 'should clear auth envvar'); + + done(); + }) + it('gets undefined if endpoint auth parameter is not set', function (done) { + this.timeout(1000); + im._loadData(); + + var data = tl.getEndpointAuthorizationParameter('id1', 'noparam', true); + assert(!data, 'should be undefined when auth param is not set'); + + done(); + }) + it('gets required endpoint auth parameters', function (done) { + this.timeout(1000); + process.env['ENDPOINT_AUTH_PARAMETER_id1_PARAM1'] = 'value1'; + im._loadData(); + + var data = tl.getEndpointAuthorizationParameterRequired('id1', 'param1'); + assert(data, 'should return a string value'); + assert.equal(data, 'value1', 'should be correct auth param'); + assert(!process.env['ENDPOINT_AUTH_PARAMETER_id1_PARAM1'], 'should clear auth envvar'); + + done(); + }) + it('throws if endpoint auth parameter is not set', function (done) { + this.timeout(1000); + im._loadData(); + + var worked: boolean = false; + try { + var data = tl.getEndpointAuthorizationParameterRequired('id1', 'noparam'); + worked = true; + } + catch (err) { } + + assert(!worked, 'get endpoint authorization parameter should have not have worked'); + + done(); + }) + it('gets an endpoint data', function (done) { + this.timeout(1000); + process.env['ENDPOINT_DATA_id1_PARAM1'] = 'val1'; + im._loadData(); + + var data = tl.getEndpointDataParameter('id1', 'param1', true); + assert(data, 'should return a string value'); + assert.equal(data, 'val1', 'should be correct object'); + + done(); + }) + it('gets undefined if endpoint data is not set', function (done) { + this.timeout(1000); + im._loadData(); + + var data = tl.getEndpointDataParameter('id1', 'noparam', true); + assert.equal(data, undefined, 'Error should occur if endpoint data is not set'); + + done(); + }) + it('gets required endpoint data', function (done) { + this.timeout(1000); + process.env['ENDPOINT_DATA_id1_PARAM1'] = 'val1'; + im._loadData(); + + var data = tl.getEndpointDataParameterRequired('id1', 'param1'); + assert(data, 'should return a string value'); + assert.equal(data, 'val1', 'should be correct object'); + + done(); + }) + it('throws if endpoint data is not set', function (done) { + this.timeout(1000); + im._loadData(); + + var worked: boolean = false; + try { + var data = tl.getEndpointDataParameterRequired('id1', 'noparam'); + worked = true; + } + catch (err) { } + + assert(!worked, 'get endpoint data should have not have worked'); + + done(); + }) + // getSecureFileName/getSecureFileTicket/getSecureFiles tests + it('gets a secure file name', function (done) { + this.timeout(1000); + + process.env['SECUREFILE_NAME_10'] = 'securefile10.p12'; + im._loadData(); + + var name = tl.getSecureFileName('10'); + assert.equal(name, 'securefile10.p12', 'name should match'); + + done(); + }) + it('gets a secure file ticket', function (done) { + this.timeout(1000); + + process.env['SECUREFILE_TICKET_10'] = 'rsaticket10'; + im._loadData(); + + var ticket = tl.getSecureFileTicket('10'); + assert(ticket, 'should return a string value'); + assert.equal(ticket, 'rsaticket10', 'should be correct ticket'); + assert(!process.env['SECUREFILE_TICKET_10'], 'should clear ticket envvar'); + + done(); + }) + // getBoolInput tests + it('gets case insensitive true bool input value', function (done) { + this.timeout(1000); + + var inputValue = 'tRuE'; + process.env['INPUT_ABOOL'] = inputValue; + im._loadData(); + + var outVal = tl.getBoolInput('abool', /*required=*/true); + assert(outVal, 'should return true'); + + done(); + }) + it('gets false bool input value', function (done) { + this.timeout(1000); + + var inputValue = 'false'; + process.env['INPUT_ABOOL'] = inputValue; + im._loadData(); + + var outVal = tl.getBoolInput('abool', /*required=*/true); + assert(!outVal, 'should return false'); + + done(); + }) + it('defaults to false bool input value', function (done) { + this.timeout(1000); + + im._loadData(); + + var outVal = tl.getBoolInput('no_such_env_var', /*required=*/ false); + assert(!outVal, 'should default to false'); + + done(); + }) + + // getDelimitedInput tests + it('gets delimited input values removes empty values', function (done) { + this.timeout(1000); + + var inputValue = 'test value'; // contains two spaces + process.env['INPUT_DELIM'] = inputValue; + im._loadData(); + + var outVal = tl.getDelimitedInput('delim', ' ', /*required*/true); + assert.equal(outVal.length, 2, 'should return array with two elements'); + assert.equal(outVal[0], 'test', 'should return correct element 1'); + assert.equal(outVal[1], 'value', 'should return correct element 2'); + + done(); + }) + it('gets delimited input for a single value', function (done) { + this.timeout(1000); + + var inputValue = 'testvalue'; + process.env['INPUT_DELIM'] = inputValue; + im._loadData(); + + var outVal = tl.getDelimitedInput('delim', ' ', /*required*/true); + assert.equal(outVal.length, 1, 'should return array with one element'); + assert.equal(outVal[0], 'testvalue', 'should return correct element 1'); + + done(); + }) + it('gets delimited input for an empty value', function (done) { + this.timeout(1000); + + var inputValue = ''; + process.env['INPUT_DELIM'] = inputValue; + im._loadData(); + + var outVal = tl.getDelimitedInput('delim', ' ', /*required*/false); + assert.equal(outVal.length, 0, 'should return array with zero elements'); + + done(); + }) + it('gets delimited input with a Regexp', function (done) { + this.timeout(1000); + + var inputValue = 'a,b\nc'; + process.env['INPUT_DELIM'] = inputValue; + im._loadData(); + + var outVal = tl.getDelimitedInput('delim', /[,\n]/, /*required*/false); + assert.equal(outVal.length, 3, 'should return array with 3 elements'); + + done(); + }) + + // getPathInput tests + it('gets path input value', function (done) { + this.timeout(1000); + + var inputValue = 'test.txt' + process.env['INPUT_PATH1'] = inputValue; + im._loadData(); + + var path = tl.getPathInput('path1', /*required=*/true, /*check=*/false); + assert(path, 'should return a path'); + assert.equal(path, inputValue, 'test path value'); + + done(); + }) + it('throws if required path not supplied', function (done) { + this.timeout(1000); + + var stdStream = testutil.createStringStream(); + tl.setStdStream(stdStream); + + var worked: boolean = false; + try { + var path = tl.getPathInput(null, /*required=*/true, /*check=*/false); + worked = true; + } + catch (err) { } + + assert(!worked, 'req path should have not have worked'); + + done(); + }) + it('get invalid checked path throws', function (done) { + this.timeout(1000); + + var stdStream = testutil.createStringStream(); + tl.setStdStream(stdStream); + + var worked: boolean = false; + try { + var path = tl.getPathInput('some_missing_path', /*required=*/true, /*check=*/true); + worked = true; + } + catch (err) { } + + assert(!worked, 'invalid checked path should have not have worked'); + + done(); + }) + it('gets path invalid value not required', function (done) { + this.timeout(1000); + + var errStream = testutil.createStringStream(); + tl.setErrStream(errStream); + + var path = tl.getPathInput('some_missing_path', /*required=*/false, /*check=*/false); + assert(!path, 'should not return a path'); + + var errMsg = errStream.getContents(); + assert.equal(errMsg, "", "no err") + + done(); + }) + it('gets path input value with space', function (done) { + this.timeout(1000); + + var inputValue = 'file name.txt'; + var expectedValue = 'file name.txt'; + process.env['INPUT_PATH1'] = inputValue; + im._loadData(); + + var path = tl.getPathInput('path1', /*required=*/true, /*check=*/false); + assert(path, 'should return a path'); + assert.equal(path, expectedValue, 'returned ' + path + ', expected: ' + expectedValue); + + done(); + }) + it('gets path value with check and exist', function (done) { + this.timeout(1000); + + var errStream = testutil.createStringStream(); + tl.setErrStream(errStream); + + var inputValue = __filename; + process.env['INPUT_PATH1'] = inputValue; + im._loadData(); + + var path = tl.getPathInput('path1', /*required=*/true, /*check=*/true); + assert(path, 'should return a path'); + assert.equal(path, inputValue, 'test path value'); + + var errMsg = errStream.getContents(); + assert(errMsg === "", "no err") + + done(); + }) + it('gets path value with check and not exist', function (done) { + this.timeout(1000); + + var stdStream = testutil.createStringStream(); + tl.setStdStream(stdStream); + + var inputValue = "someRandomFile.txt"; + process.env['INPUT_PATH1'] = inputValue; + im._loadData(); + + var worked: boolean = false; + try { + var path = tl.getPathInput('path1', /*required=*/true, /*check=*/true); + worked = true; + } + catch (err) { + assert(err.message.indexOf("Not found") >= 0, "error should have said Not found"); + } + assert(!worked, 'invalid checked path should have not have worked'); + + done(); + }) + + // getPathInputRequired tests + it('gets path input required value', function (done) { + this.timeout(1000); + + var inputValue = 'test.txt' + process.env['INPUT_PATH1'] = inputValue; + im._loadData(); + + var path = tl.getPathInputRequired('path1', /*check=*/false); + assert(path, 'should return a path'); + assert.equal(path, inputValue, 'test path value'); + + done(); + }) + it('throws if required path not supplied', function (done) { + this.timeout(1000); + + var stdStream = testutil.createStringStream(); + tl.setStdStream(stdStream); + + var worked: boolean = false; + try { + var path = tl.getPathInputRequired(null, /*check=*/false); + worked = true; + } + catch (err) { } + + assert(!worked, 'req path should have not have worked'); + + done(); + }) + it('get required path invalid checked throws', function (done) { + this.timeout(1000); + + var stdStream = testutil.createStringStream(); + tl.setStdStream(stdStream); + + var worked: boolean = false; + try { + var path = tl.getPathInputRequired('some_missing_path', /*check=*/true); + worked = true; + } + catch (err) { } + + assert(!worked, 'invalid checked path should have not have worked'); + + done(); + }) + it('gets path input required value with space', function (done) { + this.timeout(1000); + + var inputValue = 'file name.txt'; + var expectedValue = 'file name.txt'; + process.env['INPUT_PATH1'] = inputValue; + im._loadData(); + + var path = tl.getPathInputRequired('path1', /*check=*/false); + assert(path, 'should return a path'); + assert.equal(path, expectedValue, 'returned ' + path + ', expected: ' + expectedValue); + + done(); + }) + it('gets path required value with check and exist', function (done) { + this.timeout(1000); + + var errStream = testutil.createStringStream(); + tl.setErrStream(errStream); + + var inputValue = __filename; + process.env['INPUT_PATH1'] = inputValue; + im._loadData(); + + var path = tl.getPathInputRequired('path1', /*check=*/true); + assert(path, 'should return a path'); + assert.equal(path, inputValue, 'test path value'); + + var errMsg = errStream.getContents(); + assert(errMsg === "", "no err") + + done(); + }) + it('gets path required value with check and not exist', function (done) { + this.timeout(1000); + + var stdStream = testutil.createStringStream(); + tl.setStdStream(stdStream); + + var inputValue = "someRandomFile.txt"; + process.env['INPUT_PATH1'] = inputValue; + im._loadData(); + + var worked: boolean = false; + try { + var path = tl.getPathInputRequired('path1', /*check=*/true); + worked = true; + } + catch (err) { + assert(err.message.indexOf("Not found") >= 0, "error should have said Not found"); + } + assert(!worked, 'invalid checked path should have not have worked'); + + done(); + }) + + // filePathSupplied tests + it('filePathSupplied checks not supplied', function (done) { + this.timeout(1000); + + var repoRoot = '/repo/root/dir'; + process.env['INPUT_PATH1'] = repoRoot; + im._loadData(); + + process.env['BUILD_SOURCESDIRECTORY'] = repoRoot; + var supplied = tl.filePathSupplied('path1'); + assert(!supplied, 'path1 should not be supplied'); + done(); + }) + it('filePathSupplied checks supplied', function (done) { + this.timeout(1000); + + var repoRoot = '/repo/root/dir'; + process.env['INPUT_PATH1'] = repoRoot + '/some/path'; + im._loadData(); + + process.env['BUILD_SOURCESDIRECTORY'] = repoRoot; + var supplied = tl.filePathSupplied('path1'); + assert(supplied, 'path1 should be supplied'); + done(); + }) + + // resolve tests + it('resolve', function (done) { + var absolutePath = tl.resolve('/repo/root', '/repo/root/some/path'); + if (os.platform() !== 'win32') { + assert(absolutePath === '/repo/root/some/path', 'absolute path not expected, got:' + absolutePath + ' expected: /repo/root/some/path'); + } else { + var winDrive = path.parse(path.resolve('')).root; + var expectedPath = winDrive.concat('repo\\root\\some\\path'); + assert.equal(absolutePath, expectedPath, 'absolute path not as expected, got: ' + absolutePath + ' expected: ' + expectedPath); + } + done(); + }) + + // getTaskVariable tests + it('gets a task variable', function (done) { + this.timeout(1000); + + let originalAgentVersion = process.env['AGENT_VERSION']; + try { + process.env['AGENT_VERSION'] = '2.115.0'; + process.env['VSTS_TASKVARIABLE_TEST1'] = 'Task variable value 1'; + im._loadData(); + var varVal = tl.getTaskVariable('test1'); + assert.equal(varVal, 'Task variable value 1'); + } + finally { + process.env['AGENT_VERSION'] = originalAgentVersion; + } + + done(); + }) + it('sets a task variable', function (done) { + this.timeout(1000); + + let originalAgentVersion = process.env['AGENT_VERSION']; + try { + process.env['AGENT_VERSION'] = '2.115.0'; + tl.setTaskVariable('test2', 'Task variable value 2'); + let varVal: string = tl.getTaskVariable('test2'); + assert.equal(varVal, 'Task variable value 2'); + } + finally { + process.env['AGENT_VERSION'] = originalAgentVersion; + } + + done(); + }) + + // assertAgent tests + it('assert agent does not fail when empty', function (done) { + this.timeout(1000); + + let originalAgentVersion = process.env['AGENT_VERSION']; + try { + process.env['AGENT_VERSION'] = ''; + let failed = false; + try { + tl.assertAgent('2.115.0'); + } + catch (err) { + failed = true; + } + + assert(!failed, 'assert should not have thrown'); + } + finally { + process.env['AGENT_VERSION'] = originalAgentVersion; + } + + done(); + }) + it('assert agent fails when less than', function (done) { + this.timeout(1000); + + let originalAgentVersion = process.env['AGENT_VERSION']; + try { + process.env['AGENT_VERSION'] = '2.114.0'; + let failed = false; + try { + tl.assertAgent('2.115.0'); + } + catch (err) { + failed = true; + } + + assert(failed, 'assert should have thrown'); + } + finally { + process.env['AGENT_VERSION'] = originalAgentVersion; + } + + done(); + }) + it('assert succeeds when greater or equal', function (done) { + this.timeout(1000); + + let originalAgentVersion = process.env['AGENT_VERSION']; + try { + process.env['AGENT_VERSION'] = '2.115.0'; + tl.assertAgent('2.115.0'); + process.env['AGENT_VERSION'] = '2.116.0'; + tl.assertAgent('2.115.0'); + } + finally { + process.env['AGENT_VERSION'] = originalAgentVersion; + } + + done(); + }) + + // _loadData tests + it('_loadData does not run twice', function (done) { + this.timeout(5000); + + // intialize an input (stored in vault) + process.env['INPUT_SOMEINPUT'] = 'some input value'; + im._loadData(); + assert.equal(tl.getInput('SomeInput'), 'some input value'); + + // copy azure-pipelines-task-lib to a different dir and load it from + // there so it will be forced to load again + let testDir = path.join(testutil.getTestTemp(), '_loadData-not-called-twice'); + tl.mkdirP(testDir); + tl.cp(path.join(__dirname, '..', '_build'), testDir, '-R'); + require(path.join(testDir, '_build', 'task')); + + assert.equal(tl.getInput('SomeInput'), 'some input value'); + done(); + }) + + describe('Feature flags tests', () => { + + ([ + ["true", true], + ["TRUE", true], + ["TruE", true], + ["false", false], + ["treu", false], + ["fasle", false], + ["On", false], + ["", false], + [undefined, false] + ] as [string, boolean][]) + .forEach( + ( + [ + input, + expectedResult + ] + ) => { + it(`Should return ${expectedResult} if feature flag env is ${input}`, () => { + const ffName = "SOME_TEST_FF" + process.env[ffName] = input + + const ffValue = tl.getBoolFeatureFlag(ffName, false); + + assert.equal(ffValue, expectedResult); + }) + } + ); + + it(`Should return default value if feature flag env is empty`, () => { + const ffName = "SOME_TEST_FF" + process.env[ffName] = "" + + const ffValue = tl.getBoolFeatureFlag(ffName, true); + + assert.equal(ffValue, true); + }) + + it(`Should return default value if feature flag env is not specified`, () => { + const ffName = "SOME_TEST_FF" + delete process.env[ffName]; + + const ffValue = tl.getBoolFeatureFlag(ffName, true); + + assert.equal(ffValue, true); + }) + }); + + describe('Pipeline features tests', () => { + it(`Should return if no feature variable present.`, () => { + const featureName = "TestFeature" + delete process.env[im._getVariableKey(`DistributedTask.Tasks.${featureName}`)]; + + const ffValue = tl.getPipelineFeature(featureName); + + assert.deepStrictEqual(ffValue, false); + }) + + const testInputs = ([ + ["true", true], + ["TRUE", true], + ["TruE", true], + ["false", false], + ["treu", false], + ["fasle", false], + ["On", false], + ["", false], + [undefined, false] + ] as [string, boolean][]) + for (const [input, expected] of testInputs) { + it(`Should return '${expected}' if feature is '${input}'`, () => { + const featureVariable = "DISTRIBUTEDTASK_TASKS_TESTFEATURE"; + const featureName = "TestFeature"; + process.env[featureVariable] = input; + + const result = tl.getPipelineFeature(featureName); + + assert.deepStrictEqual(result, expected); + }) + } + }) +}); diff --git a/node/test/ls.ts b/node/test/ls.ts index ba7627d9d..2481ceed8 100644 --- a/node/test/ls.ts +++ b/node/test/ls.ts @@ -74,11 +74,13 @@ describe('ls cases', () => { it('Provide the folder which does not exist', (done) => { assert.ok(!fs.existsSync('/thisfolderdoesnotexist')); assert.throws(() => tl.ls('/thisfolderdoesnotexist'), { message: /^Failed ls: Error: ENOENT: no such file or directory, lstat/ }); + done(); }); it('Without arguments', (done) => { const result = tl.ls(); + assert.ok(result.includes(TEMP_FILE_1)); assert.ok(result.includes(TEMP_FILE_1_JS)); assert.ok(result.includes(TEMP_FILE_2)); @@ -86,11 +88,13 @@ describe('ls cases', () => { assert.ok(result.includes(TEMP_FILE_3_ESCAPED)); assert.ok(result.includes(TEMP_SUBDIR_1)); assert.equal(result.length, 6); + done(); }); it('Passed TEMP_DIR_1 as an argument', (done) => { const result = tl.ls(TEMP_DIR_1); + assert.ok(result.includes(TEMP_FILE_1)); assert.ok(result.includes(TEMP_FILE_1_JS)); assert.ok(result.includes(TEMP_FILE_2)); @@ -98,19 +102,23 @@ describe('ls cases', () => { assert.ok(result.includes(TEMP_FILE_3_ESCAPED)); assert.ok(result.includes(TEMP_SUBDIR_1)); assert.equal(result.length, 6); + done(); }); it('Passed file as an argument', (done) => { const result = tl.ls(TEMP_FILE_1); + assert.ok(result.includes(TEMP_FILE_1)); assert.equal(result.length, 1); + done(); }); it('Provide the -A attribute as an argument', (done) => { tl.cd(TEMP_DIR_1); const result = tl.ls('-A'); + assert.ok(result.includes(TEMP_FILE_1)); assert.ok(result.includes(TEMP_FILE_1_JS)); assert.ok(result.includes(TEMP_FILE_2)); @@ -120,11 +128,13 @@ describe('ls cases', () => { assert.ok(result.includes(TEMP_HIDDEN_FILE_1)); assert.ok(result.includes(TEMP_HIDDEN_DIR_1)); assert.equal(result.length, 8); + done(); }); it('Wildcard for TEMP_DIR_1', (done) => { const result = tl.ls(path.join(TEMP_DIR_1, '*')); + assert.ok(result.includes(TEMP_FILE_1)); assert.ok(result.includes(TEMP_FILE_1_JS)); assert.ok(result.includes(TEMP_FILE_2)); @@ -133,55 +143,67 @@ describe('ls cases', () => { assert.ok(result.includes(TEMP_SUBDIR_FILE_1)); assert.ok(result.includes(TEMP_SUBDIR_FILELINK_1)); assert.equal(result.length, 7); + done(); }); it('Wildcard for find f*l*', (done) => { const result = tl.ls(path.join(TEMP_DIR_1, 'f*l*')); + assert.ok(result.includes(TEMP_FILE_1)); assert.ok(result.includes(TEMP_FILE_1_JS)); assert.ok(result.includes(TEMP_FILE_2)); assert.ok(result.includes(TEMP_FILE_2_JS)); assert.equal(result.length, 4); + done(); }); it('Wildcard f*l*.js', (done) => { const result = tl.ls(path.join(TEMP_DIR_1, 'f*l*.js')); + assert.ok(result.includes(TEMP_FILE_1_JS)); assert.ok(result.includes(TEMP_FILE_2_JS)); assert.equal(result.length, 2); + done(); }); it('Wildcard that is not valid', (done) => { const result = tl.ls(path.join(TEMP_DIR_1, '/*.j')); + assert.equal(result.length, 0); + done(); }); it('Wildcard *.*', (done) => { const result = tl.ls(path.join(TEMP_DIR_1, '*.*')); + assert.ok(result.includes(TEMP_FILE_1_JS)); assert.ok(result.includes(TEMP_FILE_2_JS)); assert.ok(result.includes(TEMP_FILE_3_ESCAPED)); assert.equal(result.length, 3); + done(); }); it('Two wildcards in the array', (done) => { const result = tl.ls([path.join(TEMP_DIR_1, 'f*le*.js'), path.join(TEMP_SUBDIR_1, '*')]); + assert.ok(result.includes(TEMP_FILE_1_JS)); assert.ok(result.includes(TEMP_FILE_2_JS)); assert.ok(result.includes(TEMP_SUBDIR_FILE_1)); assert.ok(result.includes(TEMP_SUBDIR_FILELINK_1)); assert.equal(result.length, 4); + done(); }); it('Recursive without path argument', (done) => { tl.cd(TEMP_DIR_1); const result = tl.ls('-R'); + assert.ok(result.includes(TEMP_FILE_1)); assert.ok(result.includes(TEMP_FILE_1_JS)); assert.ok(result.includes(TEMP_FILE_2)); @@ -190,11 +212,13 @@ describe('ls cases', () => { assert.ok(result.includes(TEMP_SUBDIR_FILE_1)); assert.ok(result.includes(TEMP_SUBDIR_FILELINK_1)); assert.equal(result.length, 7); + done(); }); it('Provide path and recursive attribute', (done) => { const result = tl.ls('-R', TEMP_DIR_1); + assert.ok(result.includes(TEMP_FILE_1)); assert.ok(result.includes(TEMP_FILE_1_JS)); assert.ok(result.includes(TEMP_FILE_2)); @@ -203,11 +227,13 @@ describe('ls cases', () => { assert.ok(result.includes(TEMP_SUBDIR_FILE_1)); assert.ok(result.includes(TEMP_SUBDIR_FILELINK_1)); assert.equal(result.length, 7); + done(); }); it('Provide path and -RA attributes', (done) => { const result = tl.ls('-RA', TEMP_DIR_1); + assert.ok(result.includes(TEMP_FILE_1)); assert.ok(result.includes(TEMP_FILE_1_JS)); assert.ok(result.includes(TEMP_FILE_2)); @@ -217,38 +243,48 @@ describe('ls cases', () => { assert.ok(result.includes(TEMP_HIDDEN_FILE_1)); assert.ok(result.includes(TEMP_SUBDIR_FILELINK_1)); assert.equal(result.length, 8); + done(); }); it('Priovide -RA attribute', (done) => { const result = tl.ls('-RA', TEMP_SUBDIR_1); + assert.ok(result.includes(TEMP_SUBDIR_FILELINK_1)); assert.equal(result.length, 2); + done(); }); it('Provide path and the -R attribute', (done) => { const result = tl.ls('-R', TEMP_SUBDIR_1); + assert.ok(result.includes(TEMP_SUBDIR_FILE_1)); assert.ok(result.includes(TEMP_SUBDIR_FILELINK_1)); assert.equal(result.length, 2); + done(); }); it('Empty attributes, but several paths', (done) => { const result = tl.ls('', TEMP_SUBDIR_1, TEMP_FILE_1); + assert.ok(result.includes(TEMP_FILE_1)); assert.ok(result.includes(TEMP_SUBDIR_FILE_1)); assert.ok(result.includes(TEMP_SUBDIR_FILELINK_1)); assert.equal(result.length, 3); + done(); }); it('New one folder without content', (done) => { tl.mkdirP('foo'); + assert.doesNotThrow(() => tl.ls('foo')); assert.equal(tl.ls('foo').length, 0); + tl.rmRF('foo'); + done(); }); }); \ No newline at end of file diff --git a/node/test/mv.ts b/node/test/mv.ts index 22ce7c9b5..394f91b85 100644 --- a/node/test/mv.ts +++ b/node/test/mv.ts @@ -9,7 +9,7 @@ const DIRNAME = __dirname; import * as testutil from './testutil'; describe('mv cases', () => { - const TEMP_DIR = fs.mkdtempSync(DIRNAME + '/'); + const TEMP_DIR = fs.mkdtempSync(DIRNAME + path.sep); let TEMP_FILE_1: string; let TEMP_FILE_1_JS: string; let TEMP_FILE_2: string; @@ -40,55 +40,91 @@ describe('mv cases', () => { }); after((done) => { - fs.rmSync(TEMP_DIR, { recursive: true }); - done(); - }); + tl.cd(DIRNAME); + tl.rmRF(TEMP_DIR); - it('Provide invalid arguments', (done) => { - // @ts-ignore - assert.doesNotThrow(() => tl.mv()); - // @ts-ignore - assert.doesNotThrow(() => tl.mv('file1')); - // @ts-ignore - assert.doesNotThrow(() => tl.mv('-f')); done(); }); it('Provide an unsupported option argument', (done) => { assert.ok(fs.existsSync('file1')); - assert.doesNotThrow(() => tl.mv('file1', 'file1', '-Z')); + assert.doesNotThrow(() => tl.mv('file1', 'file1', '-Z', true)); assert.ok(fs.existsSync('file1')); + done(); }); it('Provide a source that does not exist', (done) => { - assert.doesNotThrow(() => tl.mv('pathdoesntexist1', '..')); - assert.ok(!fs.existsSync('../pathdoesntexist2')); + assert.throws(() => tl.mv('pathdoesnotexist1', '..'), { message: /Failed mv: Error: Not found mv: pathdoesnotexist1/ }); + assert.ok(!fs.existsSync('../pathdoesnotexist2')); + done(); }); - it('Provide a source that does not exist', (done) => { - assert.doesNotThrow(() => tl.mv('pathdoesntexist1', 'pathdoesntexist2', '..')); - assert.ok(!fs.existsSync('../pathdoesntexist1')); - assert.ok(!fs.existsSync('../pathdoesntexist2')); + it('Provide a source that does not exist, continue on error', (done) => { + assert.doesNotThrow(() => tl.mv('pathdoesnotexist1', '..', '', true)); + assert.ok(!fs.existsSync('../pathdoesnotexist2')); + + done(); + }); + + it('Provide a source that does not exist 2', (done) => { + assert.throws(() => tl.mv('pathdoesnotexist1', 'pathdoesnotexist2'), { message: /Failed mv: Error: Not found mv: pathdoesnotexist1/ }); + assert.ok(!fs.existsSync('../pathdoesnotexist1')); + assert.ok(!fs.existsSync('../pathdoesnotexist2')); + + done(); + }); + + it('Provide a source that does not exist, continue on error', (done) => { + assert.doesNotThrow(() => tl.mv('pathdoesnotexist1', 'pathdoesnotexist2', '', true)); + assert.ok(!fs.existsSync('../pathdoesnotexist1')); + assert.ok(!fs.existsSync('../pathdoesnotexist2')); + done(); }); it('Provide a destination that already exist', (done) => { assert.ok(fs.existsSync('file1')); assert.ok(fs.existsSync('file2')); - assert.doesNotThrow(() => tl.mv('file1', 'file2')); + + assert.throws(() => tl.mv('file1', 'file2'), { message: /Failed mv: Error: File already exists at file2/ }); + assert.ok(fs.existsSync('file1')); assert.ok(fs.existsSync('file2')); + done(); }); it('Provide a wildcard when dest is file', (done) => { - assert.doesNotThrow(() => tl.mv('file*', 'file1')); + assert.throws(() => tl.mv('file*', 'file1'), { message: /Failed mv: Error: File already exists at file1/ }); + assert.ok(fs.existsSync('file1')); + assert.ok(fs.existsSync('file2')); + assert.ok(fs.existsSync('file1.js')); + assert.ok(fs.existsSync('file2.js')); + + done(); + }); + + it('Provide a destination that already exist, continue on error', (done) => { + assert.ok(fs.existsSync('file1')); + assert.ok(fs.existsSync('file2')); + + assert.doesNotThrow(() => tl.mv('file1', 'file2', '', true)); + + assert.ok(fs.existsSync('file1')); + assert.ok(fs.existsSync('file2')); + + done(); + }); + + it('Provide a wildcard when dest is file, continue on error', (done) => { + assert.doesNotThrow(() => tl.mv('file*', 'file1', '', true)); assert.ok(fs.existsSync('file1')); assert.ok(fs.existsSync('file2')); assert.ok(fs.existsSync('file1.js')); assert.ok(fs.existsSync('file2.js')); + done(); }); @@ -99,6 +135,7 @@ describe('mv cases', () => { assert.ok(fs.existsSync('file3')); tl.mv('file3', 'file1'); assert.ok(fs.existsSync('file1')); + done(); }); @@ -107,6 +144,7 @@ describe('mv cases', () => { assert.doesNotThrow(() => tl.mv('file1', 'file2', '-f')); assert.ok(!fs.existsSync('file1')); assert.ok(fs.existsSync('file2')); + done(); }); }); \ No newline at end of file diff --git a/node/test/popd.ts b/node/test/popd.ts index 51e3534fd..6548456ca 100644 --- a/node/test/popd.ts +++ b/node/test/popd.ts @@ -26,25 +26,27 @@ describe('popd cases', () => { const TEMP_DIR_PATH = path.resolve('test_pushd', 'nested', 'dir'); before(() => { - fs.mkdirSync(path.resolve(DIRNAME, TEMP_DIR_PATH, 'a'), { recursive: true }); - fs.mkdirSync(path.resolve(DIRNAME, TEMP_DIR_PATH, 'b', 'c'), { recursive: true }); + tl.mkdirP(path.resolve(DIRNAME, TEMP_DIR_PATH, 'a')); + tl.mkdirP(path.resolve(DIRNAME, TEMP_DIR_PATH, 'b', 'c')); }); beforeEach(() => { reset(); }); - after(() => { + tl.cd(DIRNAME); reset(); - fs.rmSync(path.resolve(DIRNAME, TEMP_DIR_PATH), { recursive: true }); + tl.rmRF(path.resolve(DIRNAME, TEMP_DIR_PATH)); }); it('The default usage', (done) => { tl.pushd(TEMP_DIR_PATH); const trail = tl.popd(); + assert.ok(process.cwd(), trail[0]); assert.deepEqual(trail, [DIRNAME]); + done(); }); @@ -52,11 +54,13 @@ describe('popd cases', () => { tl.pushd(TEMP_DIR_PATH); tl.pushd('a'); const trail = tl.popd(); + assert.ok(process.cwd(), trail[0]); assert.deepEqual(trail, [ path.resolve(DIRNAME, TEMP_DIR_PATH), DIRNAME, ]); + done(); }); @@ -65,49 +69,60 @@ describe('popd cases', () => { tl.pushd('b'); tl.pushd('c'); const trail = tl.popd(); + assert.ok(process.cwd(), trail[0]); assert.deepEqual(trail, [ path.resolve(TEMP_DIR_PATH, 'b'), TEMP_DIR_PATH, DIRNAME, ]); + done(); }); it('Valid by index', (done) => { tl.pushd(TEMP_DIR_PATH); const trail = tl.popd('+0'); + assert.ok(process.cwd(), trail[0]); assert.deepEqual(trail, [DIRNAME]); + done(); }); it('Using +1 option', (done) => { tl.pushd(TEMP_DIR_PATH); const trail = tl.popd('+1'); + assert.ok(process.cwd(), trail[0]); assert.deepEqual(trail, [path.resolve(DIRNAME, TEMP_DIR_PATH)]); + done(); }); it('Using -0 option', (done) => { const r = tl.pushd(TEMP_DIR_PATH); const trail = tl.popd('-0'); + assert.ok(process.cwd(), trail[0]); assert.deepEqual(trail, [path.resolve(DIRNAME, TEMP_DIR_PATH)]); + done(); }); it('Using -1 option', (done) => { tl.pushd(TEMP_DIR_PATH); const trail = tl.popd('-1'); + assert.ok(process.cwd(), trail[0]); assert.deepEqual(trail, [DIRNAME]); + done(); }); it('Using when stack is empty', (done) => { assert.throws(() => tl.popd(), { message: 'Failed popd: directory stack empty' }); + done(); }); @@ -115,9 +130,11 @@ describe('popd cases', () => { tl.cd(TEMP_DIR_PATH); tl.pushd('b'); const trail = tl.popd(); + assert.ok(trail[0], path.resolve(DIRNAME, TEMP_DIR_PATH)); assert.ok(process.cwd(), trail[0]); assert.throws(() => tl.popd(), { message: 'Failed popd: directory stack empty' }); + done(); }); }); \ No newline at end of file diff --git a/node/test/pushd.ts b/node/test/pushd.ts index ca40fef6f..09df41bf9 100644 --- a/node/test/pushd.ts +++ b/node/test/pushd.ts @@ -26,8 +26,8 @@ describe('pushd cases', () => { const TEMP_DIR_PATH = path.resolve(DIRNAME, 'test_pushd', 'nested', 'dir'); before(() => { - fs.mkdirSync(path.resolve(TEMP_DIR_PATH, 'a'), { recursive: true }); - fs.mkdirSync(path.resolve(TEMP_DIR_PATH, 'b', 'c'), { recursive: true }); + tl.mkdirP(path.resolve(TEMP_DIR_PATH, 'a')); + tl.mkdirP(path.resolve(TEMP_DIR_PATH, 'b', 'c')); }); beforeEach(() => { @@ -36,28 +36,33 @@ describe('pushd cases', () => { after(() => { reset(); - fs.rmSync(path.resolve(TEMP_DIR_PATH), { recursive: true }); + tl.cd(DIRNAME); + tl.rmRF(path.resolve(TEMP_DIR_PATH)); }); it('Push valid directories', (done) => { const trail = tl.pushd(TEMP_DIR_PATH); + assert.equal(process.cwd(), trail[0]); assert.deepEqual(trail, [ TEMP_DIR_PATH, DIRNAME, ]); + done(); }); it('Using two directories', (done) => { tl.pushd(TEMP_DIR_PATH); const trail = tl.pushd('a'); + assert.equal(process.cwd(), trail[0]); assert.deepEqual(trail, [ path.resolve(TEMP_DIR_PATH, 'a'), TEMP_DIR_PATH, DIRNAME, ]); + done(); }); @@ -65,6 +70,7 @@ describe('pushd cases', () => { tl.pushd(TEMP_DIR_PATH); tl.pushd('a'); const trail = tl.pushd('../b'); + assert.equal(process.cwd(), trail[0]); assert.deepEqual(trail, [ path.resolve(TEMP_DIR_PATH, 'b'), @@ -72,6 +78,7 @@ describe('pushd cases', () => { TEMP_DIR_PATH, DIRNAME, ]); + done(); }); @@ -80,6 +87,7 @@ describe('pushd cases', () => { tl.pushd('a'); tl.pushd('../b'); const trail = tl.pushd('c'); + assert.equal(process.cwd(), trail[0]); assert.deepEqual(trail, [ path.resolve(TEMP_DIR_PATH, 'b', 'c'), @@ -88,6 +96,7 @@ describe('pushd cases', () => { TEMP_DIR_PATH, DIRNAME, ]); + done(); }); @@ -97,6 +106,7 @@ describe('pushd cases', () => { tl.pushd('../b'); tl.pushd('c'); const trail = tl.pushd('+0'); + assert.equal(process.cwd(), trail[0]); assert.deepEqual(trail, [ path.resolve(TEMP_DIR_PATH, 'b', 'c'), @@ -105,6 +115,7 @@ describe('pushd cases', () => { TEMP_DIR_PATH, DIRNAME, ]); + done(); }); @@ -114,6 +125,7 @@ describe('pushd cases', () => { tl.pushd('../b'); tl.pushd('c'); const trail = tl.pushd('+1'); + assert.equal(process.cwd(), trail[0]); assert.deepEqual(trail, [ path.resolve(TEMP_DIR_PATH, 'b'), @@ -122,6 +134,7 @@ describe('pushd cases', () => { DIRNAME, path.resolve(TEMP_DIR_PATH, 'b', 'c'), ]); + done(); }); @@ -131,6 +144,7 @@ describe('pushd cases', () => { tl.pushd('../b'); tl.pushd('c'); const trail = tl.pushd('+2'); + assert.equal(process.cwd(), trail[0]); assert.deepEqual(trail, [ path.resolve(TEMP_DIR_PATH, 'a'), @@ -139,6 +153,7 @@ describe('pushd cases', () => { path.resolve(TEMP_DIR_PATH, 'b', 'c'), path.resolve(TEMP_DIR_PATH, 'b'), ]); + done(); }); @@ -148,6 +163,7 @@ describe('pushd cases', () => { tl.pushd('../b'); tl.pushd('c'); const trail = tl.pushd('+3'); + assert.equal(process.cwd(), trail[0]); assert.deepEqual(trail, [ TEMP_DIR_PATH, @@ -156,6 +172,7 @@ describe('pushd cases', () => { path.resolve(TEMP_DIR_PATH, 'b'), path.resolve(TEMP_DIR_PATH, 'a'), ]); + done(); }); @@ -165,6 +182,7 @@ describe('pushd cases', () => { tl.pushd('../b'); tl.pushd('c'); const trail = tl.pushd('+4'); + assert.equal(process.cwd(), trail[0]); assert.deepEqual(trail, [ DIRNAME, @@ -173,6 +191,7 @@ describe('pushd cases', () => { path.resolve(TEMP_DIR_PATH, 'a'), TEMP_DIR_PATH, ]); + done(); }); @@ -182,6 +201,7 @@ describe('pushd cases', () => { tl.pushd('../b'); tl.pushd('c'); const trail = tl.pushd('-0'); + assert.equal(process.cwd(), trail[0]); assert.deepEqual(trail, [ DIRNAME, @@ -190,6 +210,7 @@ describe('pushd cases', () => { path.resolve(TEMP_DIR_PATH, 'a'), TEMP_DIR_PATH, ]); + done(); }); @@ -199,6 +220,7 @@ describe('pushd cases', () => { tl.pushd('../b'); tl.pushd('c'); const trail = tl.pushd('-1'); + assert.equal(process.cwd(), trail[0]); assert.deepEqual(trail, [ TEMP_DIR_PATH, @@ -207,6 +229,7 @@ describe('pushd cases', () => { path.resolve(TEMP_DIR_PATH, 'b'), path.resolve(TEMP_DIR_PATH, 'a'), ]); + done(); }); @@ -216,6 +239,7 @@ describe('pushd cases', () => { tl.pushd('../b'); tl.pushd('c'); const trail = tl.pushd('-2'); + assert.equal(process.cwd(), trail[0]); assert.deepEqual(trail, [ path.resolve(TEMP_DIR_PATH, 'a'), @@ -224,6 +248,7 @@ describe('pushd cases', () => { path.resolve(TEMP_DIR_PATH, 'b', 'c'), path.resolve(TEMP_DIR_PATH, 'b'), ]); + done(); }); @@ -233,6 +258,7 @@ describe('pushd cases', () => { tl.pushd('../b'); tl.pushd('c'); const trail = tl.pushd('-3'); + assert.equal(process.cwd(), trail[0]); assert.deepEqual(trail, [ path.resolve(TEMP_DIR_PATH, 'b'), @@ -241,6 +267,7 @@ describe('pushd cases', () => { DIRNAME, path.resolve(TEMP_DIR_PATH, 'b', 'c'), ]); + done(); }); @@ -250,6 +277,7 @@ describe('pushd cases', () => { tl.pushd('../b'); tl.pushd('c'); const trail = tl.pushd('-4'); + assert.equal(process.cwd(), trail[0]); assert.deepEqual(trail, [ path.resolve(TEMP_DIR_PATH, 'b', 'c'), @@ -258,27 +286,34 @@ describe('pushd cases', () => { TEMP_DIR_PATH, DIRNAME, ]); + done(); }); it('Using invalid directory', (done) => { const oldCwd = process.cwd(); + assert.throws(() => tl.pushd('does/not/exist'), { message: /^Failed pushd: no such file or directory:/ }); assert.equal(process.cwd(), oldCwd); + done(); }); it('Using without args swaps top two directories when stack length is 2', (done) => { let trail = tl.pushd(TEMP_DIR_PATH); + assert.equal(trail.length, 2); assert.equal(trail[0], TEMP_DIR_PATH); assert.equal(trail[1], DIRNAME); assert.equal(process.cwd(), trail[0]); + trail = tl.pushd(); + assert.equal(trail.length, 2); assert.equal(trail[0], DIRNAME); assert.equal(trail[1], TEMP_DIR_PATH); assert.equal(process.cwd(), trail[0]); + done(); }); @@ -286,16 +321,19 @@ describe('pushd cases', () => { tl.pushd(TEMP_DIR_PATH); tl.pushd(); const trail = tl.pushd(path.join(TEMP_DIR_PATH, 'a')); + assert.equal(trail.length, 3); assert.equal(trail[0], path.join(TEMP_DIR_PATH, 'a')); assert.equal(trail[1], DIRNAME); assert.equal(trail[2], TEMP_DIR_PATH); assert.equal(process.cwd(), trail[0]); + done(); }); it('Using without arguments invalid when stack is empty', (done) => { assert.throws(() => tl.pushd(), { message: 'Failed pushd: no other directory' }); + done(); }); }); \ No newline at end of file diff --git a/node/test/rm.ts b/node/test/rm.ts index 997999e4a..7ce91d20c 100644 --- a/node/test/rm.ts +++ b/node/test/rm.ts @@ -10,7 +10,7 @@ const DIRNAME = __dirname; import * as testutil from './testutil'; describe('rm cases', () => { - const TEMP_DIR = fs.mkdtempSync(DIRNAME + '/'); + const TEMP_DIR = fs.mkdtempSync(DIRNAME + path.sep); const TEMP_NESTED_DIR_LEVEL_1 = path.join(TEMP_DIR, 'a'); const TEMP_NESTED_DIR_FULL_TREE = path.join(TEMP_NESTED_DIR_LEVEL_1, 'b', 'c'); const TEMP_FILE_1 = path.join(TEMP_DIR, 'file1'); @@ -23,11 +23,14 @@ describe('rm cases', () => { } fs.writeFileSync(TEMP_FILE_1, 'test'); + done(); }); after((done) => { - fs.rmSync(TEMP_DIR, { recursive: true }); + tl.cd(DIRNAME) + tl.rmRF(TEMP_DIR); + done(); }); @@ -35,6 +38,7 @@ describe('rm cases', () => { // @ts-ignore assert.throws(() => tl.rmRF(), { message: 'Failed rmRF: The "path" argument must be of type string or an instance of Buffer or URL. Received undefined' }); assert.doesNotThrow(() => tl.rmRF('somefolderpaththatdoesnotexist')); + done(); }); @@ -42,22 +46,27 @@ describe('rm cases', () => { assert.ok(fs.existsSync(TEMP_FILE_1)); assert.doesNotThrow(() => tl.rmRF(TEMP_FILE_1)); assert.ok(!fs.existsSync(TEMP_FILE_1)); + done(); }); it('Remove subdirectory recursive at TEMP_NESTED_DIR_LEVEL_1', (done) => { tl.mkdirP(TEMP_NESTED_DIR_FULL_TREE); + assert.ok(fs.existsSync(TEMP_NESTED_DIR_FULL_TREE)); assert.doesNotThrow(() => tl.rmRF(TEMP_NESTED_DIR_LEVEL_1)); assert.ok(!fs.existsSync(TEMP_NESTED_DIR_LEVEL_1)); + done(); }); it('Remove subdirectory recursive at TEMP_NESTED_DIR_LEVEL_1 with absolute path', (done) => { tl.mkdirP(TEMP_NESTED_DIR_FULL_TREE); + assert.ok(fs.existsSync(TEMP_NESTED_DIR_FULL_TREE)); assert.doesNotThrow(() => tl.rmRF(path.resolve(`./${TEMP_NESTED_DIR_LEVEL_1}`))); assert.ok(!fs.existsSync(`./${TEMP_NESTED_DIR_LEVEL_1}`)); + done(); }); @@ -66,9 +75,11 @@ describe('rm cases', () => { tl.mkdirP(READONLY_DIR); fs.writeFileSync(path.join(READONLY_DIR, 'file'), 'test'); fs.chmodSync(path.join(READONLY_DIR, 'file'), '0444'); + assert.doesNotThrow(() => tl.rmRF(path.join(READONLY_DIR, 'file'))); assert.ok(!fs.existsSync(path.join(READONLY_DIR, 'file'))); assert.doesNotThrow(() => tl.rmRF(READONLY_DIR)); + done(); }); @@ -77,8 +88,10 @@ describe('rm cases', () => { fs.writeFileSync(path.join(TEMP_DIR, 'tree', 'file1'), 'test'); fs.writeFileSync(path.join(TEMP_DIR, 'tree', 'file2'), 'test'); fs.chmodSync(path.join(TEMP_DIR, 'tree', 'file1'), '0444'); + assert.doesNotThrow(() => tl.rmRF(path.join(TEMP_DIR, 'tree'))); assert.ok(!fs.existsSync(path.join(TEMP_DIR, 'tree'))); + done(); }); @@ -92,26 +105,33 @@ describe('rm cases', () => { fs.chmodSync(path.join(TEMP_TREE4_DIR, 'file'), '0444'); fs.chmodSync(path.join(TEMP_TREE4_DIR, 'subtree', 'file'), '0444'); fs.chmodSync(path.join(TEMP_TREE4_DIR, '.hidden', 'file'), '0444'); + assert.doesNotThrow(() => tl.rmRF(path.join(TEMP_DIR, 'tree4'))); assert.ok(!fs.existsSync(path.join(TEMP_DIR, 'tree4'))); + done(); }); it('Removing symbolic link to a directory', (done) => { fs.mkdirSync(path.join(TEMP_DIR, 'rm', 'a_dir'), { recursive: true }); fs.symlinkSync(path.join(TEMP_DIR, 'rm', 'a_dir'), path.join(TEMP_DIR, 'rm', 'link_to_a_dir'), 'dir'); + assert.doesNotThrow(() => tl.rmRF(path.join(TEMP_DIR, 'rm', 'link_to_a_dir'))); assert.ok(!fs.existsSync(path.join(TEMP_DIR, 'rm', 'link_to_a_dir'))); assert.ok(fs.existsSync(path.join(TEMP_DIR, 'rm', 'a_dir'))); - fs.rmSync(path.join(TEMP_DIR, 'rm'), { recursive: true }); + + tl.rmRF(path.join(TEMP_DIR, 'rm')); + done(); }); it('Remove path with relative non-normalized structure', (done) => { tl.mkdirP(TEMP_NESTED_DIR_FULL_TREE); + assert.ok(fs.existsSync(TEMP_NESTED_DIR_FULL_TREE)); assert.doesNotThrow(() => tl.rmRF(path.join(TEMP_DIR, 'a', '..', './a'))); assert.ok(!fs.existsSync(path.join(TEMP_DIR, 'a'))); + done(); }); }); \ No newline at end of file From 1b7facf63c432580a64b1096e7cf3c9d2755bc57 Mon Sep 17 00:00:00 2001 From: Ivan Duplenskikh <115665590+ivanduplenskikh@users.noreply.github.com> Date: Wed, 25 Dec 2024 13:27:56 +0100 Subject: [PATCH 03/11] Change crlf to lf --- node/test/inputtests.ts | 2474 +++++++++++++++++++-------------------- 1 file changed, 1237 insertions(+), 1237 deletions(-) diff --git a/node/test/inputtests.ts b/node/test/inputtests.ts index 5f42d496b..ce1061290 100644 --- a/node/test/inputtests.ts +++ b/node/test/inputtests.ts @@ -1,1237 +1,1237 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -import assert = require('assert'); -import path = require('path'); -import os = require('os'); -import * as tl from '../_build/task'; -import * as im from '../_build/internal'; -import testutil = require('./testutil'); - -describe('Input Tests', function () { - - before(function (done) { - try { - testutil.initialize(); - } - catch (err) { - assert.fail('Failed to load task lib: ' + err.message); - } - done(); - }); - - after(function () { - - }); - - it('gets input value', function (done) { - this.timeout(1000); - - process.env['INPUT_UNITTESTINPUT'] = 'test value'; - im._loadData(); - - var inval = tl.getInput('UnitTestInput', true); - assert.equal(inval, 'test value'); - - done(); - }) - it('should clear input envvar', function (done) { - this.timeout(1000); - - process.env['INPUT_UNITTESTINPUT'] = 'test value'; - im._loadData(); - var inval = tl.getInput('UnitTestInput', true); - assert.equal(inval, 'test value'); - assert(!process.env['INPUT_UNITTESTINPUT'], 'input envvar should be cleared'); - - done(); - }) - it('required input throws', function (done) { - this.timeout(1000); - - var worked: boolean = false; - try { - var inval = tl.getInput('SomeUnsuppliedRequiredInput', true); - worked = true; - } - catch (err) { } - - assert(!worked, 'req input should have not have worked'); - - done(); - }) - - it('gets input value', function (done) { - this.timeout(1000); - - process.env['INPUT_UNITTESTINPUT'] = 'test value'; - im._loadData(); - - var inval = tl.getInputRequired('UnitTestInput'); - assert.strictEqual(inval, 'test value'); - - done(); - }) - it('should clear input envvar', function (done) { - this.timeout(1000); - - process.env['INPUT_UNITTESTINPUT'] = 'test value'; - im._loadData(); - var inval = tl.getInputRequired('UnitTestInput'); - assert.strictEqual(inval, 'test value'); - assert(!process.env['INPUT_UNITTESTINPUT'], 'input envvar should be cleared'); - - done(); - }) - it('required input throws', function (done) { - this.timeout(1000); - - var worked: boolean = false; - try { - var inval = tl.getInputRequired('SomeUnsuppliedRequiredInput'); - worked = true; - } - catch (err) { } - - assert(!worked, 'req input should have not have worked'); - - done(); - }) - - // getVariable tests - it('gets a variable', function (done) { - this.timeout(1000); - - process.env['BUILD_REPOSITORY_NAME'] = 'Test Repository'; - im._loadData(); - - var varVal = tl.getVariable('Build.Repository.Name'); - assert.equal(varVal, 'Test Repository'); - - done(); - }) - it('gets a secret variable', function (done) { - this.timeout(1000); - - process.env['SECRET_BUILD_REPOSITORY_NAME'] = 'Test Repository'; - im._loadData(); - - var varVal = tl.getVariable('Build.Repository.Name'); - assert.equal(varVal, 'Test Repository'); - - done(); - }) - it('gets a secret variable while variable also exist', function (done) { - this.timeout(1000); - - process.env['BUILD_REPOSITORY_NAME'] = 'Test Repository'; - process.env['SECRET_BUILD_REPOSITORY_NAME'] = 'Secret Test Repository'; - im._loadData(); - - var varVal = tl.getVariable('Build.Repository.Name'); - assert.equal(varVal, 'Secret Test Repository'); - - done(); - }) - it('gets a variable with special characters', (done) => { - this.timeout(1000); - - let expected = 'Value of var with special chars'; - process.env['HELLO_DOT_DOT2_SPACE_SPACE2'] = expected; - im._loadData(); - - let varVal = tl.getVariable('Hello.dot.dot2 space space2'); - assert.equal(varVal, expected); - - done(); - }) - - // setVariable tests - it('sets a variable as an env var', function (done) { - this.timeout(1000); - - tl.setVariable('Build.Repository.Uri', 'test value'); - let varVal: string = process.env['BUILD_REPOSITORY_URI']; - assert.equal(varVal, 'test value'); - - done(); - }) - it('sets a variable with special chars as an env var', (done) => { - this.timeout(1000); - - let expected = 'Set value of var with special chars'; - tl.setVariable('Hello.dot.dot2 space space2', expected); - let varVal: string = process.env['HELLO_DOT_DOT2_SPACE_SPACE2']; - assert.equal(varVal, expected); - - done(); - }) - it('sets and gets a variable', function (done) { - this.timeout(1000); - - tl.setVariable('UnitTestVariable', 'test var value'); - let varVal: string = tl.getVariable('UnitTestVariable'); - assert.equal(varVal, 'test var value'); - - done(); - }) - it('sets and gets an output variable', function (done) { - this.timeout(1000); - - tl.setVariable('UnitTestVariable', 'test var value', false, true); - let varVal: string = tl.getVariable('UnitTestVariable'); - assert.equal(varVal, 'test var value'); - - done(); - }) - it('sets and gets a secret variable', function (done) { - this.timeout(1000); - - tl.setVariable('My.Secret.Var', 'test secret value', true); - let varVal: string = tl.getVariable('My.Secret.Var'); - assert.equal(varVal, 'test secret value'); - - done(); - }) - it('does not set a secret variable as an env var', function (done) { - this.timeout(1000); - - delete process.env['MY_SECRET_VAR']; - tl.setVariable('My.Secret.Var', 'test secret value', true); - let envVal: string = process.env['MY_SECRET_VAR']; - assert(!envVal, 'env var should not be set'); - - done(); - }) - it('removes env var when sets a secret variable', function (done) { - this.timeout(1000); - - process.env['MY_SECRET_VAR'] = 'test env value'; - tl.setVariable('My.Secret.Var', 'test secret value', true); - let envVal: string = process.env['MY_SECRET_VAR']; - assert(!envVal, 'env var should not be set'); - - done(); - }) - it('does not allow a secret variable to become a public variable', function (done) { - this.timeout(1000); - - im._loadData(); - tl.setVariable('My.Secret.Var', 'test secret value', true); - tl.setVariable('My.Secret.Var', 'test modified value', false); - let vars: tl.VariableInfo[] = tl.getVariables(); - assert.equal(vars.length, 1); - assert.equal(vars[0].name, 'My.Secret.Var'); - assert.equal(vars[0].value, 'test modified value'); - assert.equal(vars[0].secret, true); - - done(); - }) - it('allows a public variable to become a secret variable', function (done) { - this.timeout(1000); - - im._loadData(); - tl.setVariable('My.Var', 'test value', false); - tl.setVariable('My.Var', 'test modified value', true); - let vars: tl.VariableInfo[] = tl.getVariables(); - assert.equal(vars.length, 1); - assert.equal(vars[0].name, 'My.Var'); - assert.equal(vars[0].value, 'test modified value'); - assert.equal(vars[0].secret, true); - - done(); - }) - it('tracks known variables using env formatted name', function (done) { - this.timeout(1000); - - im._loadData(); - tl.setVariable('My.Public.Var', 'test value'); - tl.setVariable('my_public.VAR', 'test modified value'); - let vars: tl.VariableInfo[] = tl.getVariables(); - assert.equal(vars.length, 1); - assert.equal(vars[0].name, 'my_public.VAR'); - assert.equal(vars[0].value, 'test modified value'); - - done(); - }) - it('does not allow setting a multi-line secret variable', function (done) { - this.timeout(1000); - - im._loadData(); - - // test carriage return - let failed = false; - try { - tl.setVariable('my.secret', 'line1\rline2', true); - } - catch (err) { - failed = true; - } - assert(failed, 'Should have failed setting a secret variable with a carriage return'); - - // test line feed - failed = false; - try { - tl.setVariable('my.secret', 'line1\nline2', true); - } - catch (err) { - failed = true; - } - assert(failed, 'Should have failed setting a secret variable with a line feed'); - - done(); - }) - it('allows unsafe setting a multi-line secret variable', function (done) { - this.timeout(1000); - - im._loadData(); - try { - process.env['SYSTEM_UNSAFEALLOWMULTILINESECRET'] = 'true'; - tl.setVariable('my.secret', 'line1\r\nline2', true); - } - finally { - delete process.env['SYSTEM_UNSAFEALLOWMULTILINESECRET']; - } - - assert.equal(tl.getVariable('my.secret'), 'line1\r\nline2'); - - done(); - }) - - // setSecret tests - it('does not allow setting a multi-line secret', function (done) { - this.timeout(1000); - - im._loadData(); - - // test carriage return - let failed = false; - try { - tl.setSecret('line1\rline2'); - } - catch (err) { - failed = true; - } - assert(failed, 'Should have failed setting a secret with a carriage return'); - - // test line feed - failed = false; - try { - tl.setSecret('line1\nline2'); - } - catch (err) { - failed = true; - } - assert(failed, 'Should have failed setting a secret with a line feed'); - - done(); - }) - it('allows unsafe setting a multi-line secret', function (done) { - this.timeout(1000); - - im._loadData(); - try { - process.env['SYSTEM_UNSAFEALLOWMULTILINESECRET'] = 'true'; - tl.setSecret('line1\r\nline2'); - } - finally { - delete process.env['SYSTEM_UNSAFEALLOWMULTILINESECRET']; - } - - done(); - }) - - // getVariables tests - it('gets public variables from initial load', function (done) { - this.timeout(1000); - - process.env['PUBLIC_VAR_ONE'] = 'public value 1'; - process.env['PUBLIC_VAR_TWO'] = 'public value 2'; - process.env['VSTS_PUBLIC_VARIABLES'] = '[ "Public.Var.One", "Public.Var.Two" ]'; - im._loadData(); - let vars: tl.VariableInfo[] = tl.getVariables(); - assert(vars, 'variables should not be undefined or null'); - assert.equal(vars.length, 2, 'exactly 2 variables should be returned'); - vars = vars.sort((a: tl.VariableInfo, b: tl.VariableInfo) => a.name.localeCompare(b.name)); - assert.equal(vars[0].name, 'Public.Var.One'); - assert.equal(vars[0].value, 'public value 1'); - assert.equal(vars[0].secret, false); - assert.equal(vars[1].name, 'Public.Var.Two'); - assert.equal(vars[1].value, 'public value 2'); - assert.equal(vars[1].secret, false); - - done(); - }) - it('gets secret variables from initial load', function (done) { - this.timeout(1000); - - process.env['SECRET_SECRET_VAR_ONE'] = 'secret value 1'; - process.env['SECRET_SECRET_VAR_TWO'] = 'secret value 2'; - process.env['VSTS_SECRET_VARIABLES'] = '[ "Secret.Var.One", "Secret.Var.Two" ]'; - im._loadData(); - let vars: tl.VariableInfo[] = tl.getVariables(); - assert(vars, 'variables should not be undefined or null'); - assert.equal(vars.length, 2, 'exactly 2 variables should be returned'); - vars = vars.sort((a: tl.VariableInfo, b: tl.VariableInfo) => a.name.localeCompare(b.name)); - assert.equal(vars[0].name, 'Secret.Var.One'); - assert.equal(vars[0].value, 'secret value 1'); - assert.equal(vars[0].secret, true); - assert.equal(vars[1].name, 'Secret.Var.Two'); - assert.equal(vars[1].value, 'secret value 2'); - assert.equal(vars[1].secret, true); - - done(); - }) - it('gets secret variables from initial load in pre 2.104.1 agent', function (done) { - this.timeout(1000); - - process.env['SECRET_SECRET_VAR_ONE'] = 'secret value 1'; - process.env['SECRET_SECRET_VAR_TWO'] = 'secret value 2'; - im._loadData(); - let vars: tl.VariableInfo[] = tl.getVariables(); - assert(vars, 'variables should not be undefined or null'); - assert.equal(vars.length, 2, 'exactly 2 variables should be returned'); - vars = vars.sort((a: tl.VariableInfo, b: tl.VariableInfo) => a.name.localeCompare(b.name)); - assert.equal(vars[0].name, 'SECRET_VAR_ONE'); - assert.equal(vars[0].value, 'secret value 1'); - assert.equal(vars[0].secret, true); - assert.equal(vars[1].name, 'SECRET_VAR_TWO'); - assert.equal(vars[1].value, 'secret value 2'); - assert.equal(vars[1].secret, true); - - done(); - }) - it('gets public variables from setVariable', function (done) { - this.timeout(1000); - - process.env['INITIAL_PUBLIC_VAR'] = 'initial public value'; - process.env['VSTS_PUBLIC_VARIABLES'] = '[ "Initial.Public.Var" ]'; - im._loadData(); - tl.setVariable('Set.Public.Var', 'set public value'); - let vars: tl.VariableInfo[] = tl.getVariables(); - assert(vars, 'variables should not be undefined or null'); - assert.equal(vars.length, 2, 'exactly 4 variables should be returned'); - vars = vars.sort((a: tl.VariableInfo, b: tl.VariableInfo) => a.name.localeCompare(b.name)); - assert.equal(vars[0].name, 'Initial.Public.Var'); - assert.equal(vars[0].value, 'initial public value'); - assert.equal(vars[0].secret, false); - assert.equal(vars[1].name, 'Set.Public.Var'); - assert.equal(vars[1].value, 'set public value'); - assert.equal(vars[1].secret, false); - - done(); - }) - it('gets secret variables from setVariable', function (done) { - this.timeout(1000); - - process.env['SECRET_INITIAL_SECRET_VAR'] = 'initial secret value'; - process.env['VSTS_SECRET_VARIABLES'] = '[ "Initial.Secret.Var" ]'; - im._loadData(); - tl.setVariable('Set.Secret.Var', 'set secret value', true); - let vars: tl.VariableInfo[] = tl.getVariables(); - assert(vars, 'variables should not be undefined or null'); - assert.equal(vars.length, 2, 'exactly 2 variables should be returned'); - vars = vars.sort((a: tl.VariableInfo, b: tl.VariableInfo) => a.name.localeCompare(b.name)); - assert.equal(vars[0].name, 'Initial.Secret.Var'); - assert.equal(vars[0].value, 'initial secret value'); - assert.equal(vars[0].secret, true); - assert.equal(vars[1].name, 'Set.Secret.Var'); - assert.equal(vars[1].value, 'set secret value'); - assert.equal(vars[1].secret, true); - - done(); - }) - - // getEndpointUrl/getEndpointAuthorization/getEndpointData tests - it('gets an endpoint url', function (done) { - this.timeout(1000); - - process.env['ENDPOINT_URL_id1'] = 'http://url'; - im._loadData(); - - var url = tl.getEndpointUrl('id1', true); - assert.equal(url, 'http://url', 'url should match'); - - done(); - }) - it('gets a required endpoint url', function (done) { - this.timeout(1000); - - process.env['ENDPOINT_URL_id1'] = 'http://url'; - im._loadData(); - - var url = tl.getEndpointUrlRequired('id1'); - assert.equal(url, 'http://url', 'url should match'); - - done(); - }) - it('required endpoint url throws', function (done) { - this.timeout(1000); - - im._loadData(); - - var worked: boolean = false; - try { - var url = tl.getEndpointUrlRequired('SomeUnsuppliedRequiredInput'); - worked = true; - } - catch (err) { } - - assert(!worked, 'req endpoint url should have not have worked'); - - done(); - }) - it('gets an endpoint auth', function (done) { - this.timeout(1000); - - process.env['ENDPOINT_AUTH_id1'] = '{ "parameters": {"param1": "val1", "param2": "val2"}, "scheme": "UsernamePassword"}'; - im._loadData(); - - var auth = tl.getEndpointAuthorization('id1', true); - assert(auth, 'should return an auth obj'); - assert.equal(auth['parameters']['param1'], 'val1', 'should be correct object'); - - done(); - }) - it('gets null if endpoint auth not set', function (done) { - this.timeout(1000); - - // don't set - im._loadData(); - - var auth = tl.getEndpointAuthorization('id1', true); - assert.equal(auth, null, 'should not return an auth obj'); - - done(); - }) - it('should clear auth envvar', function (done) { - this.timeout(1000); - - process.env['ENDPOINT_AUTH_id1'] = '{ "parameters": {"param1": "val1", "param2": "val2"}, "scheme": "UsernamePassword"}'; - im._loadData(); - var auth = tl.getEndpointAuthorization('id1', true); - assert(auth, 'should return an auth obj'); - assert(auth['parameters']['param1'] === 'val1', 'should be correct object'); - assert(!process.env['ENDPOINT_AUTH_id1'], 'should clear auth envvar'); - - done(); - }) - it('gets endpoint auth scheme', function (done) { - this.timeout(1000); - process.env['ENDPOINT_AUTH_SCHEME_id1'] = 'scheme1'; - im._loadData(); - - var data = tl.getEndpointAuthorizationScheme('id1', true); - assert(data, 'should return a string value'); - assert.equal(data, 'scheme1', 'should be correct scheme'); - assert(!process.env['ENDPOINT_AUTH_SCHEME_id1'], 'should clear auth envvar'); - - done(); - }) - it('gets undefined if endpoint auth scheme is not set', function (done) { - this.timeout(1000); - im._loadData(); - - var data = tl.getEndpointAuthorizationScheme('id1', true); - assert(!data, 'should be undefined when auth scheme is not set'); - - done(); - }) - it('gets required endpoint auth scheme', function (done) { - this.timeout(1000); - process.env['ENDPOINT_AUTH_SCHEME_id1'] = 'scheme1'; - im._loadData(); - - var data = tl.getEndpointAuthorizationSchemeRequired('id1'); - assert(data, 'should return a string value'); - assert.equal(data, 'scheme1', 'should be correct scheme'); - assert(!process.env['ENDPOINT_AUTH_SCHEME_id1'], 'should clear auth envvar'); - - done(); - }) - it('required endpoint auth scheme throws', function (done) { - this.timeout(1000); - - im._loadData(); - - var worked: boolean = false; - try { - var data = tl.getEndpointAuthorizationSchemeRequired('SomeUnsuppliedRequiredInput'); - worked = true; - } - catch (err) { } - - assert(!worked, 'req endpoint auth scheme should have not have worked'); - - done(); - }) - it('gets endpoint auth parameters', function (done) { - this.timeout(1000); - process.env['ENDPOINT_AUTH_PARAMETER_id1_PARAM1'] = 'value1'; - im._loadData(); - - var data = tl.getEndpointAuthorizationParameter('id1', 'param1', true); - assert(data, 'should return a string value'); - assert.equal(data, 'value1', 'should be correct auth param'); - assert(!process.env['ENDPOINT_AUTH_PARAMETER_id1_PARAM1'], 'should clear auth envvar'); - - done(); - }) - it('gets undefined if endpoint auth parameter is not set', function (done) { - this.timeout(1000); - im._loadData(); - - var data = tl.getEndpointAuthorizationParameter('id1', 'noparam', true); - assert(!data, 'should be undefined when auth param is not set'); - - done(); - }) - it('gets required endpoint auth parameters', function (done) { - this.timeout(1000); - process.env['ENDPOINT_AUTH_PARAMETER_id1_PARAM1'] = 'value1'; - im._loadData(); - - var data = tl.getEndpointAuthorizationParameterRequired('id1', 'param1'); - assert(data, 'should return a string value'); - assert.equal(data, 'value1', 'should be correct auth param'); - assert(!process.env['ENDPOINT_AUTH_PARAMETER_id1_PARAM1'], 'should clear auth envvar'); - - done(); - }) - it('throws if endpoint auth parameter is not set', function (done) { - this.timeout(1000); - im._loadData(); - - var worked: boolean = false; - try { - var data = tl.getEndpointAuthorizationParameterRequired('id1', 'noparam'); - worked = true; - } - catch (err) { } - - assert(!worked, 'get endpoint authorization parameter should have not have worked'); - - done(); - }) - it('gets an endpoint data', function (done) { - this.timeout(1000); - process.env['ENDPOINT_DATA_id1_PARAM1'] = 'val1'; - im._loadData(); - - var data = tl.getEndpointDataParameter('id1', 'param1', true); - assert(data, 'should return a string value'); - assert.equal(data, 'val1', 'should be correct object'); - - done(); - }) - it('gets undefined if endpoint data is not set', function (done) { - this.timeout(1000); - im._loadData(); - - var data = tl.getEndpointDataParameter('id1', 'noparam', true); - assert.equal(data, undefined, 'Error should occur if endpoint data is not set'); - - done(); - }) - it('gets required endpoint data', function (done) { - this.timeout(1000); - process.env['ENDPOINT_DATA_id1_PARAM1'] = 'val1'; - im._loadData(); - - var data = tl.getEndpointDataParameterRequired('id1', 'param1'); - assert(data, 'should return a string value'); - assert.equal(data, 'val1', 'should be correct object'); - - done(); - }) - it('throws if endpoint data is not set', function (done) { - this.timeout(1000); - im._loadData(); - - var worked: boolean = false; - try { - var data = tl.getEndpointDataParameterRequired('id1', 'noparam'); - worked = true; - } - catch (err) { } - - assert(!worked, 'get endpoint data should have not have worked'); - - done(); - }) - // getSecureFileName/getSecureFileTicket/getSecureFiles tests - it('gets a secure file name', function (done) { - this.timeout(1000); - - process.env['SECUREFILE_NAME_10'] = 'securefile10.p12'; - im._loadData(); - - var name = tl.getSecureFileName('10'); - assert.equal(name, 'securefile10.p12', 'name should match'); - - done(); - }) - it('gets a secure file ticket', function (done) { - this.timeout(1000); - - process.env['SECUREFILE_TICKET_10'] = 'rsaticket10'; - im._loadData(); - - var ticket = tl.getSecureFileTicket('10'); - assert(ticket, 'should return a string value'); - assert.equal(ticket, 'rsaticket10', 'should be correct ticket'); - assert(!process.env['SECUREFILE_TICKET_10'], 'should clear ticket envvar'); - - done(); - }) - // getBoolInput tests - it('gets case insensitive true bool input value', function (done) { - this.timeout(1000); - - var inputValue = 'tRuE'; - process.env['INPUT_ABOOL'] = inputValue; - im._loadData(); - - var outVal = tl.getBoolInput('abool', /*required=*/true); - assert(outVal, 'should return true'); - - done(); - }) - it('gets false bool input value', function (done) { - this.timeout(1000); - - var inputValue = 'false'; - process.env['INPUT_ABOOL'] = inputValue; - im._loadData(); - - var outVal = tl.getBoolInput('abool', /*required=*/true); - assert(!outVal, 'should return false'); - - done(); - }) - it('defaults to false bool input value', function (done) { - this.timeout(1000); - - im._loadData(); - - var outVal = tl.getBoolInput('no_such_env_var', /*required=*/ false); - assert(!outVal, 'should default to false'); - - done(); - }) - - // getDelimitedInput tests - it('gets delimited input values removes empty values', function (done) { - this.timeout(1000); - - var inputValue = 'test value'; // contains two spaces - process.env['INPUT_DELIM'] = inputValue; - im._loadData(); - - var outVal = tl.getDelimitedInput('delim', ' ', /*required*/true); - assert.equal(outVal.length, 2, 'should return array with two elements'); - assert.equal(outVal[0], 'test', 'should return correct element 1'); - assert.equal(outVal[1], 'value', 'should return correct element 2'); - - done(); - }) - it('gets delimited input for a single value', function (done) { - this.timeout(1000); - - var inputValue = 'testvalue'; - process.env['INPUT_DELIM'] = inputValue; - im._loadData(); - - var outVal = tl.getDelimitedInput('delim', ' ', /*required*/true); - assert.equal(outVal.length, 1, 'should return array with one element'); - assert.equal(outVal[0], 'testvalue', 'should return correct element 1'); - - done(); - }) - it('gets delimited input for an empty value', function (done) { - this.timeout(1000); - - var inputValue = ''; - process.env['INPUT_DELIM'] = inputValue; - im._loadData(); - - var outVal = tl.getDelimitedInput('delim', ' ', /*required*/false); - assert.equal(outVal.length, 0, 'should return array with zero elements'); - - done(); - }) - it('gets delimited input with a Regexp', function (done) { - this.timeout(1000); - - var inputValue = 'a,b\nc'; - process.env['INPUT_DELIM'] = inputValue; - im._loadData(); - - var outVal = tl.getDelimitedInput('delim', /[,\n]/, /*required*/false); - assert.equal(outVal.length, 3, 'should return array with 3 elements'); - - done(); - }) - - // getPathInput tests - it('gets path input value', function (done) { - this.timeout(1000); - - var inputValue = 'test.txt' - process.env['INPUT_PATH1'] = inputValue; - im._loadData(); - - var path = tl.getPathInput('path1', /*required=*/true, /*check=*/false); - assert(path, 'should return a path'); - assert.equal(path, inputValue, 'test path value'); - - done(); - }) - it('throws if required path not supplied', function (done) { - this.timeout(1000); - - var stdStream = testutil.createStringStream(); - tl.setStdStream(stdStream); - - var worked: boolean = false; - try { - var path = tl.getPathInput(null, /*required=*/true, /*check=*/false); - worked = true; - } - catch (err) { } - - assert(!worked, 'req path should have not have worked'); - - done(); - }) - it('get invalid checked path throws', function (done) { - this.timeout(1000); - - var stdStream = testutil.createStringStream(); - tl.setStdStream(stdStream); - - var worked: boolean = false; - try { - var path = tl.getPathInput('some_missing_path', /*required=*/true, /*check=*/true); - worked = true; - } - catch (err) { } - - assert(!worked, 'invalid checked path should have not have worked'); - - done(); - }) - it('gets path invalid value not required', function (done) { - this.timeout(1000); - - var errStream = testutil.createStringStream(); - tl.setErrStream(errStream); - - var path = tl.getPathInput('some_missing_path', /*required=*/false, /*check=*/false); - assert(!path, 'should not return a path'); - - var errMsg = errStream.getContents(); - assert.equal(errMsg, "", "no err") - - done(); - }) - it('gets path input value with space', function (done) { - this.timeout(1000); - - var inputValue = 'file name.txt'; - var expectedValue = 'file name.txt'; - process.env['INPUT_PATH1'] = inputValue; - im._loadData(); - - var path = tl.getPathInput('path1', /*required=*/true, /*check=*/false); - assert(path, 'should return a path'); - assert.equal(path, expectedValue, 'returned ' + path + ', expected: ' + expectedValue); - - done(); - }) - it('gets path value with check and exist', function (done) { - this.timeout(1000); - - var errStream = testutil.createStringStream(); - tl.setErrStream(errStream); - - var inputValue = __filename; - process.env['INPUT_PATH1'] = inputValue; - im._loadData(); - - var path = tl.getPathInput('path1', /*required=*/true, /*check=*/true); - assert(path, 'should return a path'); - assert.equal(path, inputValue, 'test path value'); - - var errMsg = errStream.getContents(); - assert(errMsg === "", "no err") - - done(); - }) - it('gets path value with check and not exist', function (done) { - this.timeout(1000); - - var stdStream = testutil.createStringStream(); - tl.setStdStream(stdStream); - - var inputValue = "someRandomFile.txt"; - process.env['INPUT_PATH1'] = inputValue; - im._loadData(); - - var worked: boolean = false; - try { - var path = tl.getPathInput('path1', /*required=*/true, /*check=*/true); - worked = true; - } - catch (err) { - assert(err.message.indexOf("Not found") >= 0, "error should have said Not found"); - } - assert(!worked, 'invalid checked path should have not have worked'); - - done(); - }) - - // getPathInputRequired tests - it('gets path input required value', function (done) { - this.timeout(1000); - - var inputValue = 'test.txt' - process.env['INPUT_PATH1'] = inputValue; - im._loadData(); - - var path = tl.getPathInputRequired('path1', /*check=*/false); - assert(path, 'should return a path'); - assert.equal(path, inputValue, 'test path value'); - - done(); - }) - it('throws if required path not supplied', function (done) { - this.timeout(1000); - - var stdStream = testutil.createStringStream(); - tl.setStdStream(stdStream); - - var worked: boolean = false; - try { - var path = tl.getPathInputRequired(null, /*check=*/false); - worked = true; - } - catch (err) { } - - assert(!worked, 'req path should have not have worked'); - - done(); - }) - it('get required path invalid checked throws', function (done) { - this.timeout(1000); - - var stdStream = testutil.createStringStream(); - tl.setStdStream(stdStream); - - var worked: boolean = false; - try { - var path = tl.getPathInputRequired('some_missing_path', /*check=*/true); - worked = true; - } - catch (err) { } - - assert(!worked, 'invalid checked path should have not have worked'); - - done(); - }) - it('gets path input required value with space', function (done) { - this.timeout(1000); - - var inputValue = 'file name.txt'; - var expectedValue = 'file name.txt'; - process.env['INPUT_PATH1'] = inputValue; - im._loadData(); - - var path = tl.getPathInputRequired('path1', /*check=*/false); - assert(path, 'should return a path'); - assert.equal(path, expectedValue, 'returned ' + path + ', expected: ' + expectedValue); - - done(); - }) - it('gets path required value with check and exist', function (done) { - this.timeout(1000); - - var errStream = testutil.createStringStream(); - tl.setErrStream(errStream); - - var inputValue = __filename; - process.env['INPUT_PATH1'] = inputValue; - im._loadData(); - - var path = tl.getPathInputRequired('path1', /*check=*/true); - assert(path, 'should return a path'); - assert.equal(path, inputValue, 'test path value'); - - var errMsg = errStream.getContents(); - assert(errMsg === "", "no err") - - done(); - }) - it('gets path required value with check and not exist', function (done) { - this.timeout(1000); - - var stdStream = testutil.createStringStream(); - tl.setStdStream(stdStream); - - var inputValue = "someRandomFile.txt"; - process.env['INPUT_PATH1'] = inputValue; - im._loadData(); - - var worked: boolean = false; - try { - var path = tl.getPathInputRequired('path1', /*check=*/true); - worked = true; - } - catch (err) { - assert(err.message.indexOf("Not found") >= 0, "error should have said Not found"); - } - assert(!worked, 'invalid checked path should have not have worked'); - - done(); - }) - - // filePathSupplied tests - it('filePathSupplied checks not supplied', function (done) { - this.timeout(1000); - - var repoRoot = '/repo/root/dir'; - process.env['INPUT_PATH1'] = repoRoot; - im._loadData(); - - process.env['BUILD_SOURCESDIRECTORY'] = repoRoot; - var supplied = tl.filePathSupplied('path1'); - assert(!supplied, 'path1 should not be supplied'); - done(); - }) - it('filePathSupplied checks supplied', function (done) { - this.timeout(1000); - - var repoRoot = '/repo/root/dir'; - process.env['INPUT_PATH1'] = repoRoot + '/some/path'; - im._loadData(); - - process.env['BUILD_SOURCESDIRECTORY'] = repoRoot; - var supplied = tl.filePathSupplied('path1'); - assert(supplied, 'path1 should be supplied'); - done(); - }) - - // resolve tests - it('resolve', function (done) { - var absolutePath = tl.resolve('/repo/root', '/repo/root/some/path'); - if (os.platform() !== 'win32') { - assert(absolutePath === '/repo/root/some/path', 'absolute path not expected, got:' + absolutePath + ' expected: /repo/root/some/path'); - } else { - var winDrive = path.parse(path.resolve('')).root; - var expectedPath = winDrive.concat('repo\\root\\some\\path'); - assert.equal(absolutePath, expectedPath, 'absolute path not as expected, got: ' + absolutePath + ' expected: ' + expectedPath); - } - done(); - }) - - // getTaskVariable tests - it('gets a task variable', function (done) { - this.timeout(1000); - - let originalAgentVersion = process.env['AGENT_VERSION']; - try { - process.env['AGENT_VERSION'] = '2.115.0'; - process.env['VSTS_TASKVARIABLE_TEST1'] = 'Task variable value 1'; - im._loadData(); - var varVal = tl.getTaskVariable('test1'); - assert.equal(varVal, 'Task variable value 1'); - } - finally { - process.env['AGENT_VERSION'] = originalAgentVersion; - } - - done(); - }) - it('sets a task variable', function (done) { - this.timeout(1000); - - let originalAgentVersion = process.env['AGENT_VERSION']; - try { - process.env['AGENT_VERSION'] = '2.115.0'; - tl.setTaskVariable('test2', 'Task variable value 2'); - let varVal: string = tl.getTaskVariable('test2'); - assert.equal(varVal, 'Task variable value 2'); - } - finally { - process.env['AGENT_VERSION'] = originalAgentVersion; - } - - done(); - }) - - // assertAgent tests - it('assert agent does not fail when empty', function (done) { - this.timeout(1000); - - let originalAgentVersion = process.env['AGENT_VERSION']; - try { - process.env['AGENT_VERSION'] = ''; - let failed = false; - try { - tl.assertAgent('2.115.0'); - } - catch (err) { - failed = true; - } - - assert(!failed, 'assert should not have thrown'); - } - finally { - process.env['AGENT_VERSION'] = originalAgentVersion; - } - - done(); - }) - it('assert agent fails when less than', function (done) { - this.timeout(1000); - - let originalAgentVersion = process.env['AGENT_VERSION']; - try { - process.env['AGENT_VERSION'] = '2.114.0'; - let failed = false; - try { - tl.assertAgent('2.115.0'); - } - catch (err) { - failed = true; - } - - assert(failed, 'assert should have thrown'); - } - finally { - process.env['AGENT_VERSION'] = originalAgentVersion; - } - - done(); - }) - it('assert succeeds when greater or equal', function (done) { - this.timeout(1000); - - let originalAgentVersion = process.env['AGENT_VERSION']; - try { - process.env['AGENT_VERSION'] = '2.115.0'; - tl.assertAgent('2.115.0'); - process.env['AGENT_VERSION'] = '2.116.0'; - tl.assertAgent('2.115.0'); - } - finally { - process.env['AGENT_VERSION'] = originalAgentVersion; - } - - done(); - }) - - // _loadData tests - it('_loadData does not run twice', function (done) { - this.timeout(5000); - - // intialize an input (stored in vault) - process.env['INPUT_SOMEINPUT'] = 'some input value'; - im._loadData(); - assert.equal(tl.getInput('SomeInput'), 'some input value'); - - // copy azure-pipelines-task-lib to a different dir and load it from - // there so it will be forced to load again - let testDir = path.join(testutil.getTestTemp(), '_loadData-not-called-twice'); - tl.mkdirP(testDir); - tl.cp(path.join(__dirname, '..', '_build'), testDir, '-R'); - require(path.join(testDir, '_build', 'task')); - - assert.equal(tl.getInput('SomeInput'), 'some input value'); - done(); - }) - - describe('Feature flags tests', () => { - - ([ - ["true", true], - ["TRUE", true], - ["TruE", true], - ["false", false], - ["treu", false], - ["fasle", false], - ["On", false], - ["", false], - [undefined, false] - ] as [string, boolean][]) - .forEach( - ( - [ - input, - expectedResult - ] - ) => { - it(`Should return ${expectedResult} if feature flag env is ${input}`, () => { - const ffName = "SOME_TEST_FF" - process.env[ffName] = input - - const ffValue = tl.getBoolFeatureFlag(ffName, false); - - assert.equal(ffValue, expectedResult); - }) - } - ); - - it(`Should return default value if feature flag env is empty`, () => { - const ffName = "SOME_TEST_FF" - process.env[ffName] = "" - - const ffValue = tl.getBoolFeatureFlag(ffName, true); - - assert.equal(ffValue, true); - }) - - it(`Should return default value if feature flag env is not specified`, () => { - const ffName = "SOME_TEST_FF" - delete process.env[ffName]; - - const ffValue = tl.getBoolFeatureFlag(ffName, true); - - assert.equal(ffValue, true); - }) - }); - - describe('Pipeline features tests', () => { - it(`Should return if no feature variable present.`, () => { - const featureName = "TestFeature" - delete process.env[im._getVariableKey(`DistributedTask.Tasks.${featureName}`)]; - - const ffValue = tl.getPipelineFeature(featureName); - - assert.deepStrictEqual(ffValue, false); - }) - - const testInputs = ([ - ["true", true], - ["TRUE", true], - ["TruE", true], - ["false", false], - ["treu", false], - ["fasle", false], - ["On", false], - ["", false], - [undefined, false] - ] as [string, boolean][]) - for (const [input, expected] of testInputs) { - it(`Should return '${expected}' if feature is '${input}'`, () => { - const featureVariable = "DISTRIBUTEDTASK_TASKS_TESTFEATURE"; - const featureName = "TestFeature"; - process.env[featureVariable] = input; - - const result = tl.getPipelineFeature(featureName); - - assert.deepStrictEqual(result, expected); - }) - } - }) -}); +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +import assert = require('assert'); +import path = require('path'); +import os = require('os'); +import * as tl from '../_build/task'; +import * as im from '../_build/internal'; +import testutil = require('./testutil'); + +describe('Input Tests', function () { + + before(function (done) { + try { + testutil.initialize(); + } + catch (err) { + assert.fail('Failed to load task lib: ' + err.message); + } + done(); + }); + + after(function () { + + }); + + it('gets input value', function (done) { + this.timeout(1000); + + process.env['INPUT_UNITTESTINPUT'] = 'test value'; + im._loadData(); + + var inval = tl.getInput('UnitTestInput', true); + assert.equal(inval, 'test value'); + + done(); + }) + it('should clear input envvar', function (done) { + this.timeout(1000); + + process.env['INPUT_UNITTESTINPUT'] = 'test value'; + im._loadData(); + var inval = tl.getInput('UnitTestInput', true); + assert.equal(inval, 'test value'); + assert(!process.env['INPUT_UNITTESTINPUT'], 'input envvar should be cleared'); + + done(); + }) + it('required input throws', function (done) { + this.timeout(1000); + + var worked: boolean = false; + try { + var inval = tl.getInput('SomeUnsuppliedRequiredInput', true); + worked = true; + } + catch (err) { } + + assert(!worked, 'req input should have not have worked'); + + done(); + }) + + it('gets input value', function (done) { + this.timeout(1000); + + process.env['INPUT_UNITTESTINPUT'] = 'test value'; + im._loadData(); + + var inval = tl.getInputRequired('UnitTestInput'); + assert.strictEqual(inval, 'test value'); + + done(); + }) + it('should clear input envvar', function (done) { + this.timeout(1000); + + process.env['INPUT_UNITTESTINPUT'] = 'test value'; + im._loadData(); + var inval = tl.getInputRequired('UnitTestInput'); + assert.strictEqual(inval, 'test value'); + assert(!process.env['INPUT_UNITTESTINPUT'], 'input envvar should be cleared'); + + done(); + }) + it('required input throws', function (done) { + this.timeout(1000); + + var worked: boolean = false; + try { + var inval = tl.getInputRequired('SomeUnsuppliedRequiredInput'); + worked = true; + } + catch (err) { } + + assert(!worked, 'req input should have not have worked'); + + done(); + }) + + // getVariable tests + it('gets a variable', function (done) { + this.timeout(1000); + + process.env['BUILD_REPOSITORY_NAME'] = 'Test Repository'; + im._loadData(); + + var varVal = tl.getVariable('Build.Repository.Name'); + assert.equal(varVal, 'Test Repository'); + + done(); + }) + it('gets a secret variable', function (done) { + this.timeout(1000); + + process.env['SECRET_BUILD_REPOSITORY_NAME'] = 'Test Repository'; + im._loadData(); + + var varVal = tl.getVariable('Build.Repository.Name'); + assert.equal(varVal, 'Test Repository'); + + done(); + }) + it('gets a secret variable while variable also exist', function (done) { + this.timeout(1000); + + process.env['BUILD_REPOSITORY_NAME'] = 'Test Repository'; + process.env['SECRET_BUILD_REPOSITORY_NAME'] = 'Secret Test Repository'; + im._loadData(); + + var varVal = tl.getVariable('Build.Repository.Name'); + assert.equal(varVal, 'Secret Test Repository'); + + done(); + }) + it('gets a variable with special characters', (done) => { + this.timeout(1000); + + let expected = 'Value of var with special chars'; + process.env['HELLO_DOT_DOT2_SPACE_SPACE2'] = expected; + im._loadData(); + + let varVal = tl.getVariable('Hello.dot.dot2 space space2'); + assert.equal(varVal, expected); + + done(); + }) + + // setVariable tests + it('sets a variable as an env var', function (done) { + this.timeout(1000); + + tl.setVariable('Build.Repository.Uri', 'test value'); + let varVal: string = process.env['BUILD_REPOSITORY_URI']; + assert.equal(varVal, 'test value'); + + done(); + }) + it('sets a variable with special chars as an env var', (done) => { + this.timeout(1000); + + let expected = 'Set value of var with special chars'; + tl.setVariable('Hello.dot.dot2 space space2', expected); + let varVal: string = process.env['HELLO_DOT_DOT2_SPACE_SPACE2']; + assert.equal(varVal, expected); + + done(); + }) + it('sets and gets a variable', function (done) { + this.timeout(1000); + + tl.setVariable('UnitTestVariable', 'test var value'); + let varVal: string = tl.getVariable('UnitTestVariable'); + assert.equal(varVal, 'test var value'); + + done(); + }) + it('sets and gets an output variable', function (done) { + this.timeout(1000); + + tl.setVariable('UnitTestVariable', 'test var value', false, true); + let varVal: string = tl.getVariable('UnitTestVariable'); + assert.equal(varVal, 'test var value'); + + done(); + }) + it('sets and gets a secret variable', function (done) { + this.timeout(1000); + + tl.setVariable('My.Secret.Var', 'test secret value', true); + let varVal: string = tl.getVariable('My.Secret.Var'); + assert.equal(varVal, 'test secret value'); + + done(); + }) + it('does not set a secret variable as an env var', function (done) { + this.timeout(1000); + + delete process.env['MY_SECRET_VAR']; + tl.setVariable('My.Secret.Var', 'test secret value', true); + let envVal: string = process.env['MY_SECRET_VAR']; + assert(!envVal, 'env var should not be set'); + + done(); + }) + it('removes env var when sets a secret variable', function (done) { + this.timeout(1000); + + process.env['MY_SECRET_VAR'] = 'test env value'; + tl.setVariable('My.Secret.Var', 'test secret value', true); + let envVal: string = process.env['MY_SECRET_VAR']; + assert(!envVal, 'env var should not be set'); + + done(); + }) + it('does not allow a secret variable to become a public variable', function (done) { + this.timeout(1000); + + im._loadData(); + tl.setVariable('My.Secret.Var', 'test secret value', true); + tl.setVariable('My.Secret.Var', 'test modified value', false); + let vars: tl.VariableInfo[] = tl.getVariables(); + assert.equal(vars.length, 1); + assert.equal(vars[0].name, 'My.Secret.Var'); + assert.equal(vars[0].value, 'test modified value'); + assert.equal(vars[0].secret, true); + + done(); + }) + it('allows a public variable to become a secret variable', function (done) { + this.timeout(1000); + + im._loadData(); + tl.setVariable('My.Var', 'test value', false); + tl.setVariable('My.Var', 'test modified value', true); + let vars: tl.VariableInfo[] = tl.getVariables(); + assert.equal(vars.length, 1); + assert.equal(vars[0].name, 'My.Var'); + assert.equal(vars[0].value, 'test modified value'); + assert.equal(vars[0].secret, true); + + done(); + }) + it('tracks known variables using env formatted name', function (done) { + this.timeout(1000); + + im._loadData(); + tl.setVariable('My.Public.Var', 'test value'); + tl.setVariable('my_public.VAR', 'test modified value'); + let vars: tl.VariableInfo[] = tl.getVariables(); + assert.equal(vars.length, 1); + assert.equal(vars[0].name, 'my_public.VAR'); + assert.equal(vars[0].value, 'test modified value'); + + done(); + }) + it('does not allow setting a multi-line secret variable', function (done) { + this.timeout(1000); + + im._loadData(); + + // test carriage return + let failed = false; + try { + tl.setVariable('my.secret', 'line1\rline2', true); + } + catch (err) { + failed = true; + } + assert(failed, 'Should have failed setting a secret variable with a carriage return'); + + // test line feed + failed = false; + try { + tl.setVariable('my.secret', 'line1\nline2', true); + } + catch (err) { + failed = true; + } + assert(failed, 'Should have failed setting a secret variable with a line feed'); + + done(); + }) + it('allows unsafe setting a multi-line secret variable', function (done) { + this.timeout(1000); + + im._loadData(); + try { + process.env['SYSTEM_UNSAFEALLOWMULTILINESECRET'] = 'true'; + tl.setVariable('my.secret', 'line1\r\nline2', true); + } + finally { + delete process.env['SYSTEM_UNSAFEALLOWMULTILINESECRET']; + } + + assert.equal(tl.getVariable('my.secret'), 'line1\r\nline2'); + + done(); + }) + + // setSecret tests + it('does not allow setting a multi-line secret', function (done) { + this.timeout(1000); + + im._loadData(); + + // test carriage return + let failed = false; + try { + tl.setSecret('line1\rline2'); + } + catch (err) { + failed = true; + } + assert(failed, 'Should have failed setting a secret with a carriage return'); + + // test line feed + failed = false; + try { + tl.setSecret('line1\nline2'); + } + catch (err) { + failed = true; + } + assert(failed, 'Should have failed setting a secret with a line feed'); + + done(); + }) + it('allows unsafe setting a multi-line secret', function (done) { + this.timeout(1000); + + im._loadData(); + try { + process.env['SYSTEM_UNSAFEALLOWMULTILINESECRET'] = 'true'; + tl.setSecret('line1\r\nline2'); + } + finally { + delete process.env['SYSTEM_UNSAFEALLOWMULTILINESECRET']; + } + + done(); + }) + + // getVariables tests + it('gets public variables from initial load', function (done) { + this.timeout(1000); + + process.env['PUBLIC_VAR_ONE'] = 'public value 1'; + process.env['PUBLIC_VAR_TWO'] = 'public value 2'; + process.env['VSTS_PUBLIC_VARIABLES'] = '[ "Public.Var.One", "Public.Var.Two" ]'; + im._loadData(); + let vars: tl.VariableInfo[] = tl.getVariables(); + assert(vars, 'variables should not be undefined or null'); + assert.equal(vars.length, 2, 'exactly 2 variables should be returned'); + vars = vars.sort((a: tl.VariableInfo, b: tl.VariableInfo) => a.name.localeCompare(b.name)); + assert.equal(vars[0].name, 'Public.Var.One'); + assert.equal(vars[0].value, 'public value 1'); + assert.equal(vars[0].secret, false); + assert.equal(vars[1].name, 'Public.Var.Two'); + assert.equal(vars[1].value, 'public value 2'); + assert.equal(vars[1].secret, false); + + done(); + }) + it('gets secret variables from initial load', function (done) { + this.timeout(1000); + + process.env['SECRET_SECRET_VAR_ONE'] = 'secret value 1'; + process.env['SECRET_SECRET_VAR_TWO'] = 'secret value 2'; + process.env['VSTS_SECRET_VARIABLES'] = '[ "Secret.Var.One", "Secret.Var.Two" ]'; + im._loadData(); + let vars: tl.VariableInfo[] = tl.getVariables(); + assert(vars, 'variables should not be undefined or null'); + assert.equal(vars.length, 2, 'exactly 2 variables should be returned'); + vars = vars.sort((a: tl.VariableInfo, b: tl.VariableInfo) => a.name.localeCompare(b.name)); + assert.equal(vars[0].name, 'Secret.Var.One'); + assert.equal(vars[0].value, 'secret value 1'); + assert.equal(vars[0].secret, true); + assert.equal(vars[1].name, 'Secret.Var.Two'); + assert.equal(vars[1].value, 'secret value 2'); + assert.equal(vars[1].secret, true); + + done(); + }) + it('gets secret variables from initial load in pre 2.104.1 agent', function (done) { + this.timeout(1000); + + process.env['SECRET_SECRET_VAR_ONE'] = 'secret value 1'; + process.env['SECRET_SECRET_VAR_TWO'] = 'secret value 2'; + im._loadData(); + let vars: tl.VariableInfo[] = tl.getVariables(); + assert(vars, 'variables should not be undefined or null'); + assert.equal(vars.length, 2, 'exactly 2 variables should be returned'); + vars = vars.sort((a: tl.VariableInfo, b: tl.VariableInfo) => a.name.localeCompare(b.name)); + assert.equal(vars[0].name, 'SECRET_VAR_ONE'); + assert.equal(vars[0].value, 'secret value 1'); + assert.equal(vars[0].secret, true); + assert.equal(vars[1].name, 'SECRET_VAR_TWO'); + assert.equal(vars[1].value, 'secret value 2'); + assert.equal(vars[1].secret, true); + + done(); + }) + it('gets public variables from setVariable', function (done) { + this.timeout(1000); + + process.env['INITIAL_PUBLIC_VAR'] = 'initial public value'; + process.env['VSTS_PUBLIC_VARIABLES'] = '[ "Initial.Public.Var" ]'; + im._loadData(); + tl.setVariable('Set.Public.Var', 'set public value'); + let vars: tl.VariableInfo[] = tl.getVariables(); + assert(vars, 'variables should not be undefined or null'); + assert.equal(vars.length, 2, 'exactly 4 variables should be returned'); + vars = vars.sort((a: tl.VariableInfo, b: tl.VariableInfo) => a.name.localeCompare(b.name)); + assert.equal(vars[0].name, 'Initial.Public.Var'); + assert.equal(vars[0].value, 'initial public value'); + assert.equal(vars[0].secret, false); + assert.equal(vars[1].name, 'Set.Public.Var'); + assert.equal(vars[1].value, 'set public value'); + assert.equal(vars[1].secret, false); + + done(); + }) + it('gets secret variables from setVariable', function (done) { + this.timeout(1000); + + process.env['SECRET_INITIAL_SECRET_VAR'] = 'initial secret value'; + process.env['VSTS_SECRET_VARIABLES'] = '[ "Initial.Secret.Var" ]'; + im._loadData(); + tl.setVariable('Set.Secret.Var', 'set secret value', true); + let vars: tl.VariableInfo[] = tl.getVariables(); + assert(vars, 'variables should not be undefined or null'); + assert.equal(vars.length, 2, 'exactly 2 variables should be returned'); + vars = vars.sort((a: tl.VariableInfo, b: tl.VariableInfo) => a.name.localeCompare(b.name)); + assert.equal(vars[0].name, 'Initial.Secret.Var'); + assert.equal(vars[0].value, 'initial secret value'); + assert.equal(vars[0].secret, true); + assert.equal(vars[1].name, 'Set.Secret.Var'); + assert.equal(vars[1].value, 'set secret value'); + assert.equal(vars[1].secret, true); + + done(); + }) + + // getEndpointUrl/getEndpointAuthorization/getEndpointData tests + it('gets an endpoint url', function (done) { + this.timeout(1000); + + process.env['ENDPOINT_URL_id1'] = 'http://url'; + im._loadData(); + + var url = tl.getEndpointUrl('id1', true); + assert.equal(url, 'http://url', 'url should match'); + + done(); + }) + it('gets a required endpoint url', function (done) { + this.timeout(1000); + + process.env['ENDPOINT_URL_id1'] = 'http://url'; + im._loadData(); + + var url = tl.getEndpointUrlRequired('id1'); + assert.equal(url, 'http://url', 'url should match'); + + done(); + }) + it('required endpoint url throws', function (done) { + this.timeout(1000); + + im._loadData(); + + var worked: boolean = false; + try { + var url = tl.getEndpointUrlRequired('SomeUnsuppliedRequiredInput'); + worked = true; + } + catch (err) { } + + assert(!worked, 'req endpoint url should have not have worked'); + + done(); + }) + it('gets an endpoint auth', function (done) { + this.timeout(1000); + + process.env['ENDPOINT_AUTH_id1'] = '{ "parameters": {"param1": "val1", "param2": "val2"}, "scheme": "UsernamePassword"}'; + im._loadData(); + + var auth = tl.getEndpointAuthorization('id1', true); + assert(auth, 'should return an auth obj'); + assert.equal(auth['parameters']['param1'], 'val1', 'should be correct object'); + + done(); + }) + it('gets null if endpoint auth not set', function (done) { + this.timeout(1000); + + // don't set + im._loadData(); + + var auth = tl.getEndpointAuthorization('id1', true); + assert.equal(auth, null, 'should not return an auth obj'); + + done(); + }) + it('should clear auth envvar', function (done) { + this.timeout(1000); + + process.env['ENDPOINT_AUTH_id1'] = '{ "parameters": {"param1": "val1", "param2": "val2"}, "scheme": "UsernamePassword"}'; + im._loadData(); + var auth = tl.getEndpointAuthorization('id1', true); + assert(auth, 'should return an auth obj'); + assert(auth['parameters']['param1'] === 'val1', 'should be correct object'); + assert(!process.env['ENDPOINT_AUTH_id1'], 'should clear auth envvar'); + + done(); + }) + it('gets endpoint auth scheme', function (done) { + this.timeout(1000); + process.env['ENDPOINT_AUTH_SCHEME_id1'] = 'scheme1'; + im._loadData(); + + var data = tl.getEndpointAuthorizationScheme('id1', true); + assert(data, 'should return a string value'); + assert.equal(data, 'scheme1', 'should be correct scheme'); + assert(!process.env['ENDPOINT_AUTH_SCHEME_id1'], 'should clear auth envvar'); + + done(); + }) + it('gets undefined if endpoint auth scheme is not set', function (done) { + this.timeout(1000); + im._loadData(); + + var data = tl.getEndpointAuthorizationScheme('id1', true); + assert(!data, 'should be undefined when auth scheme is not set'); + + done(); + }) + it('gets required endpoint auth scheme', function (done) { + this.timeout(1000); + process.env['ENDPOINT_AUTH_SCHEME_id1'] = 'scheme1'; + im._loadData(); + + var data = tl.getEndpointAuthorizationSchemeRequired('id1'); + assert(data, 'should return a string value'); + assert.equal(data, 'scheme1', 'should be correct scheme'); + assert(!process.env['ENDPOINT_AUTH_SCHEME_id1'], 'should clear auth envvar'); + + done(); + }) + it('required endpoint auth scheme throws', function (done) { + this.timeout(1000); + + im._loadData(); + + var worked: boolean = false; + try { + var data = tl.getEndpointAuthorizationSchemeRequired('SomeUnsuppliedRequiredInput'); + worked = true; + } + catch (err) { } + + assert(!worked, 'req endpoint auth scheme should have not have worked'); + + done(); + }) + it('gets endpoint auth parameters', function (done) { + this.timeout(1000); + process.env['ENDPOINT_AUTH_PARAMETER_id1_PARAM1'] = 'value1'; + im._loadData(); + + var data = tl.getEndpointAuthorizationParameter('id1', 'param1', true); + assert(data, 'should return a string value'); + assert.equal(data, 'value1', 'should be correct auth param'); + assert(!process.env['ENDPOINT_AUTH_PARAMETER_id1_PARAM1'], 'should clear auth envvar'); + + done(); + }) + it('gets undefined if endpoint auth parameter is not set', function (done) { + this.timeout(1000); + im._loadData(); + + var data = tl.getEndpointAuthorizationParameter('id1', 'noparam', true); + assert(!data, 'should be undefined when auth param is not set'); + + done(); + }) + it('gets required endpoint auth parameters', function (done) { + this.timeout(1000); + process.env['ENDPOINT_AUTH_PARAMETER_id1_PARAM1'] = 'value1'; + im._loadData(); + + var data = tl.getEndpointAuthorizationParameterRequired('id1', 'param1'); + assert(data, 'should return a string value'); + assert.equal(data, 'value1', 'should be correct auth param'); + assert(!process.env['ENDPOINT_AUTH_PARAMETER_id1_PARAM1'], 'should clear auth envvar'); + + done(); + }) + it('throws if endpoint auth parameter is not set', function (done) { + this.timeout(1000); + im._loadData(); + + var worked: boolean = false; + try { + var data = tl.getEndpointAuthorizationParameterRequired('id1', 'noparam'); + worked = true; + } + catch (err) { } + + assert(!worked, 'get endpoint authorization parameter should have not have worked'); + + done(); + }) + it('gets an endpoint data', function (done) { + this.timeout(1000); + process.env['ENDPOINT_DATA_id1_PARAM1'] = 'val1'; + im._loadData(); + + var data = tl.getEndpointDataParameter('id1', 'param1', true); + assert(data, 'should return a string value'); + assert.equal(data, 'val1', 'should be correct object'); + + done(); + }) + it('gets undefined if endpoint data is not set', function (done) { + this.timeout(1000); + im._loadData(); + + var data = tl.getEndpointDataParameter('id1', 'noparam', true); + assert.equal(data, undefined, 'Error should occur if endpoint data is not set'); + + done(); + }) + it('gets required endpoint data', function (done) { + this.timeout(1000); + process.env['ENDPOINT_DATA_id1_PARAM1'] = 'val1'; + im._loadData(); + + var data = tl.getEndpointDataParameterRequired('id1', 'param1'); + assert(data, 'should return a string value'); + assert.equal(data, 'val1', 'should be correct object'); + + done(); + }) + it('throws if endpoint data is not set', function (done) { + this.timeout(1000); + im._loadData(); + + var worked: boolean = false; + try { + var data = tl.getEndpointDataParameterRequired('id1', 'noparam'); + worked = true; + } + catch (err) { } + + assert(!worked, 'get endpoint data should have not have worked'); + + done(); + }) + // getSecureFileName/getSecureFileTicket/getSecureFiles tests + it('gets a secure file name', function (done) { + this.timeout(1000); + + process.env['SECUREFILE_NAME_10'] = 'securefile10.p12'; + im._loadData(); + + var name = tl.getSecureFileName('10'); + assert.equal(name, 'securefile10.p12', 'name should match'); + + done(); + }) + it('gets a secure file ticket', function (done) { + this.timeout(1000); + + process.env['SECUREFILE_TICKET_10'] = 'rsaticket10'; + im._loadData(); + + var ticket = tl.getSecureFileTicket('10'); + assert(ticket, 'should return a string value'); + assert.equal(ticket, 'rsaticket10', 'should be correct ticket'); + assert(!process.env['SECUREFILE_TICKET_10'], 'should clear ticket envvar'); + + done(); + }) + // getBoolInput tests + it('gets case insensitive true bool input value', function (done) { + this.timeout(1000); + + var inputValue = 'tRuE'; + process.env['INPUT_ABOOL'] = inputValue; + im._loadData(); + + var outVal = tl.getBoolInput('abool', /*required=*/true); + assert(outVal, 'should return true'); + + done(); + }) + it('gets false bool input value', function (done) { + this.timeout(1000); + + var inputValue = 'false'; + process.env['INPUT_ABOOL'] = inputValue; + im._loadData(); + + var outVal = tl.getBoolInput('abool', /*required=*/true); + assert(!outVal, 'should return false'); + + done(); + }) + it('defaults to false bool input value', function (done) { + this.timeout(1000); + + im._loadData(); + + var outVal = tl.getBoolInput('no_such_env_var', /*required=*/ false); + assert(!outVal, 'should default to false'); + + done(); + }) + + // getDelimitedInput tests + it('gets delimited input values removes empty values', function (done) { + this.timeout(1000); + + var inputValue = 'test value'; // contains two spaces + process.env['INPUT_DELIM'] = inputValue; + im._loadData(); + + var outVal = tl.getDelimitedInput('delim', ' ', /*required*/true); + assert.equal(outVal.length, 2, 'should return array with two elements'); + assert.equal(outVal[0], 'test', 'should return correct element 1'); + assert.equal(outVal[1], 'value', 'should return correct element 2'); + + done(); + }) + it('gets delimited input for a single value', function (done) { + this.timeout(1000); + + var inputValue = 'testvalue'; + process.env['INPUT_DELIM'] = inputValue; + im._loadData(); + + var outVal = tl.getDelimitedInput('delim', ' ', /*required*/true); + assert.equal(outVal.length, 1, 'should return array with one element'); + assert.equal(outVal[0], 'testvalue', 'should return correct element 1'); + + done(); + }) + it('gets delimited input for an empty value', function (done) { + this.timeout(1000); + + var inputValue = ''; + process.env['INPUT_DELIM'] = inputValue; + im._loadData(); + + var outVal = tl.getDelimitedInput('delim', ' ', /*required*/false); + assert.equal(outVal.length, 0, 'should return array with zero elements'); + + done(); + }) + it('gets delimited input with a Regexp', function (done) { + this.timeout(1000); + + var inputValue = 'a,b\nc'; + process.env['INPUT_DELIM'] = inputValue; + im._loadData(); + + var outVal = tl.getDelimitedInput('delim', /[,\n]/, /*required*/false); + assert.equal(outVal.length, 3, 'should return array with 3 elements'); + + done(); + }) + + // getPathInput tests + it('gets path input value', function (done) { + this.timeout(1000); + + var inputValue = 'test.txt' + process.env['INPUT_PATH1'] = inputValue; + im._loadData(); + + var path = tl.getPathInput('path1', /*required=*/true, /*check=*/false); + assert(path, 'should return a path'); + assert.equal(path, inputValue, 'test path value'); + + done(); + }) + it('throws if required path not supplied', function (done) { + this.timeout(1000); + + var stdStream = testutil.createStringStream(); + tl.setStdStream(stdStream); + + var worked: boolean = false; + try { + var path = tl.getPathInput(null, /*required=*/true, /*check=*/false); + worked = true; + } + catch (err) { } + + assert(!worked, 'req path should have not have worked'); + + done(); + }) + it('get invalid checked path throws', function (done) { + this.timeout(1000); + + var stdStream = testutil.createStringStream(); + tl.setStdStream(stdStream); + + var worked: boolean = false; + try { + var path = tl.getPathInput('some_missing_path', /*required=*/true, /*check=*/true); + worked = true; + } + catch (err) { } + + assert(!worked, 'invalid checked path should have not have worked'); + + done(); + }) + it('gets path invalid value not required', function (done) { + this.timeout(1000); + + var errStream = testutil.createStringStream(); + tl.setErrStream(errStream); + + var path = tl.getPathInput('some_missing_path', /*required=*/false, /*check=*/false); + assert(!path, 'should not return a path'); + + var errMsg = errStream.getContents(); + assert.equal(errMsg, "", "no err") + + done(); + }) + it('gets path input value with space', function (done) { + this.timeout(1000); + + var inputValue = 'file name.txt'; + var expectedValue = 'file name.txt'; + process.env['INPUT_PATH1'] = inputValue; + im._loadData(); + + var path = tl.getPathInput('path1', /*required=*/true, /*check=*/false); + assert(path, 'should return a path'); + assert.equal(path, expectedValue, 'returned ' + path + ', expected: ' + expectedValue); + + done(); + }) + it('gets path value with check and exist', function (done) { + this.timeout(1000); + + var errStream = testutil.createStringStream(); + tl.setErrStream(errStream); + + var inputValue = __filename; + process.env['INPUT_PATH1'] = inputValue; + im._loadData(); + + var path = tl.getPathInput('path1', /*required=*/true, /*check=*/true); + assert(path, 'should return a path'); + assert.equal(path, inputValue, 'test path value'); + + var errMsg = errStream.getContents(); + assert(errMsg === "", "no err") + + done(); + }) + it('gets path value with check and not exist', function (done) { + this.timeout(1000); + + var stdStream = testutil.createStringStream(); + tl.setStdStream(stdStream); + + var inputValue = "someRandomFile.txt"; + process.env['INPUT_PATH1'] = inputValue; + im._loadData(); + + var worked: boolean = false; + try { + var path = tl.getPathInput('path1', /*required=*/true, /*check=*/true); + worked = true; + } + catch (err) { + assert(err.message.indexOf("Not found") >= 0, "error should have said Not found"); + } + assert(!worked, 'invalid checked path should have not have worked'); + + done(); + }) + + // getPathInputRequired tests + it('gets path input required value', function (done) { + this.timeout(1000); + + var inputValue = 'test.txt' + process.env['INPUT_PATH1'] = inputValue; + im._loadData(); + + var path = tl.getPathInputRequired('path1', /*check=*/false); + assert(path, 'should return a path'); + assert.equal(path, inputValue, 'test path value'); + + done(); + }) + it('throws if required path not supplied', function (done) { + this.timeout(1000); + + var stdStream = testutil.createStringStream(); + tl.setStdStream(stdStream); + + var worked: boolean = false; + try { + var path = tl.getPathInputRequired(null, /*check=*/false); + worked = true; + } + catch (err) { } + + assert(!worked, 'req path should have not have worked'); + + done(); + }) + it('get required path invalid checked throws', function (done) { + this.timeout(1000); + + var stdStream = testutil.createStringStream(); + tl.setStdStream(stdStream); + + var worked: boolean = false; + try { + var path = tl.getPathInputRequired('some_missing_path', /*check=*/true); + worked = true; + } + catch (err) { } + + assert(!worked, 'invalid checked path should have not have worked'); + + done(); + }) + it('gets path input required value with space', function (done) { + this.timeout(1000); + + var inputValue = 'file name.txt'; + var expectedValue = 'file name.txt'; + process.env['INPUT_PATH1'] = inputValue; + im._loadData(); + + var path = tl.getPathInputRequired('path1', /*check=*/false); + assert(path, 'should return a path'); + assert.equal(path, expectedValue, 'returned ' + path + ', expected: ' + expectedValue); + + done(); + }) + it('gets path required value with check and exist', function (done) { + this.timeout(1000); + + var errStream = testutil.createStringStream(); + tl.setErrStream(errStream); + + var inputValue = __filename; + process.env['INPUT_PATH1'] = inputValue; + im._loadData(); + + var path = tl.getPathInputRequired('path1', /*check=*/true); + assert(path, 'should return a path'); + assert.equal(path, inputValue, 'test path value'); + + var errMsg = errStream.getContents(); + assert(errMsg === "", "no err") + + done(); + }) + it('gets path required value with check and not exist', function (done) { + this.timeout(1000); + + var stdStream = testutil.createStringStream(); + tl.setStdStream(stdStream); + + var inputValue = "someRandomFile.txt"; + process.env['INPUT_PATH1'] = inputValue; + im._loadData(); + + var worked: boolean = false; + try { + var path = tl.getPathInputRequired('path1', /*check=*/true); + worked = true; + } + catch (err) { + assert(err.message.indexOf("Not found") >= 0, "error should have said Not found"); + } + assert(!worked, 'invalid checked path should have not have worked'); + + done(); + }) + + // filePathSupplied tests + it('filePathSupplied checks not supplied', function (done) { + this.timeout(1000); + + var repoRoot = '/repo/root/dir'; + process.env['INPUT_PATH1'] = repoRoot; + im._loadData(); + + process.env['BUILD_SOURCESDIRECTORY'] = repoRoot; + var supplied = tl.filePathSupplied('path1'); + assert(!supplied, 'path1 should not be supplied'); + done(); + }) + it('filePathSupplied checks supplied', function (done) { + this.timeout(1000); + + var repoRoot = '/repo/root/dir'; + process.env['INPUT_PATH1'] = repoRoot + '/some/path'; + im._loadData(); + + process.env['BUILD_SOURCESDIRECTORY'] = repoRoot; + var supplied = tl.filePathSupplied('path1'); + assert(supplied, 'path1 should be supplied'); + done(); + }) + + // resolve tests + it('resolve', function (done) { + var absolutePath = tl.resolve('/repo/root', '/repo/root/some/path'); + if (os.platform() !== 'win32') { + assert(absolutePath === '/repo/root/some/path', 'absolute path not expected, got:' + absolutePath + ' expected: /repo/root/some/path'); + } else { + var winDrive = path.parse(path.resolve('')).root; + var expectedPath = winDrive.concat('repo\\root\\some\\path'); + assert.equal(absolutePath, expectedPath, 'absolute path not as expected, got: ' + absolutePath + ' expected: ' + expectedPath); + } + done(); + }) + + // getTaskVariable tests + it('gets a task variable', function (done) { + this.timeout(1000); + + let originalAgentVersion = process.env['AGENT_VERSION']; + try { + process.env['AGENT_VERSION'] = '2.115.0'; + process.env['VSTS_TASKVARIABLE_TEST1'] = 'Task variable value 1'; + im._loadData(); + var varVal = tl.getTaskVariable('test1'); + assert.equal(varVal, 'Task variable value 1'); + } + finally { + process.env['AGENT_VERSION'] = originalAgentVersion; + } + + done(); + }) + it('sets a task variable', function (done) { + this.timeout(1000); + + let originalAgentVersion = process.env['AGENT_VERSION']; + try { + process.env['AGENT_VERSION'] = '2.115.0'; + tl.setTaskVariable('test2', 'Task variable value 2'); + let varVal: string = tl.getTaskVariable('test2'); + assert.equal(varVal, 'Task variable value 2'); + } + finally { + process.env['AGENT_VERSION'] = originalAgentVersion; + } + + done(); + }) + + // assertAgent tests + it('assert agent does not fail when empty', function (done) { + this.timeout(1000); + + let originalAgentVersion = process.env['AGENT_VERSION']; + try { + process.env['AGENT_VERSION'] = ''; + let failed = false; + try { + tl.assertAgent('2.115.0'); + } + catch (err) { + failed = true; + } + + assert(!failed, 'assert should not have thrown'); + } + finally { + process.env['AGENT_VERSION'] = originalAgentVersion; + } + + done(); + }) + it('assert agent fails when less than', function (done) { + this.timeout(1000); + + let originalAgentVersion = process.env['AGENT_VERSION']; + try { + process.env['AGENT_VERSION'] = '2.114.0'; + let failed = false; + try { + tl.assertAgent('2.115.0'); + } + catch (err) { + failed = true; + } + + assert(failed, 'assert should have thrown'); + } + finally { + process.env['AGENT_VERSION'] = originalAgentVersion; + } + + done(); + }) + it('assert succeeds when greater or equal', function (done) { + this.timeout(1000); + + let originalAgentVersion = process.env['AGENT_VERSION']; + try { + process.env['AGENT_VERSION'] = '2.115.0'; + tl.assertAgent('2.115.0'); + process.env['AGENT_VERSION'] = '2.116.0'; + tl.assertAgent('2.115.0'); + } + finally { + process.env['AGENT_VERSION'] = originalAgentVersion; + } + + done(); + }) + + // _loadData tests + it('_loadData does not run twice', function (done) { + this.timeout(5000); + + // intialize an input (stored in vault) + process.env['INPUT_SOMEINPUT'] = 'some input value'; + im._loadData(); + assert.equal(tl.getInput('SomeInput'), 'some input value'); + + // copy azure-pipelines-task-lib to a different dir and load it from + // there so it will be forced to load again + let testDir = path.join(testutil.getTestTemp(), '_loadData-not-called-twice'); + tl.mkdirP(testDir); + tl.cp(path.join(__dirname, '..', '_build'), testDir, '-R'); + require(path.join(testDir, '_build', 'task')); + + assert.equal(tl.getInput('SomeInput'), 'some input value'); + done(); + }) + + describe('Feature flags tests', () => { + + ([ + ["true", true], + ["TRUE", true], + ["TruE", true], + ["false", false], + ["treu", false], + ["fasle", false], + ["On", false], + ["", false], + [undefined, false] + ] as [string, boolean][]) + .forEach( + ( + [ + input, + expectedResult + ] + ) => { + it(`Should return ${expectedResult} if feature flag env is ${input}`, () => { + const ffName = "SOME_TEST_FF" + process.env[ffName] = input + + const ffValue = tl.getBoolFeatureFlag(ffName, false); + + assert.equal(ffValue, expectedResult); + }) + } + ); + + it(`Should return default value if feature flag env is empty`, () => { + const ffName = "SOME_TEST_FF" + process.env[ffName] = "" + + const ffValue = tl.getBoolFeatureFlag(ffName, true); + + assert.equal(ffValue, true); + }) + + it(`Should return default value if feature flag env is not specified`, () => { + const ffName = "SOME_TEST_FF" + delete process.env[ffName]; + + const ffValue = tl.getBoolFeatureFlag(ffName, true); + + assert.equal(ffValue, true); + }) + }); + + describe('Pipeline features tests', () => { + it(`Should return if no feature variable present.`, () => { + const featureName = "TestFeature" + delete process.env[im._getVariableKey(`DistributedTask.Tasks.${featureName}`)]; + + const ffValue = tl.getPipelineFeature(featureName); + + assert.deepStrictEqual(ffValue, false); + }) + + const testInputs = ([ + ["true", true], + ["TRUE", true], + ["TruE", true], + ["false", false], + ["treu", false], + ["fasle", false], + ["On", false], + ["", false], + [undefined, false] + ] as [string, boolean][]) + for (const [input, expected] of testInputs) { + it(`Should return '${expected}' if feature is '${input}'`, () => { + const featureVariable = "DISTRIBUTEDTASK_TASKS_TESTFEATURE"; + const featureName = "TestFeature"; + process.env[featureVariable] = input; + + const result = tl.getPipelineFeature(featureName); + + assert.deepStrictEqual(result, expected); + }) + } + }) +}); From 339010947d2f279706c2cb83164cd1f860017d24 Mon Sep 17 00:00:00 2001 From: Ivan Duplenskikh <115665590+ivanduplenskikh@users.noreply.github.com> Date: Wed, 25 Dec 2024 13:42:59 +0100 Subject: [PATCH 04/11] Add removing symbolic link to the rmRF method --- node/task.ts | 9 +++++++++ node/test/rm.ts | 3 --- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/node/task.ts b/node/task.ts index b57b5a641..db5842b56 100644 --- a/node/task.ts +++ b/node/task.ts @@ -1701,6 +1701,15 @@ export function rmRF(inputPath: string): void { throw new Error(loc('LIB_OperationFailed', 'rmRF', errMsg)); } + return; + } else if (lstats.isSymbolicLink()) { + debug('removing symbolic link'); + try { + fs.unlinkSync(inputPath); + } catch (errMsg) { + throw new Error(loc('LIB_OperationFailed', 'rmRF', errMsg)); + } + return; } diff --git a/node/test/rm.ts b/node/test/rm.ts index 7ce91d20c..96d93e0d5 100644 --- a/node/test/rm.ts +++ b/node/test/rm.ts @@ -35,10 +35,7 @@ describe('rm cases', () => { }); it('Invalid arguments', (done) => { - // @ts-ignore - assert.throws(() => tl.rmRF(), { message: 'Failed rmRF: The "path" argument must be of type string or an instance of Buffer or URL. Received undefined' }); assert.doesNotThrow(() => tl.rmRF('somefolderpaththatdoesnotexist')); - done(); }); From 617e64e396dd47473818e49ffd56aba08826a284 Mon Sep 17 00:00:00 2001 From: Ivan Duplenskikh <115665590+ivanduplenskikh@users.noreply.github.com> Date: Wed, 25 Dec 2024 15:51:43 +0100 Subject: [PATCH 05/11] Refactoring cd unit tests and logs --- node/lib.json | 78 ++++++++++++++++++++++++---------------------- node/task.ts | 6 ++-- node/test/cd.ts | 83 +++++++++++++++++++++++++++++++++++-------------- 3 files changed, 102 insertions(+), 65 deletions(-) diff --git a/node/lib.json b/node/lib.json index 45ffe0717..144a949c7 100644 --- a/node/lib.json +++ b/node/lib.json @@ -1,38 +1,40 @@ -{ - "messages": { - "LIB_UnhandledEx": "Unhandled: %s", - "LIB_FailOnCode": "Failure return code: %d", - "LIB_MkdirFailed": "Unable to create directory '%s'. %s", - "LIB_MkdirFailedFileExists": "Unable to create directory '%s'. Conflicting file exists: '%s'", - "LIB_MkdirFailedInvalidDriveRoot": "Unable to create directory '%s'. Root directory does not exist: '%s'", - "LIB_MkdirFailedInvalidShare": "Unable to create directory '%s'. Unable to verify the directory exists: '%s'. If directory is a file share, please verify the share name is correct, the share is online, and the current process has permission to access the share.", - "LIB_MultilineSecret": "Secrets cannot contain multiple lines", - "LIB_ProcessError": "There was an error when attempting to execute the process '%s'. This may indicate the process failed to start. Error: %s", - "LIB_ProcessExitCode": "The process '%s' failed with exit code %s", - "LIB_ProcessStderr": "The process '%s' failed because one or more lines were written to the STDERR stream", - "LIB_ReturnCode": "Return code: %d", - "LIB_ResourceFileNotExist": "Resource file doesn\\'t exist: %s", - "LIB_ResourceFileAlreadySet": "Resource file has already set to: %s", - "LIB_ResourceFileNotSet": "Resource file haven\\'t set, can\\'t find loc string for key: %s", - "LIB_StdioNotClosed": "The STDIO streams did not close within %s seconds of the exit event from process '%s'. This may indicate a child process inherited the STDIO streams and has not yet exited.", - "LIB_WhichNotFound_Linux": "Unable to locate executable file: '%s'. Please verify either the file path exists or the file can be found within a directory specified by the PATH environment variable. Also check the file mode to verify the file is executable.", - "LIB_WhichNotFound_Win": "Unable to locate executable file: '%s'. Please verify either the file path exists or the file can be found within a directory specified by the PATH environment variable. Also verify the file has a valid extension for an executable file.", - "LIB_LocStringNotFound": "Can\\'t find loc string for key: %s", - "LIB_ParameterIsRequired": "%s not supplied", - "LIB_InputRequired": "Input required: %s", - "LIB_InvalidPattern": "Invalid pattern: '%s'", - "LIB_EndpointNotExist": "Endpoint not present: %s", - "LIB_EndpointDataNotExist": "Endpoint data parameter %s not present: %s", - "LIB_EndpointAuthNotExist": "Endpoint auth data not present: %s", - "LIB_InvalidEndpointAuth": "Invalid endpoint auth: %s", - "LIB_InvalidSecureFilesInput": "Invalid secure file input: %s", - "LIB_PathNotFound": "Not found %s: %s", - "LIB_PathHasNullByte": "Path cannot contain null bytes", - "LIB_OperationFailed": "Failed %s: %s", - "LIB_UseFirstGlobMatch": "Multiple workspace matches. using first.", - "LIB_MergeTestResultNotSupported": "Merging test results from multiple files to one test run is not supported on this version of build agent for OSX/Linux, each test result file will be published as a separate test run in VSO/TFS.", - "LIB_PlatformNotSupported": "Platform not supported: %s", - "LIB_CopyFileFailed": "Error while copying the file. Attempts left: %s", - "LIB_UndefinedNodeVersion": "Node version is undefined." - } -} +{ + "messages": { + "LIB_UnhandledEx": "Unhandled: %s", + "LIB_FailOnCode": "Failure return code: %d", + "LIB_MkdirFailed": "Unable to create directory '%s'. %s", + "LIB_MkdirFailedFileExists": "Unable to create directory '%s'. Conflicting file exists: '%s'", + "LIB_MkdirFailedInvalidDriveRoot": "Unable to create directory '%s'. Root directory does not exist: '%s'", + "LIB_MkdirFailedInvalidShare": "Unable to create directory '%s'. Unable to verify the directory exists: '%s'. If directory is a file share, please verify the share name is correct, the share is online, and the current process has permission to access the share.", + "LIB_MultilineSecret": "Secrets cannot contain multiple lines", + "LIB_ProcessError": "There was an error when attempting to execute the process '%s'. This may indicate the process failed to start. Error: %s", + "LIB_ProcessExitCode": "The process '%s' failed with exit code %s", + "LIB_ProcessStderr": "The process '%s' failed because one or more lines were written to the STDERR stream", + "LIB_ReturnCode": "Return code: %d", + "LIB_ResourceFileNotExist": "Resource file doesn\\'t exist: %s", + "LIB_ResourceFileAlreadySet": "Resource file has already set to: %s", + "LIB_ResourceFileNotSet": "Resource file haven\\'t set, can\\'t find loc string for key: %s", + "LIB_StdioNotClosed": "The STDIO streams did not close within %s seconds of the exit event from process '%s'. This may indicate a child process inherited the STDIO streams and has not yet exited.", + "LIB_WhichNotFound_Linux": "Unable to locate executable file: '%s'. Please verify either the file path exists or the file can be found within a directory specified by the PATH environment variable. Also check the file mode to verify the file is executable.", + "LIB_WhichNotFound_Win": "Unable to locate executable file: '%s'. Please verify either the file path exists or the file can be found within a directory specified by the PATH environment variable. Also verify the file has a valid extension for an executable file.", + "LIB_LocStringNotFound": "Can\\'t find loc string for key: %s", + "LIB_ParameterIsRequired": "%s not supplied", + "LIB_InputRequired": "Input required: %s", + "LIB_InvalidPattern": "Invalid pattern: '%s'", + "LIB_EndpointNotExist": "Endpoint not present: %s", + "LIB_EndpointDataNotExist": "Endpoint data parameter %s not present: %s", + "LIB_EndpointAuthNotExist": "Endpoint auth data not present: %s", + "LIB_InvalidEndpointAuth": "Invalid endpoint auth: %s", + "LIB_InvalidSecureFilesInput": "Invalid secure file input: %s", + "LIB_PathIsNotADirectory": "Path is not a directory: %s", + "LIB_PathNotFound": "Not found %s: %s", + "LIB_PathHasNullByte": "Path cannot contain null bytes", + "LIB_OperationFailed": "Failed %s: %s", + "LIB_UseFirstGlobMatch": "Multiple workspace matches. using first.", + "LIB_MergeTestResultNotSupported": "Merging test results from multiple files to one test run is not supported on this version of build agent for OSX/Linux, each test result file will be published as a separate test run in VSO/TFS.", + "LIB_NotFoundPreviousDirectory": "Could not find previous directory", + "LIB_PlatformNotSupported": "Platform not supported: %s", + "LIB_CopyFileFailed": "Error while copying the file. Attempts left: %s", + "LIB_UndefinedNodeVersion": "Node version is undefined." + } +} diff --git a/node/task.ts b/node/task.ts index db5842b56..7dc0aaafd 100644 --- a/node/task.ts +++ b/node/task.ts @@ -783,7 +783,7 @@ export const checkPath = im._checkPath; export function cd(path: string): void { if (path === '-') { if (!process.env.OLDPWD) { - throw new Error(`Failed cd: could not find previous directory`); + throw new Error(loc('LIB_NotFoundPreviousDirectory')); } else { path = process.env.OLDPWD; } @@ -794,11 +794,11 @@ export function cd(path: string): void { } if (!fs.existsSync(path)) { - throw new Error(`Failed cd: no such file or directory: ${path}`) + throw new Error(loc('LIB_PathNotFound', 'cd', path)); } if (!fs.statSync(path).isDirectory()) { - throw new Error(`Failed cd: not a directory: ${path}`); + throw new Error(loc('LIB_PathIsNotADirectory', path)); } try { diff --git a/node/test/cd.ts b/node/test/cd.ts index 234328940..b2547c2b7 100644 --- a/node/test/cd.ts +++ b/node/test/cd.ts @@ -10,17 +10,24 @@ const DIRNAME = __dirname; import * as testutil from './testutil'; describe('cd cases', () => { - let TEMP_DIR_PATH: string; const TEMP_DIR_1 = path.resolve(DIRNAME, 'temp1'); const TEMP_DIR_2 = path.resolve(DIRNAME, 'temp2'); + const TEMP_DIR_2_SUBDIR_1 = path.resolve(TEMP_DIR_2, 'subdir1'); const TEMP_FILE_1 = path.resolve(TEMP_DIR_1, 'file1'); + const TEMP_DIR_2_SUBDIR_1_FILE_1 = path.resolve(TEMP_DIR_2_SUBDIR_1, 'file1'); + const TEMP_DIR_2_SUBDIR_1_SYMLINK_FILE_1 = path.resolve(TEMP_DIR_2_SUBDIR_1, 'symlink_file_1'); + const TEMP_DIR_2_SUBDIR_1_SYMLINK_DIR_1 = path.resolve(TEMP_DIR_2_SUBDIR_1, 'symlink_dir_1'); before((done) => { process.chdir(DIRNAME); - TEMP_DIR_PATH = fs.mkdtempSync('temp_test_'); + tl.mkdirP(TEMP_DIR_1); tl.mkdirP(TEMP_DIR_2); + tl.mkdirP(TEMP_DIR_2_SUBDIR_1); fs.writeFileSync(TEMP_FILE_1, 'file1'); + fs.writeFileSync(TEMP_DIR_2_SUBDIR_1_FILE_1, 'file1'); + fs.symlinkSync(TEMP_FILE_1, TEMP_DIR_2_SUBDIR_1_SYMLINK_FILE_1); + fs.symlinkSync(TEMP_DIR_1, TEMP_DIR_2_SUBDIR_1_SYMLINK_DIR_1); try { testutil.initialize(); @@ -31,70 +38,98 @@ describe('cd cases', () => { done(); }); + beforeEach(() => { + process.env.OLDPWD = ''; + process.chdir(DIRNAME); + }); + after((done) => { tl.cd(DIRNAME); tl.rmRF(TEMP_DIR_1); tl.rmRF(TEMP_DIR_2); - tl.rmRF(TEMP_DIR_PATH); done(); }); - it('Check change directory for a folder that does not exist', (done) => { + it('Provide a path does not exist', (done) => { assert.ok(!fs.existsSync('/thisfolderdoesnotexist')); - assert.throws(() => tl.cd('/thisfolderdoesnotexist'), { message: "Failed cd: no such file or directory: /thisfolderdoesnotexist" }); - + assert.throws(() => tl.cd('/thisfolderdoesnotexist'), { message: 'Not found cd: /thisfolderdoesnotexist' }); done(); }); - it('Change directory to a file path', (done) => { - const filePath = path.resolve(DIRNAME, 'scripts', 'match-input-exe.cs'); + it('Provide all suite possible directories', (done) => { + assert.equal(process.cwd(), DIRNAME); + + tl.cd(TEMP_DIR_1); + assert.equal(process.cwd(), TEMP_DIR_1); + + tl.cd(TEMP_DIR_2); + assert.equal(process.cwd(), TEMP_DIR_2); + + tl.cd(TEMP_DIR_2_SUBDIR_1); + assert.equal(process.cwd(), TEMP_DIR_2_SUBDIR_1); + + tl.cd(TEMP_DIR_2_SUBDIR_1_SYMLINK_DIR_1); + assert.equal(process.cwd(), TEMP_DIR_2_SUBDIR_1_SYMLINK_DIR_1); - assert.ok(fs.existsSync(filePath)); - assert.throws(() => tl.cd(filePath), { message: `Failed cd: not a directory: ${filePath}` }); + assert.equal(process.env.OLDPWD, TEMP_DIR_2_SUBDIR_1); done(); }); - it('There is no previous directory', (done) => { - assert.throws(() => tl.cd('-'), { message: 'Failed cd: could not find previous directory' }); + it('Provide path that is a file path', (done) => { + assert.ok(fs.existsSync(TEMP_FILE_1)); + assert.throws(() => tl.cd(TEMP_FILE_1), { message: `Path is not a directory: ${TEMP_FILE_1}` }); + done(); + }); + it('Provide path that is a file symlink', (done) => { + assert.ok(fs.existsSync(TEMP_DIR_2_SUBDIR_1_SYMLINK_FILE_1)); + assert.throws(() => tl.cd(TEMP_DIR_2_SUBDIR_1_SYMLINK_FILE_1), { message: `Path is not a directory: ${TEMP_DIR_2_SUBDIR_1_SYMLINK_FILE_1}` }); done(); }); - it('Change direcotry to a relative path', (done) => { - tl.cd(TEMP_DIR_PATH); + it('Provide path that is a dir symlink', (done) => { + assert.ok(fs.existsSync(TEMP_DIR_2_SUBDIR_1_SYMLINK_DIR_1)); + assert.doesNotThrow(() => tl.cd(TEMP_DIR_2_SUBDIR_1_SYMLINK_DIR_1)); + assert.equal(process.cwd(), TEMP_DIR_2_SUBDIR_1_SYMLINK_DIR_1); + done(); + }); - assert.equal(path.basename(TEMP_DIR_PATH), TEMP_DIR_PATH); + it('Check if there is no previous directory', (done) => { + assert.throws(() => tl.cd('-'), { message: 'Could not find previous directory' }); + done(); + }); + it('Provide a relative directory path', (done) => { + const relativePath = path.relative(process.cwd(), TEMP_DIR_1); + tl.cd(relativePath); + assert.equal(path.basename(relativePath), relativePath); done(); }); - it('Change directory to an absolute path', (done) => { + it('Provide an absolute directory path', (done) => { tl.cd('/'); - assert.equal(process.cwd(), path.resolve('/')); - done(); }); it('Change directory to a previous directory -', (done) => { tl.cd('/'); tl.cd('-'); - assert.ok(process.cwd(), path.resolve(DIRNAME)); - done(); }); it('Change directory with cp', (done) => { - assert.ok(!fs.existsSync(path.resolve(TEMP_DIR_PATH, "file1"))); + const TEMP_DIR_2_FILE_1 = path.resolve(TEMP_DIR_2, 'file1'); + assert.ok(!fs.existsSync(TEMP_DIR_2_FILE_1)); tl.cd(TEMP_DIR_1); - tl.cp(TEMP_FILE_1, path.resolve("..", TEMP_DIR_PATH)); - tl.cd(path.resolve("..", TEMP_DIR_PATH)); + tl.cp(TEMP_FILE_1, path.resolve("..", TEMP_DIR_2)); + tl.cd(path.resolve("..", TEMP_DIR_2)); - assert.ok(fs.existsSync('file1')); + assert.ok(fs.existsSync(TEMP_DIR_2_FILE_1)); done(); }); From 952c027ba90f7b157445c78a4865b2ccbcb606f6 Mon Sep 17 00:00:00 2001 From: Ivan Duplenskikh <115665590+ivanduplenskikh@users.noreply.github.com> Date: Wed, 25 Dec 2024 18:03:58 +0100 Subject: [PATCH 06/11] Refactoring pushd, popd unit tests and outputs --- .../resources.resjson/en-US/resources.resjson | 45 ++++++----- node/lib.json | 81 ++++++++++--------- node/task.ts | 7 +- node/test/popd.ts | 5 +- node/test/pushd.ts | 5 +- 5 files changed, 73 insertions(+), 70 deletions(-) diff --git a/node/Strings/resources.resjson/en-US/resources.resjson b/node/Strings/resources.resjson/en-US/resources.resjson index 41844a99b..d2f90304e 100644 --- a/node/Strings/resources.resjson/en-US/resources.resjson +++ b/node/Strings/resources.resjson/en-US/resources.resjson @@ -1,36 +1,39 @@ { - "loc.messages.LIB_UnhandledEx": "Unhandled: %s", + "loc.messages.LIB_CopyFileFailed": "Error while copying the file. Attempts left: %s", + "loc.messages.LIB_DirectoryStackEmpty": "Directory stack is empty", + "loc.messages.LIB_EndpointAuthNotExist": "Endpoint auth data not present: %s", + "loc.messages.LIB_EndpointDataNotExist": "Endpoint data parameter %s not present: %s", + "loc.messages.LIB_EndpointNotExist": "Endpoint not present: %s", "loc.messages.LIB_FailOnCode": "Failure return code: %d", + "loc.messages.LIB_InputRequired": "Input required: %s", + "loc.messages.LIB_InvalidEndpointAuth": "Invalid endpoint auth: %s", + "loc.messages.LIB_InvalidPattern": "Invalid pattern: '%s'", + "loc.messages.LIB_InvalidSecureFilesInput": "Invalid secure file input: %s", + "loc.messages.LIB_LocStringNotFound": "Can\\'t find loc string for key: %s", + "loc.messages.LIB_MergeTestResultNotSupported": "Merging test results from multiple files to one test run is not supported on this version of build agent for OSX/Linux, each test result file will be published as a separate test run in VSO/TFS.", "loc.messages.LIB_MkdirFailed": "Unable to create directory '%s'. %s", "loc.messages.LIB_MkdirFailedFileExists": "Unable to create directory '%s'. Conflicting file exists: '%s'", "loc.messages.LIB_MkdirFailedInvalidDriveRoot": "Unable to create directory '%s'. Root directory does not exist: '%s'", "loc.messages.LIB_MkdirFailedInvalidShare": "Unable to create directory '%s'. Unable to verify the directory exists: '%s'. If directory is a file share, please verify the share name is correct, the share is online, and the current process has permission to access the share.", "loc.messages.LIB_MultilineSecret": "Secrets cannot contain multiple lines", + "loc.messages.LIB_NotFoundPreviousDirectory": "Could not find previous directory", + "loc.messages.LIB_OperationFailed": "Failed %s: %s", + "loc.messages.LIB_ParameterIsRequired": "%s not supplied", + "loc.messages.LIB_PathHasNullByte": "Path cannot contain null bytes", + "loc.messages.LIB_PathIsNotADirectory": "Path is not a directory: %s", + "loc.messages.LIB_PathNotFound": "Not found %s: %s", + "loc.messages.LIB_PlatformNotSupported": "Platform not supported: %s", "loc.messages.LIB_ProcessError": "There was an error when attempting to execute the process '%s'. This may indicate the process failed to start. Error: %s", "loc.messages.LIB_ProcessExitCode": "The process '%s' failed with exit code %s", "loc.messages.LIB_ProcessStderr": "The process '%s' failed because one or more lines were written to the STDERR stream", - "loc.messages.LIB_ReturnCode": "Return code: %d", - "loc.messages.LIB_ResourceFileNotExist": "Resource file doesn\\'t exist: %s", "loc.messages.LIB_ResourceFileAlreadySet": "Resource file has already set to: %s", + "loc.messages.LIB_ResourceFileNotExist": "Resource file doesn\\'t exist: %s", "loc.messages.LIB_ResourceFileNotSet": "Resource file haven\\'t set, can\\'t find loc string for key: %s", + "loc.messages.LIB_ReturnCode": "Return code: %d", "loc.messages.LIB_StdioNotClosed": "The STDIO streams did not close within %s seconds of the exit event from process '%s'. This may indicate a child process inherited the STDIO streams and has not yet exited.", - "loc.messages.LIB_WhichNotFound_Linux": "Unable to locate executable file: '%s'. Please verify either the file path exists or the file can be found within a directory specified by the PATH environment variable. Also check the file mode to verify the file is executable.", - "loc.messages.LIB_WhichNotFound_Win": "Unable to locate executable file: '%s'. Please verify either the file path exists or the file can be found within a directory specified by the PATH environment variable. Also verify the file has a valid extension for an executable file.", - "loc.messages.LIB_LocStringNotFound": "Can\\'t find loc string for key: %s", - "loc.messages.LIB_ParameterIsRequired": "%s not supplied", - "loc.messages.LIB_InputRequired": "Input required: %s", - "loc.messages.LIB_InvalidPattern": "Invalid pattern: '%s'", - "loc.messages.LIB_EndpointNotExist": "Endpoint not present: %s", - "loc.messages.LIB_EndpointDataNotExist": "Endpoint data parameter %s not present: %s", - "loc.messages.LIB_EndpointAuthNotExist": "Endpoint auth data not present: %s", - "loc.messages.LIB_InvalidEndpointAuth": "Invalid endpoint auth: %s", - "loc.messages.LIB_InvalidSecureFilesInput": "Invalid secure file input: %s", - "loc.messages.LIB_PathNotFound": "Not found %s: %s", - "loc.messages.LIB_PathHasNullByte": "Path cannot contain null bytes", - "loc.messages.LIB_OperationFailed": "Failed %s: %s", + "loc.messages.LIB_UndefinedNodeVersion": "Node version is undefined.", + "loc.messages.LIB_UnhandledEx": "Unhandled: %s", "loc.messages.LIB_UseFirstGlobMatch": "Multiple workspace matches. using first.", - "loc.messages.LIB_MergeTestResultNotSupported": "Merging test results from multiple files to one test run is not supported on this version of build agent for OSX/Linux, each test result file will be published as a separate test run in VSO/TFS.", - "loc.messages.LIB_PlatformNotSupported": "Platform not supported: %s", - "loc.messages.LIB_CopyFileFailed": "Error while copying the file. Attempts left: %s", - "loc.messages.LIB_UndefinedNodeVersion": "Node version is undefined." + "loc.messages.LIB_WhichNotFound_Linux": "Unable to locate executable file: '%s'. Please verify either the file path exists or the file can be found within a directory specified by the PATH environment variable. Also check the file mode to verify the file is executable.", + "loc.messages.LIB_WhichNotFound_Win": "Unable to locate executable file: '%s'. Please verify either the file path exists or the file can be found within a directory specified by the PATH environment variable. Also verify the file has a valid extension for an executable file." } \ No newline at end of file diff --git a/node/lib.json b/node/lib.json index 144a949c7..adbc62565 100644 --- a/node/lib.json +++ b/node/lib.json @@ -1,40 +1,41 @@ -{ - "messages": { - "LIB_UnhandledEx": "Unhandled: %s", - "LIB_FailOnCode": "Failure return code: %d", - "LIB_MkdirFailed": "Unable to create directory '%s'. %s", - "LIB_MkdirFailedFileExists": "Unable to create directory '%s'. Conflicting file exists: '%s'", - "LIB_MkdirFailedInvalidDriveRoot": "Unable to create directory '%s'. Root directory does not exist: '%s'", - "LIB_MkdirFailedInvalidShare": "Unable to create directory '%s'. Unable to verify the directory exists: '%s'. If directory is a file share, please verify the share name is correct, the share is online, and the current process has permission to access the share.", - "LIB_MultilineSecret": "Secrets cannot contain multiple lines", - "LIB_ProcessError": "There was an error when attempting to execute the process '%s'. This may indicate the process failed to start. Error: %s", - "LIB_ProcessExitCode": "The process '%s' failed with exit code %s", - "LIB_ProcessStderr": "The process '%s' failed because one or more lines were written to the STDERR stream", - "LIB_ReturnCode": "Return code: %d", - "LIB_ResourceFileNotExist": "Resource file doesn\\'t exist: %s", - "LIB_ResourceFileAlreadySet": "Resource file has already set to: %s", - "LIB_ResourceFileNotSet": "Resource file haven\\'t set, can\\'t find loc string for key: %s", - "LIB_StdioNotClosed": "The STDIO streams did not close within %s seconds of the exit event from process '%s'. This may indicate a child process inherited the STDIO streams and has not yet exited.", - "LIB_WhichNotFound_Linux": "Unable to locate executable file: '%s'. Please verify either the file path exists or the file can be found within a directory specified by the PATH environment variable. Also check the file mode to verify the file is executable.", - "LIB_WhichNotFound_Win": "Unable to locate executable file: '%s'. Please verify either the file path exists or the file can be found within a directory specified by the PATH environment variable. Also verify the file has a valid extension for an executable file.", - "LIB_LocStringNotFound": "Can\\'t find loc string for key: %s", - "LIB_ParameterIsRequired": "%s not supplied", - "LIB_InputRequired": "Input required: %s", - "LIB_InvalidPattern": "Invalid pattern: '%s'", - "LIB_EndpointNotExist": "Endpoint not present: %s", - "LIB_EndpointDataNotExist": "Endpoint data parameter %s not present: %s", - "LIB_EndpointAuthNotExist": "Endpoint auth data not present: %s", - "LIB_InvalidEndpointAuth": "Invalid endpoint auth: %s", - "LIB_InvalidSecureFilesInput": "Invalid secure file input: %s", - "LIB_PathIsNotADirectory": "Path is not a directory: %s", - "LIB_PathNotFound": "Not found %s: %s", - "LIB_PathHasNullByte": "Path cannot contain null bytes", - "LIB_OperationFailed": "Failed %s: %s", - "LIB_UseFirstGlobMatch": "Multiple workspace matches. using first.", - "LIB_MergeTestResultNotSupported": "Merging test results from multiple files to one test run is not supported on this version of build agent for OSX/Linux, each test result file will be published as a separate test run in VSO/TFS.", - "LIB_NotFoundPreviousDirectory": "Could not find previous directory", - "LIB_PlatformNotSupported": "Platform not supported: %s", - "LIB_CopyFileFailed": "Error while copying the file. Attempts left: %s", - "LIB_UndefinedNodeVersion": "Node version is undefined." - } -} +{ + "messages": { + "LIB_CopyFileFailed": "Error while copying the file. Attempts left: %s", + "LIB_DirectoryStackEmpty": "Directory stack is empty", + "LIB_EndpointAuthNotExist": "Endpoint auth data not present: %s", + "LIB_EndpointDataNotExist": "Endpoint data parameter %s not present: %s", + "LIB_EndpointNotExist": "Endpoint not present: %s", + "LIB_FailOnCode": "Failure return code: %d", + "LIB_InputRequired": "Input required: %s", + "LIB_InvalidEndpointAuth": "Invalid endpoint auth: %s", + "LIB_InvalidPattern": "Invalid pattern: '%s'", + "LIB_InvalidSecureFilesInput": "Invalid secure file input: %s", + "LIB_LocStringNotFound": "Can\\'t find loc string for key: %s", + "LIB_MergeTestResultNotSupported": "Merging test results from multiple files to one test run is not supported on this version of build agent for OSX/Linux, each test result file will be published as a separate test run in VSO/TFS.", + "LIB_MkdirFailed": "Unable to create directory '%s'. %s", + "LIB_MkdirFailedFileExists": "Unable to create directory '%s'. Conflicting file exists: '%s'", + "LIB_MkdirFailedInvalidDriveRoot": "Unable to create directory '%s'. Root directory does not exist: '%s'", + "LIB_MkdirFailedInvalidShare": "Unable to create directory '%s'. Unable to verify the directory exists: '%s'. If directory is a file share, please verify the share name is correct, the share is online, and the current process has permission to access the share.", + "LIB_MultilineSecret": "Secrets cannot contain multiple lines", + "LIB_NotFoundPreviousDirectory": "Could not find previous directory", + "LIB_OperationFailed": "Failed %s: %s", + "LIB_ParameterIsRequired": "%s not supplied", + "LIB_PathHasNullByte": "Path cannot contain null bytes", + "LIB_PathIsNotADirectory": "Path is not a directory: %s", + "LIB_PathNotFound": "Not found %s: %s", + "LIB_PlatformNotSupported": "Platform not supported: %s", + "LIB_ProcessError": "There was an error when attempting to execute the process '%s'. This may indicate the process failed to start. Error: %s", + "LIB_ProcessExitCode": "The process '%s' failed with exit code %s", + "LIB_ProcessStderr": "The process '%s' failed because one or more lines were written to the STDERR stream", + "LIB_ResourceFileAlreadySet": "Resource file has already set to: %s", + "LIB_ResourceFileNotExist": "Resource file doesn\\'t exist: %s", + "LIB_ResourceFileNotSet": "Resource file haven\\'t set, can\\'t find loc string for key: %s", + "LIB_ReturnCode": "Return code: %d", + "LIB_StdioNotClosed": "The STDIO streams did not close within %s seconds of the exit event from process '%s'. This may indicate a child process inherited the STDIO streams and has not yet exited.", + "LIB_UndefinedNodeVersion": "Node version is undefined.", + "LIB_UnhandledEx": "Unhandled: %s", + "LIB_UseFirstGlobMatch": "Multiple workspace matches. using first.", + "LIB_WhichNotFound_Linux": "Unable to locate executable file: '%s'. Please verify either the file path exists or the file can be found within a directory specified by the PATH environment variable. Also check the file mode to verify the file is executable.", + "LIB_WhichNotFound_Win": "Unable to locate executable file: '%s'. Please verify either the file path exists or the file can be found within a directory specified by the PATH environment variable. Also verify the file has a valid extension for an executable file." + } +} \ No newline at end of file diff --git a/node/task.ts b/node/task.ts index 7dc0aaafd..c52687305 100644 --- a/node/task.ts +++ b/node/task.ts @@ -833,7 +833,7 @@ export function pushd(dir: string = ''): string[] { if (dirs.length > 1) { dirs.splice(0, 0, ...dirs.splice(1, 1)); } else { - throw new Error(`Failed pushd: no other directory`); + throw new Error(loc('LIB_DirectoryStackEmpty')); } } else if (!isNaN(maybeIndex)) { if (maybeIndex < dirStack.length + 1) { @@ -850,11 +850,12 @@ export function pushd(dir: string = ''): string[] { cd(_path); } catch (error) { if (!fs.existsSync(_path)) { - throw new Error(`Failed pushd: no such file or directory: ${_path}`); + throw new Error(loc('Not found', 'pushd', _path)); } throw error; } + dirStack.splice(0, dirStack.length, ...dirs); return getActualStack(); } @@ -867,7 +868,7 @@ export function pushd(dir: string = ''): string[] { */ export function popd(index: string = ''): string[] { if (dirStack.length === 0) { - throw new Error(`Failed popd: directory stack empty`); + throw new Error(loc('LIB_DirectoryStackEmpty')); } let maybeIndex = parseInt(index); diff --git a/node/test/popd.ts b/node/test/popd.ts index 6548456ca..460b0dbfe 100644 --- a/node/test/popd.ts +++ b/node/test/popd.ts @@ -1,4 +1,3 @@ -import fs = require('node:fs'); import path = require('node:path'); import assert = require('node:assert'); @@ -121,7 +120,7 @@ describe('popd cases', () => { }); it('Using when stack is empty', (done) => { - assert.throws(() => tl.popd(), { message: 'Failed popd: directory stack empty' }); + assert.throws(() => tl.popd(), { message: 'Directory stack is empty' }); done(); }); @@ -133,7 +132,7 @@ describe('popd cases', () => { assert.ok(trail[0], path.resolve(DIRNAME, TEMP_DIR_PATH)); assert.ok(process.cwd(), trail[0]); - assert.throws(() => tl.popd(), { message: 'Failed popd: directory stack empty' }); + assert.throws(() => tl.popd(), { message: 'Directory stack is empty' }); done(); }); diff --git a/node/test/pushd.ts b/node/test/pushd.ts index 09df41bf9..ab05aa18e 100644 --- a/node/test/pushd.ts +++ b/node/test/pushd.ts @@ -1,4 +1,3 @@ -import fs = require('node:fs'); import path = require('node:path'); import assert = require('node:assert'); @@ -293,7 +292,7 @@ describe('pushd cases', () => { it('Using invalid directory', (done) => { const oldCwd = process.cwd(); - assert.throws(() => tl.pushd('does/not/exist'), { message: /^Failed pushd: no such file or directory:/ }); + assert.throws(() => tl.pushd('does/not/exist'), { message: /^Not found pushd/ }); assert.equal(process.cwd(), oldCwd); done(); @@ -332,7 +331,7 @@ describe('pushd cases', () => { }); it('Using without arguments invalid when stack is empty', (done) => { - assert.throws(() => tl.pushd(), { message: 'Failed pushd: no other directory' }); + assert.throws(() => tl.pushd(), { message: 'Directory stack is empty' }); done(); }); From d5bf33eaadaabb5f7104936f73c8d146b07f886b Mon Sep 17 00:00:00 2001 From: Ivan Duplenskikh <115665590+ivanduplenskikh@users.noreply.github.com> Date: Wed, 25 Dec 2024 18:53:01 +0100 Subject: [PATCH 07/11] Refactoring ls method and unit tests --- node/task.ts | 53 +++++++++++++++++++++++++++++++------------------ node/test/ls.ts | 36 ++++++++++++++++++++++++++++++--- 2 files changed, 67 insertions(+), 22 deletions(-) diff --git a/node/task.ts b/node/task.ts index c52687305..2d18b60b5 100644 --- a/node/task.ts +++ b/node/task.ts @@ -975,29 +975,45 @@ export function resolve(...pathSegments: any[]): string { export const which = im._which; /** - * Returns array of files in the given path, or in current directory if no path provided. See shelljs.ls + * Returns array of files in the given path, or in current directory if no path provided. * @param {string} options Available options: -R (recursive), -A (all files, include files beginning with ., except for . and ..) * @param {string[]} paths Paths to search. * @return {string[]} An array of files in the given path(s). */ -export function ls(optionsOrPaths?: string | string[], ...paths: string[]): string[] { +export function ls(optionsOrPaths?: string | string[], ...paths: string[]): string[]; +export function ls(optionsOrPaths?: string | string[], paths?: string[]): string[]; +export function ls(optionsOrPaths?: string | string[], paths?: string): string[]; + +export function ls(optionsOrPaths?: string | string[], ...paths: unknown[]): string[] { let isRecursive = false; let includeHidden = false; let handleAsOptions = false; - if (typeof optionsOrPaths == 'string' && optionsOrPaths.startsWith('-')) { + if (typeof optionsOrPaths === 'string' && optionsOrPaths.startsWith('-')) { optionsOrPaths = optionsOrPaths.toLowerCase(); isRecursive = optionsOrPaths.includes('r'); includeHidden = optionsOrPaths.includes('a'); - } else { + } + + // Flatten paths if the paths argument is array + if (Array.isArray(paths)) { + paths = paths.flat(Infinity); + } + + // If the first argument is not options, then it is a path + if (typeof optionsOrPaths !== 'string' || !optionsOrPaths.startsWith('-')) { + let pathsFromOptions: string[] = []; + + if (Array.isArray(optionsOrPaths)) { + pathsFromOptions = optionsOrPaths; + } else if (optionsOrPaths) { + pathsFromOptions = [optionsOrPaths]; + } + if (paths === undefined || paths.length === 0) { - if (Array.isArray(optionsOrPaths)) { - paths = optionsOrPaths as string[]; - } else if (optionsOrPaths && !handleAsOptions) { - paths = [optionsOrPaths]; - } else { - paths = []; - } + paths = pathsFromOptions; + } else { + paths.push(...pathsFromOptions); } } @@ -1048,7 +1064,7 @@ export function ls(optionsOrPaths?: string | string[], ...paths: string[]): stri return entries; } catch (error) { if (error.code === 'ENOENT') { - throw new Error(`Failed ls: ${error}`); + throw new Error(loc('LIB_PathNotFound', 'ls', error.message)); } else { throw new Error(loc('LIB_OperationFailed', 'ls', error)); } @@ -1084,7 +1100,10 @@ function retryer(func: Function, retryCount: number = 0, continueOnError: boolea * @param {boolean} [continueOnError] - Optional. whether to continue on error. * @param {number} [retryCount=0] - Optional. Retry count to copy the file. It might help to resolve intermittent issues e.g. with UNC target paths on a remote host. */ -export function cp(sourceOrOptions: string, destinationOrSource: string, optionsOrDestination?: string, continueOnError?: boolean, retryCount: number = 0): void { +export function cp(source: string, destination: string, options?: string, continueOnError?: boolean, retryCount: number = 0): void; +export function cp(options: string, source: string, destination: string, continueOnError?: boolean, retryCount: number = 0): void; + +export function cp(sourceOrOptions: string, destinationOrSource: string, optionsOrDestination: string, continueOnError?: boolean, retryCount: number = 0): void { retryer(() => { let recursive = false; let force = true; @@ -1107,7 +1126,7 @@ export function cp(sourceOrOptions: string, destinationOrSource: string, options } if (!fs.existsSync(destination) && !force) { - throw new Error(`ENOENT: no such file or directory: ${destination}`); + throw new Error(loc('LIB_PathNotFound', 'cp', destination)); } const lstatSource = fs.lstatSync(source); @@ -1131,11 +1150,7 @@ export function cp(sourceOrOptions: string, destinationOrSource: string, options fs.cpSync(source, path.join(destination, path.basename(source)), { recursive, force }); } } catch (error) { - if (error.code === 'ENOENT') { - throw new Error(error); - } else { - throw new Error(loc('LIB_OperationFailed', 'cp', error)); - } + throw new Error(loc('LIB_OperationFailed', 'cp', error)); } }, retryCount, continueOnError); } diff --git a/node/test/ls.ts b/node/test/ls.ts index 2481ceed8..68d8c7b47 100644 --- a/node/test/ls.ts +++ b/node/test/ls.ts @@ -73,8 +73,7 @@ describe('ls cases', () => { it('Provide the folder which does not exist', (done) => { assert.ok(!fs.existsSync('/thisfolderdoesnotexist')); - assert.throws(() => tl.ls('/thisfolderdoesnotexist'), { message: /^Failed ls: Error: ENOENT: no such file or directory, lstat/ }); - + assert.throws(() => tl.ls('/thisfolderdoesnotexist'), { message: /^Not found ls: ENOENT: no such file or directory, lstat/ }); done(); }); @@ -266,7 +265,7 @@ describe('ls cases', () => { done(); }); - it('Empty attributes, but several paths', (done) => { + it('Empty attributes, but several paths as multiple arguments', (done) => { const result = tl.ls('', TEMP_SUBDIR_1, TEMP_FILE_1); assert.ok(result.includes(TEMP_FILE_1)); @@ -277,6 +276,37 @@ describe('ls cases', () => { done(); }); + it('Empty attributes, but several paths in array', (done) => { + const result = tl.ls('', [TEMP_SUBDIR_1]); + + assert.ok(result.includes(TEMP_SUBDIR_FILE_1)); + assert.ok(result.includes(TEMP_SUBDIR_FILELINK_1)); + assert.equal(result.length, 2); + + done(); + }); + + it('Empty attributes, but one path', (done) => { + const result = tl.ls('', TEMP_SUBDIR_1); + + assert.ok(result.includes(TEMP_SUBDIR_FILE_1)); + assert.ok(result.includes(TEMP_SUBDIR_FILELINK_1)); + assert.equal(result.length, 2); + + done(); + }); + + it('Provide path as first argument and subdir as second argument', (done) => { + const result = tl.ls(TEMP_FILE_1, TEMP_SUBDIR_1); + + assert.ok(result.includes(TEMP_FILE_1)); + assert.ok(result.includes(TEMP_SUBDIR_FILE_1)); + assert.ok(result.includes(TEMP_SUBDIR_FILELINK_1)); + assert.equal(result.length, 3); + + done(); + }); + it('New one folder without content', (done) => { tl.mkdirP('foo'); From c79f2394799bb8193377e182e762d18433b2e127 Mon Sep 17 00:00:00 2001 From: Ivan Duplenskikh <115665590+ivanduplenskikh@users.noreply.github.com> Date: Wed, 25 Dec 2024 18:56:44 +0100 Subject: [PATCH 08/11] Remove init from overloads --- node/task.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/node/task.ts b/node/task.ts index 2d18b60b5..68c4971be 100644 --- a/node/task.ts +++ b/node/task.ts @@ -1100,8 +1100,8 @@ function retryer(func: Function, retryCount: number = 0, continueOnError: boolea * @param {boolean} [continueOnError] - Optional. whether to continue on error. * @param {number} [retryCount=0] - Optional. Retry count to copy the file. It might help to resolve intermittent issues e.g. with UNC target paths on a remote host. */ -export function cp(source: string, destination: string, options?: string, continueOnError?: boolean, retryCount: number = 0): void; -export function cp(options: string, source: string, destination: string, continueOnError?: boolean, retryCount: number = 0): void; +export function cp(source: string, destination: string, options?: string, continueOnError?: boolean, retryCount?: number): void; +export function cp(options: string, source: string, destination: string, continueOnError?: boolean, retryCount?: number): void; export function cp(sourceOrOptions: string, destinationOrSource: string, optionsOrDestination: string, continueOnError?: boolean, retryCount: number = 0): void { retryer(() => { From ddc88d6c9fa583166124e3e62967cf7122217195 Mon Sep 17 00:00:00 2001 From: Ivan Duplenskikh <115665590+ivanduplenskikh@users.noreply.github.com> Date: Wed, 25 Dec 2024 19:53:04 +0100 Subject: [PATCH 09/11] Check realpath when change directory to a directory symbolic link --- node/test/cd.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/node/test/cd.ts b/node/test/cd.ts index b2547c2b7..409e1ad8d 100644 --- a/node/test/cd.ts +++ b/node/test/cd.ts @@ -70,7 +70,7 @@ describe('cd cases', () => { assert.equal(process.cwd(), TEMP_DIR_2_SUBDIR_1); tl.cd(TEMP_DIR_2_SUBDIR_1_SYMLINK_DIR_1); - assert.equal(process.cwd(), TEMP_DIR_2_SUBDIR_1_SYMLINK_DIR_1); + assert.equal(fs.realpathSync('.'), TEMP_DIR_1); assert.equal(process.env.OLDPWD, TEMP_DIR_2_SUBDIR_1); @@ -92,7 +92,7 @@ describe('cd cases', () => { it('Provide path that is a dir symlink', (done) => { assert.ok(fs.existsSync(TEMP_DIR_2_SUBDIR_1_SYMLINK_DIR_1)); assert.doesNotThrow(() => tl.cd(TEMP_DIR_2_SUBDIR_1_SYMLINK_DIR_1)); - assert.equal(process.cwd(), TEMP_DIR_2_SUBDIR_1_SYMLINK_DIR_1); + assert.equal(fs.realpathSync('.'), TEMP_DIR_1); done(); }); From b36d3c0589d5aced4f074f85e45fd0ef8d360ce7 Mon Sep 17 00:00:00 2001 From: Ivan Duplenskikh <115665590+ivanduplenskikh@users.noreply.github.com> Date: Wed, 25 Dec 2024 20:00:47 +0100 Subject: [PATCH 10/11] Fix paths in popd unit tests --- node/test/popd.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/node/test/popd.ts b/node/test/popd.ts index 460b0dbfe..65b9f7d99 100644 --- a/node/test/popd.ts +++ b/node/test/popd.ts @@ -22,11 +22,11 @@ function reset() { } describe('popd cases', () => { - const TEMP_DIR_PATH = path.resolve('test_pushd', 'nested', 'dir'); + const TEMP_DIR_PATH = path.resolve(DIRNAME, 'test_pushd', 'nested', 'dir'); before(() => { - tl.mkdirP(path.resolve(DIRNAME, TEMP_DIR_PATH, 'a')); - tl.mkdirP(path.resolve(DIRNAME, TEMP_DIR_PATH, 'b', 'c')); + tl.mkdirP(path.resolve(TEMP_DIR_PATH, 'a')); + tl.mkdirP(path.resolve(TEMP_DIR_PATH, 'b', 'c')); }); beforeEach(() => { @@ -36,7 +36,7 @@ describe('popd cases', () => { after(() => { tl.cd(DIRNAME); reset(); - tl.rmRF(path.resolve(DIRNAME, TEMP_DIR_PATH)); + tl.rmRF(path.resolve(TEMP_DIR_PATH)); }); it('The default usage', (done) => { @@ -56,7 +56,7 @@ describe('popd cases', () => { assert.ok(process.cwd(), trail[0]); assert.deepEqual(trail, [ - path.resolve(DIRNAME, TEMP_DIR_PATH), + path.resolve(TEMP_DIR_PATH), DIRNAME, ]); @@ -94,7 +94,7 @@ describe('popd cases', () => { const trail = tl.popd('+1'); assert.ok(process.cwd(), trail[0]); - assert.deepEqual(trail, [path.resolve(DIRNAME, TEMP_DIR_PATH)]); + assert.deepEqual(trail, [path.resolve(TEMP_DIR_PATH)]); done(); }); @@ -104,7 +104,7 @@ describe('popd cases', () => { const trail = tl.popd('-0'); assert.ok(process.cwd(), trail[0]); - assert.deepEqual(trail, [path.resolve(DIRNAME, TEMP_DIR_PATH)]); + assert.deepEqual(trail, [path.resolve(TEMP_DIR_PATH)]); done(); }); @@ -130,7 +130,7 @@ describe('popd cases', () => { tl.pushd('b'); const trail = tl.popd(); - assert.ok(trail[0], path.resolve(DIRNAME, TEMP_DIR_PATH)); + assert.ok(trail[0], path.resolve(TEMP_DIR_PATH)); assert.ok(process.cwd(), trail[0]); assert.throws(() => tl.popd(), { message: 'Directory stack is empty' }); From 10db8356af59c7aba6981bc5c259292a2cc64c69 Mon Sep 17 00:00:00 2001 From: Ivan Duplenskikh <115665590+ivanduplenskikh@users.noreply.github.com> Date: Thu, 26 Dec 2024 11:45:51 +0100 Subject: [PATCH 11/11] Add OptionsPermutations, fix overloads, remove duplicate retryer func, sort test/tsconfig.json files --- node/task.ts | 184 ++++++++++++++++++++++++++-------------- node/test/mv.ts | 1 + node/test/tsconfig.json | 42 ++++----- 3 files changed, 141 insertions(+), 86 deletions(-) diff --git a/node/task.ts b/node/task.ts index 68c4971be..4abdf3e93 100644 --- a/node/task.ts +++ b/node/task.ts @@ -9,6 +9,13 @@ import tcm = require('./taskcommand'); import trm = require('./toolrunner'); import semver = require('semver'); +type OptionCases = `-${Uppercase | Lowercase}`; + +type OptionsPermutations = + T extends `${infer First}${infer Rest}` + ? OptionCases<`${U}${First}`> | OptionCases<`${First}${U}`> | OptionsPermutations | OptionCases + : OptionCases | ''; + export enum TaskResult { Succeeded = 0, SucceededWithIssues = 1, @@ -777,8 +784,8 @@ export const checkPath = im._checkPath; /** * Change working directory. * - * @param path new working directory path - * @returns void + * @param {string} path - New working directory path + * @returns {void} */ export function cd(path: string): void { if (path === '-') { @@ -819,8 +826,8 @@ function getActualStack() { /** * Change working directory and push it on the stack * - * @param dir new working directory path - * @returns void + * @param {string} dir - New working directory path + * @returns {void} */ export function pushd(dir: string = ''): string[] { const dirs = getActualStack(); @@ -863,8 +870,8 @@ export function pushd(dir: string = ''): string[] { /** * Change working directory back to previously pushed directory * - * @param index index to remove from the stack - * @returns void + * @param {string} index - Index to remove from the stack + * @returns {void} */ export function popd(index: string = ''): string[] { if (dirStack.length === 0) { @@ -891,11 +898,11 @@ export function popd(index: string = ''): string[] { } /** - * Make a directory. Creates the full path with folders in between + * Make a directory. Creates the full path with folders in between * Will throw if it fails * - * @param p path to create - * @returns void + * @param {string} p - Path to create + * @returns {void} */ export function mkdirP(p: string): void { if (!p) { @@ -974,25 +981,67 @@ export function resolve(...pathSegments: any[]): string { export const which = im._which; +type ListOptionsVariants = OptionsPermutations<'ra'>; + +/** + * Returns array of files in the given path, or in current directory if no path provided. + * @param {ListOptionsVariants} options - Available options: -R (recursive), -A (all files, include files beginning with ., except for . and ..) + * @param {...string[]} paths - Paths to search. + * @return {string[]} - An array of files in the given path(s). + */ +export function ls(options: ListOptionsVariants, ...paths: string[]): string[]; + +/** + * Returns array of files in the given path, or in current directory if no path provided. + * @param {ListOptionsVariants} options - Available options: -R (recursive), -A (all files, include files beginning with ., except for . and ..) + * @param {string[]} paths - Paths to search. + * @return {string[]} - An array of files in the given path(s). + */ +export function ls(options: ListOptionsVariants, paths: string[]): string[]; + +/** + * Returns array of files in the given path, or in current directory if no path provided. + * @param {ListOptionsVariants} options - Available options: -R (recursive), -A (all files, include files beginning with ., except for . and ..) + * @param {string} paths - Paths to search. + * @return {string[]} - An array of files in the given path(s). + */ +export function ls(options: ListOptionsVariants, paths: string): string[]; + +/** + * Returns array of files in the given path, or in current directory if no path provided. + * @param {string} path - Paths to search. + * @return {string[]} - An array of files in the given path(s). + */ +export function ls(path: string): string[]; + /** * Returns array of files in the given path, or in current directory if no path provided. - * @param {string} options Available options: -R (recursive), -A (all files, include files beginning with ., except for . and ..) - * @param {string[]} paths Paths to search. - * @return {string[]} An array of files in the given path(s). + * @param {string[]} paths - Paths to search. + * @return {string[]} - An array of files in the given path(s). */ -export function ls(optionsOrPaths?: string | string[], ...paths: string[]): string[]; -export function ls(optionsOrPaths?: string | string[], paths?: string[]): string[]; -export function ls(optionsOrPaths?: string | string[], paths?: string): string[]; +export function ls(paths: string[]): string[]; -export function ls(optionsOrPaths?: string | string[], ...paths: unknown[]): string[] { +/** + * Returns array of files in the given path, or in current directory if no path provided. + * @param {...string[]} paths - Paths to search. + * @return {string[]} - An array of files in the given path(s). + */ +export function ls(...paths: string[]): string[]; + +/** + * Returns array of files in the given path, or in current directory if no path provided. + * @param {unknown} optionsOrPaths - Available options: -R (recursive), -A (all files, include files beginning with ., except for . and ..) + * @param {unknown[]} paths - Paths to search. + * @return {string[]} - An array of files in the given path(s). + */ +export function ls(optionsOrPaths?: unknown, ...paths: unknown[]): string[] { let isRecursive = false; let includeHidden = false; - let handleAsOptions = false; if (typeof optionsOrPaths === 'string' && optionsOrPaths.startsWith('-')) { - optionsOrPaths = optionsOrPaths.toLowerCase(); - isRecursive = optionsOrPaths.includes('r'); - includeHidden = optionsOrPaths.includes('a'); + const options = String(optionsOrPaths).toLowerCase(); + isRecursive = options.includes('r'); + includeHidden = options.includes('a'); } // Flatten paths if the paths argument is array @@ -1006,7 +1055,7 @@ export function ls(optionsOrPaths?: string | string[], ...paths: unknown[]): str if (Array.isArray(optionsOrPaths)) { pathsFromOptions = optionsOrPaths; - } else if (optionsOrPaths) { + } else if (optionsOrPaths && typeof optionsOrPaths === 'string') { pathsFromOptions = [optionsOrPaths]; } @@ -1071,57 +1120,58 @@ export function ls(optionsOrPaths?: string | string[], ...paths: unknown[]): str } } -function retryer(func: Function, retryCount: number = 0, continueOnError: boolean = false) { - while (retryCount >= 0) { - try { - return func(); - } catch (error) { - if (!continueOnError || error.code === "ENOENT") { - throw error; - } - - console.log(loc('LIB_CopyFileFailed', retryCount)); - retryCount--; +type CopyOptionsVariants = OptionsPermutations<'frn'>; - if (retryCount < 0) { - warning(error, IssueSource.TaskInternal); - break; - } - } - } -} +/** + * Copies a file or folder. + * @param {string} source - Source path. + * @param {string} destination - Destination path. + * @param {string} [options] - Options string '-r', '-f' , '-n' or '-rfn' for recursive, force and no-clobber. + * @param {boolean} [continueOnError=false] - Optional. Whether to continue on error. + * @param {number} [retryCount=0] - Optional. Retry count to copy the file. It might help to resolve intermittent issues e.g. with UNC target paths on a remote host. + * @returns {void} + */ +export function cp(source: string, destination: string, options?: CopyOptionsVariants, continueOnError?: boolean, retryCount?: number): void; /** * Copies a file or folder. - * - * @param {string} sourceOrOptions - Either the source path or an option string '-r', '-f' or '-rf' for recursive and force. - * @param {string} destinationOrSource - The destination path or the source path. - * @param {string} [optionsOrDestination] - Options string or the destination path. - * @param {boolean} [continueOnError] - Optional. whether to continue on error. - * @param {number} [retryCount=0] - Optional. Retry count to copy the file. It might help to resolve intermittent issues e.g. with UNC target paths on a remote host. + * @param {string} options - Options string '-r', '-f' , '-n' or '-rfn' for recursive, force and no-clobber. + * @param {string} source - Source path. + * @param {string} [destination] - Destination path. + * @param {boolean} [continueOnError=false] - Optional. Whether to continue on error. + * @param {number} [retryCount=0] - Optional. Retry count to copy the file. It might help to resolve intermittent issues e.g. with UNC target paths on a remote host. + * @returns {void} */ -export function cp(source: string, destination: string, options?: string, continueOnError?: boolean, retryCount?: number): void; -export function cp(options: string, source: string, destination: string, continueOnError?: boolean, retryCount?: number): void; +export function cp(options: CopyOptionsVariants, source: string, destination: string, continueOnError?: boolean, retryCount?: number): void; -export function cp(sourceOrOptions: string, destinationOrSource: string, optionsOrDestination: string, continueOnError?: boolean, retryCount: number = 0): void { - retryer(() => { +/** + * Copies a file or folder. + * @param {string} sourceOrOptions - Either the source path or an option string '-r', '-f' , '-n' or '-rfn' for recursive, force and no-clobber. + * @param {string} destinationOrSource - Destination path or the source path. + * @param {string} [optionsOrDestination] - Options string or the destination path. + * @param {boolean} [continueOnError=false] - Optional. Whether to continue on error. + * @param {number} [retryCount=0] - Optional. Retry count to copy the file. It might help to resolve intermittent issues e.g. with UNC target paths on a remote host. + * @returns {void} + */ +export function cp(sourceOrOptions: unknown, destinationOrSource: string, optionsOrDestination: unknown, continueOnError: boolean = false, retryCount: number = 0): void { + retry(() => { let recursive = false; let force = true; - let source = sourceOrOptions; + let source = String(sourceOrOptions); let destination = destinationOrSource; let options = ''; - if (sourceOrOptions.startsWith('-')) { + if (typeof sourceOrOptions === 'string' && sourceOrOptions.startsWith('-')) { options = sourceOrOptions.toLowerCase(); recursive = options.includes('r'); force = !options.includes('n'); source = destinationOrSource; - destination = optionsOrDestination!; - } else if (optionsOrDestination && optionsOrDestination.startsWith('-')) { + destination = String(optionsOrDestination)!; + } else if (typeof optionsOrDestination === 'string' && optionsOrDestination && optionsOrDestination.startsWith('-')) { options = optionsOrDestination.toLowerCase(); recursive = options.includes('r'); force = !options.includes('n'); - source = sourceOrOptions; + source = String(sourceOrOptions); destination = destinationOrSource; } @@ -1152,23 +1202,26 @@ export function cp(sourceOrOptions: string, destinationOrSource: string, options } catch (error) { throw new Error(loc('LIB_OperationFailed', 'cp', error)); } - }, retryCount, continueOnError); + }, [], { retryCount, continueOnError}); } +type MoveOptionsVariants = OptionsPermutations<'fn'>; + /** * Moves a path. * - * @param source source path - * @param dest destination path - * @param options string -f or -n for force and no clobber - * @param continueOnError optional. whether to continue on error + * @param {string} source - Source path. + * @param {string} dest - Destination path. + * @param {MoveOptionsVariants} [options] - Option string -f or -n for force and no clobber. + * @param {boolean} [continueOnError] - Optional. Whether to continue on error. + * @returns {void} */ -export function mv(source: string, dest: string, options?: string, continueOnError?: boolean): void { +export function mv(source: string, dest: string, options?: MoveOptionsVariants, continueOnError?: boolean): void { let force = false; - if (options && options.startsWith('-')) { - options = options.toLowerCase(); - force = options.includes('f') && !options.includes('n'); + if (options && typeof options === 'string' && options.startsWith('-')) { + const lowercasedOptions = String(options).toLowerCase(); + force = lowercasedOptions.includes('f') && !lowercasedOptions.includes('n'); } const sourceExists = fs.existsSync(source); @@ -1646,8 +1699,9 @@ function _legacyFindFiles_getMatchingItems( /** * Remove a path recursively with force * - * @param inputPath path to remove - * @throws when the file or directory exists but could not be deleted. + * @param {string} inputPath - Path to remove + * @return {void} + * @throws When the file or directory exists but could not be deleted. */ export function rmRF(inputPath: string): void { debug('rm -rf ' + inputPath); diff --git a/node/test/mv.ts b/node/test/mv.ts index 394f91b85..a0217932a 100644 --- a/node/test/mv.ts +++ b/node/test/mv.ts @@ -48,6 +48,7 @@ describe('mv cases', () => { it('Provide an unsupported option argument', (done) => { assert.ok(fs.existsSync('file1')); + // @ts-ignore assert.doesNotThrow(() => tl.mv('file1', 'file1', '-Z', true)); assert.ok(fs.existsSync('file1')); diff --git a/node/test/tsconfig.json b/node/test/tsconfig.json index 31131f42e..58d802d2b 100644 --- a/node/test/tsconfig.json +++ b/node/test/tsconfig.json @@ -7,35 +7,35 @@ "moduleResolution": "node" }, "files": [ - "mv.ts", - "ls.ts", - "cp.ts", - "rm.ts", + "cctests.ts", "cd.ts", - "popd.ts", - "pushd.ts", + "commandtests.ts", + "cp.ts", "dirtests.ts", + "disktests.ts", + "fakeModules/fakemodule1.ts", + "fakeModules/fakemodule2.ts", + "filtertests.ts", + "findmatchtests.ts", + "gethttpproxytests.ts", "inputtests.ts", "internalhelpertests.ts", - "commandtests.ts", - "resulttests.ts", - "disktests.ts", + "isuncpathtests.ts", "legacyfindfilestests.ts", - "vaulttests.ts", - "toolrunnertests.ts", - "toolrunnerTestsWithExecAsync.ts", - "cctests.ts", "loctests.ts", + "ls.ts", "matchtests.ts", - "filtertests.ts", - "findmatchtests.ts", + "mockertests.ts", "mocktests.ts", + "mv.ts", + "popd.ts", + "pushd.ts", + "resulttests.ts", "retrytests.ts", - "isuncpathtests.ts", - "gethttpproxytests.ts", - "mockertests.ts", - "fakeModules/fakemodule1.ts", - "fakeModules/fakemodule2.ts", - "taskissuecommandtests.ts" + "rm.ts", + "taskissuecommandtests.ts", + "toolrunnertests.ts", + "toolrunnerTestsWithExecAsync.ts", + "vaulttests.ts" ] }