testaro 5.18.1 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README-mixed.md +707 -0
- package/README.md +95 -115
- package/commands.js +24 -18
- package/high.js +63 -4
- package/package.json +1 -1
- package/run.js +2 -1
- package/{runHost.js → runScript.js} +9 -12
- package/samples/{scripts/simple.json → simple.json} +1 -0
- package/samples/{scripts/tp16.json → tp18.json} +10 -4
- package/tests/filter.js +47 -0
- package/tests/titledEl.js +2 -2
- package/validation/executors/debug.js +2 -2
- package/validation/executors/high.js +57 -0
- package/validation/executors/low.js +2 -2
- package/validation/executors/tenon.js +2 -2
- package/validation/executors/test.js +2 -2
- package/validation/executors/tests.js +2 -2
- package/validation/tests/scripts/filter.json +46 -0
- package/validation/tests/scripts/zIndex.json +1 -1
- package/validation/tests/targets/filter/bad.html +27 -0
- package/validation/tests/targets/filter/good.html +19 -0
- package/watch.js +3 -3
- package/batchify.js +0 -25
- package/create.js +0 -172
- package/samples/batches/eurail.json +0 -11
- package/samples/batches/railpass.json +0 -11
- package/samples/batches/weborgs.json +0 -16
- package/samples/scripts/tp15.json +0 -173
- package/validation/executors/high1.js +0 -32
- package/validation/executors/high2.js +0 -37
package/run.js
CHANGED
|
@@ -38,6 +38,7 @@ const tests = {
|
|
|
38
38
|
docType: 'document without a doctype property',
|
|
39
39
|
elements: 'data on specified elements',
|
|
40
40
|
embAc: 'active elements embedded in links or buttons',
|
|
41
|
+
filter: 'filter styles on elements',
|
|
41
42
|
focAll: 'focusable and Tab-focused elements',
|
|
42
43
|
focInd: 'focus indicators',
|
|
43
44
|
focOp: 'focusability and operability',
|
|
@@ -1548,7 +1549,7 @@ const injectLaunches = acts => {
|
|
|
1548
1549
|
}
|
|
1549
1550
|
};
|
|
1550
1551
|
// Handles a request.
|
|
1551
|
-
exports.
|
|
1552
|
+
exports.doJob = async report => {
|
|
1552
1553
|
// If the report object is valid:
|
|
1553
1554
|
if(isValidReport(report)) {
|
|
1554
1555
|
// Add a start time to the log.
|
|
@@ -1,25 +1,24 @@
|
|
|
1
1
|
/*
|
|
2
|
-
|
|
3
|
-
Runs a
|
|
2
|
+
runScript.js
|
|
3
|
+
Runs a script and writes a report file.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
// ########## IMPORTS
|
|
7
7
|
|
|
8
|
-
const {
|
|
8
|
+
const {doJob} = require('./run');
|
|
9
9
|
|
|
10
10
|
// ########## FUNCTIONS
|
|
11
11
|
|
|
12
|
-
// Runs
|
|
13
|
-
const
|
|
12
|
+
// Runs a script and returns the report.
|
|
13
|
+
const runScript = async (id, scriptJSON) => {
|
|
14
14
|
const report = {
|
|
15
15
|
id,
|
|
16
|
-
host: JSON.parse(hostJSON),
|
|
17
16
|
log: [],
|
|
18
17
|
script: JSON.parse(scriptJSON),
|
|
19
18
|
acts: []
|
|
20
19
|
};
|
|
21
20
|
let reportJSON = JSON.stringify(report, null, 2);
|
|
22
|
-
await
|
|
21
|
+
await doJob(report);
|
|
23
22
|
report.acts.forEach(act => {
|
|
24
23
|
try {
|
|
25
24
|
JSON.stringify(act);
|
|
@@ -37,15 +36,13 @@ const runHost = async (id, scriptJSON, hostJSON) => {
|
|
|
37
36
|
});
|
|
38
37
|
try {
|
|
39
38
|
reportJSON = JSON.stringify(report, null, 2);
|
|
39
|
+
return reportJSON;
|
|
40
40
|
}
|
|
41
41
|
catch(error) {
|
|
42
42
|
console.log(`ERROR: report for host ${id} not JSON (${error.message})`);
|
|
43
|
+
return '';
|
|
43
44
|
}
|
|
44
|
-
process.send(reportJSON, () => {
|
|
45
|
-
process.disconnect();
|
|
46
|
-
process.exit();
|
|
47
|
-
});
|
|
48
45
|
};
|
|
49
46
|
|
|
50
47
|
// ########## OPERATION
|
|
51
|
-
|
|
48
|
+
runScript(... process.argv.slice(2));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"id": "
|
|
3
|
-
"what": "Alfa, Axe, Continuum, HTML CodeSniffer, IBM, Nu Html Checker, Tenon, WAVE, and
|
|
2
|
+
"id": "tp18",
|
|
3
|
+
"what": "Alfa, Axe, Continuum, HTML CodeSniffer, IBM, Nu Html Checker, Tenon, WAVE, and 22 custom tests",
|
|
4
4
|
"strict": true,
|
|
5
5
|
"timeLimit": 500,
|
|
6
6
|
"commands": [
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
},
|
|
12
12
|
{
|
|
13
13
|
"type": "url",
|
|
14
|
-
"which": "https
|
|
14
|
+
"which": "https://example.com/",
|
|
15
15
|
"what": "any page"
|
|
16
16
|
},
|
|
17
17
|
{
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
37
|
"type": "url",
|
|
38
|
-
"which": "https
|
|
38
|
+
"which": "https://example.com/",
|
|
39
39
|
"what": "any page"
|
|
40
40
|
},
|
|
41
41
|
{
|
|
@@ -59,6 +59,12 @@
|
|
|
59
59
|
"withItems": true,
|
|
60
60
|
"what": "active elements incorrectly embedded in each other"
|
|
61
61
|
},
|
|
62
|
+
{
|
|
63
|
+
"type": "test",
|
|
64
|
+
"which": "filter",
|
|
65
|
+
"withItems": true,
|
|
66
|
+
"what": "filter styles"
|
|
67
|
+
},
|
|
62
68
|
{
|
|
63
69
|
"type": "test",
|
|
64
70
|
"which": "focAll",
|
package/tests/filter.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/*
|
|
2
|
+
filter
|
|
3
|
+
This test reports elements whose styles include filter. The filter style property is considered
|
|
4
|
+
inherently inaccessible, because it modifies the rendering of content, overriding user settings,
|
|
5
|
+
and requires the user to apply custom styles to neutralize it, which is difficult or impossible
|
|
6
|
+
in some user environments.
|
|
7
|
+
*/
|
|
8
|
+
// Runs the test and returns the results.
|
|
9
|
+
exports.reporter = async (page, withItems) => {
|
|
10
|
+
// Identify the elements with filter style properties.
|
|
11
|
+
const data = await page.evaluate(withItems => {
|
|
12
|
+
// Returns a space-minimized copy of a string.
|
|
13
|
+
const compact = string => string.replace(/[\t\n]/g, '').replace(/\s{2,}/g, ' ').trim();
|
|
14
|
+
// Get all elements in the body.
|
|
15
|
+
const elements = Array.from(document.body.querySelectorAll('*'));
|
|
16
|
+
// Get those that have filter styles.
|
|
17
|
+
const filterElements = elements.filter(element => {
|
|
18
|
+
const elementStyles = window.getComputedStyle(element);
|
|
19
|
+
return elementStyles.filter !== 'none';
|
|
20
|
+
});
|
|
21
|
+
const filterData = filterElements.map(element => ({
|
|
22
|
+
element,
|
|
23
|
+
impact: element.querySelectorAll('*').length
|
|
24
|
+
}));
|
|
25
|
+
// Initialize the result.
|
|
26
|
+
const data = {
|
|
27
|
+
totals: {
|
|
28
|
+
styledElements: filterElements.length,
|
|
29
|
+
impactedElements: filterData.reduce((total, current) => total + current.impact, 0)
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
// If itemization is required:
|
|
33
|
+
if (withItems) {
|
|
34
|
+
// Add it to the result.
|
|
35
|
+
data.items = [];
|
|
36
|
+
filterData.forEach(filterDatum => {
|
|
37
|
+
data.items.push({
|
|
38
|
+
tagName: filterDatum.element.tagName,
|
|
39
|
+
text: compact(filterDatum.element.textContent),
|
|
40
|
+
impact: filterDatum.impact
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return data;
|
|
45
|
+
}, withItems);
|
|
46
|
+
return {result: data};
|
|
47
|
+
};
|
package/tests/titledEl.js
CHANGED
|
@@ -11,8 +11,8 @@ exports.reporter = async (page, withItems) => {
|
|
|
11
11
|
// FUNCTION DEFINITION START
|
|
12
12
|
// Returns a space-minimized copy of a string.
|
|
13
13
|
const compact = string => string
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
? string.replace(/[\t\n]/g, '').replace(/\s{2,}/g, ' ').trim()
|
|
15
|
+
: '';
|
|
16
16
|
// FUNCTION DEFINITION END
|
|
17
17
|
return badTitleElements.map(element => ({
|
|
18
18
|
tagName: element.tagName,
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Validator for Testaro tests.
|
|
3
3
|
|
|
4
4
|
const fs = require('fs').promises;
|
|
5
|
-
const {
|
|
5
|
+
const {doJob} = require(`${__dirname}/../../run`);
|
|
6
6
|
const validateTests = async () => {
|
|
7
7
|
const totals = {
|
|
8
8
|
attempts: 0,
|
|
@@ -18,7 +18,7 @@ const validateTests = async () => {
|
|
|
18
18
|
const report = {script};
|
|
19
19
|
report.log = [];
|
|
20
20
|
report.acts = [];
|
|
21
|
-
await
|
|
21
|
+
await doJob(report);
|
|
22
22
|
const {log, acts} = report;
|
|
23
23
|
if (log.length === 2 && log[1].event === 'endTime' && /^\d{4}-.+$/.test(log[1].value)) {
|
|
24
24
|
console.log('Success: Log has been correctly populated');
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// high.js
|
|
2
|
+
// Validator for high-level invocation of Testaro.
|
|
3
|
+
|
|
4
|
+
// ########## IMPORTS
|
|
5
|
+
|
|
6
|
+
const fs = require('fs/promises');
|
|
7
|
+
|
|
8
|
+
// ########## CONSTANTS
|
|
9
|
+
|
|
10
|
+
const projectRoot = `${__dirname}/../..`;
|
|
11
|
+
process.env.REPORTDIR = `${projectRoot}/temp`;
|
|
12
|
+
const reportDir = process.env.REPORTDIR;
|
|
13
|
+
process.env.SCRIPTDIR = `${projectRoot}/samples`;
|
|
14
|
+
|
|
15
|
+
// ########## OPERATION
|
|
16
|
+
|
|
17
|
+
// Run the simple script and write a report.
|
|
18
|
+
const {runJob} = require(`${projectRoot}/high`);
|
|
19
|
+
runJob('simple')
|
|
20
|
+
.then(
|
|
21
|
+
// When the report has been written:
|
|
22
|
+
async () => {
|
|
23
|
+
// Open it.
|
|
24
|
+
const fileNames = await fs.readdir(reportDir);
|
|
25
|
+
const reportNames = fileNames.filter(name => name.endsWith('-simple.json'));
|
|
26
|
+
if (reportNames.length) {
|
|
27
|
+
try {
|
|
28
|
+
// Check its log and act lengths against expectations.
|
|
29
|
+
const reportJSON = await fs.readFile(`${reportDir}/${reportNames[0]}`);
|
|
30
|
+
const report = JSON.parse(reportJSON);
|
|
31
|
+
const {log, acts} = report;
|
|
32
|
+
if (log.length !== 2) {
|
|
33
|
+
console.log(
|
|
34
|
+
`Failure: log length is ${log.length} instead of 2 (see temp/${reportNames[0]}})`
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
else if (acts.length !== 3) {
|
|
38
|
+
console.log(
|
|
39
|
+
`Failure: acts length is ${acts.length} instead of 3 (see temp/${reportNames[0]}})`
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
console.log(`Success (report is in temp/${reportNames[0]})`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch(error) {
|
|
47
|
+
console.log(`ERROR: ${error.message}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
console.log('ERROR: report not found');
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
error => {
|
|
55
|
+
console.log(`ERROR running script (${error.message})`);
|
|
56
|
+
}
|
|
57
|
+
);
|
|
@@ -13,8 +13,8 @@ const validate = async () => {
|
|
|
13
13
|
log: [],
|
|
14
14
|
acts: []
|
|
15
15
|
};
|
|
16
|
-
const {
|
|
17
|
-
await
|
|
16
|
+
const {doJob} = require('../../run');
|
|
17
|
+
await doJob(report);
|
|
18
18
|
const {log, acts} = report;
|
|
19
19
|
if (log.length !== 2) {
|
|
20
20
|
console.log(`Failure: log length is ${log.length} instead of 2`);
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Test executor for tenon sample script.
|
|
3
3
|
|
|
4
4
|
const fs = require('fs');
|
|
5
|
-
const {
|
|
5
|
+
const {doJob} = require('../../run');
|
|
6
6
|
const scriptJSON = fs.readFileSync('samples/scripts/tenon.json', 'utf8');
|
|
7
7
|
const script = JSON.parse(scriptJSON);
|
|
8
8
|
const report = {
|
|
@@ -12,7 +12,7 @@ const report = {
|
|
|
12
12
|
acts: []
|
|
13
13
|
};
|
|
14
14
|
(async () => {
|
|
15
|
-
await
|
|
15
|
+
await doJob(report);
|
|
16
16
|
console.log(`Report log:\n${JSON.stringify(report.log, null, 2)}\n`);
|
|
17
17
|
console.log(`Report acts:\n${JSON.stringify(report.acts, null, 2)}`);
|
|
18
18
|
})();
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Execution example: node validation/executors/test focOp
|
|
4
4
|
|
|
5
5
|
const fs = require('fs').promises;
|
|
6
|
-
const {
|
|
6
|
+
const {doJob} = require(`${__dirname}/../../run`);
|
|
7
7
|
const test = process.argv[2];
|
|
8
8
|
const validateTests = async () => {
|
|
9
9
|
const scriptFileNames = await fs.readdir(`${__dirname}/../tests/scripts`);
|
|
@@ -16,7 +16,7 @@ const validateTests = async () => {
|
|
|
16
16
|
const report = {script};
|
|
17
17
|
report.log = [];
|
|
18
18
|
report.acts = [];
|
|
19
|
-
await
|
|
19
|
+
await doJob(report);
|
|
20
20
|
const {log, acts} = report;
|
|
21
21
|
if (log.length === 2 && log[1].event === 'endTime' && /^\d{4}-.+$/.test(log[1].value)) {
|
|
22
22
|
console.log('Success: Log has been correctly populated');
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Validator for Testaro tests.
|
|
3
3
|
|
|
4
4
|
const fs = require('fs').promises;
|
|
5
|
-
const {
|
|
5
|
+
const {doJob} = require(`${__dirname}/../../run`);
|
|
6
6
|
const validateTests = async () => {
|
|
7
7
|
const totals = {
|
|
8
8
|
attempts: 0,
|
|
@@ -18,7 +18,7 @@ const validateTests = async () => {
|
|
|
18
18
|
const report = {script};
|
|
19
19
|
report.log = [];
|
|
20
20
|
report.acts = [];
|
|
21
|
-
await
|
|
21
|
+
await doJob(report);
|
|
22
22
|
const {log, acts} = report;
|
|
23
23
|
if (log.length === 2 && log[1].event === 'endTime' && /^\d{4}-.+$/.test(log[1].value)) {
|
|
24
24
|
console.log('Success: Log has been correctly populated');
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"what": "validation of filter test",
|
|
3
|
+
"strict": true,
|
|
4
|
+
"commands": [
|
|
5
|
+
{
|
|
6
|
+
"type": "launch",
|
|
7
|
+
"which": "chromium",
|
|
8
|
+
"what": "usual browser"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"type": "url",
|
|
12
|
+
"which": "__targets__/filter/good.html",
|
|
13
|
+
"what": "page with no filter styles"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"type": "test",
|
|
17
|
+
"which": "filter",
|
|
18
|
+
"what": "filter",
|
|
19
|
+
"withItems": true,
|
|
20
|
+
"expect": [
|
|
21
|
+
["totals.styledElements", "=", 0],
|
|
22
|
+
["totals.impactedElements", "=", 0]
|
|
23
|
+
]
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"type": "url",
|
|
27
|
+
"which": "__targets__/filter/bad.html",
|
|
28
|
+
"what": "page with filter styles"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"type": "test",
|
|
32
|
+
"which": "filter",
|
|
33
|
+
"what": "filter",
|
|
34
|
+
"withItems": true,
|
|
35
|
+
"expect": [
|
|
36
|
+
["totals.styledElements", "=", 2],
|
|
37
|
+
["totals.impactedElements", "=", 7],
|
|
38
|
+
["items.0.tagName", "=", "MAIN"],
|
|
39
|
+
["items.1.tagName", "=", "UL"],
|
|
40
|
+
["items.1.text", "i", "Item"],
|
|
41
|
+
["items.0.impact", "=", 5],
|
|
42
|
+
["items.1.impact", "=", 2]
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en-US">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>Page with filter styles</title>
|
|
6
|
+
<meta name="description" content="tester">
|
|
7
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
8
|
+
<style>
|
|
9
|
+
main {
|
|
10
|
+
filter: blur(5px);
|
|
11
|
+
}
|
|
12
|
+
ul {
|
|
13
|
+
filter: opacity(20%);
|
|
14
|
+
}
|
|
15
|
+
</style>
|
|
16
|
+
</head>
|
|
17
|
+
<body>
|
|
18
|
+
<main>
|
|
19
|
+
<h1>Page with filter styles</h1>
|
|
20
|
+
<p>This is a paragraph.</p>
|
|
21
|
+
<ul>
|
|
22
|
+
<li>Item 1</li>
|
|
23
|
+
<li>Item 2</li>
|
|
24
|
+
</ul>
|
|
25
|
+
</main>
|
|
26
|
+
</body>
|
|
27
|
+
</html>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en-US">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>Page without filter styles</title>
|
|
6
|
+
<meta name="description" content="tester">
|
|
7
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<main>
|
|
11
|
+
<h1>Page without filter styles</h1>
|
|
12
|
+
<p>This is a paragraph.</p>
|
|
13
|
+
<ul>
|
|
14
|
+
<li>Item 1</li>
|
|
15
|
+
<li>Item 2</li>
|
|
16
|
+
</ul>
|
|
17
|
+
</main>
|
|
18
|
+
</body>
|
|
19
|
+
</html>
|
package/watch.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
watch.js
|
|
3
|
-
Watches for
|
|
3
|
+
Watches for a script and runs it.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
// ########## IMPORTS
|
|
@@ -11,7 +11,7 @@ require('dotenv').config();
|
|
|
11
11
|
// Module to read and write files.
|
|
12
12
|
const fs = require('fs/promises');
|
|
13
13
|
// Module to perform tests.
|
|
14
|
-
const {
|
|
14
|
+
const {doJob} = require('./run');
|
|
15
15
|
// Module to convert a script and a batch to a batch-based array of scripts.
|
|
16
16
|
const {batchify} = require('./batchify');
|
|
17
17
|
|
|
@@ -162,7 +162,7 @@ const runHost = async (jobID, timeStamp, id, script) => {
|
|
|
162
162
|
script,
|
|
163
163
|
acts: []
|
|
164
164
|
};
|
|
165
|
-
await
|
|
165
|
+
await doJob(report);
|
|
166
166
|
if (watchType === 'dir') {
|
|
167
167
|
return await writeDirReport(report);
|
|
168
168
|
}
|
package/batchify.js
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
batchify.js
|
|
3
|
-
Creates a set of scripts from a script and a batch.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
// Converts a script to a batch-based array of scripts.
|
|
7
|
-
exports.batchify = (script, batch, timeStamp) => {
|
|
8
|
-
const {hosts} = batch;
|
|
9
|
-
const specs = hosts.map(host => {
|
|
10
|
-
const newScript = JSON.parse(JSON.stringify(script));
|
|
11
|
-
newScript.commands.forEach(command => {
|
|
12
|
-
if (command.type === 'url') {
|
|
13
|
-
command.which = host.which;
|
|
14
|
-
command.what = host.what;
|
|
15
|
-
}
|
|
16
|
-
});
|
|
17
|
-
const spec = {
|
|
18
|
-
id: `${timeStamp}-${host.id}`,
|
|
19
|
-
host,
|
|
20
|
-
script: newScript
|
|
21
|
-
};
|
|
22
|
-
return spec;
|
|
23
|
-
});
|
|
24
|
-
return specs;
|
|
25
|
-
};
|
package/create.js
DELETED
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
create.js
|
|
3
|
-
Creates and runs a file-based job and writes a report file.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
// ########## IMPORTS
|
|
7
|
-
|
|
8
|
-
// Module to keep secrets.
|
|
9
|
-
require('dotenv').config();
|
|
10
|
-
// Module to read and write files.
|
|
11
|
-
const fs = require('fs/promises');
|
|
12
|
-
const {fork} = require('child_process');
|
|
13
|
-
const {handleRequest} = require('./run');
|
|
14
|
-
// Module to convert a script and a batch to a batch-based array of scripts.
|
|
15
|
-
const {batchify} = require('./batchify');
|
|
16
|
-
|
|
17
|
-
// ########## CONSTANTS
|
|
18
|
-
|
|
19
|
-
const scriptDir = process.env.SCRIPTDIR;
|
|
20
|
-
const batchDir = process.env.BATCHDIR;
|
|
21
|
-
const reportDir = process.env.REPORTDIR;
|
|
22
|
-
const successHosts = [];
|
|
23
|
-
const crashHosts = [];
|
|
24
|
-
const timeoutHosts = [];
|
|
25
|
-
|
|
26
|
-
// ########## VARIABLES
|
|
27
|
-
|
|
28
|
-
let healthy = true;
|
|
29
|
-
// Set 5 minutes as a default time limit per host script.
|
|
30
|
-
let timeLimit = 300;
|
|
31
|
-
let reportCount = 0;
|
|
32
|
-
let specCount = Infinity;
|
|
33
|
-
|
|
34
|
-
// ########## FUNCTIONS
|
|
35
|
-
|
|
36
|
-
// Runs one script with no batch and writes a report file.
|
|
37
|
-
const runHost = async (id, script) => {
|
|
38
|
-
const report = {
|
|
39
|
-
id,
|
|
40
|
-
host: {},
|
|
41
|
-
log: [],
|
|
42
|
-
script,
|
|
43
|
-
acts: []
|
|
44
|
-
};
|
|
45
|
-
await handleRequest(report);
|
|
46
|
-
const reportJSON = JSON.stringify(report, null, 2);
|
|
47
|
-
await fs.writeFile(`${reportDir}/${id}.json`, reportJSON);
|
|
48
|
-
};
|
|
49
|
-
// Recursively runs host scripts.
|
|
50
|
-
const runHosts = async (timeStamp, specs) => {
|
|
51
|
-
if (specs.length >= specCount) {
|
|
52
|
-
console.log(`ERROR: Tried to run again with host count ${specs.length}`);
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
else {
|
|
56
|
-
specCount = specs.length;
|
|
57
|
-
}
|
|
58
|
-
// If any host scripts remain to be run and the process has not been interrupted:
|
|
59
|
-
if (specs.length && healthy) {
|
|
60
|
-
// Remove the first host script from the list.
|
|
61
|
-
const spec = specs.shift();
|
|
62
|
-
const {id, host, script} = spec;
|
|
63
|
-
// Fork a child process to run that host script.
|
|
64
|
-
const subprocess = fork(
|
|
65
|
-
'runHost', [id, JSON.stringify(script), JSON.stringify(host)],
|
|
66
|
-
{
|
|
67
|
-
detached: true,
|
|
68
|
-
stdio: [0, 1, 'ignore', 'ipc']
|
|
69
|
-
}
|
|
70
|
-
);
|
|
71
|
-
let runMoreTimer = null;
|
|
72
|
-
// Let it run until it ends or, if the script or default time limit expires:
|
|
73
|
-
const timer = setTimeout(async () => {
|
|
74
|
-
clearTimeout(timer);
|
|
75
|
-
// Record the host script as timed out.
|
|
76
|
-
timeoutHosts.push(id);
|
|
77
|
-
// Kill the child process.
|
|
78
|
-
subprocess.kill('SIGKILL');
|
|
79
|
-
console.log(`Script for host ${id} took more than ${timeLimit} seconds, so was killed`);
|
|
80
|
-
// Wait 10 seconds. Then:
|
|
81
|
-
runMoreTimer = setTimeout(async () => {
|
|
82
|
-
clearTimeout(runMoreTimer);
|
|
83
|
-
// If the timeout did not coincide with the termination of the script:
|
|
84
|
-
if (! (successHosts.includes(id) || crashHosts.includes(id))) {
|
|
85
|
-
// Run the remaining host scripts.
|
|
86
|
-
console.log('Continuing with the remaining host scripts');
|
|
87
|
-
await runHosts(timeStamp, specs);
|
|
88
|
-
}
|
|
89
|
-
}, 10000);
|
|
90
|
-
}, 1000 * (script.timeLimit || timeLimit));
|
|
91
|
-
// If the child process succeeds:
|
|
92
|
-
subprocess.on('message', async message => {
|
|
93
|
-
clearTimeout(runMoreTimer);
|
|
94
|
-
clearTimeout(timer);
|
|
95
|
-
// Save its report as a file.
|
|
96
|
-
await fs.writeFile(`${reportDir}/${id}.json`, message);
|
|
97
|
-
console.log(`Report ${id}.json saved in ${reportDir}`);
|
|
98
|
-
reportCount++;
|
|
99
|
-
successHosts.push(id);
|
|
100
|
-
// Run the remaining host scripts.
|
|
101
|
-
await runHosts(timeStamp, specs);
|
|
102
|
-
});
|
|
103
|
-
// If the child process ends:
|
|
104
|
-
subprocess.on('exit', async () => {
|
|
105
|
-
// Wait 5 seconds, then:
|
|
106
|
-
const postExitTimer = setTimeout(async () => {
|
|
107
|
-
clearTimeout(postExitTimer);
|
|
108
|
-
// If its end was not due to success or a timeout:
|
|
109
|
-
if (! (successHosts.includes(id) || timeoutHosts.includes(id))) {
|
|
110
|
-
clearTimeout(runMoreTimer);
|
|
111
|
-
clearTimeout(timer);
|
|
112
|
-
// Record the host as having crashed.
|
|
113
|
-
crashHosts.push(id);
|
|
114
|
-
console.log(`Script for host ${id} crashed`);
|
|
115
|
-
// Run the remaining host scripts.
|
|
116
|
-
await runHosts(timeStamp, specs);
|
|
117
|
-
}
|
|
118
|
-
}, 5000);
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
// Otherwise, i.e. if no more host scripts are to be run:
|
|
122
|
-
else {
|
|
123
|
-
// Report the metadata.
|
|
124
|
-
console.log(`Count of ${timeStamp}- reports saved in ${reportDir}: ${reportCount}`);
|
|
125
|
-
if (timeoutHosts.length) {
|
|
126
|
-
console.log(`Hosts timed out:\n${JSON.stringify(timeoutHosts, null, 2)}`);
|
|
127
|
-
}
|
|
128
|
-
if (crashHosts.length) {
|
|
129
|
-
console.log(`Hosts crashed:\n${JSON.stringify(crashHosts, null, 2)}`);
|
|
130
|
-
}
|
|
131
|
-
return '';
|
|
132
|
-
}
|
|
133
|
-
};
|
|
134
|
-
// Runs a file-based job and writes a report file for the script or each host.
|
|
135
|
-
exports.runJob = async (scriptID, batchID) => {
|
|
136
|
-
process.on('SIGINT', () => {
|
|
137
|
-
console.log('ERROR: Terminal interrupted runJob');
|
|
138
|
-
healthy = false;
|
|
139
|
-
});
|
|
140
|
-
if (scriptID) {
|
|
141
|
-
try {
|
|
142
|
-
const scriptJSON = await fs.readFile(`${scriptDir}/${scriptID}.json`, 'utf8');
|
|
143
|
-
const script = JSON.parse(scriptJSON);
|
|
144
|
-
// Get the time limit of the script or, if none, set it to 5 minutes.
|
|
145
|
-
timeLimit = script.timeLimit || timeLimit;
|
|
146
|
-
// Identify the start time and a timestamp.
|
|
147
|
-
const timeStamp = Math.floor((Date.now() - Date.UTC(2022, 1)) / 2000).toString(36);
|
|
148
|
-
// If there is a batch:
|
|
149
|
-
let batch = null;
|
|
150
|
-
if (batchID) {
|
|
151
|
-
// Convert the script to a batch-based set of host scripts.
|
|
152
|
-
const batchJSON = await fs.readFile(`${batchDir}/${batchID}.json`, 'utf8');
|
|
153
|
-
batch = JSON.parse(batchJSON);
|
|
154
|
-
const specs = batchify(script, batch, timeStamp);
|
|
155
|
-
// Recursively run each host script and save the reports.
|
|
156
|
-
await runHosts(timeStamp, specs);
|
|
157
|
-
}
|
|
158
|
-
// Otherwise, i.e. if there is no batch:
|
|
159
|
-
else {
|
|
160
|
-
// Run the script and save the result with a timestamp ID.
|
|
161
|
-
await runHost(timeStamp, script);
|
|
162
|
-
console.log(`Report ${timeStamp}.json in ${process.env.REPORTDIR}`);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
catch(error) {
|
|
166
|
-
console.log(`ERROR running job (${error.message})\n${error.stack}`);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
else {
|
|
170
|
-
console.log('ERROR: no script specified');
|
|
171
|
-
}
|
|
172
|
-
};
|