"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 __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.runTests = void 0;
const build_1 = require("./build");
const rxjs_1 = require("rxjs");
const child_process_1 = require("child_process");
const chalk_1 = __importDefault(require("chalk"));
const platform_1 = require("../utils/platform");
const spinnies_1 = __importDefault(require("spinnies"));
const fs_extra_1 = require("fs-extra");
const path_1 = require("path");
var TestStatus;
(function (TestStatus) {
    TestStatus[TestStatus["TESTING"] = 0] = "TESTING";
    TestStatus[TestStatus["PASS"] = 1] = "PASS";
    TestStatus[TestStatus["FAIL"] = 2] = "FAIL";
    TestStatus[TestStatus["SKIPPED"] = 3] = "SKIPPED";
})(TestStatus || (TestStatus = {}));
class TestRunner {
    constructor(target, concurrency, type) {
        this._logs = new Map();
        this._services = Array.isArray(target) ? target : [target];
        this._concurrency = concurrency;
        this._type = type;
        this._logs = new Map(this._services.map((s) => [s, []]));
    }
    _getCmd() {
        const cmd = 'npm';
        let args;
        if (!this._type) {
            args = ['test'];
        }
        else if (this._type === 'unit') {
            args = ['run', 'test:unit'];
        }
        else {
            args = ['run', 'test:functional'];
        }
        return { cmd, args };
    }
    runTests() {
        const testNode = (node, type) => {
            return new rxjs_1.Observable((obs) => {
                const now = Date.now();
                obs.next({ type, node, status: TestStatus.TESTING });
                let hasTests = false;
                const testKey = `test:${type}`;
                try {
                    const packageJson = fs_extra_1.readJSONSync(path_1.join(node.getLocation(), 'package.json'));
                    hasTests = packageJson.scripts && packageJson.scripts[testKey] != null;
                }
                catch (e) {
                    console.error(e);
                    obs.next({ type, node, status: TestStatus.FAIL, took: Date.now() - now });
                }
                if (!hasTests) {
                    obs.next({ type, node, status: TestStatus.SKIPPED, took: Date.now() - now });
                    return obs.complete();
                }
                const testProcess = child_process_1.spawn('npm', ['run', `test:${type}`], {
                    cwd: node.getLocation(),
                    env: Object.assign(Object.assign({}, process.env), { FORCE_COLOR: '2' }),
                });
                testProcess.stderr.on('data', (data) => {
                    this._logs.get(node).push(data.toString());
                });
                testProcess.stdout.on('data', (data) => {
                    this._logs.get(node).push(data.toString());
                });
                testProcess.on('close', (code) => {
                    if (code !== 0) {
                        obs.next({ type, node, status: TestStatus.FAIL, took: Date.now() - now });
                        return obs.complete();
                    }
                    obs.next({ type, node, status: TestStatus.PASS, took: Date.now() - now });
                    return obs.complete();
                });
                testProcess.on('error', (err) => {
                    console.error(err);
                    obs.next({ type, node, status: TestStatus.FAIL, took: Date.now() - now });
                    return obs.complete();
                });
            });
        };
        const hasUnit = !this._type || this._type === 'unit';
        const hasFunctional = !this._type || this._type === 'functional';
        const unitTestJobs$ = hasUnit ? this._services.map((s) => testNode(s, 'unit')) : [];
        const functionalTestJobs$ = hasFunctional ? this._services.map((s) => testNode(s, 'functional')) : [];
        const unit$ = rxjs_1.merge(...unitTestJobs$, this._concurrency);
        const functional$ = rxjs_1.concat(...functionalTestJobs$);
        return rxjs_1.concat(unit$, functional$);
    }
    printLogs(service) {
        const { cmd, args } = this._getCmd();
        console.log(`${chalk_1.default.bold(service.getName())} - ${chalk_1.default.gray([cmd, ...args].join(' '))}`);
        console.log('\n');
        console.log(this._logs.get(service).join(''));
        console.log('\n');
    }
}
exports.runTests = (cmd, scheduler, logger) => __awaiter(void 0, void 0, void 0, function* () {
    const concurrency = cmd.C ? platform_1.getThreads(Number(cmd.C)) : platform_1.getDefaultThreads();
    let type = null;
    if (cmd.functional && cmd.unit) {
        console.error(chalk_1.default.red('Cannot use both --unit and --functional flags'));
        process.exit(1);
    }
    else if (cmd.functional) {
        type = 'functional';
    }
    else if (cmd.unit) {
        type = 'unit';
    }
    const { graph, service } = yield build_1.beforeBuild(cmd, scheduler, logger, true);
    const target = cmd.S ? service : graph;
    if (cmd.recompile) {
        try {
            console.info('\nBuilding dependency graph\n');
            yield build_1.typeCheck(scheduler, target, cmd.onlySelf, false);
        }
        catch (e) {
            process.exit(1);
        }
    }
    console.info('\nRunning tests\n');
    const spinnies = new spinnies_1.default({
        failColor: 'white',
        succeedColor: 'white',
        spinnerColor: 'cyan',
    });
    const runner = new TestRunner(cmd.S ? service : graph.getNodes(), concurrency, type);
    const failures = [];
    const onNext = (evt) => {
        switch (evt.status) {
            case TestStatus.TESTING: {
                spinnies.add(evt.node.getName(), { text: `[${evt.type}] Testing ${evt.node.getName()}` });
                break;
            }
            case TestStatus.PASS: {
                spinnies.succeed(evt.node.getName(), {
                    text: `${chalk_1.default.black.bgGreen(' PASS ')} [${chalk_1.default.cyan(evt.type)}] ${evt.node.getName()} ${chalk_1.default.gray(evt.took + 'ms')}`,
                });
                break;
            }
            case TestStatus.SKIPPED: {
                spinnies.succeed(evt.node.getName(), {
                    text: `${chalk_1.default.black.bgYellow(' SKIPPED ')} [${chalk_1.default.cyan(evt.type)}] ${evt.node.getName()} - No script test:${evt.type} in package.json`,
                });
                break;
            }
            case TestStatus.FAIL: {
                failures.push(evt.node);
                spinnies.succeed(evt.node.getName(), {
                    text: `${chalk_1.default.black.bgRedBright(' FAIL ')} [${chalk_1.default.cyan(evt.type)}] ${evt.node.getName()} ${chalk_1.default.gray(evt.took + 'ms')}`,
                });
            }
        }
    };
    const onError = (evt) => {
        spinnies.fail(evt.node.getName(), { text: `[${evt.type}]  Error testing ${evt.node.getName()}` });
        failures.push(evt.node);
    };
    const onComplete = () => {
        if (failures.length) {
            failures.forEach((s) => runner.printLogs(s));
            process.exit(1);
        }
        process.exit(0);
    };
    runner.runTests().subscribe(onNext, onError, onComplete);
});
//# sourceMappingURL=test.js.map