Source: test/node-tee.test.js

/**
 * tee (GNU coreutils) 8.32
 * Copyright (C) 2020 Free Software Foundation, Inc.
 * License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
 * This is free software: you are free to change and redistribute it.
 * There is NO WARRANTY, to the extent permitted by law.
 *
 * Written by Mike Parker, Richard M. Stallman, and David MacKenzie.
 *
 * Implemented in node.js by Essam A. El-Sherif.
 *
 * @module  node-tee-test
 * @desc    Testing module for 'node-tee' core utility.
 * @version 1.0.1
 * @author  Essam A. El-Sherif
 */

/* Import nodeJS core modules */
import os                 from 'node:os';
import process            from 'node:process';
import assert             from 'node:assert/strict';
import fs                 from 'node:fs';
import { exec, execSync } from 'node:child_process';
import { fileURLToPath }  from 'node:url';
import { dirname, join }  from 'node:path';

/** @const {string} CMD_SHELL - The coreutils shell command. */
const CMD_SHELL = 'tee';

/** @const {string} CMD_NODE - The coreutils node command. */
const CMD_NODE = 'node-tee';

/** @const {string} CMD_NODE_TEST - The coreutils node testing command. */
const CMD_NODE_TEST = 'node-tee-test';

/* Emulate commonJS __filename and __dirname constants */
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

/* Prepare test environment */
const cmdNode = join(__dirname, '..', 'lib/node-tee.js');
const cmdNodeTest = join(__dirname, '..', 'test/node-tee.test.js');

let cmdShell = CMD_SHELL;
let cmdShellVer = cmdShell;

const devNull = os.devNull;
const tmpDir  = os.tmpdir();
const noOpCmd = 'exit';

let testCount   = 1;
let passCount   = 0;
let failCount   = 0;
let cancelCount = 0
let skipCount   = 0;
let todoCount   = 0;
let startTime = Date.now();

const suites = new Map();

/** @const {object} cmdOptions - Command line arguments. */
const cmdOptions = {
	node    : true,  // -n --node / - d --def
	verbose : false, // -v --verbose
};

/**
 *     function main()
 * function verifyShellCmd()
 * function loadTestData()
 * function nodeRunner()
 * function defRunner()
 * async function makeTest()
 * function getCmdOutput()
 * function parseCmdLine()
 * function getHelp()
 * function getError()
 *
 * @func Main
 * @desc The application entry point function.
 */
(() => {
	parseCmdLine();

	if(process.env['NODE_TEE_TEST']){
		process.stdout.write(JSON.stringify(cmdOptions));
	}
	else{
		verifyShellCmd();
		loadTestData();

		if(cmdOptions.node){

			import('node:test')
				.then(runner => {
					cmdOptions.verbose = false;
					nodeRunner(runner);
				})
				/* node:coverage disable */
				.catch((e) => {
					defRunner();
				});
		}
		else{
			defRunner();
		}
	}
	/* node:coverage enable */
})('Main Function');

/**
 * function main()
 *     function verifyShellCmd()
 * function loadTestData()
 * function nodeRunner()
 * function defRunner()
 * async function makeTest()
 * function getCmdOutput()
 * function parseCmdLine()
 * function getHelp()
 * function getError()
 *
 * @func verifyShellCmd
 * @desc Verify existance of the core utility command and test its version.
 */
function verifyShellCmd(){

let cmdVer = `\
tee (GNU coreutils) 8.32
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Written by Mike Parker, Richard M. Stallman, and David MacKenzie.
`;
	try{
		const ver = execSync(`${cmdShell} --version`, {encoding: 'UTF-8', stdio: ['pipe', 'pipe', 'ignore']});

		if(ver !== cmdVer)
			cmdShellVer = '';
	}
	/* node:coverage disable */
	catch(e){
		cmdShell = '';
		cmdShellVer = '';
	}
	/* node:coverage enable */
}

/**
 * function main()
 * function verifyShellCmd()
 *     function loadTestData()
 * function nodeRunner()
 * function defRunner()
 * async function makeTest()
 * function getCmdOutput()
 * function parseCmdLine()
 * function getHelp()
 * function getError()
 *
 * @func loadTestData
 * @desc Load test data.
 */
