Source: config.js

"use strict";
/**
 * Configures `GlaceJS` before tests run.
 *
 * @namespace GlaceConfig
 * @prop {object} cluster - Cluster namespace.
 * @prop {boolean} [cluster.isMaster=true] - Mark current process as master.
 * @prop {boolean} [cluster.isSlave=false] - Mark current process as slave.
 * @prop {number} [cluster.slavesNum=0] - Number of slaves to launch.
 * @prop {number} [cluster.slaveId=null] - ID of slave.
 * @prop {object} session - Session namespace.
 * @prop {string} session.name - Session name. By default contains timestamp.
 *  Can be overridden with CLI option `--session-name`.
 * @prop {string} session.id - Session ID. Default is timestamp.
 * @prop {boolean} [session.errors=[]] - List of session errors.
 * @prop {boolean} [session.isPassed=false] - Flag to define if tests session
 *  run is passed or no.
 * @prop {array<string>} [session.preloads=[]] - Array of paths to `js` modules
 *  which will be loaded before tests session run.
 * @prop {boolean} [session.interactive=false] - Flag to launch interactive
 *  session. Can be overridden with CLI option `-i / --interactive`.
 * @prop {boolean} [session.debugOnFail=false] - Flag to enter to interactive
 *  mode on step failure. Can be overridden with CLI option `--debug-on-fail`.
 * @prop {boolean} [session.exitOnFail=false] - Flag to break tests session on
 *  first test failure. Can be overridden with CLI option `--exit-on-fail`.
 * @prop {string} [session.uncaughtException=log] - Strategy to process uncaught
 *  exceptions. Available values are `log`, `fail`, `mocha`. Can be overridden
 *  with CLI option `--uncaught`.
 * @prop {string} session.rootConftest - Path to `js` module which will be
 *  loaded right after preloads. Can be overridden with CLI
 *  option `--root-conftest`.
 * @prop {array<string>} session.killProcs - Array of process names which will
 *  be killed before tests session run. Can be overridden with CLI
 *  option `--kill-procs`.
 * @prop {object} retry - Retry settings namespace.
 * @prop {integer} [retry.id=0] - Number of retried session.
 * @prop {object} [retry.chunkIds={}] - Chunk ids grouped by retry values. For
 * example: `{1: ['1_1', '1_2'], 3: ['2_1'], 2: ['3_1', '3_2']}`. Populated on
 * first session run.
 * @prop {?array<string>} [retry.curChunkIds=null] - Reference to currently
 * populated group in `retry.chunkIds`.
 * @prop {object} test - Test settings namespace.
 * @prop {integer} [test.id=0] - Number of currently created test. Each test has
 * unique incremental id. On first session run it's assigned, on retry is used
 * to find test by id, because test name can be non-unique.
 * @prop {?TestCase} [test.curCase=null] - Currently executed test case.
 * @prop {array<TestCase>} [test.cases=[]] - Array of session test cases.
 * @prop {array<string>} [test.languages=[]] - Array of language names. Can be
 *  overridden with CLI option `--languages`.
 * @prop {array<string>} [test.dirs=[]] - Array of test files or folders. Can
 *  be overridden with CLI arguments or option `--targets` separated with comma.
 * @prop {boolean} [test.checkNames=true] - Flag to check test names uniqueness.
 *  Can be overridden with CLI option `--dont-check-names`.
 * @prop {integer} [test.retries=0] - <a name="test-retry" href="#test-retry">#</a>
 * Number of test retries on failure. Overridden with CLI option `--retry`.
 * @prop {object} chunk - Chunk settings namespace.
 * @prop {integer} [chunk.id=0] - Number of chunk inside a test, incremental,
 * starts from `0` in each test. Fully unique chunk id is consist of `test.id` &
 * `chunk.id`, like `1_1`, `1_2`, `2_1`.
 * @prop {?string} [chunk.curId=null] - Currently executed fully unique chunk id.
 * @prop {array<string>} [chunk.passedIds=[]] - Fully unique ids of passed chunks.
 * Used to skip already passed chunks on retry.
 * @prop {integer} [chunk.retries=0] - <a name="test-chunk-retry" href="#test-chunk-retry">#</a>
 * Number of chunk retries on failure. Overridden with CLI option `--chunk-retry`.
 * @prop {integer} [chunk.timeout=180] - <a name="test-chunk-timeout" href="#test-chunk-timeout">#</a>
 * Timeout of chunk execution, **sec**. Overridden with CLI option `--chunk-timeout`.
 * @prop {object} report - Report namespace.
 * @prop {string} [report.dir=cwd/report] - Folder to save tests session report.
 * @prop {string} [report.testDir] - Folder to save test-specific artifacts.
 * @prop {boolean} [report.clear=true] - Flag to clear report before tests run.
 *  Can be overridden with CLI option `--dont-clear-report`.
 * @prop {boolean} [report.errorsNow=false] - Flag to print test error right
 *  after its capture. Can be overridden with CLI option `--errors-now`.
 * @prop {string} [report.failedTestsPath=cwd/failed-tests.json] - Path to file
 *  where info about failed tests will be saved to. Can be overridden with CLI
 *  option `--failed-tests-path`.
 * @prop {object} [xunit] - xUnit report namespace.
 * @prop {boolean} [xunit.use=false] - Flag to activate xUnit report. Can be
 *  overridden with CLI option `--xunit`.
 * @prop {string} [xunit.path=report.dir/xunit.xml] - Path to xUnit report. Can
 *  be overridden with CLI option `--xunit-path`.
 * @prop {string} [xunit.suiteName=session.name] - xUnit suite name. Can be
 *  overridden with CLI option `--xunit-suite-name`.
 * @prop {object} [allure] - Allure report namespace.
 * @prop {boolean} [allure.use=false] - Flag to activate allure report. Can be
 *  overridden with CLI option `--allure`.
 * @prop {string} [allure.dir=report.dir/allure] - Folder to save allure report.
 *  Can be overridden with CLI option `--allure-dir`.
 * @prop {string} [allure.suiteName=session.name] - Allure suite name. Can be
 *  overridden with CLI option `--allure-suite-name`.
 * @prop {object} testrail - Testrail report namespace.
 * @prop {boolean} [testrail.use=false] - Flag to activate testrail report. Can
 *  be overridden with CLI option `--testrail`.
 * @prop {string} testrail.host - Testrail host. Can be overridden with CLI
 *  option `--testrail-host`.
 * @prop {string} testrail.user - Testrail user name or email. Can be overridden
 *  with CLI option `--testrail-user`.
 * @prop {string} testrail.token - Testrail auth token. Can be overridden with
 *  CLI option `--testrail-token`.
 * @prop {string} testrail.projectId - Testrail project ID. Can be overridden
 *  with CLI option `--testrail-project-id`.
 * @prop {string} testrail.suiteId - Testrail suite ID. Can be overridden with
 *  CLI option `--testrail-suite-id`.
 * @prop {string} testrail.runName - Testrail run name. Can be overridden with
 *  CLI option `--testrail-run-name`.
 * @prop {string} testrail.runDescription - Testrail run description. Can be
 *  overridden with CLI option `--testrail-run-description`.
 * @prop {object} plugins - Plugins namespace.
 * @prop {string} plugins.dir - Folder with custom plugins. Can be overridden
 *  with CLI option `--plugins-dir`.
 * @prop {object} filter - Tests filter namespace.
 * @prop {string} filter.grep - Mocha grep option to filter tests, scopes and
 *  suites. Can be overridden with CLI option `-g / --grep`.
 * @prop {array<string>} filter.include - List of test names which should be
 *  included to tests session. Can be overridden with CLI option `--include`.
 * @prop {array<string>} filter.exclude - List of test names which should be
 *  excluded from tests session. Can be overridden with CLI option `--exclude`.
 * @prop {boolean} [filter.precise=false] - Flag for precise tests inclusion or
 *  exclusion (not substring pattern). Can be overridden with CLI option
 *  `--precise`.
 * @prop {object} tools - Tools namespace.
 * @prop {boolean} [tools.stepsList=false] - Flag to list available steps only.
 *  Can be overridden with CLI option `--list-steps`.
 * @prop {string} tools.stepsFilter - String to filter steps. Can be overridden
 *  with CLI option `--list-steps`.
 * @prop {boolean} [tools.testsList=false] - Flag to list implemented tests
 *  only. Can be overridden with CLI option `--list-tests`.
 * @prop {string} tools.testsFilter - String to filter tests. Can be overridden
 *  with CLI option `--list-tests`.
 * @prop {boolean} [tools.fixturesList=false] - Flag to list available fixtures
 *  only. Can be overridden with CLI option `--list-fixtures`.
 * @prop {string} tools.fixturesFilter - String to filter fixtures. Can be
 *  overridden with CLI option `--list-fixtures`.
 * @prop {boolean} [tools.checkTestrail=false] - Flag to check matching of
 *  testrail cases with implemented tests only. Can be overridden with CLI
 *  option `--testrail-check`.
 */

