risei 2.0.1 → 3.1.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/system/TestDef.js CHANGED
@@ -4,6 +4,8 @@ import TypeAnalyzer from "./TypeAnalyzer.js";
4
4
  import NameAnalyzer from "./NameAnalyzer.js";
5
5
  import TypeIdentifier from "./TypeIdentifier.js";
6
6
  import Types from "./Types.js";
7
+ import CallTypes from "./CallTypes.js";
8
+ import TotalDisplayer from "./TotalDisplayer.js";
7
9
 
8
10
  /* Defines what is found in a test definition, typically as
9
11
  a nonce object rather than an instance of this class. */
@@ -13,6 +15,7 @@ export default class TestDef {
13
15
 
14
16
  static staticName = "static";
15
17
  static throwName = "throw";
18
+ static polyName = "poly";
16
19
  static constructorName = "constructor";
17
20
  static nonceLocalCallableName = "nonce";
18
21
 
@@ -56,13 +59,12 @@ export default class TestDef {
56
59
  target;
57
60
 
58
61
  actual;
59
- thrown;
62
+ thrown; // Throw in inner scope (tested code).
63
+ trouble; // Throw in outer scope (test system or .do / .undo / .from).
60
64
 
61
65
  // endregion Fields
62
66
 
63
- // region Properties
64
-
65
- // region Test definition, short names
67
+ // region Definition properties
66
68
 
67
69
  /* These short names make JSON-like definitions easier. */
68
70
 
@@ -154,9 +156,9 @@ export default class TestDef {
154
156
  this.counteract = value;
155
157
  }
156
158
 
157
- // endregion Test definition, short names
159
+ // endregion Definition properties
158
160
 
159
- // region Def state
161
+ // region State properties
160
162
 
161
163
  get isRunnable() /* passed */ {
162
164
  let isRunnable
@@ -182,19 +184,28 @@ export default class TestDef {
182
184
 
183
185
  return is || stated;
184
186
  }
185
-
187
+
186
188
  get isThrowTest() /* passed */ {
187
189
  let is = this.andStringContains(TestDef.throwName);
188
190
  return is;
189
191
  }
190
192
 
193
+ get isMonoCallTest() /* passed */ {
194
+ return !this.isPolyCallTest;
195
+ }
196
+
197
+ get isPolyCallTest() /* passed */ {
198
+ let is = this.andStringContains(TestDef.polyName);
199
+ return is;
200
+ }
201
+
191
202
  get isConstructorTest() /* passed */ {
192
203
  let plainTarget = NameAnalyzer.plainNameOf(this.of);
193
204
  return plainTarget === TestDef.constructorName;
194
205
  }
195
206
 
196
207
  get isMethodTest() /* passed */ {
197
- return !this.isPropertyTest;
208
+ return !this.isPropertyTest && !this.isConstructorTest;
198
209
  }
199
210
 
200
211
  get isPropertyTest() /* passed */ {
@@ -207,6 +218,22 @@ export default class TestDef {
207
218
  return isPropertyTest;
208
219
  }
209
220
 
221
+ get callType() /* passed */ {
222
+ let type;
223
+
224
+ if (this.isMethodTest) {
225
+ type = this.isMonoCallTest ? CallTypes.methodMono : CallTypes.methodPoly;
226
+ }
227
+ else if (this.isPropertyTest) {
228
+ type = this.isMonoCallTest ? CallTypes.propMono : CallTypes.propPoly;
229
+ }
230
+ else if (this.isConstructorTest) {
231
+ type = this.isMonoCallTest ? CallTypes.conMono : CallTypes.conPoly;
232
+ }
233
+
234
+ return type;
235
+ }
236
+
210
237
  get isRetrievalTest() /* passed */ {
211
238
  // Falsy code elements never are a retrieval.
212
239
  if (!this.from) {
@@ -252,20 +279,34 @@ export default class TestDef {
252
279
  get runName() /* passed */ {
253
280
  let plain = NameAnalyzer.plainNameOf(this.of);
254
281
 
255
- if (this.isMethodTest) {
282
+ if (!this.isPropertyTest) {
256
283
  return `${ plain }()`;
257
284
  }
258
-
259
- if (this.isPropertyTest) {
285
+ else {
260
286
  return `.${ plain }`;
261
287
  }
262
288
  }
263
289
 
264
- // endregion Def state
290
+ get anyUnsoughtThrow() /* passed */ {
291
+ // Prop is true when throw is sought.
292
+ if (this.isThrowTest) {
293
+ return;
294
+ }
265
295
 
266
- // endregion Properties
296
+ return this.thrown;
297
+ }
267
298
 
268
- // region Def state methods
299
+ get doesHaveUnsoughtThrow() /* passed */ {
300
+ return this.anyUnsoughtThrow !== undefined;
301
+ }
302
+
303
+ get doesHaveTrouble() /* passed */ {
304
+ return this.trouble !== undefined;
305
+ }
306
+
307
+ // endregion State properties
308
+
309
+ // region State methods
269
310
 
270
311
  /* Needed externally, and dependency
271
312
  of all .and-based properties. */
@@ -277,12 +318,11 @@ export default class TestDef {
277
318
  return doesContain;
278
319
  }
279
320
 
280
- // endregion Def state methods
321
+ // endregion State methods
281
322
 
282
323
  // region Initing, including constructor()
283
324
 
284
- /* Constructor can't use parameter names like `for`:
285
- when not .-prefixed, they are purely keywords. */
325
+ /* Long names used, since some short names, like `for`, are parsed as keywords. */
286
326
  constructor(nature, type, spoofed, initors, method, inputs, output, source, factors, enact, counteract) /* ok */ {
287
327
  this.nature = nature; // .for
288
328
  this.type = type; // .on
@@ -416,4 +456,22 @@ export default class TestDef {
416
456
 
417
457
  // endregion Initing, including constructor()
418
458
 
459
+ toString() /* verified */ {
460
+ let displayer = new TotalDisplayer();
461
+
462
+ let text = `TestDef{ for:${ displayer.display(this.for) }, `
463
+ + `on:${ displayer.display(this.on) }, `
464
+ + `with:${ displayer.display(this.with) }, `
465
+ + `of:${ displayer.display(this.of) }, `
466
+ + `plus:${ displayer.display(this.plus) }, `
467
+ + `in:${ displayer.display(this.in) }, `
468
+ + `out:${ displayer.display(this.out) }, `
469
+ + `from:${ displayer.display(this.from) }, `
470
+ + `and:${ displayer.display(this.and) }, `
471
+ + `do:${ displayer.display(this.do) }, `
472
+ + `undo:${ displayer.display(this.undo) } }`
473
+
474
+ return text;
475
+ }
476
+
419
477
  }
@@ -1,14 +1,14 @@
1
1
  /**/
2
2
 
3
- /* TestFinder is an ATestFinder that finds tests in the places identified in package.json. */
3
+ /* TestFinder retrieves test sources based on .risei.tests in package.json. */
4
4
 
5
- import ATestFinder from "./ATestFinder.js";
6
5
  import ATestSource from "./ATestSource.js";
6
+ import Choices from "./Choices.js";
7
7
  import fs from "node:fs";
8
8
  import path from "node:path";
9
9
  import { minimatch } from "minimatch";
10
10
 
11
- export default class TestFinder extends ATestFinder {
11
+ export default class TestFinder {
12
12
  // region Definitions
13
13
 
14
14
  static NO_SUCH_FILE = "ENOENT";
@@ -21,20 +21,37 @@ export default class TestFinder extends ATestFinder {
21
21
  // endregion Definitions
22
22
 
23
23
  // region Fields
24
-
24
+
25
+ #testSources;
26
+ #thrown = [];
27
+
25
28
  #sought;
26
29
 
27
30
  // endregion Fields
28
31
 
32
+ // region Properties
33
+
34
+ get testSources() {
35
+ return this.#testSources;
36
+ }
37
+
38
+ set testSources(value) {
39
+ this.#testSources = value;
40
+ }
41
+
42
+ get thrown() {
43
+ return this.#thrown;
44
+ }
45
+
46
+ // endregion Properties
47
+
29
48
  constructor() {
30
- super();
49
+ this.#testSources = [];
31
50
  }
32
51
 
33
52
  async findAllTests() {
34
- // Paths for test sources are listed in package.json, possibly with globbing.
35
- let source = this.sourceBySearch();
36
- this.#sought = source.tests;
37
- let testPaths = this.pathsFromTargetingSource();
53
+ this.#sought = this.supplyTestTargeting();
54
+ let testPaths = this.pathsFromTargeting();
38
55
 
39
56
  testPaths = this.sortTestPaths(testPaths);
40
57
 
@@ -54,28 +71,13 @@ export default class TestFinder extends ATestFinder {
54
71
  return tests;
55
72
  }
56
73
 
57
- sourceBySearch() {
58
- // App's package.json always found at process.cwd().
59
- let packageSite = process.cwd();
60
- let pathAndName = path.join(packageSite, TestFinder.METADATA_FILE);
61
-
62
- // Contents of package.json are just a single JSON object.
63
- let packageJson = fs.readFileSync(pathAndName, { encoding: "utf8" });
64
- packageJson = JSON.parse(packageJson);
65
-
66
- // Can't run or reasonably recover if this is not defined.
67
- if (!packageJson.risei) {
68
- throw new Error(
69
- `For Risei tests to run, this app's package.json must contain `
70
- + `a node like this: "risei": { tests: "path-or-glob-here" }. `
71
- + `One glob or path may be used, or an array of them.`);
72
- }
73
-
74
- // Only Risei's own metadata is needed.
75
- return packageJson.risei;
74
+ // Spoofing point for self-testing.
75
+ supplyTestTargeting() {
76
+ // Value retrieved from package.json.
77
+ return Choices.testTargeting;
76
78
  }
77
79
 
78
- pathsFromTargetingSource() {
80
+ pathsFromTargeting() {
79
81
  let paths = [];
80
82
  let root = process.cwd();
81
83
 
@@ -9,12 +9,12 @@ export default class TestFrame {
9
9
  // A TestStages is inited for each test.
10
10
  this.#stages = new TestStages();
11
11
  }
12
-
13
- run(test) /* passed */ {
12
+
13
+ async run(test) /* passed */ {
14
14
  // Outer try for fails of groundwork requested.
15
15
  try {
16
16
  // Spoofing and so on.
17
- this.#stages.anyPreTargetingGroundwork(test);
17
+ await this.#stages.anyPreTargetingGroundwork(test);
18
18
 
19
19
  // Target may be instance, prototype for constructors, the class
20
20
  // itself for statics, or nonce for properties. Sets test.target.
@@ -25,17 +25,18 @@ export default class TestFrame {
25
25
  // Method under test may be on instance, class,
26
26
  // or nonce for properties. Sets test.callable.
27
27
  let callable = this.#stages.supplyCallableName(test);
28
-
28
+
29
29
  // Setting (spoofing) properties and so on.
30
- this.#stages.anyPostTargetingGroundwork(test);
31
-
30
+ await this.#stages.anyPostTargetingGroundwork(test);
31
+
32
32
  // Inner try for fails of targeted code only.
33
33
  try {
34
34
  // Tests usually look for this.
35
- test.actual = target[callable](...test.in);
35
+ test.actual = await target[callable](...test.in);
36
36
  }
37
37
  catch (thrown) {
38
38
  // Sometimes tests look for this.
39
+ // Otherwise, listed in gold bar.
39
40
  test.thrown = thrown;
40
41
  }
41
42
 
@@ -46,13 +47,21 @@ export default class TestFrame {
46
47
  test.didPass = this.#stages.compareResults(test.output, test.actual);
47
48
  }
48
49
  catch (thrown) {
49
- /* Block needed, but no operations. */
50
+ // Fail of the test system, or of code in .do or .from.
51
+ test.trouble = thrown;
50
52
  }
51
53
  finally {
52
- // Undoing any groundwork changes made.
53
- this.#stages.anyGroundworkReversal(test);
54
+ // Always undoing any groundwork changes made.
55
+ try {
56
+ await this.#stages.anyGroundworkReversal(test);
57
+ }
58
+ catch (thrown) {
59
+ // Fail of the test system, or of code in .undo;
60
+ // only needed when no preceding throw.
61
+ if (test.trouble === undefined) {
62
+ test.trouble = thrown;
63
+ }
64
+ }
54
65
  }
55
-
56
- // console.log(`cruft : q, outer try-catch : after`);
57
66
  }
58
67
  }
@@ -1,25 +1,25 @@
1
- /**/
2
-
3
- export default class TestGroup {
4
- // region Private fields
5
-
6
- #group;
7
-
8
- // endregion Private fields
9
-
10
- // region Properties
11
-
12
- get group() {
13
- return this.#group;
14
- }
15
-
16
- set group(value) {
17
- this.#group = value;
18
- }
19
-
20
- // endregion Properties
21
-
22
- constructor(group) {
23
- this.#group = group;
24
- }
25
- }
1
+ /**/
2
+
3
+ export default class TestGroup {
4
+ // region Private fields
5
+
6
+ #group;
7
+
8
+ // endregion Private fields
9
+
10
+ // region Properties
11
+
12
+ get group() {
13
+ return this.#group;
14
+ }
15
+
16
+ set group(value) {
17
+ this.#group = value;
18
+ }
19
+
20
+ // endregion Properties
21
+
22
+ constructor(group) {
23
+ this.#group = group;
24
+ }
25
+ }
@@ -19,9 +19,19 @@ export default class TestResult {
19
19
  #expectedText;
20
20
  #actualText;
21
21
 
22
+ #thrown;
23
+ #trouble;
24
+
25
+ #thrownText;
26
+ #troubleText;
27
+
28
+ #wasThrowTest;
29
+
30
+ #doesHaveThrow;
31
+ #doesHaveTrouble;
32
+
22
33
  #didPass;
23
34
  #actual;
24
- #thrown;
25
35
 
26
36
  // endregion Fields
27
37
 
@@ -91,19 +101,39 @@ export default class TestResult {
91
101
  this.#didPass = value;
92
102
  }
93
103
 
104
+ get wasThrowTest() {
105
+ return this.#wasThrowTest;
106
+ }
107
+
108
+ get doesHaveThrow() {
109
+ return this.#doesHaveThrow;
110
+ }
111
+
112
+ get doesHaveTrouble() {
113
+ return this.#doesHaveTrouble;
114
+ }
115
+
94
116
  get anyThrow() {
95
117
  return this.#thrown;
96
118
  }
97
119
 
98
- set anyThrow(value) {
99
- this.#thrown = value;
120
+ get anyTrouble() {
121
+ return this.#trouble;
122
+ }
123
+
124
+ get anyThrowText() {
125
+ return this.#thrownText;
126
+ }
127
+
128
+ get anyTroubleText() {
129
+ return this.#troubleText;
100
130
  }
101
131
 
102
132
  // endregion Outcome properties
103
133
 
104
134
  // region Construction
105
135
 
106
- constructor(test) {
136
+ constructor(test) /* ok */ {
107
137
  this.#test = test;
108
138
  this.#displayer = new TotalDisplayer();
109
139
  }
@@ -112,56 +142,76 @@ export default class TestResult {
112
142
 
113
143
  // region Before run: setNature() and dependencies
114
144
 
115
- setNature() {
145
+ setNature() /* passed */ {
116
146
  this.#identityText = this.#calculateIdentityText();
117
147
  this.#initorsText = this.#calculateInitorsText();
118
148
  this.#inputsText = this.#calculateInputsText();
119
149
  this.#expectedText = this.#calculateExpectedText();
150
+ this.#wasThrowTest = this.#test.isThrowTest;
120
151
  }
121
152
 
122
- #calculateIdentityText() {
153
+ #calculateIdentityText() /* verified */ {
123
154
  let text = `${ this.test.for } `;
124
155
  return text;
125
156
  }
126
157
 
127
- #calculateInitorsText() {
158
+ #calculateInitorsText() /* verified */ {
128
159
  let text = this.#displayItemRow(this.test.with);
129
160
  text = `Initors: ${ text }. `;
130
161
  return text;
131
162
  }
132
163
 
133
- #calculateInputsText() {
164
+ #calculateInputsText() /* verified */ {
134
165
  let text = this.#displayItemRow(this.test.in);
135
166
  text = `Inputs: ${ text }. `;
136
167
  return text;
137
168
  }
138
169
 
139
- #calculateExpectedText() {
170
+ #calculateExpectedText() /* verified */ {
140
171
  let text = this.#displaySingleItem(this.test.out);
141
172
  text = `Expected: ${ text }. `;
142
173
  return text;
143
174
  }
144
-
175
+
145
176
  // endregion Before run: setNature() and dependencies
146
177
 
147
178
  // region After run: setResults() and dependencies
148
179
 
149
- setResults() {
180
+ setResults() /* passed */ {
150
181
  // Direct results.
151
182
  this.#didPass = this.test.didPass;
152
183
  this.#actual = this.test.actual;
153
- this.#thrown = this.test.anyThrow;
154
184
 
185
+ this.#thrown = this.test.thrown;
186
+ this.#trouble = this.test.trouble;
187
+
188
+ this.#doesHaveThrow = this.test.doesHaveUnsoughtThrow;
189
+ this.#doesHaveTrouble = this.test.doesHaveTrouble;
190
+
155
191
  // Derived displayable results.
156
192
  this.#actualText = this.#calculateActualText();
193
+ this.#thrownText = this.#calculateThrownText("Unsought throw in tested code", this.#thrown);
194
+ this.#troubleText = this.#calculateThrownText("Throw in framing code", this.#trouble);
157
195
  }
158
196
 
159
- #calculateActualText() {
197
+ #calculateActualText() /* verified */ {
160
198
  let text = this.#displaySingleItem(this.test.actual);
161
199
  text = `Actual: ${ text }. `;
162
200
  return text;
163
201
  }
164
202
 
203
+ #calculateThrownText(scope, thrown) /* verified */ {
204
+ // No throw to display.
205
+ if (thrown === undefined) {
206
+ return; // Undefined.
207
+ }
208
+
209
+ let text = `${ scope }: ${ this.#test.on.name }: ${ this.#test.runName }: `
210
+ + `\"${ this.#test.for }\": ${ thrown.message }`;
211
+
212
+ return text;
213
+ }
214
+
165
215
  // endregion After run: setResults() and dependencies
166
216
 
167
217
  // region Displaying test args
@@ -1,6 +1,6 @@
1
1
  /**/
2
2
 
3
- /* Runs tests one by one as an ATestCaller requests them. */
3
+ /* Runs tests one by one as TestCaller or equivalent requests them. */
4
4
 
5
5
  import TestDef from "./TestDef.js";
6
6
  import TestGroup from "./TestGroup.js";
@@ -25,6 +25,10 @@ export default class TestRunner {
25
25
  #numberRun;
26
26
  #numberPassed;
27
27
  #numberFailed;
28
+
29
+ #thrown;
30
+ #trouble;
31
+
28
32
  #allDidPass;
29
33
 
30
34
  // endregion Private fields
@@ -67,6 +71,14 @@ export default class TestRunner {
67
71
  this.#numberFailed = value;
68
72
  }
69
73
 
74
+ get thrown() {
75
+ return this.#thrown;
76
+ }
77
+
78
+ get trouble() {
79
+ return this.#trouble;
80
+ }
81
+
70
82
  get allDidPass() {
71
83
  return this.#allDidPass;
72
84
  }
@@ -86,6 +98,9 @@ export default class TestRunner {
86
98
  this.#numberPassed = 0;
87
99
  this.#numberFailed = 0;
88
100
 
101
+ this.#thrown = [];
102
+ this.#trouble = [];
103
+
89
104
  this.#allDidPass = true;
90
105
  }
91
106
 
@@ -101,9 +116,12 @@ export default class TestRunner {
101
116
 
102
117
  // Generator for all test results,
103
118
  // including groups and summary.
104
- * [Symbol.iterator]() {
119
+ async * runTests() {
105
120
  if (!this.#tests) {
106
- throw new Error("No tests available to run. TestRunner's .tests must be set before an attempt to run is made.");
121
+ throw new Error(
122
+ "No tests available to run. TestRunner's .tests "
123
+ + "must be set before an attempt to run is made."
124
+ );
107
125
  }
108
126
 
109
127
  // Converting so that property names can be relied on.
@@ -126,21 +144,24 @@ export default class TestRunner {
126
144
 
127
145
  // Each new method / prop name should be a group,
128
146
  // and so should the first method for a class.
129
- if (test.runName !== methodGroup.group || atFirstClassMethod) {
130
- methodGroup.group = test.runName;
147
+ let runName = test.runName;
148
+
149
+ if (runName !== methodGroup.group || atFirstClassMethod) {
150
+ methodGroup.group = runName;
131
151
  atFirstClassMethod = false;
132
152
  yield methodGroup;
133
153
  }
134
154
 
135
- let result = this.runOneTest(test);
155
+ let result = await this.#runOneTest(test);
156
+
136
157
  yield result;
137
158
  }
138
159
 
139
160
  // Iterative returning of final results summary.
140
- yield this.summarize();
161
+ yield this.#summarize();
141
162
  }
142
163
 
143
- runOneTest(test) {
164
+ async #runOneTest(test) {
144
165
  // Default outputs. Never left undefined.
145
166
  test.didPass = false;
146
167
  test.anyThrow = null;
@@ -151,13 +172,13 @@ export default class TestRunner {
151
172
  result.setNature();
152
173
 
153
174
  // Actually running the test.
154
- this.#frame.run(test);
175
+ await this.#frame.run(test);
155
176
 
156
177
  // Gathering facts based on the test run.
157
178
  result.setResults();
158
179
 
159
180
  // For later summarizing.
160
- this.#retainTestResults(test);
181
+ this.#retainTestResults(result);
161
182
 
162
183
  // Results and test back to the caller.
163
184
  return result;
@@ -165,22 +186,30 @@ export default class TestRunner {
165
186
 
166
187
  // region Dependencies of runOneTest()
167
188
 
168
- #retainTestResults(test) {
189
+ #retainTestResults(result) {
169
190
  this.#numberRun++;
170
191
 
171
- if (test.didPass) {
192
+ if (result.doesHaveThrow) {
193
+ this.#thrown.push(result.anyThrowText);
194
+ }
195
+
196
+ if (result.doesHaveTrouble) {
197
+ this.#trouble.push(result.anyTroubleText);
198
+ }
199
+
200
+ if (result.didPass) {
172
201
  this.#numberPassed++;
173
202
  }
174
203
  else {
175
204
  this.#numberFailed++;
176
205
  }
177
206
 
178
- this.#allDidPass &&= test.didPass;
207
+ this.#allDidPass &&= result.didPass;
179
208
  }
180
209
 
181
210
  // endregion Dependencies of runOneTest()
182
211
 
183
- summarize() {
212
+ #summarize() {
184
213
  let summary = `Ran ${ this.#numberRun } test${ this.#numberRun !== 1 ? "s" : "" }. `
185
214
  + `${ this.#allDidPass ? "All tests passed. " : "" }`
186
215
  + `${ this.#numberPassed } passed. `
@@ -188,9 +217,8 @@ export default class TestRunner {
188
217
 
189
218
  let anyWereRun = this.#numberRun > 0;
190
219
 
191
- return new TestSummary(summary, this.#allDidPass, anyWereRun);
220
+ return new TestSummary(summary, this.#allDidPass, anyWereRun, this.#thrown, this.#trouble);
192
221
  }
193
222
 
194
223
  // endregion Running tests
195
-
196
224
  }