function loadTestData(){

	let cmdData = null;
	let suiteDesc = '';
	let actFile = '', expFile = '';

	// TEST SUITE #0 - Self test this test module
	suiteDesc = 'Self test this test module';
	suites.set(suiteDesc, []);

	// TEST ### - node-tee-test --help
	cmdData = {cmd_aux: null};

	cmdData.cmd_act = `node ${cmdNodeTest} --help`;
	cmdData.cmd_exp = '';
	cmdData.cmd_inp = '';

	cmdData.cmd_out = getHelp();
	cmdData.cmd_err = '';

	cmdData.cmd_opt = {encoding: 'UTF-8'};
	cmdData.cmd_ext = 0;
	cmdData.cmd_desc = `${CMD_NODE_TEST} --help`;

	cmdData.cmd_skip = false;
	suites.get(suiteDesc).push(cmdData);

	// TEST ### - node-tee-test -h
	cmdData = {cmd_aux: null};

	cmdData.cmd_act = `node ${cmdNodeTest} -h`;
	cmdData.cmd_exp = '';
	cmdData.cmd_inp = '';

	cmdData.cmd_out = getHelp();
	cmdData.cmd_err = '';

	cmdData.cmd_opt = {encoding: 'UTF-8'};
	cmdData.cmd_ext = 0;
	cmdData.cmd_desc = `${CMD_NODE_TEST} -h`;

	cmdData.cmd_skip = false;
	suites.get(suiteDesc).push(cmdData);

	// TEST ### - NODE_TEE_TEST=1 node-tee-test
	cmdData = {cmd_aux: null};

	cmdData.cmd_act = `node ${cmdNodeTest}`;
	cmdData.cmd_exp = '';
	cmdData.cmd_inp = '';

	cmdData.cmd_out = `{"node":true,"verbose":false}`;
	cmdData.cmd_err = '';

	cmdData.cmd_opt = {encoding: 'UTF-8', env:{NODE_TEE_TEST:1}};
	cmdData.cmd_ext = 0;
	cmdData.cmd_desc = `NODE_TEE_TEST=1 ${CMD_NODE_TEST}`;

	cmdData.cmd_skip = false;
	suites.get(suiteDesc).push(cmdData);

	// TEST ### - NODE_TEE_TEST=1 node-tee-test --node
	cmdData = {cmd_aux: null};

	cmdData.cmd_act = `node ${cmdNodeTest} --node`;
	cmdData.cmd_exp = '';
	cmdData.cmd_inp = '';

	cmdData.cmd_out = `{"node":true,"verbose":false}`;
	cmdData.cmd_err = '';

	cmdData.cmd_opt = {encoding: 'UTF-8', env:{NODE_TEE_TEST:1}};
	cmdData.cmd_ext = 0;
	cmdData.cmd_desc = `NODE_TEE_TEST=1 ${CMD_NODE_TEST} --node`;

	cmdData.cmd_skip = false;
	suites.get(suiteDesc).push(cmdData);

	// TEST ### - NODE_TEE_TEST=1 node-tee-test -n
	cmdData = {cmd_aux: null};

	cmdData.cmd_act = `node ${cmdNodeTest} -n`;
	cmdData.cmd_exp = '';
	cmdData.cmd_inp = '';

	cmdData.cmd_out = `{"node":true,"verbose":false}`;
	cmdData.cmd_err = '';

	cmdData.cmd_opt = {encoding: 'UTF-8', env:{NODE_TEE_TEST:1}};
	cmdData.cmd_ext = 0;
	cmdData.cmd_desc = `NODE_TEE_TEST=1 ${CMD_NODE_TEST} -n`;

	cmdData.cmd_skip = false;
	suites.get(suiteDesc).push(cmdData);

	// TEST ### - NODE_TEE_TEST=1 node-tee-test --def
	cmdData = {cmd_aux: null};

	cmdData.cmd_act = `node ${cmdNodeTest} --def`;
	cmdData.cmd_exp = '';
	cmdData.cmd_inp = '';

	cmdData.cmd_out = `{"node":false,"verbose":false}`;
	cmdData.cmd_err = '';

	cmdData.cmd_opt = {encoding: 'UTF-8', env:{NODE_TEE_TEST:1}};
	cmdData.cmd_ext = 0;
	cmdData.cmd_desc = `NODE_TEE_TEST=1 ${CMD_NODE_TEST} --def`;

	cmdData.cmd_skip = false;
	suites.get(suiteDesc).push(cmdData);

	// TEST ### - NODE_TEE_TEST=1 node-tee-test -d
	cmdData = {cmd_aux: null};

	cmdData.cmd_act = `node ${cmdNodeTest} -d`;
	cmdData.cmd_exp = '';
	cmdData.cmd_inp = '';

	cmdData.cmd_out = `{"node":false,"verbose":false}`;
	cmdData.cmd_err = '';

	cmdData.cmd_opt = {encoding: 'UTF-8', env:{NODE_TEE_TEST:1}};
	cmdData.cmd_ext = 0;
	cmdData.cmd_desc = `NODE_TEE_TEST=1 ${CMD_NODE_TEST} -d`;

	cmdData.cmd_skip = false;
	suites.get(suiteDesc).push(cmdData);

	// TEST ### - NODE_TEE_TEST=1 node-tee-test --verbose
	cmdData = {cmd_aux: null};

	cmdData.cmd_act = `node ${cmdNodeTest} --verbose`;
	cmdData.cmd_exp = '';
	cmdData.cmd_inp = '';

	cmdData.cmd_out = `{"node":true,"verbose":true}`;
	cmdData.cmd_err = '';

	cmdData.cmd_opt = {encoding: 'UTF-8', env:{NODE_TEE_TEST:1}};
	cmdData.cmd_ext = 0;
	cmdData.cmd_desc = `NODE_TEE_TEST=1 ${CMD_NODE_TEST} --verbose`;

	cmdData.cmd_skip = false;
	suites.get(suiteDesc).push(cmdData);

	// TEST ### - NODE_TEE_TEST=1 node-tee-test -v
	cmdData = {cmd_aux: null};

	cmdData.cmd_act = `node ${cmdNodeTest} -v`;
	cmdData.cmd_exp = '';
	cmdData.cmd_inp = '';

	cmdData.cmd_out = `{"node":true,"verbose":true}`;
	cmdData.cmd_err = '';

	cmdData.cmd_opt = {encoding: 'UTF-8', env:{NODE_TEE_TEST:1}};
	cmdData.cmd_ext = 0;
	cmdData.cmd_desc = `NODE_TEE_TEST=1 ${CMD_NODE_TEST} -v`;

	cmdData.cmd_skip = false;
	suites.get(suiteDesc).push(cmdData);

	// TEST ### - NODE_TEE_TEST=1 node-tee-test --xxx
	cmdData = {cmd_aux: null};

	cmdData.cmd_act = `node ${cmdNodeTest} --xxx`;
	cmdData.cmd_exp = '';
	cmdData.cmd_inp = '';

cmdData.cmd_out = ``;
cmdData.cmd_err = `\
${CMD_NODE_TEST}: invalid option -- 'xxx'
Try '${CMD_NODE_TEST} --help' for more information.
`;

	cmdData.cmd_opt = {encoding: 'UTF-8', env:{NODE_TEE_TEST:1}};
	cmdData.cmd_ext = 1;
	cmdData.cmd_desc = `NODE_TEE_TEST=1 ${CMD_NODE_TEST} --xxx`;

	cmdData.cmd_skip = false;
	suites.get(suiteDesc).push(cmdData);

	// TEST ### - NODE_TEE_TEST=1 node-tee-test -x
	cmdData = {cmd_aux: null};

	cmdData.cmd_act = `node ${cmdNodeTest} -x`;
	cmdData.cmd_exp = '';
	cmdData.cmd_inp = '';

cmdData.cmd_out = ``;
cmdData.cmd_err = `\
${CMD_NODE_TEST}: unrecognized option '-x'
Try '${CMD_NODE_TEST} --help' for more information.
`,

	cmdData.cmd_opt = {encoding: 'UTF-8', env:{NODE_TEE_TEST:1}};
	cmdData.cmd_ext = 1;
	cmdData.cmd_desc = `NODE_TEE_TEST=1 ${CMD_NODE_TEST} -x`;

	cmdData.cmd_skip = false;
	suites.get(suiteDesc).push(cmdData);

	// TEST ### - NODE_TEE_TEST=1 node-tee-test xxx
	cmdData = {cmd_aux: null};

	cmdData.cmd_act = `node ${cmdNodeTest} xxx`;
	cmdData.cmd_exp = '';
	cmdData.cmd_inp = '';

cmdData.cmd_out = ``;
cmdData.cmd_err = `\
${CMD_NODE_TEST}: unrecognized option 'xxx'
Try '${CMD_NODE_TEST} --help' for more information.
`,

	cmdData.cmd_opt = {encoding: 'UTF-8', env:{NODE_TEE_TEST:1}};
	cmdData.cmd_ext = 1;
	cmdData.cmd_desc = `NODE_TEE_TEST=1 ${CMD_NODE_TEST} xxx`;

	cmdData.cmd_skip = false;
	suites.get(suiteDesc).push(cmdData);

	// TEST SUITE #1 - Test common coreutils options
	suiteDesc = 'Test common coreutils options';
	suites.set(suiteDesc, []);

	// TEST ### - tee --help
	cmdData = {cmd_aux: null};

	cmdData.cmd_act = `node ${cmdNode} --help`;
	cmdData.cmd_exp = '';
	cmdData.cmd_inp = '';

cmdData.cmd_out = `\
Usage: node-tee [OPTION]... [FILE]...
Copy standard input to each FILE, and also to standard output.

  -a, --append              append to the given FILEs, do not overwrite
  -i, --ignore-interrupts   ignore interrupt signals
  -p                        diagnose errors writing to non pipes
      --output-error[=MODE]   set behavior on write error.  See MODE below
      --help     display this help and exit
      --version  output version information and exit

MODE determines behavior with write errors on the outputs:
  'warn'         diagnose errors writing to any output
  'warn-nopipe'  diagnose errors writing to any output not a pipe
  'exit'         exit on error writing to any output
  'exit-nopipe'  exit on error writing to any output not a pipe
The default MODE for the -p option is 'warn-nopipe'.
The default operation when --output-error is not specified, is to
exit immediately on error writing to a pipe, and diagnose errors
writing to non pipe outputs.
`;
cmdData.cmd_err = '';

	cmdData.cmd_opt = {encoding: 'UTF-8'};
	cmdData.cmd_ext = 0;
	cmdData.cmd_desc = `${CMD_NODE} --help`;

	cmdData.cmd_skip = false;
	suites.get(suiteDesc).push(cmdData);

	// TEST ### - tee --version
	cmdData = {cmd_aux: null};

	cmdData.cmd_act = `node ${cmdNode} --version`;
	cmdData.cmd_exp = '';
	cmdData.cmd_inp = '';

	cmdData.cmd_out = `v1.0.1\n`;
	cmdData.cmd_err = '';

	cmdData.cmd_opt = {encoding: 'UTF-8'};
	cmdData.cmd_ext = 0;
	cmdData.cmd_desc = `${CMD_NODE} --version`;

	cmdData.cmd_skip = false;
	suites.get(suiteDesc).push(cmdData);

	// TEST ### - tee --
	cmdData = {cmd_aux: null};

	cmdData.cmd_act = `node ${cmdNode} --`;
	cmdData.cmd_exp = cmdShell && `${cmdShell} --`;
	cmdData.cmd_inp = '';

	cmdData.cmd_out = '';
	cmdData.cmd_err = '';

	cmdData.cmd_opt = {encoding: 'UTF-8'};
	cmdData.cmd_ext = 0;
	cmdData.cmd_desc = `${CMD_NODE} --`;

	cmdData.cmd_skip = false;
	suites.get(suiteDesc).push(cmdData);

	// TEST SUITE #2 - Validate command line arguments
	suiteDesc = 'Validate command line arguments';
	suites.set(suiteDesc, []);

	// TEST ### - tee --xxx
	cmdData = {cmd_aux: null};

	cmdData.cmd_act = `node ${cmdNode} --xxx`;
	cmdData.cmd_exp = cmdShellVer && `${cmdShell} --xxx`;
	cmdData.cmd_inp = '';

cmdData.cmd_out = '';
cmdData.cmd_err = `${CMD_NODE}: unrecognized option '--xxx'
Try '${CMD_NODE} --help' for more information.\n`;

	cmdData.cmd_opt = {encoding: 'UTF-8'};
	cmdData.cmd_ext = 1;
	cmdData.cmd_desc = `${CMD_NODE} --xxx`;

	cmdData.cmd_skip = false;
	suites.get(suiteDesc).push(cmdData);

	// TEST ### - tee --output-error=
	cmdData = {cmd_aux: null};

	cmdData.cmd_act = `node ${cmdNode} --output-error=`;
	cmdData.cmd_exp = cmdShellVer && `${cmdShell} --output-error=`;
	cmdData.cmd_inp = '';

cmdData.cmd_out = '';
cmdData.cmd_err = `\
${CMD_NODE}: ambiguous argument ‘’ for ‘--output-error’
Valid arguments are:
  - ‘warn’
  - ‘warn-nopipe’
  - ‘exit’
  - ‘exit-nopipe’
Try '${CMD_NODE} --help' for more information.\n`;

	cmdData.cmd_opt = {encoding: 'UTF-8'};
	cmdData.cmd_ext = 1;
	cmdData.cmd_desc = `${CMD_NODE} --output-error=`;

	cmdData.cmd_skip = false;
	suites.get(suiteDesc).push(cmdData);

	// TEST ### - tee --output-error=xxx
	cmdData = {cmd_aux: null};

	cmdData.cmd_act = `node ${cmdNode} --output-error=xxx`;
	cmdData.cmd_exp = cmdShellVer && `${cmdShell} --output-error=xxx`;
	cmdData.cmd_inp = '';

cmdData.cmd_out = '';
cmdData.cmd_err = `\
${CMD_NODE}: invalid argument ‘xxx’ for ‘--output-error’
Valid arguments are:
  - ‘warn’
  - ‘warn-nopipe’
  - ‘exit’
  - ‘exit-nopipe’
Try '${CMD_NODE} --help' for more information.\n`;


	cmdData.cmd_opt = {encoding: 'UTF-8'};
	cmdData.cmd_ext = 1;
	cmdData.cmd_desc = `${CMD_NODE} --output-error=xxx`;

	cmdData.cmd_skip = false;
	suites.get(suiteDesc).push(cmdData);

	// TEST ### - tee -x
	cmdData = {cmd_aux: null};

	cmdData.cmd_act = `node ${cmdNode} -x`;
	cmdData.cmd_exp = cmdShellVer && `${cmdShell} -x`;
	cmdData.cmd_inp = '';

cmdData.cmd_out = '';
cmdData.cmd_err = `${CMD_NODE}: invalid option -- 'x'
Try '${CMD_NODE} --help' for more information.\n`;

	cmdData.cmd_opt = {encoding: 'UTF-8'};
	cmdData.cmd_ext = 1;
	cmdData.cmd_desc = `${CMD_NODE} -x`;

	cmdData.cmd_skip = false;
	suites.get(suiteDesc).push(cmdData);

	// TEST SUITE #3 - Test normal operation
	suiteDesc = 'Test normal operation';
	suites.set(suiteDesc, []);

	// TEST ### - tee
	cmdData = {cmd_aux: null};

	cmdData.cmd_act = `node ${cmdNode}`;
	cmdData.cmd_exp = cmdShell && `${cmdShell}`;
	cmdData.cmd_inp = '';

	cmdData.cmd_out = '';
	cmdData.cmd_err = '';

	cmdData.cmd_opt = {encoding: 'UTF-8'};
	cmdData.cmd_ext = 0;
	cmdData.cmd_desc = `${CMD_NODE}`;

	cmdData.cmd_skip = false;
	suites.get(suiteDesc).push(cmdData);

	// TEST ### - tee << \\x00\\x01\\x02\\x03\\x04
	cmdData = {cmd_aux: null};

	cmdData.cmd_act = `node ${cmdNode}`;
	cmdData.cmd_exp = cmdShell && `${cmdShell}`;
	cmdData.cmd_inp = '\x00\x01\x02\x03\x04';

	cmdData.cmd_out = `\x00\x01\x02\x03\x04`;
	cmdData.cmd_err = '';

	cmdData.cmd_opt = {encoding: 'UTF-8'};
	cmdData.cmd_ext = 0;
	cmdData.cmd_desc = `${CMD_NODE}` + ` << \\x00\\x01\\x02\\x03\\x04`;

	cmdData.cmd_skip = false;
	suites.get(suiteDesc).push(cmdData);

	// TEST ### - tee << test data\\nline 1\\nline 2\\x04\\nline 3
	cmdData = {cmd_aux: null};

	cmdData.cmd_act = `node ${cmdNode}`;
	cmdData.cmd_exp = cmdShell && `${cmdShell}`;
	cmdData.cmd_inp = 'test data\nline 1\nline 2\x04\nline 3';

cmdData.cmd_out = `test data
line 1
line 2\x04
line 3`;
cmdData.cmd_err = '';

	cmdData.cmd_opt = {encoding: 'UTF-8'};
	cmdData.cmd_ext = 0;
	cmdData.cmd_desc = `${CMD_NODE}` + ` << test data\\nline 1\\nline 2\\x04\\nline 3`;

	cmdData.cmd_skip = false;
	suites.get(suiteDesc).push(cmdData);

	// TEST ### - tee -i << test data\\nline 1\\nline 2\\x04\\nline 3
	cmdData = {cmd_aux: null};

	cmdData.cmd_act = `node ${cmdNode} -i`;
	cmdData.cmd_exp = cmdShell && `${cmdShell} -i`;
	cmdData.cmd_inp = 'test data\nline 1\nline 2\x04\nline 3';

cmdData.cmd_out = `test data
line 1
line 2\x04
line 3`;
cmdData.cmd_err = '';

	cmdData.cmd_opt = {encoding: 'UTF-8'};
	cmdData.cmd_ext = 0;
	cmdData.cmd_desc = `${CMD_NODE} -i` + ` << test data\\nline 1\\nline 2\\x04\\nline 3`;

	cmdData.cmd_skip = false;
	suites.get(suiteDesc).push(cmdData);

	// TEST ### - tee --ignore-interrupts << test data\\nline 1\\nline 2\\x04\\nline 3
	cmdData = {cmd_aux: null};

	cmdData.cmd_act = `node ${cmdNode} --ignore-interrupts`;
	cmdData.cmd_exp = cmdShell && `${cmdShell} --ignore-interrupts`;
	cmdData.cmd_inp = 'test data\nline 1\nline 2\x04\nline 3';

cmdData.cmd_out = `test data
line 1
line 2\x04
line 3`;
cmdData.cmd_err = '';

	cmdData.cmd_opt = {encoding: 'UTF-8'};
	cmdData.cmd_ext = 0;
	cmdData.cmd_desc = `${CMD_NODE} --ignore-interrupts` + ` << test data\\nline 1\\nline 2\\x04\\nline 3`;

	cmdData.cmd_skip = false;
	suites.get(suiteDesc).push(cmdData);

	// TEST ### - tee << test data << CTRL-C signal
	cmdData = {cmd_aux: null};

	cmdData.cmd_act = `node ${cmdNode}`;
	cmdData.cmd_exp = cmdShell && `${cmdShell}`;
	cmdData.cmd_inp = 'test data';

	cmdData.cmd_out = `test data`;
	cmdData.cmd_err = '';

	cmdData.cmd_opt = {encoding: 'UTF-8'};
	cmdData.cmd_ext = 0;
	cmdData.cmd_desc = `${CMD_NODE}` + ` << test data << CTRL-C signal`;

	cmdData.signal = 'SIGINT';

	cmdData.cmd_skip = false;
	suites.get(suiteDesc).push(cmdData);

	// TEST ### - tee -i << test data << CTRL-C signal
	cmdData = {cmd_aux: null};

	cmdData.cmd_act = `node ${cmdNode} -i`;
	cmdData.cmd_exp = cmdShell && `${cmdShell} -i`;
	cmdData.cmd_inp = 'test data';

	cmdData.cmd_out = `test data`;
	cmdData.cmd_err = '';

	cmdData.cmd_opt = {encoding: 'UTF-8'};
	cmdData.cmd_ext = 0;
	cmdData.cmd_desc = `${CMD_NODE} -i` + ` << test data << CTRL-C signal`;

	cmdData.signal = 'SIGINT';

	cmdData.cmd_skip = false;
	suites.get(suiteDesc).push(cmdData);

	// TEST ### - tee tmpDir/testTeeAct-1.pid << test data
	cmdData = {cmd_aux: null};
	cmdData.actFile = join(tmpDir, `testTeeAct-1.${process.pid}`);
	cmdData.expFile = join(tmpDir, `testTeeExp-1.${process.pid}`);

	fs.writeFileSync(cmdData.actFile, 'test data',{encoding: 'utf8'});
	fs.writeFileSync(cmdData.expFile, 'test data',{encoding: 'utf8'});

	cmdData.cmd_act = `node ${cmdNode} ${cmdData.actFile}`;
	cmdData.cmd_exp = cmdShell && `${cmdShell} ${cmdData.expFile}`;
	cmdData.cmd_inp = 'test data';

	cmdData.cmd_out = 'test data';
	cmdData.cmd_err = '';

	cmdData.cmd_opt = {encoding: 'UTF-8'};
	cmdData.cmd_ext = 0;
	cmdData.cmd_desc = `${CMD_NODE} ${cmdData.actFile}` + ` << test data`;

	cmdData.cmd_aux = (cmdData) => {
		fs.open(cmdData.actFile, (err, fd) => {
			fs.readFile(fd, 'utf8', (err, data) => {
				assert.strictEqual(data, 'test data');
				fs.unlink(cmdData.actFile, (err)=>{});
			});
		});
		fs.open(cmdData.expFile, (err, fd) => {
			fs.readFile(fd, 'utf8', (err, data) => {
				assert.strictEqual(data, 'test data');
				fs.unlink(cmdData.expFile, (err)=>{});
			});
		});
	};

	cmdData.cmd_skip = false;
	suites.get(suiteDesc).push(cmdData);

	// TEST ### - tee -a tmpDir/testTeeAct-2.pid << test data
	cmdData = {cmd_aux: null};
	cmdData.actFile = join(tmpDir, `testTeeAct-2.${process.pid}`);
	cmdData.expFile = join(tmpDir, `testTeeExp-2.${process.pid}`);

	fs.writeFileSync(cmdData.actFile, 'test data',{encoding: 'utf8'});
	fs.writeFileSync(cmdData.expFile, 'test data',{encoding: 'utf8'});

	cmdData.cmd_act = `node ${cmdNode} -a ${cmdData.actFile}`;
	cmdData.cmd_exp = cmdShell && `${cmdShell} -a ${cmdData.expFile}`;
	cmdData.cmd_inp = 'test data';

	cmdData.cmd_out = 'test data';
	cmdData.cmd_err = '';

	cmdData.cmd_opt = {encoding: 'UTF-8'};
	cmdData.cmd_ext = 0;
	cmdData.cmd_desc = `${CMD_NODE} -a ${cmdData.actFile}` + ` << test data`;

	cmdData.cmd_aux = (cmdData) => {
		fs.open(cmdData.actFile, (err, fd) => {
			fs.readFile(fd, 'utf8', (err, data) => {
				assert.strictEqual(data, 'test datatest data');
				fs.unlink(cmdData.actFile, (err)=>{});
			});
		});
		fs.open(cmdData.expFile, (err, fd) => {
			fs.readFile(fd, 'utf8', (err, data) => {
				if(cmdShell)
					assert.strictEqual(data, 'test datatest data');
				fs.unlink(cmdData.expFile, (err)=>{});
			});
		});
	};

	cmdData.cmd_skip = false;
	suites.get(suiteDesc).push(cmdData);

	// TEST SUITE #4 - Test warn/error conditions of output files
	suiteDesc = 'Test warn/error conditions of output files';
	suites.set(suiteDesc, []);

	// TEST ### - tee non-existent/file << test data
	cmdData = {cmd_aux: null};

	cmdData.cmd_act = `node ${cmdNode} non-existent/file`;
	cmdData.cmd_exp = cmdShell && `${cmdShell} non-existent/file`;
	cmdData.cmd_inp = 'test data';

	cmdData.cmd_out = `test data`;
	cmdData.cmd_err = `${CMD_NODE}: non-existent/file: No such file or directory\n`;

	cmdData.cmd_opt = {encoding: 'UTF-8'};
	cmdData.cmd_ext = 1;
	cmdData.cmd_desc = `${CMD_NODE} non-existent/file` + (cmdData.cmd_inp && ` << ${cmdData.cmd_inp}`);

	cmdData.cmd_skip = false;
	suites.get(suiteDesc).push(cmdData);

	// TEST ### - tee --output-error=warn non-existent/file << test data
	cmdData = {cmd_aux: null};

	cmdData.cmd_act = `node ${cmdNode} --output-error=warn non-existent/file`;
	cmdData.cmd_exp = cmdShell && `${cmdShell} --output-error=warn non-existent/file`;
	cmdData.cmd_inp = 'test data';

	cmdData.cmd_out = `test data`;
	cmdData.cmd_err = `${CMD_NODE}: non-existent/file: No such file or directory\n`;

	cmdData.cmd_opt = {encoding: 'UTF-8'};
	cmdData.cmd_ext = 1;
	cmdData.cmd_desc = `${CMD_NODE} --output-error=warn non-existent/file` + (cmdData.cmd_inp && ` << ${cmdData.cmd_inp}`);

	cmdData.cmd_skip = false;
	suites.get(suiteDesc).push(cmdData);

	// TEST ### - tee --output-error=warn-nopipe non-existent/file << test data
	cmdData = {cmd_aux: null};

	cmdData.cmd_act = `node ${cmdNode} --output-error=warn-nopipe non-existent/file`;
	cmdData.cmd_exp = cmdShell && `${cmdShell} --output-error=warn-nopipe non-existent/file`;
	cmdData.cmd_inp = 'test data';

	cmdData.cmd_out = `test data`;
	cmdData.cmd_err = `${CMD_NODE}: non-existent/file: No such file or directory\n`;

	cmdData.cmd_opt = {encoding: 'UTF-8'};
	cmdData.cmd_ext = 1;
	cmdData.cmd_desc = `${CMD_NODE} --output-error=warn-nopipe non-existent/file` + (cmdData.cmd_inp && ` << ${cmdData.cmd_inp}`);

	cmdData.cmd_skip = false;
	suites.get(suiteDesc).push(cmdData);

	// TEST ### - tee --output-error=exit non-existent/file << test data
	cmdData = {cmd_aux: null};

	cmdData.cmd_act = `node ${cmdNode} --output-error=exit non-existent/file`;
	cmdData.cmd_exp = cmdShell && `${cmdShell} --output-error=exit non-existent/file`;
	cmdData.cmd_inp = 'test data';

	cmdData.cmd_out = ``;
	cmdData.cmd_err = `${CMD_NODE}: non-existent/file: No such file or directory\n`;

	cmdData.cmd_opt = {encoding: 'UTF-8'};
	cmdData.cmd_ext = 1;
	cmdData.cmd_desc = `${CMD_NODE} --output-error=exit non-existent/file` + (cmdData.cmd_inp && ` << ${cmdData.cmd_inp}`);

	cmdData.cmd_skip = false;
	suites.get(suiteDesc).push(cmdData);

	// TEST ### - tee --output-error=exit-nopipe non-existent/file << test data
	cmdData = {cmd_aux: null};

	cmdData.cmd_act = `node ${cmdNode} --output-error=exit-nopipe non-existent/file`;
	cmdData.cmd_exp = cmdShell && `${cmdShell} --output-error=exit-nopipe non-existent/file`;
	cmdData.cmd_inp = 'test data';

	cmdData.cmd_out = ``;
	cmdData.cmd_err = `${CMD_NODE}: non-existent/file: No such file or directory\n`;

	cmdData.cmd_opt = {encoding: 'UTF-8'};
	cmdData.cmd_ext = 1;
	cmdData.cmd_desc = `${CMD_NODE} --output-error=exit-nopipe non-existent/file` + (cmdData.cmd_inp && ` << ${cmdData.cmd_inp}`);

	cmdData.cmd_skip = false;
	suites.get(suiteDesc).push(cmdData);

	// TEST ### - tee /dev/null non-existent/file /dev/null << test data
	cmdData = {cmd_aux: null};

	cmdData.cmd_act = `node ${cmdNode} ${devNull} non-existent/file ${devNull}`;
	cmdData.cmd_exp = cmdShell && `${cmdShell} ${devNull} non-existent/file ${devNull}`;
	cmdData.cmd_inp = 'test data';

	cmdData.cmd_out = `test data`;
	cmdData.cmd_err = `${CMD_NODE}: non-existent/file: No such file or directory\n`;

	cmdData.cmd_opt = {encoding: 'UTF-8'};
	cmdData.cmd_ext = 1;
	cmdData.cmd_desc = `${CMD_NODE} ${devNull} non-existent/file ${devNull}` + (cmdData.cmd_inp && ` << ${cmdData.cmd_inp}`);

	cmdData.cmd_skip = false;
	suites.get(suiteDesc).push(cmdData);

	// TEST ### - tee --output-error=warn /dev/null non-existent/file /dev/null << test data
	cmdData = {cmd_aux: null};

	cmdData.cmd_act = `node ${cmdNode} --output-error=warn ${devNull} non-existent/file ${devNull}`;
	cmdData.cmd_exp = cmdShell && `${cmdShell} --output-error=warn ${devNull} non-existent/file ${devNull}`;
	cmdData.cmd_inp = 'test data';

	cmdData.cmd_out = `test data`;
	cmdData.cmd_err = `${CMD_NODE}: non-existent/file: No such file or directory\n`;

	cmdData.cmd_opt = {encoding: 'UTF-8'};
	cmdData.cmd_ext = 1;
	cmdData.cmd_desc = `${CMD_NODE} --output-error=warn ${devNull} non-existent/file ${devNull}` + (cmdData.cmd_inp && ` << ${cmdData.cmd_inp}`);

	cmdData.cmd_skip = false;
	suites.get(suiteDesc).push(cmdData);

	// TEST ### - tee --output-error=warn-nopipe /dev/null non-existent/file /dev/null << test data
	cmdData = {cmd_aux: null};

	cmdData.cmd_act = `node ${cmdNode} --output-error=warn-nopipe ${devNull} non-existent/file ${devNull}`;
	cmdData.cmd_exp = cmdShell && `${cmdShell} --output-error=warn-nopipe ${devNull} non-existent/file ${devNull}`;
	cmdData.cmd_inp = 'test data';

	cmdData.cmd_out = `test data`;
	cmdData.cmd_err = `${CMD_NODE}: non-existent/file: No such file or directory\n`;

	cmdData.cmd_opt = {encoding: 'UTF-8'};
	cmdData.cmd_ext = 1;
	cmdData.cmd_desc = `${CMD_NODE} --output-error=warn-nopipe ${devNull} non-existent/file ${devNull}` + (cmdData.cmd_inp && ` << ${cmdData.cmd_inp}`);

	cmdData.cmd_skip = false;
	suites.get(suiteDesc).push(cmdData);

	// TEST ### - tee --output-error=exit /dev/null non-existent/file /dev/null << test data
	cmdData = {cmd_aux: null};

	cmdData.cmd_act = `node ${cmdNode} --output-error=exit ${devNull} non-existent/file ${devNull}`;
	cmdData.cmd_exp = cmdShell && `${cmdShell} --output-error=exit ${devNull} non-existent/file ${devNull}`;
	cmdData.cmd_inp = 'test data';

	cmdData.cmd_out = ``;
	cmdData.cmd_err = `${CMD_NODE}: non-existent/file: No such file or directory\n`;

	cmdData.cmd_opt = {encoding: 'UTF-8'};
	cmdData.cmd_ext = 1;
	cmdData.cmd_desc = `${CMD_NODE} --output-error=exit ${devNull} non-existent/file ${devNull}` + (cmdData.cmd_inp && ` << ${cmdData.cmd_inp}`);

	cmdData.cmd_skip = false;
	suites.get(suiteDesc).push(cmdData);

	// TEST ### - tee --output-error=exit-nopipe /dev/null non-existent/file /dev/null << test data
	cmdData = {cmd_aux: null};

	cmdData.cmd_act = `node ${cmdNode} --output-error=exit-nopipe ${devNull} non-existent/file ${devNull}`;
	cmdData.cmd_exp = cmdShell && `${cmdShell} --output-error=exit-nopipe ${devNull} non-existent/file ${devNull}`;
	cmdData.cmd_inp = 'test data';

	cmdData.cmd_out = ``;
	cmdData.cmd_err = `${CMD_NODE}: non-existent/file: No such file or directory\n`;

	cmdData.cmd_opt = {encoding: 'UTF-8'};
	cmdData.cmd_ext = 1;
	cmdData.cmd_desc = `${CMD_NODE} --output-error=exit-nopipe ${devNull} non-existent/file ${devNull}` + (cmdData.cmd_inp && ` << ${cmdData.cmd_inp}`);

	cmdData.cmd_skip = false;
	suites.get(suiteDesc).push(cmdData);

	// TEST SUITE #5 - Test warn/error conditions of broken pipes
	suiteDesc = 'Test warn/error conditions of broken pipes';
	suites.set(suiteDesc, []);

	// TEST ### - tee | noOpCmd << test data
	cmdData = {cmd_aux: null};

	cmdData.cmd_act = `node ${cmdNode} | ${noOpCmd}`;
	cmdData.cmd_exp = cmdShell && `${cmdShell} | ${noOpCmd}`;
	cmdData.cmd_inp = 'test data';

	cmdData.cmd_out = ``;
	cmdData.cmd_err = ``;

	cmdData.cmd_opt = {encoding: 'UTF-8'};
	cmdData.cmd_ext = 0;
	cmdData.cmd_desc = `${CMD_NODE} | ${noOpCmd}` + (cmdData.cmd_inp && ` << ${cmdData.cmd_inp}`);

	cmdData.cmd_skip = false;
	suites.get(suiteDesc).push(cmdData);

	// TEST ### - tee --output-error=warn | noOpCmd << test data
	cmdData = {cmd_aux: null};

	cmdData.cmd_act = `node ${cmdNode} --output-error=warn | ${noOpCmd}`;
	cmdData.cmd_exp = cmdShell && `${cmdShell} --output-error=warn | ${noOpCmd}`;
	cmdData.cmd_inp = 'test data';

	cmdData.cmd_out = '';
	cmdData.cmd_err = `${CMD_NODE}: 'standard output': Broken pipe\n`;

	cmdData.cmd_opt = {encoding: 'UTF-8'};
	cmdData.cmd_ext = 0;
	cmdData.cmd_desc = `${CMD_NODE} --output-error=warn | ${noOpCmd}` + (cmdData.cmd_inp && ` << ${cmdData.cmd_inp}`);

	cmdData.cmd_skip = false;
	suites.get(suiteDesc).push(cmdData);

	// TEST ### - tee --output-error=warn-nopipe | noOpCmd << test data
	cmdData = {cmd_aux: null};

	cmdData.cmd_act = `node ${cmdNode} --output-error=warn-nopipe | ${noOpCmd}`;
	cmdData.cmd_exp = cmdShell && `${cmdShell} --output-error=warn-nopipe | ${noOpCmd}`;
	cmdData.cmd_inp = 'test data';

	cmdData.cmd_out = ``;
	cmdData.cmd_err = ``;

	cmdData.cmd_opt = {encoding: 'UTF-8'};
	cmdData.cmd_ext = 0;
	cmdData.cmd_desc = `${CMD_NODE} --output-error=warn-nopipe | ${noOpCmd}` + (cmdData.cmd_inp && ` << ${cmdData.cmd_inp}`);

	cmdData.cmd_skip = false;
	suites.get(suiteDesc).push(cmdData);

	// TEST ### - tee --output-error=exit | noOpCmd << test data
	cmdData = {cmd_aux: null};

	cmdData.cmd_act = `node ${cmdNode} --output-error=exit | ${noOpCmd}`;
	cmdData.cmd_exp = cmdShell && `${cmdShell} --output-error=exit | ${noOpCmd}`;
	cmdData.cmd_inp = 'test data';

	cmdData.cmd_out = ``;
	cmdData.cmd_err = `${CMD_NODE}: 'standard output': Broken pipe\n`;

	cmdData.cmd_opt = {encoding: 'UTF-8'};
	cmdData.cmd_ext = 0;
	cmdData.cmd_desc = `${CMD_NODE} --output-error=exit | ${noOpCmd}` + (cmdData.cmd_inp && ` << ${cmdData.cmd_inp}`);

	cmdData.cmd_skip = false;
	suites.get(suiteDesc).push(cmdData);

	// TEST ### - tee --output-error=exit-nopipe | noOpCmd << test data
	cmdData = {cmd_aux: null};

	cmdData.cmd_act = `node ${cmdNode} --output-error=exit-nopipe | ${noOpCmd}`;
	cmdData.cmd_exp = cmdShell && `${cmdShell} --output-error=exit-nopipe | ${noOpCmd}`;
	cmdData.cmd_inp = 'test data';

	cmdData.cmd_out = ``;
	cmdData.cmd_err = ``;

	cmdData.cmd_opt = {encoding: 'UTF-8'};
	cmdData.cmd_ext = 0;
	cmdData.cmd_desc = `${CMD_NODE} --output-error=exit-nopipe | ${noOpCmd}` + (cmdData.cmd_inp && ` << ${cmdData.cmd_inp}`);

	cmdData.cmd_skip = false;
	suites.get(suiteDesc).push(cmdData);

	// TEST SUITE #6 - Test warn/error conditions of output files and broken pipes
	suiteDesc = 'Test warn/error conditions of output files and broken pipes';
	suites.set(suiteDesc, []);

	// TEST ### - tee /dev/null non-existent/file /dev/null | noOpCmd << test data
	cmdData = {cmd_aux: null};

	cmdData.cmd_act = `node ${cmdNode} ${devNull} non-existent/file ${devNull} | ${noOpCmd}`;
	cmdData.cmd_exp = cmdShell && `${cmdShell} ${devNull} non-existent/file ${devNull} | ${noOpCmd}`;
	cmdData.cmd_inp = 'test data';

	cmdData.cmd_out = ``;
	cmdData.cmd_err = `${CMD_NODE}: non-existent/file: No such file or directory\n`;

	cmdData.cmd_opt = {encoding: 'UTF-8'};
	cmdData.cmd_ext = 0;
	cmdData.cmd_desc = `${CMD_NODE} ${devNull} non-existent/file ${devNull} | ${noOpCmd}` + (cmdData.cmd_inp && ` << ${cmdData.cmd_inp}`);

	cmdData.cmd_skip = false;
	suites.get(suiteDesc).push(cmdData);

	// TEST ### - tee --output-error=warn /dev/null non-existent/file /dev/null | noOpCmd << test data
	cmdData = {cmd_aux: null};

	cmdData.cmd_act = `node ${cmdNode} --output-error=warn ${devNull} non-existent/file ${devNull} | ${noOpCmd}`;
	cmdData.cmd_exp = cmdShell && `${cmdShell} --output-error=warn ${devNull} non-existent/file ${devNull} | ${noOpCmd}`;
	cmdData.cmd_inp = 'test data';

cmdData.cmd_out = '';
cmdData.cmd_err = `\
${CMD_NODE}: non-existent/file: No such file or directory
${CMD_NODE}: 'standard output': Broken pipe\n`;

	cmdData.cmd_opt = {encoding: 'UTF-8'};
	cmdData.cmd_ext = 0;
	cmdData.cmd_desc = `${CMD_NODE} --output-error=warn ${devNull} non-existent/file ${devNull} | ${noOpCmd}` + (cmdData.cmd_inp && ` << ${cmdData.cmd_inp}`);

	cmdData.cmd_skip = false;
	suites.get(suiteDesc).push(cmdData);

	// TEST ### - tee --output-error=warn-nopipe /dev/null non-existent/file /dev/null | noOpCmd << test data
	cmdData = {cmd_aux: null};

	cmdData.cmd_act = `node ${cmdNode} --output-error=warn-nopipe ${devNull} non-existent/file ${devNull} | ${noOpCmd}`;
	cmdData.cmd_exp = cmdShell && `${cmdShell} --output-error=warn-nopipe ${devNull} non-existent/file ${devNull} | ${noOpCmd}`;
	cmdData.cmd_inp = 'test data';

	cmdData.cmd_out = ``;
	cmdData.cmd_err = `${CMD_NODE}: non-existent/file: No such file or directory\n`;

	cmdData.cmd_opt = {encoding: 'UTF-8'};
	cmdData.cmd_ext = 0;
	cmdData.cmd_desc = `${CMD_NODE} --output-error=warn-nopipe ${devNull} non-existent/file ${devNull} | ${noOpCmd}` + (cmdData.cmd_inp && ` << ${cmdData.cmd_inp}`);

	cmdData.cmd_skip = false;
	suites.get(suiteDesc).push(cmdData);

	// TEST ### - tee --output-error=exit /dev/null non-existent/file /dev/null | noOpCmd << test data
	cmdData = {cmd_aux: null};

	cmdData.cmd_act = `node ${cmdNode} --output-error=exit ${devNull} non-existent/file ${devNull} | ${noOpCmd}`;
	cmdData.cmd_exp = cmdShell && `${cmdShell} --output-error=exit ${devNull} non-existent/file ${devNull} | ${noOpCmd}`;
	cmdData.cmd_inp = 'test data';

	cmdData.cmd_out = ``;
	cmdData.cmd_err = `${CMD_NODE}: non-existent/file: No such file or directory\n`;

	cmdData.cmd_opt = {encoding: 'UTF-8'};
	cmdData.cmd_ext = 0;
	cmdData.cmd_desc = `${CMD_NODE} --output-error=exit ${devNull} non-existent/file ${devNull} | ${noOpCmd}` + (cmdData.cmd_inp && ` << ${cmdData.cmd_inp}`);

	cmdData.cmd_skip = false;
	suites.get(suiteDesc).push(cmdData);

	// TEST #30 - tee --output-error=exit-nopipe /dev/null non-existent/file /dev/null | noOpCmd << test data
	cmdData = {cmd_aux: null};

	cmdData.cmd_act = `node ${cmdNode} --output-error=exit-nopipe ${devNull} non-existent/file ${devNull} | ${noOpCmd}`;
	cmdData.cmd_exp = cmdShell && `${cmdShell} --output-error=exit-nopipe ${devNull} non-existent/file ${devNull} | ${noOpCmd}`;
	cmdData.cmd_inp = 'test data';

	cmdData.cmd_out = ``;
	cmdData.cmd_err = `${CMD_NODE}: non-existent/file: No such file or directory\n`;

	cmdData.cmd_opt = {encoding: 'UTF-8'};
	cmdData.cmd_ext = 0;
	cmdData.cmd_desc = `${CMD_NODE} --output-error=exit-nopipe ${devNull} non-existent/file ${devNull} | ${noOpCmd}` + (cmdData.cmd_inp && ` << ${cmdData.cmd_inp}`);

	cmdData.cmd_skip = false;
	suites.get(suiteDesc).push(cmdData);
}