const fs = require("fs");
const path = require("path");

require("colors");
const _ = require("lodash");
const expect = require("chai").expect;

const U = require("glace-utils");
U.docString();

const plugins = require("./plugins");

let config = U.config;
const args = config.args;

config.cluster = U.defVal(config.cluster, {});
config.cluster.slavesNum = U.defVal(args.slaves, 0);
if (config.cluster.slavesNum === "auto") {
    config.cluster.slavesNum = require("os").cpus().length;
} else {
    config.cluster.slavesNum = +config.cluster.slavesNum;
}
config.cluster.slaveId = parseInt(process.env.GLACE_SLAVE_ID) || null;
config.cluster.isSlave = !!process.env.GLACE_SLAVE_ID;
config.cluster.isMaster = !!config.cluster.slavesNum && !config.cluster.isSlave;
config.cluster.artifactsDir = path.resolve(U.cwd, U.defVal(args.reportDir, "report"));

config.session = U.defVal(config.session, {});
const date = new Date();
config.session.name = U.defVal(args.sessionName, `Session ${date.toLocaleString()}`);
config.session.id = date.getTime();
config.session.errors = [];
config.session.isPassed = false;
config.session.preloads = [];
config.session.interactive = args.i || args.interactive || false;
if (config.cluster.slavesNum) {
    expect(config.session.interactive,
        "Interactive mode is incompatible with `--slaves`").to.be.false;
}
config.session.debugOnFail = args.debugOnFail || false;
if (config.cluster.slavesNum) {
    expect(config.session.debugOnFail,
        "`--debug-on-fail` is incompatible with `--slaves`").to.be.false;
}
config.session.exitOnFail = args.exitOnFail || false;
config.session.uncaughtException = (args.uncaught || "log").toLowerCase();
expect([ "log", "fail", "mocha" ],
    "Invalid `--uncaught` option").include(config.session.uncaughtException);
