testilo 13.7.0 → 13.9.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 +45 -1
- package/call.js +26 -0
- package/package.json +1 -1
- package/procs/analyze/credit.js +97 -0
- package/procs/score/tic28.js +7425 -0
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
|
-
##
|
|
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
|
@@ -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
|
+
};
|