/**
 * function main()
 * function verifyShellCmd()
 * function loadTestData()
 *     function nodeRunner()
 * function defRunner()
 * async function makeTest()
 * function getCmdOutput()
 * function parseCmdLine()
 * function getHelp()
 * function getError()
 *
 * @func  nodeRunner
 * @param {object} runner - The node core module 'node:test' object.
 * @desc  Carry out the loaded tests using node test runner.
 */
function nodeRunner(runner){

	for(let [suiteDesc, suiteTests] of suites){
		runner.suite(suiteDesc, () => {
			for(let cmdObj of suiteTests){
				runner.test(cmdObj.cmd_desc, {skip: cmdObj.cmd_skip}, async () => {
					await makeTest(cmdObj);
				});
			}
		});
	}
}

/* node:coverage disable */
/**
 * function main()
 * function verifyShellCmd()
 * function loadTestData()
 * function nodeRunner()
 *     function defRunner()
 * async function makeTest()
 * function getCmdOutput()
 * function parseCmdLine()
 * function getHelp()
 * function getError()
 *
 * @func  defRunner
 * @desc  Carry out the loaded tests using this developed test runner.
 */
function defRunner(){

	cmdOptions.verbose && process.on('exit', () => {
		console.log();
		console.log('▶ tests',       --testCount);
		console.log('▶ suites',      suites.size);
		console.log('▶ pass',        passCount);
		console.log('▶ fail',        failCount);
		console.log('▶ cancelled',   cancelCount);
		console.log('▶ skipped',     skipCount);
		console.log('▶ todo',        todoCount);
		console.log('▶ duration_ms', Math.round(Date.now() - startTime));
	});

	cmdOptions.verbose && console.error();
	for(let [suiteDesc, suiteTests] of suites)
		for(let cmdObj of suiteTests)
			if(!cmdObj.cmd_skip)
				makeTest(cmdObj);

	cmdOptions.verbose && console.log();
}
/* node:coverage enable */

