"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __generator = (this && this.__generator) || function (thisArg, body) {
    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    function verb(n) { return function (v) { return step([n, v]); }; }
    function step(op) {
        if (f) throw new TypeError("Generator is already executing.");
        while (g && (g = 0, op[0] && (_ = 0)), _) try {
            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
            if (y = 0, t) op = [op[0] & 2, t.value];
            switch (op[0]) {
                case 0: case 1: t = op; break;
                case 4: _.label++; return { value: op[1], done: false };
                case 5: _.label++; y = op[1]; op = [0]; continue;
                case 7: op = _.ops.pop(); _.trys.pop(); continue;
                default:
                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
                    if (t[2]) _.ops.pop();
                    _.trys.pop(); continue;
            }
            op = body.call(thisArg, _);
        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MockTestRunner = void 0;
var cp = require("child_process");
var fs = require("fs");
var os = require("os");
var path = require("path");
var cmdm = require("./taskcommand");
var shelljs = require("shelljs");
var Downloader = require("nodejs-file-downloader");
var COMMAND_TAG = '[command]';
var COMMAND_LENGTH = COMMAND_TAG.length;
var downloadDirectory = path.join(process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE, 'azure-pipelines-task-lib', '_download');
var MockTestRunner = /** @class */ (function () {
    function MockTestRunner(testPath, taskJsonPath) {
        this._testPath = '';
        this._taskJsonPath = '';
        this.nodePath = '';
        this.stdout = '';
        this.stderr = '';
        this.cmdlines = {};
        this.invokedToolCount = 0;
        this.succeeded = false;
        this.errorIssues = [];
        this.warningIssues = [];
        if (testPath === undefined)
            return;
        this._taskJsonPath = taskJsonPath || '';
        this._testPath = testPath;
    }
    MockTestRunner.prototype.LoadAsync = function (testPath, taskJsonPath) {
        return __awaiter(this, void 0, void 0, function () {
            var _this = this;
            return __generator(this, function (_a) {
                return [2 /*return*/, new Promise(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
                        var _a;
                        return __generator(this, function (_b) {
                            switch (_b.label) {
                                case 0:
                                    if (this.nodePath != '') {
                                        resolve(this);
                                        return [2 /*return*/];
                                    }
                                    if (testPath) {
                                        this._testPath = testPath;
                                    }
                                    if (taskJsonPath) {
                                        this._taskJsonPath = taskJsonPath;
                                    }
                                    _a = this;
                                    return [4 /*yield*/, this.getNodePath()];
                                case 1:
                                    _a.nodePath = _b.sent();
                                    resolve(this);
                                    return [2 /*return*/];
                            }
                        });
                    }); })];
            });
        });
    };
    Object.defineProperty(MockTestRunner.prototype, "failed", {
        get: function () {
            return !this.succeeded;
        },
        enumerable: false,
        configurable: true
    });
    MockTestRunner.prototype.ran = function (cmdline) {
        return this.cmdlines.hasOwnProperty(cmdline.trim());
    };
    MockTestRunner.prototype.createdErrorIssue = function (message) {
        return this.errorIssues.indexOf(message.trim()) >= 0;
    };
    MockTestRunner.prototype.createdWarningIssue = function (message) {
        return this.warningIssues.indexOf(message.trim()) >= 0;
    };
    MockTestRunner.prototype.stdOutContained = function (message) {
        return this.stdout.indexOf(message) > 0;
    };
    MockTestRunner.prototype.stdErrContained = function (message) {
        return this.stderr.indexOf(message) > 0;
    };
    MockTestRunner.prototype.runAsync = function (nodeVersion) {
        return __awaiter(this, void 0, void 0, function () {
            var nodePath, spawn, lines, traceFile;
            var _this = this;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        this.cmdlines = {};
                        this.invokedToolCount = 0;
                        this.succeeded = true;
                        this.errorIssues = [];
                        this.warningIssues = [];
                        nodePath = this.nodePath;
                        if (!nodeVersion) return [3 /*break*/, 2];
                        return [4 /*yield*/, this.getNodePath(nodeVersion)];
                    case 1:
                        nodePath = _a.sent();
                        return [3 /*break*/, 4];
                    case 2:
                        if (!!nodePath) return [3 /*break*/, 4];
                        return [4 /*yield*/, this.getNodePath()];
                    case 3:
                        nodePath = _a.sent();
                        this.nodePath = nodePath;
                        _a.label = 4;
                    case 4:
                        spawn = cp.spawnSync(nodePath, [this._testPath]);
                        // Clean environment
                        Object.keys(process.env)
                            .filter(function (key) { return (key.substr(0, 'INPUT_'.length) === 'INPUT_' ||
                            key.substr(0, 'SECRET_'.length) === 'SECRET_' ||
                            key.substr(0, 'VSTS_TASKVARIABLE_'.length) === 'VSTS_TASKVARIABLE_'); })
                            .forEach(function (key) { return delete process.env[key]; });
                        if (spawn.error) {
                            console.error('Running test failed');
                            console.error(spawn.error.message);
                            return [2 /*return*/];
                        }
                        this.stdout = spawn.stdout.toString();
                        this.stderr = spawn.stderr.toString();
                        if (process.env['TASK_TEST_TRACE']) {
                            console.log('');
                        }
                        lines = this.stdout.replace(/\r\n/g, '\n').split('\n');
                        traceFile = this._testPath + '.log';
                        lines.forEach(function (line) {
                            var ci = line.indexOf('##vso[');
                            var cmd;
                            var cmi = line.indexOf(COMMAND_TAG);
                            if (ci >= 0) {
                                cmd = cmdm.commandFromString(line.substring(ci));
                                if (cmd.command === 'task.complete' && cmd.properties['result'] === 'Failed') {
                                    _this.succeeded = false;
                                }
                                if (cmd.command === 'task.issue' && cmd.properties['type'] === 'error') {
                                    _this.errorIssues.push(cmd.message.trim());
                                }
                                if (cmd.command === 'task.issue' && cmd.properties['type'] === 'warning') {
                                    _this.warningIssues.push(cmd.message.trim());
                                }
                            }
                            else if (cmi == 0 && line.length > COMMAND_LENGTH) {
                                var cmdline = line.substr(COMMAND_LENGTH).trim();
                                _this.cmdlines[cmdline] = true;
                                _this.invokedToolCount++;
                            }
                            if (process.env['TASK_TEST_TRACE']) {
                                fs.appendFileSync(traceFile, line + os.EOL);
                                if (line && !cmd) {
                                    console.log(line);
                                }
                                // don't print task.debug commands to console - too noisy.
                                // otherwise omit command details - can interfere during CI.
                                else if (cmd && cmd.command != 'task.debug') {
                                    console.log("".concat(cmd.command, " details omitted"));
                                }
                            }
                        });
                        if (this.stderr && process.env['TASK_TEST_TRACE']) {
                            console.log('STDERR: ' + this.stderr);
                            fs.appendFileSync(traceFile, 'STDERR: ' + this.stderr + os.EOL);
                        }
                        if (process.env['TASK_TEST_TRACE']) {
                            console.log('TRACE FILE: ' + traceFile);
                        }
                        return [2 /*return*/];
                }
            });
        });
    };
    // Returns a path to node.exe with the correct version for this task (based on if its node10 or node)
    MockTestRunner.prototype.getNodePath = function (nodeVersion) {
        return __awaiter(this, void 0, void 0, function () {
            var version, versions, downloadVersion, downloadDestination, pathToExe, result;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        version = nodeVersion || this.getNodeVersion();
                        versions = {
                            20: 'v20.13.1',
                            16: 'v16.20.2',
                            10: 'v10.24.1',
                            6: 'v6.17.1',
                        };
                        downloadVersion = versions[version];
                        if (!downloadVersion) {
                            throw new Error('Invalid node version, must be 6, 10, 16 or 20 (received ' + version + ')');
                        }
                        downloadDestination = path.join(downloadDirectory, 'node' + version);
                        pathToExe = this.getPathToNodeExe(downloadVersion, downloadDestination);
                        if (!pathToExe) return [3 /*break*/, 1];
                        return [2 /*return*/, pathToExe];
                    case 1: return [4 /*yield*/, this.downloadNode(downloadVersion, downloadDestination)];
                    case 2:
                        result = _a.sent();
                        return [2 /*return*/, result];
                }
            });
        });
    };
    // Determines the correct version of node to use based on the contents of the task's task.json. Defaults to Node 16.
    MockTestRunner.prototype.getNodeVersion = function () {
        var taskJsonPath = this.getTaskJsonPath();
        if (!taskJsonPath) {
            console.warn('Unable to find task.json, defaulting to use Node 16');
            return 16;
        }
        var taskJsonContents = fs.readFileSync(taskJsonPath, { encoding: 'utf-8' });
        var taskJson = JSON.parse(taskJsonContents);
        var nodeVersion = 0;
        var executors = ['execution', 'prejobexecution', 'postjobexecution'];
        for (var _i = 0, executors_1 = executors; _i < executors_1.length; _i++) {
            var executor = executors_1[_i];
            if (!taskJson[executor])
                continue;
            for (var _a = 0, _b = Object.keys(taskJson[executor]); _a < _b.length; _a++) {
                var key = _b[_a];
                var currExecutor = key.toLocaleLowerCase();
                if (!currExecutor.startsWith('node'))
                    continue;
                var version = currExecutor.replace('node', '');
                var intVersion = parseInt(version) || 6; // node handler is node v6 by default
                nodeVersion = Math.max(intVersion, nodeVersion);
            }
        }
        if (nodeVersion === 0) {
            console.warn('Unable to determine execution type from task.json, defaulting to use Node 16');
            return 16;
        }
        return nodeVersion;
    };
    // Returns the path to the task.json for the task being tested. Returns null if unable to find it.
    // Searches by moving up the directory structure from the initial starting point and checking at each level.
    MockTestRunner.prototype.getTaskJsonPath = function () {
        if (this._taskJsonPath) {
            return this._taskJsonPath;
        }
        var curPath = this._testPath;
        var newPath = path.join(this._testPath, '..');
        while (curPath != newPath) {
            curPath = newPath;
            var taskJsonPath = path.join(curPath, 'task.json');
            if (fs.existsSync(taskJsonPath)) {
                return taskJsonPath;
            }
            newPath = path.join(curPath, '..');
        }
        return '';
    };
    // Downloads the specified node version to the download destination. Returns a path to node.exe
    MockTestRunner.prototype.downloadNode = function (nodeVersion, downloadDestination) {
        return __awaiter(this, void 0, void 0, function () {
            var nodeUrl, downloadPath, _a, marker;
            return __generator(this, function (_b) {
                switch (_b.label) {
                    case 0:
                        shelljs.rm('-rf', downloadDestination);
                        nodeUrl = process.env['TASK_NODE_URL'] || 'https://nodejs.org/dist';
                        nodeUrl = nodeUrl.replace(/\/$/, ''); // ensure there is no trailing slash on the base URL
                        downloadPath = '';
                        _a = this.getPlatform();
                        switch (_a) {
                            case 'darwin': return [3 /*break*/, 1];
                            case 'linux': return [3 /*break*/, 3];
                            case 'win32': return [3 /*break*/, 5];
                        }
                        return [3 /*break*/, 8];
                    case 1: return [4 /*yield*/, this.downloadTarGz(nodeUrl + '/' + nodeVersion + '/node-' + nodeVersion + '-darwin-x64.tar.gz', downloadDestination)];
                    case 2:
                        _b.sent();
                        downloadPath = path.join(downloadDestination, 'node-' + nodeVersion + '-darwin-x64', 'bin', 'node');
                        return [3 /*break*/, 8];
                    case 3: return [4 /*yield*/, this.downloadTarGz(nodeUrl + '/' + nodeVersion + '/node-' + nodeVersion + '-linux-x64.tar.gz', downloadDestination)];
                    case 4:
                        _b.sent();
                        downloadPath = path.join(downloadDestination, 'node-' + nodeVersion + '-linux-x64', 'bin', 'node');
                        return [3 /*break*/, 8];
                    case 5: return [4 /*yield*/, this.downloadFile(nodeUrl + '/' + nodeVersion + '/win-x64/node.exe', downloadDestination, 'node.exe')];
                    case 6:
                        _b.sent();
                        return [4 /*yield*/, this.downloadFile(nodeUrl + '/' + nodeVersion + '/win-x64/node.lib', downloadDestination, 'node.lib')];
                    case 7:
                        _b.sent();
                        downloadPath = path.join(downloadDestination, 'node.exe');
                        _b.label = 8;
                    case 8:
                        marker = downloadDestination + '.completed';
                        fs.writeFileSync(marker, '');
                        return [2 /*return*/, downloadPath];
                }
            });
        });
    };
    // Downloads file to the downloadDestination, making any necessary folders along the way.
    MockTestRunner.prototype.downloadFile = function (url, downloadDestination, fileName) {
        return __awaiter(this, void 0, void 0, function () {
            var downloader;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        if (!url) {
                            throw new Error('Parameter "url" must be set.');
                        }
                        if (!downloadDestination) {
                            throw new Error('Parameter "downloadDestination" must be set.');
                        }
                        console.log('Downloading file:', url);
                        shelljs.mkdir('-p', downloadDestination);
                        downloader = new Downloader({
                            url: url,
                            directory: downloadDestination,
                            fileName: fileName
                        });
                        return [4 /*yield*/, downloader.download()];
                    case 1:
                        _a.sent();
                        return [2 /*return*/];
                }
            });
        });
    };
    // Downloads tarGz to the download destination, making any necessary folders along the way.
    MockTestRunner.prototype.downloadTarGz = function (url, downloadDestination) {
        return __awaiter(this, void 0, void 0, function () {
            var tarGzName, originalCwd;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        if (!url) {
                            throw new Error('Parameter "url" must be set.');
                        }
                        if (!downloadDestination) {
                            throw new Error('Parameter "downloadDestination" must be set.');
                        }
                        tarGzName = 'node.tar.gz';
                        return [4 /*yield*/, this.downloadFile(url, downloadDestination, tarGzName)];
                    case 1:
                        _a.sent();
                        originalCwd = process.cwd();
                        process.chdir(downloadDestination);
                        try {
                            cp.execSync("tar -xzf \"".concat(path.join(downloadDestination, tarGzName), "\""));
                        }
                        catch (_b) {
                            throw new Error('Failed to unzip node tar.gz from ' + url);
                        }
                        finally {
                            process.chdir(originalCwd);
                        }
                        return [2 /*return*/];
                }
            });
        });
    };
    // Checks if node is installed at downloadDestination. If it is, returns a path to node.exe, otherwise returns null.
    MockTestRunner.prototype.getPathToNodeExe = function (nodeVersion, downloadDestination) {
        var exePath = '';
        switch (this.getPlatform()) {
            case 'darwin':
                exePath = path.join(downloadDestination, 'node-' + nodeVersion + '-darwin-x64', 'bin', 'node');
                break;
            case 'linux':
                exePath = path.join(downloadDestination, 'node-' + nodeVersion + '-linux-x64', 'bin', 'node');
                break;
            case 'win32':
                exePath = path.join(downloadDestination, 'node.exe');
        }
        // Only use path if marker is found indicating download completed successfully (and not partially)
        var marker = downloadDestination + '.completed';
        if (fs.existsSync(exePath) && fs.existsSync(marker)) {
            return exePath;
        }
        else {
            return '';
        }
    };
    MockTestRunner.prototype.getPlatform = function () {
        var platform = os.platform();
        if (platform != 'darwin' && platform != 'linux' && platform != 'win32') {
            throw new Error('Unexpected platform: ' + platform);
        }
        return platform;
    };
    return MockTestRunner;
}());
exports.MockTestRunner = MockTestRunner;
