testilo 13.7.0 → 13.8.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.md CHANGED
@@ -446,7 +446,7 @@ The optional third argument to call (`75m` in this example) is a report selector
446
446
 
447
447
  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”.
448
448
 
449
- ## Rport digesting
449
+ ## Report digesting
450
450
 
451
451
  ### Introduction
452
452
 
@@ -544,6 +544,50 @@ The fourth argument to `call` (`23pl` in this example) is optional. If it is omi
544
544
 
545
545
  The comparative report created by `compare` is an HTML file, and it expects a `style.css` file to exist in its directory. The `reports/comparative/style.css` file in Testilo is an appropriate stylesheet to be copied into the directory where comparative reports are written.
546
546
 
547
+ ### Tool crediting
548
+
549
+ If you use Testilo to perform all the tests of all the tools on multiple targets and score the reports with a score proc that maps tool rules onto tool-agnostic issues, you may want to tabulate the comparative efficacy of each tool in discovering instances of issues. Testilo can help you do this by producing a _credit report_.
550
+
551
+ The `credit` module tabulates the contribution of each tool to the discovery of issue instances in a collection of scored reports. Its `credit()` function takes one argument: an array of scored reports.
552
+
553
+ The credit report contains four sections:
554
+ - `counts`: for each issue, how many instances each tool reported
555
+ - `onlies`: for each issue of which only 1 tool reported instances, which tool it was
556
+ - `mosts`: for each issue of which at least 2 tools reported instances, which tool(s) reported the maximum instance count
557
+ - `tools`: for each tool, two sections:
558
+ - `onlies`: a list of the issues that only the tool reported instances of
559
+ - `mosts`: a list of the issues for which the instance count of the tool was not surpassed by that of any other tool
560
+
561
+ ### Invocation
562
+
563
+ There are two ways to use the `credit` module.
564
+
565
+ #### By a module
566
+
567
+ A module can invoke `credit` in this way:
568
+
569
+ ```javaScript
570
+ const {credit} = require('testilo/credit');
571
+ credit(scoredReports)
572
+ .then(creditReport => {…});
573
+ ```
574
+
575
+ The argument to `credit()` is an array of scored report objects. The `credit()` function returns a credit report. The invoking module can further dispose of the credit report as needed.
576
+
577
+ #### By a user
578
+
579
+ A user can invoke `credit` in this way:
580
+
581
+ ```bash
582
+ node call credit legislators 23pl
583
+ ```
584
+
585
+ When a user invokes `credit` in this example, the `call` module:
586
+ - gets all the reports in the `scored` subdirectory of the `process.env.REPORTDIR` directory whose file names begin with `23pl`.
587
+ - writes the credit report as JSON file named `legislators.json` to the `credit` subdirectory of the `process.env.REPORTDIR` directory.
588
+
589
+ The third argument to `call` (`23pl` in this example) is optional. If it is omitted, `call` will get and `credit()` will tabulate all the reports in the `scored` directory.
590
+
547
591
  ### Validation
548
592
 
549
593
  To test the `compare` module, in the project directory you can execute the statement `node validation/compare/validate`. If `compare` is valid, all logging statements will begin with “Success” and none will begin with “ERROR”.
package/call.js CHANGED
@@ -167,6 +167,26 @@ const callCompare = async (compareProcID, comparisonNameBase, selector = '') =>
167
167
  console.log(`Comparison completed and saved in ${comparisonDir}`);
168
168
  }
169
169
  };
170
+ // Fulfills a credit request.
171
+ const callCredit = async (tallyID, selector = '') => {
172
+ // Get the scored reports to be tallied.
173
+ const reports = await getReports('scored', selector);
174
+ // If any exist:
175
+ if (reports.length) {
176
+ // Get the tallier.
177
+ const {credit} = require(`${functionDir}/analyze/credit`);
178
+ // Tally the reports.
179
+ const tally = credit(reports);
180
+ // Save the tally.
181
+ await fs.writeFile(`${reportDir}/credit/${tallyID}.json`, JSON.stringify(tally, null, 2));
182
+ console.log(`Reports tallied and credit report saved in ${reportDir}/credit`);
183
+ }
184
+ // Otherwise, i.e. if no scored reports are to be tallied:
185
+ else {
186
+ // Report this.
187
+ console.log('ERROR: No scored reports to be tallied');
188
+ }
189
+ };
170
190
 
171
191
  // ########## OPERATION
172
192
 