/**
 * function main()
 * function verifyShellCmd()
 * function loadTestData()
 * function nodeRunner()
 * function defRunner()
 *     async function makeTest()
 * function getCmdOutput()
 * function parseCmdLine()
 * function getHelp()
 * function getError()
 *
 * @func  makeTest
 * @async
 * @param {object} obj - The test data object.
 * @desc  Carry out a single test.
 */
async function makeTest(obj){

	const testID   = testCount++;

	let preMsg = `Test#${(testID).toString().padStart(3, '0')} ... `;
	let postMsg = preMsg;

	preMsg += `Initiate ... ${obj.cmd_desc}`;
	cmdOptions.verbose && console.error(preMsg);

	let [out_act, out_exp, prc_act, prc_exp] = await getCmdOutput(obj);

	if(out_exp !== null)
		out_exp.stderr = out_exp.stderr.replace(new RegExp(CMD_SHELL, 'g'), CMD_NODE);
	else{
		out_exp = {stdout: obj.cmd_out, stderr: obj.cmd_err};
		prc_exp = {exitCode: obj.cmd_ext};
	}

	if(!cmdOptions.verbose){

		assert.strictEqual(out_act.stdout, out_exp.stdout);
		assert.strictEqual(out_act.stderr, out_exp.stderr);
		assert.strictEqual(prc_act.exitCode, prc_exp.exitCode);
		obj.cmd_aux && obj.cmd_aux(obj);
	}
	/* node:coverage disable */
	else{
		try{
			assert.strictEqual(out_act.stdout, out_exp.stdout);
			assert.strictEqual(out_act.stderr, out_exp.stderr);
			assert.strictEqual(prc_act.exitCode, prc_exp.exitCode);
			obj.cmd_aux && obj.cmd_aux(obj);

			passCount++;

			postMsg += `Success ... ${obj.cmd_desc}`;
			cmdOptions.verbose && console.error(postMsg);
		}
		catch(e){
			failCount++;

			postMsg += `Failure ... ${obj.cmd_desc}`;
			cmdOptions.verbose && console.error(postMsg);
		}
	}
	/* node:coverage enable */
}