if (args.rootConftest) config.session.rootConftest = path.resolve(U.cwd, args.rootConftest);
if (args.killProcs && !config.cluster.isSlave) config.session.killProcs = U.splitBy(args.killProcs, ",");

config.retry = U.defVal(config.retry, {});
config.retry.id = 0;
config.retry.chunkIds = {};
config.retry.curChunkIds = null;

const get_targets = () => {
    let tt;
    if (args._ && args._.length) {
        tt = args._;
    } else if (args.targets && args.targets.length) {
        tt = U.splitBy(args.targets, ",");
    } else {
        tt = ["tests"];
    };
    return tt.map(t => path.resolve(U.cwd, t));
};

config.test = U.defVal(config.test, {});
config.test.id = 0;
config.test.curCase = null;
config.test.cases = [];
config.test.languages = [];
if (args.languages) config.test.languages = U.splitBy(args.languages, ",");
config.test.dirs = get_targets();
config.test.checkNames = !args.dontCheckNames;
config.test.retries = Math.max(0, U.defVal(args.retry, 0));

config.chunk = U.defVal(config.chunk, {});
config.chunk.id = 0;
config.chunk.curId = null;
config.chunk.passedIds = [];
config.chunk.retries = Math.max(0, U.defVal(args.chunkRetry, 0));
config.chunk.timeout = (args.chunkTimeout || 180) * 1000 || Infinity;

config.report = U.defVal(config.report, {});
config.report.dots = args.dots || false;
config.report.dir = config.cluster.artifactsDir;
if (config.cluster.isMaster) {
    config.report.dir = path.resolve(config.report.dir, "master");
}
if (config.cluster.isSlave) {
    config.report.dir = path.resolve(config.report.dir, "slave-" + config.cluster.slaveId);
}
config.report.testDir = null;
config.report.clear = !args.dontClearReport;
config.report.errorsNow = args.errorsNow || false;
config.report.deepErrors = args.deepErrors || false;
config.report.failedTestsPath = path.resolve(config.report.dir, U.defVal(args.failedTestsPath, "failed-tests.json"));
if (!config.report.failedTestsPath.endsWith(".json")) config.report.failedTestsPath += ".json";

