@suffolklitlab/alkiln 5.13.2 → 5.13.3-feat-random
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/.github/workflows/github_server.yml +7 -7
- package/.github/workflows/playground.yml +2 -1
- package/CHANGELOG.md +32 -1
- package/action.yml +89 -29
- package/action_for_github_server/action.yml +73 -29
- package/docs/4_environments.md +243 -0
- package/docs/artifacts_folder_name.md +135 -0
- package/docs/constrained_random_tests.md +254 -0
- package/docs/decisions/006_which_log_level_to_pick_2024_09_5.md +70 -0
- package/docs/decisions/007_getting_and_storing_logs.md +179 -0
- package/docs/decisions/move_runtime_config_to_sources_folder.md +5 -0
- package/docs/decisions/reports_and_logs_2024_08_11.md +161 -0
- package/docs/kinds_of_errors.md +26 -0
- package/docs/prioritize_constrained_random_tests.md +60 -0
- package/lib/docassemble/docassemble_api_interface.js +77 -51
- package/lib/docassemble/server_install.js +32 -9
- package/lib/docassemble/setup.js +41 -24
- package/lib/docassemble/takedown.js +33 -10
- package/lib/globals.js +10 -0
- package/lib/run_cucumber.js +155 -50
- package/lib/scope.js +101 -77
- package/lib/setup/TestGenerator.js +1745 -0
- package/lib/setup/artifacts.js +20 -0
- package/lib/setup/generate.js +42 -0
- package/lib/setup/get_Gherkin_AST.js +26 -0
- package/lib/setup/validate.js +89 -0
- package/lib/steps.js +176 -51
- package/lib/utils/Log.js +1132 -0
- package/lib/utils/_alkiln-constrained_random_tests_old.js +1727 -0
- package/lib/utils/files.js +18 -11
- package/lib/utils/get_error_stacks.js +16 -0
- package/lib/utils/reports.js +95 -28
- package/lib/utils/session_vars.js +102 -73
- package/lib/utils/set_sources_paths.js +98 -0
- package/lib/utils/time.js +44 -0
- package/package.json +16 -8
- package/tests/log_codes/check_codes.sh +19 -14
- package/tests/log_codes/log_code_expected_instances.txt +69 -9
- package/tests/unit_tests/constrained_random_answers/invalid.fixtures.js +155 -0
- package/tests/unit_tests/constrained_random_answers/invalid.test.js +233 -0
- package/tests/unit_tests/constrained_random_answers/valid.fixtures.js +905 -0
- package/tests/unit_tests/constrained_random_answers/valid.test.js +521 -0
- package/tests/unit_tests/example_of_chai_test_lifecycle.js +78 -0
- package/tests/unit_tests/getAllFields.test.js +5 -0
- package/tests/unit_tests/getMatchingRows.test.js +5 -0
- package/tests/unit_tests/log.test.js +557 -0
- package/tests/unit_tests/normalizeTable.test.js +5 -0
- package/lib/utils/log.js +0 -96
- /package/docs/{expected_inernal_results.md → expected_internal_results.md} +0 -0
package/lib/utils/files.js
CHANGED
|
@@ -21,20 +21,27 @@ files.readable_date = function() {
|
|
|
21
21
|
return fancy_str;
|
|
22
22
|
}; // Ends files.readable_date()
|
|
23
23
|
|
|
24
|
-
files.make_artifacts_folder = function (
|
|
25
|
-
/** Makes a folder in the root of the project
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (
|
|
31
|
-
|
|
24
|
+
files.make_artifacts_folder = function (path) {
|
|
25
|
+
/** Makes a folder in the root of the project if it doesn't
|
|
26
|
+
* already exist, either with the given name or with a default name.
|
|
27
|
+
*
|
|
28
|
+
* Works for local development as well, without action.yml or workflow files.
|
|
29
|
+
* */
|
|
30
|
+
if (path) {
|
|
31
|
+
let name_parts = path.split(`/`);
|
|
32
|
+
let safe_parts = [];
|
|
33
|
+
for ( let part of name_parts ) {
|
|
34
|
+
safe_parts.push(safe_filename( part ));
|
|
35
|
+
}
|
|
36
|
+
path = safe_parts.join(`/`);
|
|
32
37
|
} else {
|
|
33
|
-
|
|
38
|
+
path = `alkiln-${ files.readable_date() }`
|
|
34
39
|
}
|
|
35
40
|
|
|
36
|
-
|
|
37
|
-
|
|
41
|
+
// `recursive` avoids errors if folder already exists
|
|
42
|
+
// https://nodejs.org/docs/latest-v18.x/api/fs.html#fspromisesmkdirpath-options
|
|
43
|
+
fs.mkdirSync( path, { recursive: true });
|
|
44
|
+
return path;
|
|
38
45
|
}; // Ends files.make_artifacts_folder()
|
|
39
46
|
|
|
40
47
|
module.exports = files;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module.exports = function get_error_stacks({ errors }) {
|
|
2
|
+
/** Given a list of Error instances, return a list of their stacks
|
|
3
|
+
*
|
|
4
|
+
* Discuss: Move this into `Log`. Potentially print each stack with the
|
|
5
|
+
* same icon as the message. `{to_stack: [Errors]}.
|
|
6
|
+
*
|
|
7
|
+
* @param {object} obj - Named arguments
|
|
8
|
+
* @param {[Error]} obj.errors - A list of Error instances
|
|
9
|
+
*
|
|
10
|
+
* @returns {[Error.stack]} - A list of Error instances' `.stack` properties
|
|
11
|
+
* */
|
|
12
|
+
let stacks = errors.map(function( error ) {
|
|
13
|
+
return error.stack;
|
|
14
|
+
});
|
|
15
|
+
return stacks;
|
|
16
|
+
};
|
package/lib/utils/reports.js
CHANGED
|
@@ -57,7 +57,7 @@ reports.addToReport = function( scope, { type, code, value }) {
|
|
|
57
57
|
// thrown errors if needed.
|
|
58
58
|
|
|
59
59
|
// Debugging logs
|
|
60
|
-
let debug_line = getDebugLine({ type, code, value });
|
|
60
|
+
let debug_line = getDebugLine({ type, code, value, indent: global_indent });
|
|
61
61
|
if ( session_vars.get_debug() ) {
|
|
62
62
|
console.log( `Report: ${ debug_line }` );
|
|
63
63
|
}
|
|
@@ -66,24 +66,43 @@ reports.addToReport = function( scope, { type, code, value }) {
|
|
|
66
66
|
const MISC_ARTIFACTS_DIR = `_alkiln-misc_artifacts`;
|
|
67
67
|
if ( !scope.paths ) {
|
|
68
68
|
// Can this be usefully abstracted?
|
|
69
|
-
|
|
69
|
+
let date = files.readable_date();
|
|
70
|
+
scope.paths = {
|
|
71
|
+
debug_log: `${ MISC_ARTIFACTS_DIR }/unit_tests_debug_log-${ date }.txt`,
|
|
72
|
+
report_log: `${ MISC_ARTIFACTS_DIR }/unit_tests_report_log-${ date }.txt`,
|
|
73
|
+
};
|
|
70
74
|
}
|
|
71
75
|
|
|
72
76
|
try {
|
|
73
77
|
fs.appendFileSync( scope.paths.debug_log, `\nReport:\n${debug_line}` );
|
|
74
|
-
|
|
78
|
+
fs.appendFileSync( scope.paths.report_log, `\nReport:\n${debug_line}` );
|
|
79
|
+
} catch ( file_error ) {
|
|
75
80
|
fs.mkdirSync( MISC_ARTIFACTS_DIR );
|
|
76
81
|
fs.appendFileSync( scope.paths.debug_log, `\nReport:\n${debug_line}` );
|
|
82
|
+
fs.appendFileSync( scope.paths.report_log, `\nReport:\n${debug_line}` );
|
|
77
83
|
// An error in here should throw (for now)
|
|
78
84
|
}
|
|
79
85
|
|
|
80
86
|
// Report stuff
|
|
81
87
|
let scenario = reports.makeSureScenarioExists( scope );
|
|
82
|
-
scenario.lines.push({ type, code, value });
|
|
88
|
+
scenario.lines.push({ type, code, value, indent: global_indent });
|
|
83
89
|
|
|
84
|
-
return getLineText({ type, code, value });
|
|
90
|
+
return getLineText({ type, code, value, indent: global_indent });
|
|
85
91
|
}; // Ends reports.addToReport()
|
|
86
92
|
|
|
93
|
+
|
|
94
|
+
let indent_num = 0;
|
|
95
|
+
let global_indent = ``;
|
|
96
|
+
reports.indent = function() {
|
|
97
|
+
indent_num += 2;
|
|
98
|
+
global_indent = ` `.repeat( indent_num );
|
|
99
|
+
}
|
|
100
|
+
reports.outdent = function() {
|
|
101
|
+
indent_num = Math.max( 0, indent_num - 2 );
|
|
102
|
+
global_indent = ` `.repeat( indent_num );
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
|
|
87
106
|
reports.create = function( scope ) {
|
|
88
107
|
reports.makeSureReportExists( scope );
|
|
89
108
|
}
|
|
@@ -115,25 +134,66 @@ reports.saveFinalScenarioData = function( scope, { status=`in progress` }) {
|
|
|
115
134
|
this.setReportScenarioStatus( scope, { status });
|
|
116
135
|
let scenario = scope.report.get( scope.scenario_id );
|
|
117
136
|
let report = this.getPrintableScenario( scenario );
|
|
118
|
-
if ( scenario.status === `
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
else { this.others.push( report ); }
|
|
123
|
-
|
|
124
|
-
// Save to failures.txt (worry about optimization later)
|
|
125
|
-
if ( this.failures.length > 1 || this.others.length > 1 ) {
|
|
126
|
-
fs.writeFileSync(
|
|
127
|
-
scope.paths.failures,
|
|
128
|
-
this.title + this.getPrintableUnexpected( scope )
|
|
129
|
-
);
|
|
137
|
+
if ( scenario.status === `PASSED` ) {
|
|
138
|
+
this.successes.push( report );
|
|
139
|
+
} else {
|
|
140
|
+
this._save_unexpected( scope, { status: scenario.status, report });
|
|
130
141
|
}
|
|
142
|
+
// if ( scenario.status === `FAILED` ) {
|
|
143
|
+
// this.failures.push( report );
|
|
144
|
+
// }
|
|
145
|
+
// else if ( scenario.status === `PASSED` ) { this.successes.push( report ); }
|
|
146
|
+
// // else if ( scenario.status === `WARNING` ) { passed_reports += report; this.warnings.push( report ); }
|
|
147
|
+
// // else if ( scenario.status === `UNDEFINED` ) { passed_reports += report; this.undefineds.push( report ); }
|
|
148
|
+
// else { this.others.push( report ); }
|
|
149
|
+
|
|
150
|
+
// // Save to unexpected results files (worry about optimization later)
|
|
151
|
+
// if ( this.failures.length > 1 || this.others.length > 1 ) {
|
|
152
|
+
// let unexpected_results = this.title + this.getPrintableUnexpected( scope );
|
|
153
|
+
// fs.writeFileSync( scope.paths.report_of_unexpected, unexpected_results );
|
|
154
|
+
// fs.appendFileSync( scope.paths.debug_of_unexpected, unexpected_results );
|
|
155
|
+
// }
|
|
156
|
+
|
|
131
157
|
// Save to reports.txt (worry about optimization later)
|
|
132
158
|
fs.writeFileSync( scope.paths.report, this.getPrintableReport( scope ) );
|
|
133
159
|
|
|
134
160
|
return scenario;
|
|
135
161
|
}; // Ends reports.saveFinalScenarioData()
|
|
136
162
|
|
|
163
|
+
reports._save_unexpected = function(scope, { status, report }) {
|
|
164
|
+
/** Store the unexpected report and write to the unexpected report file.
|
|
165
|
+
*
|
|
166
|
+
* @param {Object} scope - State and methods of the current tests
|
|
167
|
+
* @param {Object} data
|
|
168
|
+
* @param {Object} data.status - Status of the current scenario
|
|
169
|
+
* @param {string} data.report - Report for the current scenario
|
|
170
|
+
* */
|
|
171
|
+
if ( status === `FAILED` ) {
|
|
172
|
+
this.failures.push( report );
|
|
173
|
+
// } else if ( status === `WARNING` ) { passed_reports += report; this.warnings.push( report );
|
|
174
|
+
// } else if ( status === `UNDEFINED` ) { passed_reports += report; this.undefineds.push( report ); }
|
|
175
|
+
} else {
|
|
176
|
+
this.others.push( report );
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// If it's our first unexpected result, add the title to the top of the page
|
|
180
|
+
let already_unexpected = ``;
|
|
181
|
+
try { already_unexpected = fs.readFileSync( scope.paths.report_of_unexpected ); }
|
|
182
|
+
catch ( ignore_non_critical_error ) {}
|
|
183
|
+
if ( already_unexpected === `` ) {
|
|
184
|
+
try { fs.appendFileSync( scope.paths.report_of_unexpected, this.title ); }
|
|
185
|
+
catch ( ignore_non_critical_error ) {}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
try { fs.appendFileSync( scope.paths.report_of_unexpected, report ); }
|
|
189
|
+
catch ( ignore_non_critical_error ) {}
|
|
190
|
+
|
|
191
|
+
return this;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
reports.progress = function ({ logs=[] }) {
|
|
195
|
+
// TODO: save stdout-style to report_log, object, and getting printable scenario
|
|
196
|
+
};
|
|
137
197
|
|
|
138
198
|
reports.setReportScenarioStatus = function( scope, { status=`PASSED` }) {
|
|
139
199
|
/** Add final status of scenario to scenario report object - passed, failed, etc. */
|
|
@@ -197,38 +257,45 @@ reports.getPrintableScenario = function( scenario ) {
|
|
|
197
257
|
/** Return a string generated from scenario list of report items. */
|
|
198
258
|
let report = ``;
|
|
199
259
|
for ( let line of scenario.lines ) {
|
|
200
|
-
let { type, code, value } = line;
|
|
201
|
-
report += `${ getLineText({ type, code, value }) }\n`;
|
|
260
|
+
let { type, code, value, indent } = line;
|
|
261
|
+
report += `${ getLineText({ type, code, value, indent }) }\n`;
|
|
202
262
|
}
|
|
203
263
|
return report;
|
|
204
264
|
}; // Ends reports.getPrintableScenario()
|
|
205
265
|
|
|
206
|
-
let getLineText = function ({ type=`no type`, code=`
|
|
207
|
-
|
|
266
|
+
let getLineText = function ({ type=`no type`, code=`ALK00rl`, value=``, indent=`` }) {
|
|
267
|
+
|
|
268
|
+
value = `${ value.replace( /\n/g, "\n" + indent ) }`;
|
|
269
|
+
|
|
270
|
+
if ( type === `as_is` ) { return `${ indent }${ value }`; }
|
|
271
|
+
|
|
272
|
+
let start = indent;
|
|
273
|
+
if ( type.includes(`outcome`) ) { start = `\n`; }
|
|
274
|
+
|
|
208
275
|
if ( type.includes(`page_id`) ) { start += `screen id: `; }
|
|
209
|
-
else if ( type.includes(`outcome`) ) { start += `\n`; } // extra new line
|
|
210
276
|
else if ( type.includes(`warning`) ) { start += `🔎 ${ code } WARNING: `; }
|
|
211
277
|
else if ( type.includes(`error`) ) { start += `\n🤕 ${ code } ERROR: `; } // extra new line
|
|
212
|
-
|
|
278
|
+
let result = `${ start }${ value }`;
|
|
279
|
+
return result;
|
|
213
280
|
}; // Ends getLine()
|
|
214
281
|
|
|
215
|
-
let getDebugLine = function ({ type=``, code=`
|
|
282
|
+
let getDebugLine = function ({ type=``, code=`ALK00rd`, value=``, indent=`` }) {
|
|
216
283
|
/** In debug log lines, include all the information possible. */
|
|
217
284
|
|
|
218
285
|
// `plain` is usually used for visual clarity and shouldn't have
|
|
219
286
|
// codes and such associated with it. Return right away.
|
|
220
|
-
if ( type.includes(`plain`) ) { return value
|
|
287
|
+
if ( type.includes(`plain`) ) { return `${ indent }${ value }`; }
|
|
221
288
|
|
|
222
|
-
let start =
|
|
289
|
+
let start = indent;
|
|
223
290
|
|
|
224
291
|
// Get all the relevant emoji
|
|
225
|
-
if ( type.includes(`debug`) ) { start +=
|
|
292
|
+
if ( type.includes(`debug`) ) { start += `🐛`; }
|
|
226
293
|
if ( type.includes(`info`) ) { start += `💡`; }
|
|
227
294
|
if ( type.includes(`success`) ) { start += `🌈`; }
|
|
228
295
|
if ( type.includes(`warning`) ) { start += `🔎`; }
|
|
229
296
|
if ( type.includes(`error`) ) { start += `🤕`; }
|
|
230
297
|
// Default
|
|
231
|
-
if ( start ===
|
|
298
|
+
if ( start === indent ) { start += `?`; }
|
|
232
299
|
|
|
233
300
|
start += ` ${ code }`;
|
|
234
301
|
|
|
@@ -3,19 +3,29 @@ const fg = require('fast-glob');
|
|
|
3
3
|
const path = require('path');
|
|
4
4
|
require('dotenv').config();
|
|
5
5
|
|
|
6
|
+
/** WARNING: Avoid importing other project files into here. Every other
|
|
7
|
+
* file should be able to reference this one without worrying
|
|
8
|
+
* about circular references.
|
|
9
|
+
* */
|
|
10
|
+
|
|
11
|
+
/** This file provides user info variable getters and setters.
|
|
12
|
+
* E.g. server address. It often uses `process.env` vars or
|
|
13
|
+
* vars that have been built from `process.env` vars. It is
|
|
14
|
+
* meant to avoid duplication, make testing easier, and leave
|
|
15
|
+
* room for future manipulation.
|
|
16
|
+
*
|
|
17
|
+
* No other file should need to use `process.env`.
|
|
18
|
+
*
|
|
19
|
+
* We use `.get_foo()` functions partly because it makes the code
|
|
20
|
+
* easier to test.
|
|
21
|
+
* */
|
|
22
|
+
|
|
6
23
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
* `process.env` vars or vars that have been built from `process.env` vars.
|
|
13
|
-
* It is meant to avoid duplication, make testing easier, and leave room for
|
|
14
|
-
* future manipulation. No other file should need to use `process.env`.
|
|
15
|
-
*
|
|
16
|
-
* We have a bunch of `.get_foo()` functions partly because it makes the code
|
|
17
|
-
* much easier to test.
|
|
18
|
-
*/
|
|
24
|
+
* Discuss: consider returning most logs (info, error, warning) so that the
|
|
25
|
+
* calling functions can use a `Log` to log them properly instead of logging
|
|
26
|
+
* to the console. Alternatively, move everything in here that `Log` uses
|
|
27
|
+
* into its own file so that `session_vars` can have its own `Log` instance.
|
|
28
|
+
* */
|
|
19
29
|
|
|
20
30
|
const session_vars = {};
|
|
21
31
|
|
|
@@ -59,6 +69,18 @@ session_vars.get_setup_timeout_ms = function () {
|
|
|
59
69
|
return timeout;
|
|
60
70
|
};
|
|
61
71
|
|
|
72
|
+
session_vars.get_max_tests_per_generator_scenario = function() {
|
|
73
|
+
/** Returns the maximum number of tests to generate for any one generator
|
|
74
|
+
* Scenario.
|
|
75
|
+
*
|
|
76
|
+
* Assumes an average of 30 secs per test with a max of 20 min. for all tests
|
|
77
|
+
* for that one generator Scenario.
|
|
78
|
+
*
|
|
79
|
+
* @returns {int} - Max number of tests for one generator Scenario
|
|
80
|
+
* */
|
|
81
|
+
return process.env.MAX_RANDOM_TESTS_PER_SCENARIO || 40;
|
|
82
|
+
}
|
|
83
|
+
|
|
62
84
|
session_vars.get_da_server_url = function () {
|
|
63
85
|
try {
|
|
64
86
|
let url = process.env.SERVER_URL || process.env.BASE_URL;
|
|
@@ -136,7 +158,7 @@ session_vars.save_project_name = function ( project_name ) {
|
|
|
136
158
|
let file = fs.writeFileSync( runtime_config_path, JSON.stringify( json, null, 2 ) );
|
|
137
159
|
|
|
138
160
|
if ( session_vars.get_debug() ) {
|
|
139
|
-
console.
|
|
161
|
+
console.debug( `🐛 ALK0036 DEBUG: Stored name of Project "${ project_name }" locally.` );
|
|
140
162
|
}
|
|
141
163
|
return file;
|
|
142
164
|
}; // Ends save_project_name()
|
|
@@ -148,7 +170,7 @@ session_vars.get_project_name = function () {
|
|
|
148
170
|
}
|
|
149
171
|
let json = JSON.parse( fs.readFileSync( runtime_config_path ));
|
|
150
172
|
let project_name = json[ project_name_key ] || null;
|
|
151
|
-
if ( session_vars.get_debug() ) { console.
|
|
173
|
+
if ( session_vars.get_debug() ) { console.debug( `🐛 ALK0037 DEBUG: Project name from file is "${ project_name }".` ); }
|
|
152
174
|
return project_name;
|
|
153
175
|
}; // Ends get_project_name()
|
|
154
176
|
|
|
@@ -172,29 +194,41 @@ session_vars.delete_project_name = function () {
|
|
|
172
194
|
// const file_storing_artifact_path = `_alkiln_test_artifacts_dir_name.json`;
|
|
173
195
|
const artifacts_path_key = `artifacts_path`;
|
|
174
196
|
session_vars.save_artifacts_path_name = function ( path_name ) {
|
|
175
|
-
|
|
197
|
+
/** Save a key with the name of the current artifacts folder
|
|
198
|
+
* in case it doesn't exist.
|
|
199
|
+
* */
|
|
176
200
|
return session_vars.save_runtime_config_var({ key: artifacts_path_key, value: path_name });
|
|
177
201
|
}; // Ends save_artifacts_path_name()
|
|
178
202
|
session_vars.get_artifacts_path_name = function () {
|
|
179
|
-
|
|
203
|
+
/** Get the name of the current artifacts path. */
|
|
180
204
|
return session_vars.get_runtime_config_var( artifacts_path_key );
|
|
181
205
|
}; // Ends get_artifacts_path_name()
|
|
206
|
+
session_vars.delete_artifacts_path_name = function () {
|
|
207
|
+
/** Delete the runtime config value of the artifacts path. */
|
|
208
|
+
return session_vars.delete_runtime_config_var({ key: artifacts_path_key });
|
|
209
|
+
};
|
|
182
210
|
|
|
183
211
|
|
|
212
|
+
/** We use `da_install_method` for temp server vs. author's account (Playground)
|
|
213
|
+
* on author's server. For example, for deciding how to get the url of a
|
|
214
|
+
* server. See docs on 4 environments. TODO: Change `_method` to `_location`?
|
|
215
|
+
* */
|
|
184
216
|
const install_method_key = `da_install_method`;
|
|
185
217
|
session_vars.save_install_method = function ( install_method ) {
|
|
186
|
-
|
|
218
|
+
/** Save a key with the name of the current artifacts folder in case it
|
|
219
|
+
* doesn't exist. */
|
|
187
220
|
return session_vars.save_runtime_config_var({ key: install_method_key, value: install_method });
|
|
188
221
|
}; // Ends save_install_method()
|
|
189
222
|
session_vars.get_install_method = function () {
|
|
190
|
-
|
|
223
|
+
/** Get the name of the current install method. */
|
|
191
224
|
return session_vars.get_runtime_config_var( install_method_key );
|
|
192
225
|
}; // Ends get_install_method()
|
|
193
226
|
|
|
194
227
|
|
|
195
228
|
const repo_folder_key = `da_repo_folder_name`;
|
|
196
229
|
session_vars.save_repo_folder_name = function (repo_folder_name) {
|
|
197
|
-
/** Save a key with the name of the main package folder in the repository.
|
|
230
|
+
/** Save a key with the name of the main package folder in the repository. For
|
|
231
|
+
* example, EvictionDefense in the docassemble-EvictionDefense folder. */
|
|
198
232
|
return session_vars.save_runtime_config_var({ key: repo_folder_key, value: repo_folder_name });
|
|
199
233
|
}; // Ends session_vars.save_repo_folder_name()
|
|
200
234
|
session_vars.get_repo_folder_name = function () {
|
|
@@ -208,10 +242,11 @@ session_vars.save_runtime_config_var = function ({ key, value }) {
|
|
|
208
242
|
* the json object there. The functions calling this are keeping
|
|
209
243
|
* track of the key, so they're useful to keep around.
|
|
210
244
|
*
|
|
211
|
-
* @param
|
|
212
|
-
* @param
|
|
245
|
+
* @param {object} obj - Named arguments
|
|
246
|
+
* @param {str} obj.key - Key to use to store `value` in runtime config file
|
|
247
|
+
* @param {any} obj.value - Value to store in runtime config object
|
|
213
248
|
*
|
|
214
|
-
* @returns
|
|
249
|
+
* @returns {binary?} file Runtime config file
|
|
215
250
|
*/
|
|
216
251
|
|
|
217
252
|
// Ensure runtime config file exists
|
|
@@ -226,82 +261,76 @@ session_vars.save_runtime_config_var = function ({ key, value }) {
|
|
|
226
261
|
let file = fs.writeFileSync( runtime_config_path, JSON.stringify( json, null, 2 ) );
|
|
227
262
|
|
|
228
263
|
if ( session_vars.get_debug() ) {
|
|
229
|
-
console.
|
|
264
|
+
console.debug( `🐛 ALK0040 DEBUG: Stored runtime config var "${key}" locally as "${value}".` );
|
|
230
265
|
}
|
|
231
266
|
return file;
|
|
232
267
|
}; // Ends session_vars.save_runtime_config_var()
|
|
233
268
|
|
|
234
269
|
session_vars.get_runtime_config_var = function ( key ) {
|
|
235
270
|
/** Get the value of the given key from the runtime config file.
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
271
|
+
*
|
|
272
|
+
* @param {str} key - Key in runtime config file that contains the desired val
|
|
273
|
+
*
|
|
274
|
+
* @returns {any} - Value of given key in runtime config file or null
|
|
275
|
+
*/
|
|
241
276
|
try {
|
|
242
277
|
let json = JSON.parse( fs.readFileSync( runtime_config_path ));
|
|
243
278
|
let value = json[ key ];
|
|
244
279
|
if ( session_vars.get_debug() ) {
|
|
245
|
-
console.
|
|
280
|
+
console.debug( `🐛 ALK0041 DEBUG: Runtime config var "${ key }" is "${ value }".` );
|
|
246
281
|
}
|
|
247
|
-
|
|
248
282
|
return value;
|
|
249
|
-
} catch (
|
|
283
|
+
} catch ( get_var_error ) {
|
|
284
|
+
|
|
285
|
+
console.debug(`🖊️ ALK0271 NOTE: Skipped getting runtime config var "${ key }".`, get_var_error.stack );
|
|
250
286
|
return null;
|
|
287
|
+
|
|
251
288
|
}
|
|
252
289
|
}; // Ends session_vars.get_runtime_config_var()
|
|
253
290
|
|
|
291
|
+
session_vars.delete_runtime_config_var = function ({ key }) {
|
|
292
|
+
/** Delete the runtime config var of the given `key`
|
|
293
|
+
*
|
|
294
|
+
* @param {object} obj - Named arguments
|
|
295
|
+
* @param {str} obj.key - Key in runtime config file object to delete
|
|
296
|
+
*
|
|
297
|
+
* @returns {null}
|
|
298
|
+
* */
|
|
299
|
+
try {
|
|
300
|
+
if ( !fs.existsSync( runtime_config_path )) { return null; }
|
|
301
|
+
|
|
302
|
+
let json = JSON.parse( fs.readFileSync( runtime_config_path ));
|
|
303
|
+
delete json[ key ];
|
|
304
|
+
fs.writeFileSync( runtime_config_path, JSON.stringify( json, null, 2 ));
|
|
305
|
+
return null;
|
|
254
306
|
|
|
255
|
-
|
|
256
|
-
/** Returns whether at least one of the given path exists. Store
|
|
257
|
-
* a list of the existing paths. Warn of any missing paths. Can be
|
|
258
|
-
* used for getting, e.g .feature files, files to upload, etc.
|
|
259
|
-
*
|
|
260
|
-
* Our code should ensure this gets run before cucumber tests get triggered.
|
|
261
|
-
*
|
|
262
|
-
* @param paths { [str] } List of paths to the 'sources' folder.
|
|
263
|
-
*
|
|
264
|
-
* @returns bool
|
|
265
|
-
*/
|
|
307
|
+
} catch ( delete_var_error ) {
|
|
266
308
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
let missing_paths = [];
|
|
270
|
-
for ( let path of paths ) {
|
|
271
|
-
let dirs = fg.sync(
|
|
272
|
-
[ path ],
|
|
273
|
-
{ onlyFiles: false, suppressErrors: true }
|
|
274
|
-
);
|
|
275
|
-
|
|
276
|
-
if ( dirs.length > 0 ) {
|
|
277
|
-
existing_paths.push( ...dirs );
|
|
278
|
-
} else {
|
|
279
|
-
missing_paths.push( path );
|
|
280
|
-
}
|
|
281
|
-
}
|
|
309
|
+
console.debug( `🖊️ ALK0272 NOTE: Skipped deleting runtime config var "${ key }".`, delete_var_error.stack );
|
|
310
|
+
return null
|
|
282
311
|
|
|
283
|
-
// If no paths were valid
|
|
284
|
-
if ( existing_paths.length <= 0 ) {
|
|
285
|
-
session_vars._sources_paths = null;
|
|
286
|
-
return false;
|
|
287
312
|
}
|
|
313
|
+
}; // Ends session_vars.delete_runtime_config_var()
|
|
288
314
|
|
|
289
|
-
console.info(
|
|
290
|
-
`🌈 ALK0042 SUCCESS: Did find "sources" file(s) at "${ existing_paths.join('", "') }".`
|
|
291
|
-
)
|
|
292
315
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
316
|
+
session_vars.set_sources_paths = function ( paths ) {
|
|
317
|
+
/** Set the globally accessible value of the paths to the sources folder.
|
|
318
|
+
* Saves and returns the argument it was given.
|
|
319
|
+
*
|
|
320
|
+
* @param paths {[string]} List of paths to the 'sources' folder.
|
|
321
|
+
*
|
|
322
|
+
* @returns {null | [string]}
|
|
323
|
+
* */
|
|
324
|
+
session_vars._sources_paths = paths;
|
|
325
|
+
return paths || null;
|
|
296
326
|
}; // End session_vars.set_sources_paths()
|
|
297
327
|
|
|
298
328
|
session_vars.get_sources_paths = function () {
|
|
299
|
-
/** Get the "sources" folder
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
return session_vars._sources_paths;
|
|
329
|
+
/** Get the previously stored "sources" folder paths.
|
|
330
|
+
*
|
|
331
|
+
* @returns {null | [string]}
|
|
332
|
+
* */
|
|
333
|
+
return session_vars._sources_paths || null;
|
|
305
334
|
};
|
|
306
335
|
|
|
307
336
|
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
const fast_glob = require(`fast-glob`);
|
|
2
|
+
|
|
3
|
+
const session_vars = require(`./session_vars`);
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
module.exports = function set_sources_paths({ log }) {
|
|
7
|
+
/** Set the globally accessible value of the paths to the sources folder,
|
|
8
|
+
* where authors store tests and other relevant test files. Can throw an
|
|
9
|
+
* error.
|
|
10
|
+
*
|
|
11
|
+
* @param {object} obj - Named arguments
|
|
12
|
+
* @param {Log} obj.log - Logs and saves useful info for later debugging.
|
|
13
|
+
*
|
|
14
|
+
* @returns {[string]} - All paths found for package sources folders.
|
|
15
|
+
* */
|
|
16
|
+
session_vars.validateEnvironment();
|
|
17
|
+
|
|
18
|
+
let maybe_paths = get_potential_paths();
|
|
19
|
+
|
|
20
|
+
let sources_paths = get_sources_paths({ maybe_paths });
|
|
21
|
+
if ( sources_paths === 0 ) {
|
|
22
|
+
// If we don't throw, it'll be more confusing when no tests run
|
|
23
|
+
log.throw({ code: `ALK0050`, context: `sources`,
|
|
24
|
+
error: new ReferenceError(
|
|
25
|
+
`ALKiln can't find your "sources" folder(s) which should contain tests and their related files.`
|
|
26
|
+
),
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
log.info({ code: `ALK0042`, context: `sources` },
|
|
31
|
+
`Did find "sources" file(s) at "${ sources_paths.join('", "') }".`
|
|
32
|
+
);
|
|
33
|
+
session_vars.set_sources_paths( sources_paths );
|
|
34
|
+
|
|
35
|
+
return sources_paths;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
function get_potential_paths() {
|
|
40
|
+
/** Use command line args and session vars to return a list of strings of
|
|
41
|
+
* possible sources files paths.
|
|
42
|
+
*
|
|
43
|
+
* @returns {[string]} - Potential sources paths.
|
|
44
|
+
* */
|
|
45
|
+
// `project` is the name in the server docassemble "Project" page
|
|
46
|
+
let project = session_vars.get_user_project_name();
|
|
47
|
+
let id = session_vars.get_user_id();
|
|
48
|
+
|
|
49
|
+
// process.argv is a list of strings of commands and args.
|
|
50
|
+
// Internal note: If using `npm run cucumber`, use `--` before
|
|
51
|
+
// `--sources=./foo`
|
|
52
|
+
let cli_args = require(`minimist`)( process.argv.slice(2), {
|
|
53
|
+
default: {
|
|
54
|
+
sources: [
|
|
55
|
+
`./docassemble/*/data/sources`, // In GitHub folder
|
|
56
|
+
`/usr/share/docassemble/files/playgroundsources/${id}/${project}/*.feature`, // From playground without S3
|
|
57
|
+
`/tmp/playgroundsources/${id}/${project}/*.feature` // From playground with S3
|
|
58
|
+
],
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Make sure `sources` is always a list
|
|
63
|
+
let potential_paths_list = cli_args.sources;
|
|
64
|
+
if ( !Array.isArray( cli_args.sources )) {
|
|
65
|
+
potential_paths_list = [ cli_args.sources ];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return potential_paths_list;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
function get_sources_paths({ maybe_paths }) {
|
|
73
|
+
/** Of the given possible paths, return the paths that actually exist. Maybe
|
|
74
|
+
* none.
|
|
75
|
+
*
|
|
76
|
+
* @param {object} obj - Named arguments
|
|
77
|
+
* @param {[string]} obj.maybe_paths - All possible paths
|
|
78
|
+
*
|
|
79
|
+
* @returns {[string]} - List of all existing paths
|
|
80
|
+
* */
|
|
81
|
+
// Check every path, tracking which exist and which are missing
|
|
82
|
+
let existing_paths = [];
|
|
83
|
+
let missing_paths = [];
|
|
84
|
+
for ( let path of maybe_paths ) {
|
|
85
|
+
let dirs = fast_glob.sync(
|
|
86
|
+
[ path ],
|
|
87
|
+
{ onlyFiles: false, suppressErrors: true }
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
if ( dirs.length > 0 ) {
|
|
91
|
+
existing_paths.push( ...dirs );
|
|
92
|
+
} else {
|
|
93
|
+
missing_paths.push( path );
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return existing_paths;
|
|
98
|
+
}
|
package/lib/utils/time.js
CHANGED
|
@@ -24,3 +24,47 @@ time.waitForTimeout = function( milliseconds ) {
|
|
|
24
24
|
setTimeout(resolve, milliseconds);
|
|
25
25
|
});
|
|
26
26
|
};
|
|
27
|
+
time.sleep = time.waitForTimeout;
|
|
28
|
+
time.wait = time.waitForTimeout;
|
|
29
|
+
|
|
30
|
+
time.filename_date = function() {
|
|
31
|
+
/** Return the current timestamp in a human-readable
|
|
32
|
+
* filename-safe format to match the workflow artifact
|
|
33
|
+
* date format.
|
|
34
|
+
* See https://stackoverflow.com/a/48729396/14144258 and https://stackoverflow.com/a/6040556/14144258
|
|
35
|
+
*/
|
|
36
|
+
// let date = new Date(); // now
|
|
37
|
+
// let day = ("0" + date.getUTCDate()).slice(-2);
|
|
38
|
+
// let month = ("0" + (date.getUTCMonth() + 1)).slice(-2);
|
|
39
|
+
// let year = date.getUTCFullYear();
|
|
40
|
+
// let hours = ("0" + date.getUTCHours()).slice(-2);
|
|
41
|
+
// let mins = ("0" + date.getUTCMinutes()).slice(-2);
|
|
42
|
+
// let secs = ("0" + date.getUTCSeconds()).slice(-2);
|
|
43
|
+
|
|
44
|
+
let dt = time.get_time_parts();
|
|
45
|
+
let safe_date = `${ dt.y }-${ dt.mon }-${ dt.d } at ${ dt.h }h${ dt.min }m${ dt.s }sUTC`;
|
|
46
|
+
return safe_date;
|
|
47
|
+
}; // Ends time.filename_date()
|
|
48
|
+
|
|
49
|
+
time.log_date = function () {
|
|
50
|
+
/** Return the current timestamp in a human-readable
|
|
51
|
+
* more compact log format.
|
|
52
|
+
* See https://stackoverflow.com/a/48729396/14144258 and https://stackoverflow.com/a/6040556/14144258
|
|
53
|
+
*/
|
|
54
|
+
let dt = time.get_time_parts();
|
|
55
|
+
let short_date = `${ dt.y }-${ dt.mon }-${ dt.d } ${ dt.h }:${ dt.min }:${ dt.s }UTC`;
|
|
56
|
+
return short_date;
|
|
57
|
+
}; // Ends time.log_date()
|
|
58
|
+
|
|
59
|
+
time.get_time_parts = function () {
|
|
60
|
+
/** Get all the parts of the date as numbers. */
|
|
61
|
+
let date = new Date(); // now
|
|
62
|
+
return ({
|
|
63
|
+
y: date.getUTCFullYear(),
|
|
64
|
+
mon: ("0" + (date.getUTCMonth() + 1)).slice(-2),
|
|
65
|
+
d: ("0" + date.getUTCDate()).slice(-2),
|
|
66
|
+
h: ("0" + date.getUTCHours()).slice(-2),
|
|
67
|
+
min: ("0" + date.getUTCMinutes()).slice(-2),
|
|
68
|
+
s: ("0" + date.getUTCSeconds()).slice(-2),
|
|
69
|
+
});
|
|
70
|
+
}
|