/**
 * function main()
 * function verifyShellCmd()
 * function loadTestData()
 * function nodeRunner()
 * function defRunner()
 * async function makeTest()
 *     function getCmdOutput()
 * function parseCmdLine()
 * function getHelp()
 * function getError()
 *
 * @func  getCmdOutput
 * @param {object} cmdObj - The test data object.
 * @desc  Carry out a single test.
 */
function getCmdOutput(cmdObj){

	let proc_act, proc_exp;

	let prom_act = new Promise((resolve, reject) => {
		proc_act = exec(cmdObj.cmd_act, cmdObj.cmd_opt, (err, stdout, stderr) => {
			resolve({stdout, stderr});
		});
		proc_act.stdin.write(cmdObj.cmd_inp);
		cmdObj.cmd_signal && proc_act.kill(cmdObj.cmd_signal);
		proc_act.stdin.end();
	});

	if(cmdObj.cmd_exp){
		let prom_exp = new Promise((resolve, reject) => {
			proc_exp = exec(cmdObj.cmd_exp, cmdObj.cmd_opt, (err, stdout, stderr) => {
				resolve({stdout, stderr});
			});
			proc_exp.stdin.write(cmdObj.cmd_inp);
			cmdObj.cmd_signal && proc_exp.kill(cmdObj.cmd_signal);
			proc_exp.stdin.end();
		});

		return Promise.all([prom_act, prom_exp, proc_act, proc_exp]);
	}

	return Promise.all([prom_act, null, proc_act, null]);
}

