testilo 32.2.4 → 33.0.1
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.md +79 -6
- package/call.js +39 -0
- package/package.json +1 -1
- package/rescore.js +108 -0
package/README.md
CHANGED
|
@@ -471,7 +471,10 @@ Testilo can enhance such a report by:
|
|
|
471
471
|
- adding scores
|
|
472
472
|
- creating digests
|
|
473
473
|
- creating difgests
|
|
474
|
-
-
|
|
474
|
+
- summarizing reports
|
|
475
|
+
- comparing scores
|
|
476
|
+
- tracking score changes
|
|
477
|
+
- crediting tools
|
|
475
478
|
|
|
476
479
|
### Scoring
|
|
477
480
|
|
|
@@ -485,7 +488,11 @@ A scoring function defines scoring rules. The Testilo package contains a `procs/
|
|
|
485
488
|
|
|
486
489
|
#### Scorers
|
|
487
490
|
|
|
488
|
-
The built-in scoring functions are named `scorer()` and are exported by files whose names begin with `tsp` (for Testilo scoring proc).
|
|
491
|
+
The built-in scoring functions are named `scorer()` and are exported by files whose names begin with `tsp` (for Testilo scoring proc).
|
|
492
|
+
|
|
493
|
+
##### Issues
|
|
494
|
+
|
|
495
|
+
Those functions make use of `issues` objects defined in files whose names begin with `tic`. An `issues` object defines an issue classification: a body of data about rules of tools and the tool-agnostic issues that those rules are deemed to belong to.
|
|
489
496
|
|
|
490
497
|
The properties of an `issues` object are issue objects: objects containing data about issues. Here is an example from `tic40.js`:
|
|
491
498
|
|
|
@@ -529,6 +536,10 @@ The `quality` property is usually 1, but if the test of the rule is known to be
|
|
|
529
536
|
|
|
530
537
|
Some issue objects (such as `flash` in `tic40.js`) have a `max` property, equal to the maximum possible count of instances. That property allows a scorer to ascribe a greater weight to an instance of that issue.
|
|
531
538
|
|
|
539
|
+
##### Output
|
|
540
|
+
|
|
541
|
+
A scorer adds a `score` property to the report that it scores.
|
|
542
|
+
|
|
532
543
|
#### Invocation
|
|
533
544
|
|
|
534
545
|
There are two ways to invoke the `score` module.
|
|
@@ -569,7 +580,69 @@ When a user invokes `score()` in this example, the `call` module:
|
|
|
569
580
|
|
|
570
581
|
To test the `score` module, in the project directory you can execute the statement `node validation/score/validate`. If `score` is valid, all logging statements will begin with “Success” and none will begin with “ERROR”.
|
|
571
582
|
|
|
572
|
-
###
|
|
583
|
+
### Rescoring
|
|
584
|
+
|
|
585
|
+
The `rescore` module of Testilo creates a new report for a subset of the results in an existing report.
|
|
586
|
+
|
|
587
|
+
Any scored report is based on a set of tests of a set of tools. Suppose you want to disregard some of those tests and get a revised report for only the remaining tests. The `rescore` module does this for you.
|
|
588
|
+
|
|
589
|
+
A typical use case is your desire to examine results for only one or only some of the tools that were used for a report. All the needed information is in the report, so it is not necessary to create, perform, and await a new job and report. You want a new report whose standard results and score data are what a new job would have produced.
|
|
590
|
+
|
|
591
|
+
The `rescore()` function of the `rescore` module takes four arguments:
|
|
592
|
+
- a scoring function
|
|
593
|
+
- a report object
|
|
594
|
+
- a restriction type (`'tools'` or `'issues'`)
|
|
595
|
+
- an array of IDs of the tools or issues to be included
|
|
596
|
+
|
|
597
|
+
Then the `rescore()` function copies the report, removes the no-longer-relevant acts, removes the no-longer-relevant instances from and revises the totals of the `standardResult` properties, replaces the `score` property with a new one, and returns the revised report.
|
|
598
|
+
|
|
599
|
+
The new report is not identical to the report that a new job would have produced, because:
|
|
600
|
+
- Any original (non-standardized) results and data that survive in the new report are not revised.
|
|
601
|
+
- Any scores arising from causes other than test results, such as latency or browser warnings, are not revised.
|
|
602
|
+
- The `score` property object now includes a `rescore` property that identifies the original report ID (in case it is later changed), the date and time of the rescoring, the restriction type, and an array of the tool or issue IDs included by the restriction.
|
|
603
|
+
|
|
604
|
+
#### Invocation
|
|
605
|
+
|
|
606
|
+
There are two ways to invoke the `rescore` module.
|
|
607
|
+
|
|
608
|
+
##### By a module
|
|
609
|
+
|
|
610
|
+
A module can invoke `rescore()` in this way:
|
|
611
|
+
|
|
612
|
+
```javaScript
|
|
613
|
+
const {rescore} = require('testilo/rescore');
|
|
614
|
+
const {scorer} = require('testilo/procs/score/tsp99');
|
|
615
|
+
const report = …;
|
|
616
|
+
const restrictionType = 'tools';
|
|
617
|
+
const restrictions = ['axe', 'nuVal'];
|
|
618
|
+
rescore(scorer, report, restrictionType, restrictions);
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
The invoking module can further dispose of the rescored report as needed. Disposal may require revising the ID of the report so that the original and the rescored reports have distinct IDs.
|
|
622
|
+
|
|
623
|
+
##### By a user
|
|
624
|
+
|
|
625
|
+
A user can invoke `rescore()` in this way:
|
|
626
|
+
|
|
627
|
+
```bash
|
|
628
|
+
node call rescore tsp99 '' tools axe nuVal
|
|
629
|
+
node call rescore tsp99 240922 tools axe nuVal
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
When a user invokes `rescore()` in this example, the `call` module:
|
|
633
|
+
- gets the scoring module `tsp99` from its JSON file `tsp99.json` in the `score` subdirectory of the `FUNCTIONDIR` directory.
|
|
634
|
+
- gets all reports, or if the third argument to `call()` is nonempty the reports whose file names begin with `'240922'`, from the `scored` subdirectory of the `REPORTDIR` directory.
|
|
635
|
+
- defines an ID suffix.
|
|
636
|
+
- revises the `stardardResult` properties in each report.
|
|
637
|
+
- replaces the `score` property in each report.
|
|
638
|
+
- appends the ID suffix to the ID of each report.
|
|
639
|
+
- writes each rescored report in JSON format to the `scored` subdirectory of the `REPORTDIR` directory.
|
|
640
|
+
|
|
641
|
+
#### Validation
|
|
642
|
+
|
|
643
|
+
To test the `rescore` module, in the project directory you can execute the statement `node validation/rescore/validate`. If `rescore` is valid, all logging statements will begin with “Success” and none will begin with “ERROR”.
|
|
644
|
+
|
|
645
|
+
### Digesting
|
|
573
646
|
|
|
574
647
|
#### Introduction
|
|
575
648
|
|
|
@@ -603,7 +676,7 @@ digest(digester, scoredReport)
|
|
|
603
676
|
|
|
604
677
|
The first argument to `digest()` is a digester. In this example, it has been obtained from a file in the Testilo package, but it could be custom-made.
|
|
605
678
|
|
|
606
|
-
The second argument to `digest()` is a scored report object. It may have been read from a JSON file and parsed, or may be a
|
|
679
|
+
The second argument to `digest()` is a scored report object. It may have been read from a JSON file and parsed, or may be a report scored by `score()`.
|
|
607
680
|
|
|
608
681
|
The `digest()` function returns a promise resolved with a digest. The invoking module can further dispose of the digest as needed.
|
|
609
682
|
|
|
@@ -625,7 +698,7 @@ When a user invokes `digest()` in this example, the `call` module:
|
|
|
625
698
|
|
|
626
699
|
The digests created by `digest()` are HTML files, and they expect a `style.css` file to exist in their directory. The `reports/digested/style.css` file in Testilo is an appropriate stylesheet to be copied into the directory where digested reports are written.
|
|
627
700
|
|
|
628
|
-
###
|
|
701
|
+
### Difgesting
|
|
629
702
|
|
|
630
703
|
#### Introduction
|
|
631
704
|
|
|
@@ -688,7 +761,7 @@ To test the `digest` module, in the project directory you can execute the statem
|
|
|
688
761
|
|
|
689
762
|
### Summarization
|
|
690
763
|
|
|
691
|
-
The `summarize` module of Testilo can summarize a scored report. The summary
|
|
764
|
+
The `summarize` module of Testilo can summarize a scored report. The summary is an object that contains these properties from the report: `id`, `endTime`, `sources`, and `score` (the value of the `score.total` property of the report).
|
|
692
765
|
|
|
693
766
|
#### Invocation
|
|
694
767
|
|
package/call.js
CHANGED
|
@@ -28,6 +28,8 @@ const {script, toolIDs} = require('./script');
|
|
|
28
28
|
const {merge} = require('./merge');
|
|
29
29
|
// Function to score reports.
|
|
30
30
|
const {score} = require('./score');
|
|
31
|
+
// Function to rescore reports.
|
|
32
|
+
const {rescore} = require('./rescore');
|
|
31
33
|
// Function to digest reports.
|
|
32
34
|
const {digest} = require('./digest');
|
|
33
35
|
// Function to difgest reports.
|
|
@@ -246,6 +248,37 @@ const callScore = async (scorerID, selector = '') => {
|
|
|
246
248
|
console.log('ERROR: No raw reports to be scored');
|
|
247
249
|
}
|
|
248
250
|
};
|
|
251
|
+
// Fulfills a rescoring request.
|
|
252
|
+
const callRescore = async (scorerID, selector, restrictionType, ... includedIDs) => {
|
|
253
|
+
// Get the raw reports to be rescored.
|
|
254
|
+
const reportIDs = await getReportIDs('scored', selector);
|
|
255
|
+
// If any exist:
|
|
256
|
+
if (reportIDs.length) {
|
|
257
|
+
// Get the scorer.
|
|
258
|
+
const {scorer} = require(`${functionDir}/score/${scorerID}`);
|
|
259
|
+
// Rescore and save the reports.
|
|
260
|
+
const scoredReportDir = `${reportDir}/scored`;
|
|
261
|
+
await fs.mkdir(scoredReportDir, {recursive: true});
|
|
262
|
+
const rescoreIDSuffix = `-${getRandomString(2)}`;
|
|
263
|
+
for (const reportID of reportIDs) {
|
|
264
|
+
const report = await getReport('scored', reportID);
|
|
265
|
+
rescore(scorer, report, restrictionType, includedIDs);
|
|
266
|
+
const newID = report.id += rescoreIDSuffix;
|
|
267
|
+
report.id = newID;
|
|
268
|
+
await fs.writeFile(
|
|
269
|
+
`${scoredReportDir}/${newID}.json`, `${JSON.stringify(report, null, 2)}\n`
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
console.log(
|
|
273
|
+
`Reports rescored and saved with ID suffix ${rescoreIDSuffix} in ${scoredReportDir}`
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
// Otherwise, i.e. if no raw reports are to be scored:
|
|
277
|
+
else {
|
|
278
|
+
// Report this.
|
|
279
|
+
console.log('ERROR: No scored reports to be rescored');
|
|
280
|
+
}
|
|
281
|
+
};
|
|
249
282
|
// Fulfills a digesting request.
|
|
250
283
|
const callDigest = async (digesterID, selector = '') => {
|
|
251
284
|
// Get the base base names (equal to the IDs) of the scored reports to be digested.
|
|
@@ -449,6 +482,12 @@ else if (fn === 'score' && fnArgs.length > 0 && fnArgs.length < 3) {
|
|
|
449
482
|
console.log('Execution completed');
|
|
450
483
|
});
|
|
451
484
|
}
|
|
485
|
+
else if (fn === 'rescore' && fnArgs.length > 3) {
|
|
486
|
+
callRescore(... fnArgs)
|
|
487
|
+
.then(() => {
|
|
488
|
+
console.log('Execution completed');
|
|
489
|
+
});
|
|
490
|
+
}
|
|
452
491
|
else if (fn === 'multiScore' && fnArgs.length === 1) {
|
|
453
492
|
callMultiScore(... fnArgs)
|
|
454
493
|
.then(() => {
|
package/package.json
CHANGED
package/rescore.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/*
|
|
2
|
+
rescore.js
|
|
3
|
+
Rescores and returns a scored report.
|
|
4
|
+
Arguments:
|
|
5
|
+
0. scoring function.
|
|
6
|
+
1. scored report.
|
|
7
|
+
2. restriction type (tools or issues).
|
|
8
|
+
3. array of IDs of tools or issues to be included.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// ########## IMPORTS
|
|
12
|
+
|
|
13
|
+
// Module to perform common operations.
|
|
14
|
+
const {getNowStamp} = require('./procs/util');
|
|
15
|
+
|
|
16
|
+
// ########## FUNCTIONS
|
|
17
|
+
|
|
18
|
+
// Rescores a report.
|
|
19
|
+
exports.rescore = (scorer, report, restrictionType, includedIDs) => {
|
|
20
|
+
// If tools are restricted:
|
|
21
|
+
const {acts, id, score} = report;
|
|
22
|
+
if (restrictionType === 'tools') {
|
|
23
|
+
// If all the tools included by the restriction are in the report:
|
|
24
|
+
const reportToolIDs = new Set(acts.filter(act => act.type === 'test').map(act => act.which));
|
|
25
|
+
if (includedIDs.every(
|
|
26
|
+
includedID => acts.some(act => act.type === 'test' && act.which === includedID)
|
|
27
|
+
)) {
|
|
28
|
+
// Delete the acts that are tests of other tools.
|
|
29
|
+
report.acts = acts.filter(act => act.type !== 'test' || includedIDs.includes(act.which));
|
|
30
|
+
}
|
|
31
|
+
// Otherwise, i.e. if any tool included by the restriction is not in the report:
|
|
32
|
+
else {
|
|
33
|
+
// Report this.
|
|
34
|
+
console.log(`ERROR: Report includes only tools ${Array.from(reportToolIDs).join(', ')}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
// Otherwise, if issues are restricted:
|
|
38
|
+
else if (restrictionType === 'issues') {
|
|
39
|
+
// Initialize data on the violated rules of the issues included by the restriction.
|
|
40
|
+
const ruleData = {};
|
|
41
|
+
// For each issue with any rule violations:
|
|
42
|
+
const issueDetails = score.details.issue;
|
|
43
|
+
const reportIssueIDs = Object.keys(issueDetails);
|
|
44
|
+
reportIssueIDs.forEach(reportIssueID => {
|
|
45
|
+
// If the restriction includes the issue:
|
|
46
|
+
if (includedIDs.includes(reportIssueID)) {
|
|
47
|
+
// For each tool with any violated rules of the issue:
|
|
48
|
+
const issueToolIDs = Object.keys(issueDetails[reportIssueID].tools);
|
|
49
|
+
issueToolIDs.forEach(issueToolID => {
|
|
50
|
+
// For each violated rule of the issue of the tool:
|
|
51
|
+
const issueToolRuleIDs = Object.keys(issueDetails[reportIssueID].tools[issueToolID]);
|
|
52
|
+
issueToolRuleIDs.forEach(issueToolRuleID => {
|
|
53
|
+
// Add the rule to the rule data.
|
|
54
|
+
ruleData[issueToolID] ??= [];
|
|
55
|
+
ruleData[issueToolID].push(issueToolRuleID);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
// For each act:
|
|
61
|
+
acts.forEach(act => {
|
|
62
|
+
// If it is a test act:
|
|
63
|
+
if (act.type === 'test') {
|
|
64
|
+
// Delete any standard instances of rules not included by the restriction.
|
|
65
|
+
const {data, standardResult} = act;
|
|
66
|
+
standardResult.instances = standardResult.instances.filter(
|
|
67
|
+
instance => ruleData[act.which].includes(instance.ruleID)
|
|
68
|
+
);
|
|
69
|
+
// Reinitialize the totals of the standard result.
|
|
70
|
+
standardResult.totals = [0, 0, 0, 0];
|
|
71
|
+
const {totals} = standardResult;
|
|
72
|
+
// If the tool of the act is Testaro:
|
|
73
|
+
if (act.which === 'testaro') {
|
|
74
|
+
// Recalculate the totals of the act.
|
|
75
|
+
const {ruleTotals} = data;
|
|
76
|
+
ruleTotals.forEach(ruleTotal => {
|
|
77
|
+
totals.forEach((total, index) => {
|
|
78
|
+
totals[index] += ruleTotal[index];
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
// Otherwise, i.e. if the tool is not Testaro:
|
|
83
|
+
else {
|
|
84
|
+
// Recalculate the totals of the act.
|
|
85
|
+
const {instances} = standardResult;
|
|
86
|
+
instances.forEach(instance => {
|
|
87
|
+
const {count, ordinalSeverity} = instance;
|
|
88
|
+
totals[ordinalSeverity] += count * ordinalSeverity;
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
// Otherwise, i.e. if neither tools nor issues are restricted:
|
|
95
|
+
else {
|
|
96
|
+
// Report this.
|
|
97
|
+
console.log('ERROR: Neither tools nor issues are restricted');
|
|
98
|
+
}
|
|
99
|
+
// Add rescoring data to the report.
|
|
100
|
+
report.rescore = {
|
|
101
|
+
originalID: id,
|
|
102
|
+
timeStamp: getNowStamp(),
|
|
103
|
+
restrictionType,
|
|
104
|
+
includedIDs
|
|
105
|
+
}
|
|
106
|
+
// Score the revised report.
|
|
107
|
+
scorer(report);
|
|
108
|
+
}
|