@@ -225,6 +245,12 @@ else if (fn === 'compare' && fnArgs.length > 1 && fnArgs.length < 4) {
225
245
  console.log('Execution completed');
226
246
  });
227
247
  }
248
+ else if (fn === 'credit' && fnArgs.length === 2) {
249
+ callCredit(... fnArgs)
250
+ .then(() => {
251
+ console.log('Execution completed');
252
+ });
253
+ }
228
254
  else {
229
255
  console.log('ERROR: Invalid statement');
230
256
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testilo",
3
- "version": "13.7.0",
3
+ "version": "13.8.0",
4
4
  "description": "Prepares and processes Testaro reports",
5
5
  "main": "aim.js",
6
6
  "scripts": {
@@ -0,0 +1,97 @@
1
+ /*
2
+ credit.js
3
+ Analyzes tool coverages of issues in a set of reports.
4
+ */
5
+
6
+ // Returns a tabulation of the instance counts of issues reported by tools in scored reports.
7
+ exports.credit = reports => {
8
+ const tally = {
9
+ counts: {},
10
+ onlies: {},
11
+ mosts: {},
12
+ tools: {}
13
+ };
14
+ const {counts, onlies, mosts, tools} = tally;
15
+ // For each report:
16
+ reports.forEach(report => {
17
+ // If it is valid:
18
+ if (report.score && report.score.details && report.score.details.issue) {
19
+ // For each issue:
20
+ const issues = report.score.details.issue;
21
+ Object.keys(issues).forEach(issueID => {
22
+ // For each tool with any complaints about it:
23
+ if (! counts[issueID]) {
24
+ counts[issueID] = {};
25
+ }
26
+ const issueTools = issues[issueID].tools;
27
+ if (issueTools) {
28
+ Object.keys(issueTools).forEach(toolID => {
29
+ // For each rule cited by any of those complaints:
30
+ if (! counts[issueID][toolID]) {
31
+ counts[issueID][toolID] = 0;
32
+ }
33
+ Object.keys(issueTools[toolID]).forEach(ruleID => {
34
+ // If an instance count was recorded:
35
+ const {complaints} = issueTools[toolID][ruleID];
36
+ if (complaints && complaints.countTotal) {
37
+ // Add it to the tally.
38
+ counts[issueID][toolID] += complaints.countTotal;
39
+ }
40
+ // Otherwise, i.e. if no instance count was recorded:
41
+ else {
42
+ // Report this.
43
+ console.log(`ERROR: Missing countTotal for ${toolID} in ${issueID}`);
44
+ }
45
+ });
46
+ });
47
+ }
48
+ else {
49
+ console.log(`ERROR: Missing tools for ${issueID}`);
50
+ }
51
+ });
52
+ }
53
+ // Otherwise, i.e. if it is invalid:
54
+ else {
55
+ // Report this.
56
+ console.log(`ERROR: Report ${report.id} missing score data`);
57
+ }
58
+ });
59
+ // For each tallied issue:
60
+ Object.keys(counts).forEach(issueID => {
61
+ // If only 1 tool complained about it:
62
+ const toolIDs = Object.keys(counts[issueID])
63
+ if (toolIDs.length === 1) {
64
+ // Add this to the tally.
65
+ onlies[issueID] = toolIDs[0];
66
+ }
67
+ // Otherwise, i.e. if multiple tools complained about it:
68
+ else {
69
+ // Add the tools with the maximum instance count to the tally.
70
+ const maxCount = Object
71
+ .values(counts[issueID])
72
+ .reduce((max, current) => Math.max(max, current));
73
+ Object.keys(counts[issueID]).forEach(toolID => {
74
+ if (counts[issueID][toolID] === maxCount) {
75
+ if (! mosts[issueID]) {
76
+ mosts[issueID] = [];
77
+ }
78
+ mosts[issueID].push(toolID);
79
+ }
80
+ });
81
+ }
82
+ });
83
+ // Add the onlies and mosts to the tool tabulation in the tally.
84
+ [[onlies, 'onlies'], [mosts, 'mosts']].forEach(qualityPair => {
85
+ Object.keys(qualityPair[0]).forEach(issueID => {
86
+ const toolID = qualityPair[0][issueID];
87
+ if (! tools[toolID]) {
88
+ tools[toolID] = {
89
+ onlies: [],
90
+ mosts: []
91
+ };
92
+ }
93
+ tools[toolID][qualityPair[1]].push(issueID);
94
+ });
95
+ });
96
+ return tally;
97
+ };