/**
 * function main()
 * function verifyShellCmd()
 * function loadTestData()
 * function nodeRunner()
 * function defRunner()
 * async function makeTest()
 * function getCmdOutput()
 *     function parseCmdLine()
 * function getHelp()
 * function getError()
 *
 * @func parseCmdLine
 * @desc Command line parser function.
 */
function parseCmdLine(){
	for(let i = 2; i < process.argv.length; i++){
		let opt = process.argv[i];

		if(opt.startsWith('--')){
			if(opt === '--help'){
				process.stdout.write(`${getHelp()}`);
				process.exit(0);
			}
			else
			if(opt === '--verbose'){
				cmdOptions.verbose = true;
			}
			else
			if(opt === '--node'){
				cmdOptions.node = true;
			}
			else
			if(opt === '--def'){
				cmdOptions.node = false;
			}
			else{
				process.stderr.write(`${getError(0).replace('__', opt.substring(2))}\n`);
				process.exit(1);
			}
		}
		else
		if(opt.startsWith('-')){
			if(opt === '-h'){
				process.stdout.write(`${getHelp()}`);
				process.exit(0);
			}
			else
			if(opt === '-v'){
				cmdOptions.verbose = true;
			}
			else
			if(opt === '-n'){
				cmdOptions.node = true;
			}
			else
			if(opt === '-d'){
				cmdOptions.node = false;
			}
			else{
				process.stderr.write(`${getError(1).replace('__', opt)}\n`);
				process.exit(1);
			}
		}
		else{
			process.stderr.write(`${getError(1).replace('__', opt)}\n`);
			process.exit(1);
		}
	}
}

