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.
Files changed (4) hide show
  1. package/README.md +79 -6
  2. package/call.js +39 -0
  3. package/package.json +1 -1
  4. 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
- - creating comparisons
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). 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.
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
- ### Report digesting
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 scored report output by `score()`.
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
- ### Report difgesting
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 contains, insofar as they exist in the report, its ID, end time, `sources` property, and total score.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testilo",
3
- "version": "32.2.4",
3
+ "version": "33.0.1",
4
4
  "description": "Prepares and processes Testaro reports",
5
5
  "main": "call.js",
6
6
  "scripts": {
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
+ }