const tests_filter = filter => {
    const filePath = path.resolve(U.cwd, filter);
    if (fs.existsSync(filePath)) {
        config.filter.precise = true;
        return U.loadJson(filePath);
    } else {
        return U.splitBy(filter, "|").map(e => ({ id: e }));
    }
};

config.filter = U.defVal(config.filter, {});
config.filter.grep = args.g || args.grep;
config.filter.precise = args.preciseMatch || false;
if (args.include) config.filter.include = tests_filter(args.include);
if (args.exclude) config.filter.exclude = tests_filter(args.exclude);
if (config.filter.include) {
    for (const include of config.filter.include) {
        if (!include.passed_chunk_ids) continue;
        config.chunk.passedIds = config.chunk.passedIds.concat(include.passed_chunk_ids);
    }
}
if (process.env.GLACE_TEST_IDS) {
    config.filter.testIds = U.splitBy(process.env.GLACE_TEST_IDS, ",").map(i => +i);
} else {
    config.filter.testIds = null;
}

config.xunit = U.defVal(config.xunit, {});
config.xunit.use = U.defVal(args.xunit, false);
config.xunit.path = path.resolve(config.report.dir, U.defVal(args.xunitPath, "xunit.xml"));
config.xunit.suiteName = U.defVal(args.xunitSuiteName, config.session.name);

config.allure = U.defVal(config.allure, {});
config.allure.use = U.defVal(args.allure, false);
config.allure.dir = path.resolve(config.report.dir, U.defVal(args.allureDir, "allure"));
config.allure.suiteName = U.defVal(args.allureSuiteName, config.session.name);

config.testrail = U.defVal(config.testrail, {});
config.testrail.use = U.defVal(args.testrail, false);
config.testrail.host = U.defVal(args.testrailHost);
config.testrail.user = U.defVal(args.testrailUser);
config.testrail.token = U.defVal(args.testrailToken);
config.testrail.projectId = U.defVal(args.testrailProjectId);
config.testrail.suiteId = U.defVal(args.testrailSuiteId);
config.testrail.runName = U.defVal(args.testrailRunName);
config.testrail.runDescription = U.defVal(args.testrailRunDesc);

config.plugins = U.defVal(config.plugins, {});
if (args.pluginsDir) config.plugins.dir = path.join(U.cwd, args.pluginsDir);
config.plugins.disableDefault = U.defVal(args.disableDefaultPlugins, false);

plugins.getModules("config");

config.tools = U.defVal(config.tools, {});
config.tools.stepsList = !!args.listSteps;
config.tools.stepsFilter = typeof(args.listSteps) === "string" ? args.listSteps : null;
config.tools.testsList = !!args.listTests;
config.tools.testsFilter = typeof(args.listTests) === "string" ? args.listTests : null;
config.tools.fixturesList = !!args.listFixtures;
config.tools.fixturesFilter = typeof(args.listFixtures) === "string" ? args.listFixtures : null;
config.tools.pluginsList = !!args.listPlugins;
config.tools.checkTestrail = U.defVal(args.testrailCheck, false);

if (config.session.debugOnFail) {
    config.chunk.timeout = Infinity;
    config.xunit.use = false;
    config.allure.use = false;
    config.testrail.use = false;
}

if (config.session.interactive) {
    const temp = require("temp").track();
    const tempPath = temp.path({ prefix: "test", suffix: ".js" });
    const tempData = "test('interactive', () => chunk(async () => await $.debug()));";
    fs.writeFileSync(tempPath, tempData);
    config.test.dirs = [tempPath];
    config.chunk.timeout = Infinity;
    config.filter.grep = null;
    config.filter.include = null;
    config.filter.exclude = null;
    config.xunit.use = false;
    config.allure.use = false;
    config.testrail.use = false;
}

let userConfig = {};
const userConfigPath = path.resolve(U.cwd, (args.userConfig || "config.js"));
if (fs.existsSync(userConfigPath)) userConfig = require(userConfigPath);
_.assign(config, userConfig);

module.exports = config;