/**
 * function main()
 * function verifyShellCmd()
 * function loadTestData()
 * function nodeRunner()
 * function defRunner()
 * async function makeTest()
 * function getCmdOutput()
 * function parseCmdLine()
 *     function getHelp()
 * function getError()
 *
 * @func   getHelp
 * @return {string} The help string.
 * @desc   Function to return help info.
 */
function getHelp(){
	return `\
Usage: ${CMD_NODE_TEST} [OPTIONS]...
Test the developed 'node-tee'.

With no options, testing will be done using nodejs test runner API if supported.

  -n  --node     use nodejs test runner API if supported
  -d  --def      use default test runner
  -v  --verbose  make the testing operation more talkative
  -h  --help     display this help and exit

'node-tee' was tested againts the GNU 'tee' command version 8.32.
`;
}

/**
 * function verifyShellCmd()
 * function loadTestData()
 * function nodeRunner()
 * function defRunner()
 * async function makeTest()
 * function getCmdOutput()
 * function parseCmdLine()
 * function getHelp()
 *     function getError()
 *
 * @func   getError
 * @param  {number} Error number.
 * @return {string} Error message.
 * @desc   Function to return error message.
 */
function getError(n){

const error = [
// error[0]
`\
${CMD_NODE_TEST}: invalid option -- '__'
Try '${CMD_NODE_TEST} --help' for more information.`,

// error[1]
`\
${CMD_NODE_TEST}: unrecognized option '__'
Try '${CMD_NODE_TEST} --help' for more information.`,

// error[2]
`${CMD_NODE_TEST}: option '__' requires an argument
Try '${CMD_NODE_TEST} --help' for more information.`,

// error[3]
`${CMD_NODE_TEST}: invalid argument ‘__’ for ‘__’
Try '${CMD_NODE_TEST} --help' for more information.`,
];

	return error[n];
}