testaro 29.1.1 → 30.0.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
@@ -762,58 +762,67 @@ The `call` module will find the first job file with a matching name, execute the
762
762
 
763
763
  #### Watch
764
764
 
765
- In watch mode, Testaro periodically checks for a job to run. When such a job exists, Testaro runs it and produces a report. Testaro may continue watching after the first report, or may quit.
765
+ In watch mode, Testaro periodically checks for a job to run and, when a job is obtained, performs it.
766
766
 
767
- ##### By a module
767
+ ##### Directory watch
768
768
 
769
- ```javaScript
770
- const {cycle} = require('./watch');
771
- cycle(true, true, 30);
772
- ```
769
+ Testaro can watch for a job in a directory, with the `dirWatch` function, which can be executed by either a module or a user.
773
770
 
774
- ##### By a user
771
+ ###### By a module
775
772
 
776
773
  ```javaScript
777
- node call watch true true 30
774
+ const {dirWatch} = require('./watch');
775
+ dirWatch(true, 300);
778
776
  ```
779
777
 
780
- ##### Arguments
778
+ In this example, a module asks Testaro to check a directory for a job every 300 seconds, to perform the jobs in the directory if any are found, and then to continue checking. If the first argument is `false`, Testaro will stop checking after performing 1 job. If it is `true`, Testaro continues checking until the process is stopped.
781
779
 
782
- The arguments passed to `cycle` by a module or to `watch` by a user are:
783
- - whether to watch a directory (`true`) or the network (`false`)
784
- - whether to continue watching indefinitely after the first report (`true` or `false`)
785
- - how many seconds to wait after finding no job before checking again (a nonnegative number)
786
- - optionally, where the watched jobs are located
780
+ The directory where Testaro checks for jobs is specified by `process.env.JOBDIR`. Testaro checks for jobs in its `todo` subdirectory and, when it has performed a job, moves it into the `done` subdirectory.
787
781
 
788
- ##### Directory watch
782
+ Testaro creates a report for each job and saves the report in the directory specified by `process.env.REPORTDIR`.
783
+
784
+ ###### By a user
785
+
786
+ A user can choose between two methods:
787
+
788
+ ```javaScript
789
+ node call dirWatch true 300
790
+ ```
789
791
 
790
- With directory watch, Testaro checks whether the `todo` subdirectory of the job directory contains a job. The job directory is given by the fourth passed argument, if present, or, if not, then by `process.env.JOBDIR`.
792
+ ```javaScript
793
+ node dirWatch true 300
794
+ ```
791
795
 
792
- When Testaro finds one or more jobs to do, the `watch` module runs the first job, saves the report in the `raw` subdirectory of the report directory. The report directory is given by `process.env.REPORTDIR`. Testaro also moves the job file from the `todo` subdirectory to the `done` subdirectory of the job directory.
796
+ The arguments and behaviors described above for execution by a module apply here, too.
793
797
 
794
- Since Testaro runs the first job (i.e. the job whose file name is first in ASCII order), whoever populates the `todo` subdirectory of the job directory with job files has control over the order in which Testaro runs them. For example, to force a new job to be run before the already waiting jobs, one can give it a filename that comes before that of the first waiting job.
798
+ The second, shorter method spawns a new watch subprocess after each job performance, to decrease the risk of process corruption involving bogus timeout messages from Playwright during jobs. That method requires you to enter `CTRL-c` to stop the watching.
795
799
 
796
800
  ##### Network watch
797
801
 
798
- Network watching is designed for a situation in which:
799
- - A managing server may be able to give work to multiple workstations that run Testaro.
800
- - A workstation running Testaro can contact a managing server, but the server may not be able to contact a workstation.
802
+ Testaro can poll servers for jobs to be performed.
801
803
 
802
- With network watch, the initiator of an interaction is Testaro, not the server. When Testaro is available, it requests a job from a server. If the response is a JSON representation of a job, Testaro runs the job and sends the report to the server.
804
+ An instance of Testaro is an _agent_ and has an identifier specified by `process.env.AGENT`. A Testaro instance identifies itself when polling servers, allowing servers to decide whether to give the instance a job to do.
803
805
 
804
- If multiple workstations run Testaro and do work for the same server, the server can assign jobs to specific agents by requiring each instance of Testaro to have a distinct value of `process.env.AGENT`.
806
+ The URLs polled by Testaro are specified by `process.env.JOB_URLS`. The format of that environment variable is a `+`-delimited list of URLs, including schemes. If one of the URLs is `https://testrunner.org/a11ytest/api/job`, and if a Testaro instance has the agent ID `tester3`, then a job request is a `GET` request to `https://testrunner.org/a11ytest/api/job?agent=tester3`.
805
807
 
806
- The URL from which Testaro requests jobs is given by the fourth passed argument, if present, or, if not, then by `process.env.JOB_URL`.
808
+ Once a Testaro instance obtains a network job, the report is sent in a `POST` request to the URL specified by the `sources.sendReportTo` property of the job.
807
809
 
808
- The URL to which Testaro sends reports is given by the `sources.sendReportTo` property of each job, if the property exists, or, if not, by `process.env.REPORT_URL`.
810
+ ###### By a module
809
811
 
810
- ##### Job isolation
812
+ ```javaScript
813
+ const {netWatch} = require('./watch');
814
+ netWatch(true, 300);
815
+ ```
816
+
817
+ In this example, a module asks Testaro to check the servers for a job every 300 seconds, to perform any jobs obtained from the servers, and then to continue checking until the process is stopped. If the first argument is `false`, Testaro will stop checking after performing 1 job.
811
818
 
812
- If you execute a repeating watch and the watch process becomes corrupted, the corruption can damage an indefinite subsequent sequence of job performances. Under some conditions, for example, Playwright has been observed to throw errors with a message starting with “Navigation timeout of 30000 ms exceeded” and ending with the entire HTML content of the web page, even when page navigation has been subjected to shorter time limits. Thereafter, Playwright issues that error message again about every 15 seconds, even if the browser has been closed.
819
+ ###### By a user
813
820
 
814
- To counteract such corruption, you can perform repeating directory watches with job isolation. To do this, use the statement `node dirWatch n`, where `n` is the number of seconds Testaro should wait after finding no job before looking again. The `dirWatch` module spawns a new process for each job, and also shrinks Playwright error messages before they are logged on the console.
821
+ ```javaScript
822
+ node call netWatch true 300
823
+ ```
815
824
 
816
- You can stop a repeating directory watch of this kind by entering `CTRL-c`.
825
+ The arguments and behaviors described above for execution by a module apply here, too. If the first argument is `true`, you can terminate the process by entering `CTRL-c`.
817
826
 
818
827
  ### Environment variables
819
828
 
@@ -824,7 +833,6 @@ Before making Testaro run a job, you can optionally also set `process.env.DEBUG`
824
833
  You may store environment variables in an untracked `.env` file if you wish, and Testaro will recognize them. Here is a template for a `.env` file:
825
834
 
826
835
  ```conf
827
- URL_INJECT=yes
828
836
  WAVE_KEY=yourwavekey
829
837
  JOB_URLs=https://yourserver.tld/job+http://localhost:3004/testapp
830
838
  JOBDIR=../testing/jobs/ThisWorkstation
package/call-old.js ADDED
@@ -0,0 +1,86 @@
1
+ /*
2
+ call.js
3
+ Invokes Testaro modules with arguments.
4
+ This is the universal module for use of Testaro from a command line.
5
+ Arguments:
6
+ 0. function to execute.
7
+ 1+. arguments to pass to the function.
8
+ Usage examples:
9
+ node call run ts25
10
+ node call watch true true 30
11
+ */
12
+
13
+ // ########## IMPORTS
14
+
15
+ // Module to keep secrets.
16
+ require('dotenv').config();
17
+ // Module to process files.
18
+ const fs = require('fs/promises');
19
+ // Function to process a testing request.
20
+ const {doJob} = require('./run');
21
+ // Function to watch for jobs.
22
+ const {watch} = require('./watch');
23
+
24
+ // ########## CONSTANTS
25
+
26
+ const fn = process.argv[2];
27
+ const fnArgs = process.argv.slice(3);
28
+ const jobDir = process.env.JOBDIR;
29
+ const todoDir = `${jobDir}/todo`;
30
+ const reportDir = process.env.REPORTDIR;
31
+ const rawDir = `${reportDir}/raw`;
32
+
33
+ // ########## FUNCTIONS
34
+
35
+ // Fulfills a testing request.
36
+ const callRun = async jobIDStart => {
37
+ // Find the job.
38
+ const jobDirFileNames = await fs.readdir(todoDir);
39
+ const jobFileName = jobDirFileNames.find(fileName => fileName.startsWith(jobIDStart));
40
+ // If it exists:
41
+ if (jobFileName) {
42
+ // Get it.
43
+ const jobJSON = await fs.readFile(`${todoDir}/${jobFileName}`, 'utf8');
44
+ const report = JSON.parse(jobJSON);
45
+ // Run it.
46
+ await doJob(report);
47
+ // Archive it.
48
+ await fs.rename(`${todoDir}/${jobFileName}`, `${jobDir}/done/${jobFileName}`);
49
+ // Save the report.
50
+ await fs.writeFile(`${rawDir}/${jobFileName}`, JSON.stringify(report, null, 2));
51
+ console.log(`Job completed and report ${report.id}.json saved in ${rawDir}`);
52
+ }
53
+ // Otherwise, i.e. if the job does not exist.
54
+ else {
55
+ // Report the error.
56
+ console.log(`ERROR: No to-do job ID starts with ${jobIDStart}`);
57
+ }
58
+ };
59
+ // Starts a watch.
60
+ const callWatch = async (isDirWatch, interval) => {
61
+ const whenType = interval > -1 ? 'repeating' : 'one-time';
62
+ const whereType = isDirWatch === 'true' ? 'directory' : 'network';
63
+ console.log(`Starting ${whenType} ${whereType} watch`);
64
+ await watch(isDirWatch === 'true', Number.parseInt(interval, 10));
65
+ };
66
+
67
+ // ########## OPERATION
68
+
69
+ // Execute the requested function.
70
+ if (fn === 'run' && fnArgs.length === 1) {
71
+ callRun(fnArgs)
72
+ .then(() => {
73
+ console.log('Execution completed\n');
74
+ process.exit(0);
75
+ });
76
+ }
77
+ else if (fn === 'watch' && fnArgs.length === 2) {
78
+ callWatch(... fnArgs)
79
+ .then(() => {
80
+ console.log('Execution completed\n');
81
+ process.exit(0);
82
+ });
83
+ }
84
+ else {
85
+ console.log('ERROR: Invalid statement');
86
+ }
package/call.js CHANGED
@@ -1,16 +1,17 @@
1
+
1
2
  /*
2
3
  call.js
3
- Invokes Testaro modules with arguments.
4
+ Invokes Testaro modules.
4
5
  This is the universal module for use of Testaro from a command line.
5
6
  Arguments:
6
7
  0. function to execute.
7
8
  1+. arguments to pass to the function.
8
9
  Usage examples:
9
- node call run ts25
10
- node call watch true true 30
10
+ node call run ts99
11
+ node call dirWatch true 30
11
12
  */
12
13
 
13
- // ########## IMPORTS
14
+ // IMPORTS
14
15
 
15
16
  // Module to keep secrets.
16
17
  require('dotenv').config();
@@ -19,9 +20,9 @@ const fs = require('fs/promises');
19
20
  // Function to process a testing request.
20
21
  const {doJob} = require('./run');
21
22
  // Function to watch for jobs.
22
- const {cycle} = require('./watch');
23
+ const {dirWatch, netWatch} = require('./watch');
23
24
 
24
- // ########## CONSTANTS
25
+ // CONSTANTS
25
26
 
26
27
  const fn = process.argv[2];
27
28
  const fnArgs = process.argv.slice(3);
@@ -30,7 +31,7 @@ const todoDir = `${jobDir}/todo`;
30
31
  const reportDir = process.env.REPORTDIR;
31
32
  const rawDir = `${reportDir}/raw`;
32
33
 
33
- // ########## FUNCTIONS
34
+ // FUNCTIONS
34
35
 
35
36
  // Fulfills a testing request.
36
37
  const callRun = async jobIDStart => {
@@ -56,15 +57,16 @@ const callRun = async jobIDStart => {
56
57
  console.log(`ERROR: No to-do job ID starts with ${jobIDStart}`);
57
58
  }
58
59
  };
59
- // Starts a watch.
60
- const callWatch = async (isDirWatch, isForever, interval, watchee = null) => {
61
- const whenType = isForever === 'true' ? 'repeating' : 'one-time';
62
- const whereType = isDirWatch === 'true' ? 'directory' : 'network';
63
- console.log(`Starting ${whenType} ${whereType} watch`);
64
- await cycle(isDirWatch === 'true', isForever === 'true', Number.parseInt(interval, 10), watchee);
60
+ // Starts a directory watch, converting the interval argument to a number.
61
+ const callDirWatch = async (isForever, interval) => {
62
+ await dirWatch(isForever === 'true', Math.max(5, Number.parseInt(interval, 10)));
63
+ };
64
+ // Starts a network watch, converting the interval argument to a number.
65
+ const callNetWatch = async(isForever, interval) => {
66
+ netWatch(isForever === 'true', Number.parseInt(interval, 10));
65
67
  };
66
68
 
67
- // ########## OPERATION
69
+ // OPERATION
68
70
 
69
71
  // Execute the requested function.
70
72
  if (fn === 'run' && fnArgs.length === 1) {
@@ -74,12 +76,11 @@ if (fn === 'run' && fnArgs.length === 1) {
74
76
  process.exit(0);
75
77
  });
76
78
  }
77
- else if (fn === 'watch' && fnArgs.length === 3) {
78
- callWatch(... fnArgs)
79
- .then(() => {
80
- console.log('Execution completed\n');
81
- process.exit(0);
82
- });
79
+ else if (fn === 'dirWatch' && fnArgs.length === 2) {
80
+ callDirWatch(... fnArgs);
81
+ }
82
+ else if (fn === 'netWatch' && fnArgs.length === 2) {
83
+ callNetWatch(... fnArgs);
83
84
  }
84
85
  else {
85
86
  console.log('ERROR: Invalid statement');
package/dirWatch.js CHANGED
@@ -18,7 +18,7 @@ const interval = process.argv[2];
18
18
  const spawnWatch = (command, args) => spawn(command, args, {stdio: ['inherit', 'inherit', 'pipe']});
19
19
  // Repeatedly spawns a one-time directory watch.
20
20
  const reWatch = () => {
21
- const watcher = spawnWatch('node', ['call', 'watch', 'true', 'false', interval]);
21
+ const watcher = spawnWatch('node', ['call', 'watch', 'false', interval]);
22
22
  let error = '';
23
23
  watcher.stderr.on('data', data => {
24
24
  error += data.toString();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testaro",
3
- "version": "29.1.1",
3
+ "version": "30.0.0",
4
4
  "description": "Run 960 web accessibility tests from 9 tools and get a standardized report",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -11,6 +11,7 @@ const agent = process.env.AGENT;
11
11
 
12
12
  // FUNCTIONS
13
13
 
14
+ // Send a notification to an observer.
14
15
  exports.tellServer = (report, messageParams, logMessage) => {
15
16
  const observer = report.sources.sendReportTo.replace(/report$/, 'granular');
16
17
  const whoParams = `agent=${agent}&jobID=${report.id || ''}`;
package/run.js CHANGED
@@ -464,7 +464,7 @@ const doActs = async (report, actIndex, page) => {
464
464
  }
465
465
  // If granular reporting has been specified:
466
466
  if (report.observe) {
467
- // Notify the server of the act.
467
+ // Notify the observer of the act.
468
468
  const whichParam = act.which ? `&which=${act.which}` : '';
469
469
  const messageParams = `act=${act.type}${whichParam}`;
470
470
  tellServer(report, messageParams, `>>>> ${act.type}: ${actInfo}`);
@@ -744,7 +744,7 @@ const doActs = async (report, actIndex, page) => {
744
744
  // Remove it, except any important property.
745
745
  if (act.result.important) {
746
746
  act.data = act.result.important;
747
- console.log('>>>>>> Important result data protected from deletion');
747
+ console.log(`>>>>>> Important ${act.which} data included in report`);
748
748
  }
749
749
  delete act.result;
750
750
  }
package/test copy.js ADDED
@@ -0,0 +1,38 @@
1
+ /*
2
+ watch.js
3
+ Module for watching for a job and running it when found.
4
+ */
5
+
6
+ // ########## IMPORTS
7
+
8
+ const httpClient = require('http');
9
+
10
+ // ########## FUNCTIONS
11
+
12
+ // Waits.
13
+ const wait = ms => {
14
+ return new Promise(resolve => {
15
+ setTimeout(() => {
16
+ resolve('');
17
+ }, ms);
18
+ });
19
+ };
20
+ // Checks servers for a network job.
21
+ const checkNetJob = () => {
22
+ const request = httpClient.request('http://localhost:3008/testu', response => {
23
+ const chunks = [];
24
+ response.on('data', chunk => {
25
+ chunks.push(chunk);
26
+ })
27
+ // When response arrives:
28
+ .on('end', () => {
29
+ // Report it.
30
+ const content = chunks.join('');
31
+ console.log(content);
32
+ });
33
+ });
34
+ request.end();
35
+ };
36
+ exports.watch = () => {
37
+ checkNetJob();
38
+ }
package/watch-old.js ADDED
@@ -0,0 +1,275 @@
1
+ /*
2
+ watch.js
3
+ Module for watching for a job and running it when found.
4
+ */
5
+
6
+ // ########## IMPORTS
7
+
8
+ // Module to keep secrets local.
9
+ require('dotenv').config();
10
+ // Module to read and write files.
11
+ const fs = require('fs/promises');
12
+ // Module to perform tests.
13
+ const {doJob} = require('./run');
14
+ // HTTP and HTTPS clients.
15
+ const httpClient = require('http');
16
+ const httpsClient = require('https');
17
+
18
+ // ########## CONSTANTS
19
+
20
+ const jobURLs = process.env.JOB_URLs;
21
+ const agent = process.env.AGENT;
22
+ const jobDir = process.env.JOBDIR;
23
+ const reportDir = process.env.REPORTDIR;
24
+ // Get a randomized array of servers to watch from the environment.
25
+ const servers = jobURLs
26
+ .split('+')
27
+ .map(url => [Math.random(), url])
28
+ .sort((a, b) => a[0] - b[0])
29
+ .map(pair => pair[1]);
30
+
31
+ // ########## FUNCTIONS
32
+
33
+ // Returns a string representing the date and time.
34
+ const nowString = () => (new Date()).toISOString().slice(0, 19);
35
+ // Writes a directory report.
36
+ const writeDirReport = async report => {
37
+ const jobID = report && report.id;
38
+ if (jobID) {
39
+ try {
40
+ const reportJSON = JSON.stringify(report, null, 2);
41
+ const reportName = `${jobID}.json`;
42
+ const rawDir = `${reportDir}/raw`;
43
+ await fs.mkdir(rawDir, {recursive: true});
44
+ await fs.writeFile(`${rawDir}/${reportName}`, reportJSON);
45
+ console.log(`Report ${reportName} saved in ${rawDir}`);
46
+ }
47
+ catch(error) {
48
+ console.log(`ERROR: Failed to write report ${jobID} in ${rawDir} (${error.message})`);
49
+ }
50
+ }
51
+ else {
52
+ console.log('ERROR: Job has no ID');
53
+ }
54
+ };
55
+ // Waits.
56
+ const wait = ms => {
57
+ return new Promise(resolve => {
58
+ setTimeout(() => {
59
+ resolve('');
60
+ }, ms);
61
+ });
62
+ };
63
+ // Archives a job.
64
+ const archiveJob = async job => {
65
+ const {id} = job;
66
+ const jobJSON = JSON.stringify(job, null, 2);
67
+ await fs.writeFile(`${jobDir}/done/${id}.json`, jobJSON);
68
+ await fs.rm(`${jobDir}/todo/${id}.json`);
69
+ };
70
+ // Checks for a directory job and, if found, performs and reports it, once or repeatedly.
71
+ const checkDirJob = async (interval) => {
72
+ try {
73
+ // If there are any jobs to do in the watched directory:
74
+ const toDoFileNames = await fs.readdir(`${jobDir}/todo`);
75
+ const jobFileNames = toDoFileNames.filter(fileName => fileName.endsWith('.json'));
76
+ if (jobFileNames.length) {
77
+ // Get the first one.
78
+ const jobJSON = await fs.readFile(`${jobDir}/todo/${jobFileNames[0]}`, 'utf8');
79
+ try {
80
+ const job = JSON.parse(jobJSON, null, 2);
81
+ const {id} = job;
82
+ // Perform it.
83
+ console.log(`Directory job ${id} found (${nowString()})`);
84
+ await doJob(job);
85
+ console.log(`Job ${id} finished (${nowString()})`);
86
+ // Report it.
87
+ await writeDirReport(job);
88
+ // Archive it.
89
+ await archiveJob(job);
90
+ console.log(`Job ${id} archived in ${jobDir} (${nowString()})`);
91
+ // If watching is repetitive:
92
+ if (interval > -1) {
93
+ // Wait for the specified interval.
94
+ await wait(1000 * interval);
95
+ // Check the servers again.
96
+ checkDirJob(interval);
97
+ }
98
+ }
99
+ catch(error) {
100
+ console.log(`ERROR processing directory job (${error.message})`);
101
+ }
102
+ }
103
+ // Otherwise, i.e. if there are no more jobs to do in the watched directory:
104
+ else {
105
+ console.log(`No job to do in ${jobDir} (${nowString()})`);
106
+ // If checking is repetitive:
107
+ if (interval > -1) {
108
+ // Wait for the specified interval.
109
+ await wait(1000 * interval);
110
+ // Check the directory again.
111
+ await checkDirJob(interval);
112
+ }
113
+ }
114
+ }
115
+ catch(error) {
116
+ console.log(`ERROR: Directory watching failed (${error.message})`);
117
+ }
118
+ };
119
+ // Checks servers for a job and, if obtained, performs and reports it, once or repeatedly.
120
+ const checkNetJob = async (serverIndex, interval) => {
121
+ // If any servers remain to be checked:
122
+ if (serverIndex < servers.length) {
123
+ // Request a job from the indexed server.
124
+ const server = servers[serverIndex];
125
+ const logStart = `Asked ${server} for a job and got `;
126
+ const wholeURL = `${server}?agent=${agent}`;
127
+ let client = server.startsWith('https://') ? httpsClient : httpClient;
128
+ console.log(`About to make request to ${wholeURL}`);
129
+ httpClient.request(
130
+ 'http://localhost:8000', response => console.log(JSON.stringify(response, null, 2))
131
+ ).end();
132
+ /*
133
+ const jobRequest = client.request(wholeURL, {timeout: 1000}, response => {
134
+ console.log('Request made');
135
+ console.log(`Status: ${response.statusCode}`);
136
+ const chunks = [];
137
+ response.on('data', chunk => {
138
+ chunks.push(chunk);
139
+ })
140
+ // When the response is completed:
141
+ .on('end', async () => {
142
+ // If the response was JSON-formatted:
143
+ const responseJSON = chunks.join('');
144
+ try {
145
+ const responseObj = JSON.parse(responseJSON);
146
+ // If the server sent a valid job:
147
+ const {id, sources} = responseObj;
148
+ if (id && sources) {
149
+ const {sendReportTo} = sources;
150
+ if (sendReportTo) {
151
+ console.log(`Network job ${id} received from ${server} (${nowString()})`);
152
+ // Perform it.
153
+ await doJob(responseObj);
154
+ console.log(`Job ${id} finished (${nowString()})`);
155
+ // Send the report to the server and report its response.
156
+ console.log(`Sending report to ${sendReportTo}`);
157
+ client = sendReportTo.startsWith('https://') ? httpsClient : httpClient;
158
+ const request = client.request(sendReportTo, {method: 'POST'}, response => {
159
+ const chunks = [];
160
+ response.on('data', chunk => {
161
+ chunks.push(chunk);
162
+ });
163
+ // When the response arrives:
164
+ response.on('end', async () => {
165
+ const content = chunks.join('');
166
+ const logStart = `Sent report to ${sendReportTo} and got `;
167
+ try {
168
+ const ack = JSON.parse(content);
169
+ console.log(`${logStart}${ack}`);
170
+ // Archive the job.
171
+ await archiveJob(responseObj);
172
+ console.log(`Job ${id} archived (${nowString()})`);
173
+ // If watching is repetitive:
174
+ if (interval > -1) {
175
+ // Wait for the specified interval.
176
+ await wait(1000 * interval);
177
+ // Check the next server.
178
+ await checkNetJob(serverIndex + 1, interval);
179
+ }
180
+ }
181
+ catch(error) {
182
+ console.log(
183
+ `ERROR: ${logStart}status ${response.statusCode}, error message ${error.message}, and body ${content.slice(0,1000)}`
184
+ );
185
+ }
186
+ });
187
+ })
188
+ .on('error', error => {
189
+ console.log(`ERROR submitting job report (${error.message})`);
190
+ });
191
+ report.jobData.agent = agent;
192
+ const reportJSON = JSON.stringify(report, null, 2);
193
+ request.end(reportJSON);
194
+ }
195
+ // Otherwise, if the server sent a job without a report destination:
196
+ else {
197
+ // Report this.
198
+ console.log(`ERROR: ${logStart}a job with no report destination`);
199
+ }
200
+ }
201
+ // Otherwise, if the server sent a message instead of a job:
202
+ else if (responseObj.message) {
203
+ // Report it.
204
+ console.log(`${logStart}${responseObj.message}`);
205
+ }
206
+ // Otherwise, if the server sent any other JSON response:
207
+ else {
208
+ // Report it.
209
+ console.log(`${logStart} ${JSON.stringify(responseObj, null, 2)}`);
210
+ // Check the next server.
211
+ await checkNetJob(serverIndex + 1, interval);
212
+ }
213
+ }
214
+ catch(error) {
215
+ // Report any error.
216
+ console.log(
217
+ `${logStart}status ${response.statusCode} and reply ${responseJSON.slice(0, 1000)}`
218
+ );
219
+ // Check the next server.
220
+ await checkNetJob(serverIndex + 1, interval);
221
+ }
222
+ })
223
+ // If the response throws an error:
224
+ .on('error', async error => {
225
+ // Report it.
226
+ console.log(
227
+ `${logStart}status code ${response.statusCode} and error message ${error.message}`
228
+ );
229
+ // Check the next server.
230
+ await checkNetJob(serverIndex + 1, interval);
231
+ });
232
+ })
233
+ // If the request throws an error:
234
+ .on('error', async error => {
235
+ // Report it.
236
+ console.log(`${logStart} error ${error.message}`);
237
+ // Check the next server.
238
+ await checkNetJob(serverIndex + 1, interval);
239
+ })
240
+ // If the request times out:
241
+ .on('timeout', async () => {
242
+ // Report this.
243
+ console.log(`${logStart} a timeout`);
244
+ // Check the next server.
245
+ await checkNetJob(serverIndex + 1, interval);
246
+ });
247
+ // Close the request.
248
+ jobRequest.end();
249
+ console.log(`Request to ${server} ended`);
250
+ }
251
+ // Otherwise, i.e. if no servers remain to be checked:
252
+ else {
253
+ // If checking is repetitive:
254
+ if (interval > -1) {
255
+ // Wait for the specified interval.
256
+ await wait(1000 * interval);
257
+ // Check the servers again.
258
+ await checkNetJob(0, interval);
259
+ }
260
+ */
261
+ }
262
+ };
263
+ // Checks for a job, performs it, and submits a report, once or repeatedly.
264
+ exports.watch = async (isDirWatch, interval = 300) => {
265
+ const intervalSpec = interval > -1 ? `repeatedly, with ${interval}-second intervals ` : '';
266
+ console.log(`Watching started ${intervalSpec}(${nowString()})\n`);
267
+ // Start the checking.
268
+ if (isDirWatch) {
269
+ await checkDirJob(interval);
270
+ }
271
+ else {
272
+ await checkNetJob(0, interval);
273
+ }
274
+ console.log(`Watching ended (${nowString()})`);
275
+ };
package/watch-temp.js ADDED
@@ -0,0 +1,45 @@
1
+ /*
2
+ watch.js
3
+ Module for watching for a job and running it when found.
4
+ */
5
+
6
+ // ########## IMPORTS
7
+
8
+ const httpClient = require('http');
9
+
10
+ // ########## FUNCTIONS
11
+
12
+ // Returns a string representing the date and time.
13
+ const nowString = () => (new Date()).toISOString().slice(0, 19);
14
+ // Checks servers for a network job.
15
+ const checkNetJob = (serverIndex, interval) => {
16
+ const request = httpClient.request('http://localhost:3008/testu', response => {
17
+ console.log('Response fn being defined');
18
+ const chunks = [];
19
+ response.on('data', chunk => {
20
+ chunks.push(chunk);
21
+ })
22
+ // When response arrives:
23
+ .on('end', () => {
24
+ // Report it.
25
+ const content = chunks.join('');
26
+ console.log(content);
27
+ console.log(`Watching ended (${nowString()})`);
28
+ });
29
+ });
30
+ console.log('About to close request');
31
+ request.end();
32
+ console.log('Closed request');
33
+ };
34
+ // Checks for a job, performs it, and submits a report, once or repeatedly.
35
+ exports.watch = async (isDirWatch, interval = 300) => {
36
+ const intervalSpec = interval > -1 ? `repeatedly, with ${interval}-second intervals ` : '';
37
+ console.log(`Watching started ${intervalSpec}(${nowString()})\n`);
38
+ // Start the checking.
39
+ if (isDirWatch) {
40
+ await checkDirJob(interval);
41
+ }
42
+ else {
43
+ checkNetJob(0, interval);
44
+ }
45
+ };
package/watch.js CHANGED
@@ -5,146 +5,39 @@
5
5
 
6
6
  // ########## IMPORTS
7
7
 
8
- // Module to keep secrets local.
8
+ // Module to keep secrets.
9
9
  require('dotenv').config();
10
10
  // Module to read and write files.
11
11
  const fs = require('fs/promises');
12
- // Module to perform tests.
12
+ // Module to make requests to servers.
13
+ const httpClient = require('http');
14
+ const httpsClient = require('https');
15
+ // Module to perform jobs.
13
16
  const {doJob} = require('./run');
14
- // HTTP and HTTPS clients.
15
- const http = require('http');
16
- const https = require('https');
17
17
 
18
- // ########## CONSTANTS
18
+ // CONSTANTS
19
19
 
20
- const httpClient = require('http');
21
- const httpsClient = require('https');
22
- const jobURLs = process.env.JOB_URLs;
23
- const agent = process.env.AGENT;
24
20
  const jobDir = process.env.JOBDIR;
21
+ const jobURLs = process.env.JOB_URLS;
25
22
  const reportDir = process.env.REPORTDIR;
23
+ const agent = process.env.AGENT;
26
24
 
27
25
  // ########## FUNCTIONS
28
26
 
29
27
  // Returns a string representing the date and time.
30
28
  const nowString = () => (new Date()).toISOString().slice(0, 19);
31
- // Checks for a directory job.
32
- const checkDirJob = async watchee => {
33
- try {
34
- const watchJobDir = watchee || jobDir;
35
- const toDoDirFileNames = await fs.readdir(`${watchJobDir}/todo`);
36
- const jobFileNames = toDoDirFileNames.filter(fileName => fileName.endsWith('.json'));
37
- if (jobFileNames.length) {
38
- const jobJSON = await fs.readFile(`${watchJobDir}/todo/${jobFileNames[0]}`, 'utf8');
39
- try {
40
- const job = JSON.parse(jobJSON, null, 2);
41
- return job;
42
- }
43
- catch(error) {
44
- return {
45
- error: `ERROR parsing job as JSON (${error.message})`,
46
- message: error.message
47
- };
48
- }
49
- }
50
- else {
51
- console.log(`No job to do (${nowString()})`);
52
- return {};
53
- }
54
- }
55
- catch(error) {
56
- console.log('ERROR: Directory watching failed');
57
- return {};
58
- }
29
+ // Waits.
30
+ const wait = ms => {
31
+ return new Promise(resolve => {
32
+ setTimeout(() => {
33
+ resolve('');
34
+ }, ms);
35
+ });
59
36
  };
60
- // Checks for and, if obtained, returns a network job.
61
- const checkNetJob = async watchee => {
62
- let watchJobURLs = [watchee];
63
- if (! watchJobURLs[0]) {
64
- watchJobURLs = jobURLs
65
- .split('+')
66
- .map(url => [Math.random(), url])
67
- .sort((a, b) => a[0] - b[0])
68
- .map(pair => pair[1]);
69
- }
70
- // For each watchee:
71
- for (const watchJobURL of watchJobURLs) {
72
- const job = await new Promise(resolve => {
73
- // Request a job from it.
74
- const wholeURL = `${watchJobURL}?agent=${agent}`;
75
- const client = wholeURL.startsWith('https://') ? httpsClient : httpClient;
76
- const request = client.request(wholeURL, {timeout: 1000}, response => {
77
- const chunks = [];
78
- response.on('data', chunk => {
79
- chunks.push(chunk);
80
- })
81
- // When response arrives:
82
- .on('end', () => {
83
- // If the response was JSON-formatted:
84
- try {
85
- const jobJSON = chunks.join('');
86
- const job = JSON.parse(jobJSON);
87
- // Make it the response of the watchee.
88
- return resolve(job);
89
- }
90
- // Otherwise, i.e. if the response was not JSON-formatted:
91
- catch(error) {
92
- // Make an error report the response of the watchee.
93
- const errorMessage = `ERROR: Response of ${watchJobURL} was not JSON`;
94
- console.log(errorMessage);
95
- return resolve({
96
- error: errorMessage,
97
- message: error.message,
98
- status: response.statusCode
99
- });
100
- }
101
- })
102
- .on('error', error => {
103
- return resolve({
104
- error: 'ERROR getting network job',
105
- message: error.message,
106
- status: response.statusCode
107
- });
108
- });
109
- });
110
- // If the check threw an error:
111
- request.on('error', error => {
112
- // Make an error report the response of the watchee.
113
- const errorMessage = `ERROR checking ${watchJobURL} for a network job`;
114
- console.log(`${errorMessage} (${error.message})`);
115
- return resolve({
116
- error: errorMessage,
117
- message: error.message
118
- });
119
- })
120
- .on('timeout', () => {
121
- const errorMessage = `ERROR: Request to ${watchJobURL} timed out`;
122
- console.log(errorMessage);
123
- return resolve({
124
- error: errorMessage
125
- });
126
- });
127
- request.end();
128
- });
129
- // If the watchee sent a job:
130
- if (job.id) {
131
- // Stop checking and return it.
132
- console.log(`Network job ${job.id} received from ${watchJobURL} (${nowString()})`);
133
- return job;
134
- }
135
- // Otherwise, if the watchee sent a message:
136
- else if (job.message) {
137
- // Report it and continue checking.
138
- console.log(job.message);
139
- }
140
- // Otherwise, i.e. if the watchee sent neither a job nor a message:
141
- else {
142
- // Report this and continue checking.
143
- console.log(`No network job at ${watchJobURL} to do (${nowString()})`);
144
- }
145
- }
146
- // If no watchee sent a job, return this.
147
- return {};
37
+ // Serves an object in JSON format.
38
+ const serveObject = (object, response) => {
39
+ response.setHeader('Content-Type', 'application/json; charset=utf-8');
40
+ response.end(JSON.stringify(object));
148
41
  };
149
42
  // Writes a directory report.
150
43
  const writeDirReport = async report => {
@@ -153,166 +46,302 @@ const writeDirReport = async report => {
153
46
  try {
154
47
  const reportJSON = JSON.stringify(report, null, 2);
155
48
  const reportName = `${jobID}.json`;
156
- const rawDir = `${reportDir}/raw`;
157
- await fs.writeFile(`${rawDir}/${reportName}`, reportJSON);
158
- console.log(`Report ${reportName} saved in ${rawDir}`);
49
+ await fs.mkdir(reportDir, {recursive: true});
50
+ await fs.writeFile(`${reportDir}/${reportName}`, reportJSON);
51
+ console.log(`Report ${reportName} saved in ${reportDir}`);
159
52
  }
160
53
  catch(error) {
161
- console.log(`ERROR: Failed to write report ${jobID} (${error.message})`);
54
+ console.log(`ERROR: Failed to write report ${jobID} in ${reportDir} (${error.message})`);
162
55
  }
163
56
  }
164
57
  else {
165
58
  console.log('ERROR: Job has no ID');
166
59
  }
167
60
  };
168
- // Submits a network report to a server and returns the server response.
169
- const writeNetReport = async report => {
170
- const ack = await new Promise(resolve => {
171
- if (report.sources) {
172
- // If the report specifies where to send it:
173
- const destination = report.sources.sendReportTo;
174
- if (destination) {
175
- // Send it.
176
- const client = destination.startsWith('https://') ? https : http;
177
- const request = client.request(destination, {method: 'POST'}, response => {
178
- const chunks = [];
179
- response.on('data', chunk => {
180
- chunks.push(chunk);
181
- });
182
- response.on('end', () => {
183
- const content = chunks.join('');
184
- try {
185
- resolve(JSON.parse(content));
186
- }
187
- catch(error) {
188
- resolve({
189
- error: 'ERROR: Response was not JSON',
190
- message: error.message,
191
- status: response.statusCode,
192
- content: content.slice(0, 3000)
193
- });
194
- }
195
- });
196
- });
197
- report.jobData.agent = agent;
198
- request.on('error', error => {
199
- console.log(`ERROR submitting job report (${error.message})`);
200
- resolve({
201
- error: 'ERROR: Job report submission failed',
202
- message: error.message
203
- });
204
- });
205
- const reportJSON = JSON.stringify(report, null, 2);
206
- request.end(reportJSON);
207
- console.log(`Report ${report.id} submitted (${nowString()})`);
61
+ // Archives a job.
62
+ const archiveJob = async job => {
63
+ const {id} = job;
64
+ const jobJSON = JSON.stringify(job, null, 2);
65
+ await fs.writeFile(`${jobDir}/done/${id}.json`, jobJSON);
66
+ await fs.rm(`${jobDir}/todo/${id}.json`);
67
+ };
68
+ // Checks for a directory job and, if found, performs and reports it, once or repeatedly.
69
+ const checkDirJob = async (isForever, interval) => {
70
+ try {
71
+ // If there are any jobs to do in the watched directory:
72
+ const toDoFileNames = await fs.readdir(`${jobDir}/todo`);
73
+ const jobFileNames = toDoFileNames.filter(fileName => fileName.endsWith('.json'));
74
+ if (jobFileNames.length) {
75
+ // Get the first one.
76
+ const jobJSON = await fs.readFile(`${jobDir}/todo/${jobFileNames[0]}`, 'utf8');
77
+ try {
78
+ const job = JSON.parse(jobJSON, null, 2);
79
+ const {id} = job;
80
+ // Perform it.
81
+ console.log(`Directory job ${id} found (${nowString()})`);
82
+ await doJob(job);
83
+ console.log(`Job ${id} finished (${nowString()})`);
84
+ // Report it.
85
+ await writeDirReport(job);
86
+ // Archive it.
87
+ await archiveJob(job);
88
+ console.log(`Job ${id} archived in ${jobDir} (${nowString()})`);
89
+ // If watching is repetitive:
90
+ if (isForever) {
91
+ // Wait 2 seconds.
92
+ await wait(2000);
93
+ // Check the directory again.
94
+ checkDirJob(true, interval);
95
+ }
208
96
  }
209
- // Otherwise, i.e. if the report does not specify where to send it:
210
- else {
211
- // Report this.
212
- console.log('ERROR: Report specifies no submission destination');
97
+ catch(error) {
98
+ console.log(`ERROR processing directory job (${error.message})`);
213
99
  }
214
100
  }
101
+ // Otherwise, i.e. if there are no more jobs to do in the watched directory:
215
102
  else {
216
- console.log('ERROR: Report has no sources property');
103
+ console.log(`No job to do in ${jobDir} (${nowString()})`);
104
+ // Wait for the specified interval.
105
+ await wait(1000 * interval);
106
+ // Check the directory again.
107
+ await checkDirJob(true, interval);
217
108
  }
218
- });
219
- // Return the server response.
220
- if (ack) {
221
- return ack.message || ack;
222
109
  }
223
- else {
224
- return '';
110
+ catch(error) {
111
+ console.log(`ERROR: Directory watching failed (${error.message})`);
225
112
  }
226
113
  };
227
- // Archives a job.
228
- const archiveJob = async (job, watchee) => {
229
- const jobJSON = JSON.stringify(job, null, 2);
230
- const watchJobDir = watchee || jobDir;
231
- await fs.writeFile(`${watchJobDir}/done/${job.id}.json`, jobJSON);
232
- await fs.rm(`${watchJobDir}/todo/${job.id}.json`);
233
- };
234
- // Waits.
235
- const wait = ms => {
236
- return new Promise(resolve => {
237
- setTimeout(() => {
238
- resolve('');
239
- }, ms);
240
- });
241
- };
242
- // Runs a job.
243
- const runJob = async (job, isDirWatch) => {
244
- // If the job has an ID:
245
- const {id} = job;
246
- if (id) {
247
- try {
248
- // Run the job, adding to the report.
249
- await doJob(job);
250
- // If a directory was watched:
251
- if (isDirWatch) {
252
- // Save the report.
253
- await writeDirReport(job);
114
+ // Checks servers for a network job.
115
+ const checkNetJob = async (servers, serverIndex, isForever, interval, noJobCount) => {
116
+ console.log('');
117
+ // If all servers are jobless:
118
+ if (noJobCount === servers.length) {
119
+ // Wait for the specified interval.
120
+ await wait(1000 * interval);
121
+ // Reset the count of jobless servers.
122
+ noJobCount = 0;
123
+ }
124
+ // Otherwise, i.e. if any server may still have a job:
125
+ else {
126
+ // Wait 2 seconds.
127
+ await wait(2000);
128
+ }
129
+ // Check the next server.
130
+ serverIndex = serverIndex % servers.length;
131
+ const server = servers[serverIndex];
132
+ const client = server.startsWith('https://') ? httpsClient : httpClient;
133
+ const fullURL = `${server}?agent=${agent}`;
134
+ const logStart = `Requested job from server ${server} and got `;
135
+ client.request(fullURL, response => {
136
+ const chunks = [];
137
+ response
138
+ // If the response to the job request threw an error:
139
+ .on('error', async error => {
140
+ // Report it.
141
+ console.log(`${logStart}error message ${error.message}`);
142
+ // Check the next server.
143
+ await checkNetJob(servers, serverIndex + 1, isForever, interval, noJobCount + 1);
144
+ })
145
+ .on('data', chunk => {
146
+ chunks.push(chunk);
147
+ })
148
+ // When the response arrives:
149
+ .on('end', async () => {
150
+ const content = chunks.join('');
151
+ // If there was no job to do:
152
+ try {
153
+ const contentObj = JSON.parse(content);
154
+ if (! Object.keys(contentObj).length) {
155
+ // Report this.
156
+ console.log(`No job to do at ${server}`);
157
+ // Check the next server.
158
+ await checkNetJob(servers, serverIndex + 1, isForever, interval, noJobCount + 1);
159
+ }
160
+ // Otherwise, i.e. if there was a job or a message:
161
+ else {
162
+ const {message, id, sources} = contentObj;
163
+ // If the server sent a message, not a job:
164
+ if (message) {
165
+ // Report it.
166
+ console.log(`${logStart}${message}`);
167
+ // Check the next server.
168
+ await checkNetJob(servers, serverIndex + 1, isForever, interval, noJobCount + 1);
169
+ }
170
+ // Otherwise, if the server sent a valid job:
171
+ else if (id && sources) {
172
+ // Add the agent to it.
173
+ sources.agent = agent;
174
+ // If the job specifies a report destination:
175
+ const {sendReportTo} = sources;
176
+ if (sendReportTo) {
177
+ // Perform the job, adding result data to it.
178
+ console.log(`${logStart}job ${id} for ${sendReportTo} (${nowString()})`);
179
+ await doJob(contentObj);
180
+ const reportJSON = JSON.stringify(contentObj, null, 2);
181
+ console.log(`Job ${id} finished (${nowString()})`);
182
+ // Send the report to the specified server.
183
+ console.log(`Sending report ${id} to ${sendReportTo}`);
184
+ const reportClient = sendReportTo.startsWith('https://') ? httpsClient : httpClient;
185
+ const reportLogStart = `Sent report ${id} to ${sendReportTo} and got `;
186
+ reportClient.request(sendReportTo, {method: 'POST'}, repResponse => {
187
+ const chunks = [];
188
+ repResponse
189
+ // If the response to the report threw an error:
190
+ .on('error', async error => {
191
+ // Report this.
192
+ console.log(`${reportLogStart}error message ${error.message}`);
193
+ // Check the next server.
194
+ await checkNetJob(servers, serverIndex + 1, isForever, interval, noJobCount + 1);
195
+ })
196
+ .on('data', chunk => {
197
+ chunks.push(chunk);
198
+ })
199
+ // When the response arrives:
200
+ .on('end', async () => {
201
+ const content = chunks.join('');
202
+ try {
203
+ // If the server sent a message, as expected:
204
+ const ackObj = JSON.parse(content);
205
+ const {message} = ackObj;
206
+ if (message) {
207
+ // Report it.
208
+ console.log(`${reportLogStart}${message}`);
209
+ // Archive the job.
210
+ await archiveJob(contentObj);
211
+ console.log(`Job ${id} archived (${nowString()})`);
212
+ // Check the next server.
213
+ await checkNetJob(servers, serverIndex + 1, isForever, interval, 0);
214
+ }
215
+ // Otherwise, i.e. if the server sent anything else:
216
+ else {
217
+ // Report it.
218
+ console.log(
219
+ `ERROR: ${reportLogStart}status ${repResponse.statusCode} and error message ${JSON.stringify(ackObj, null, 2)}`
220
+ );
221
+ // Check the next server, disregarding the failed job.
222
+ await checkNetJob(
223
+ servers, serverIndex + 1, isForever, interval, noJobCount + 1
224
+ );
225
+ }
226
+ }
227
+ // If processing the report threw an error:
228
+ catch(error) {
229
+ // Report it.
230
+ console.log(
231
+ `ERROR: ${reportLogStart}status ${repResponse.statusCode} and response ${content.slice(0, 1000)}`
232
+ );
233
+ // Check the next server, disregarding the failed job.
234
+ await checkNetJob(
235
+ servers, serverIndex + 1, isForever, interval, noJobCount + 1
236
+ );
237
+ }
238
+ });
239
+ })
240
+ // If the report submission throws an error:
241
+ .on('error', async error => {
242
+ // Report this.
243
+ console.log(`ERROR: ${reportLogStart}error message ${error.message}`);
244
+ // Check the next server, disregarding the failed job.
245
+ await checkNetJob(servers, serverIndex + 1, isForever, interval, noJobCount + 1);
246
+ })
247
+ // Finish submitting the report.
248
+ .end(reportJSON);
249
+ }
250
+ // Otherwise, i.e. if the job specifies no report destination:
251
+ else {
252
+ // Report this.
253
+ const message = `ERROR: ${logStart}job with no report destination`;
254
+ serveObject({message}, response);
255
+ console.log(message);
256
+ // Check the next server, disregarding the defective job.
257
+ await checkNetJob(servers, serverIndex + 1, isForever, interval, noJobCount + 1);
258
+ }
259
+ }
260
+ // Otherwise, if the server sent an invalid job:
261
+ else {
262
+ // Report this.
263
+ const message
264
+ = `ERROR: ${logStart}invalid job:\n${JSON.stringify(contentObj, null, 2)}`;
265
+ console.log(message);
266
+ serveObject({message}, response);
267
+ // Check the next server, disregarding the defective job.
268
+ await checkNetJob(servers, serverIndex + 1, isForever, interval, noJobCount + 1);
269
+ }
270
+ }
254
271
  }
255
- // Otherwise, i.e. if the network was watched:
256
- else {
257
- // Send the report to the server and report its response.
258
- const ack = await writeNetReport(job);
259
- console.log(ack);
272
+ // If the response to the job request threw an error:
273
+ catch(error) {
274
+ // Report this.
275
+ console.log(
276
+ `ERROR: ${logStart}status ${response.statusCode}, error message ${error.message}, and response ${content.slice(0, 1000)}`
277
+ );
278
+ // Check the next server.
279
+ await checkNetJob(servers, serverIndex + 1, isForever, interval, noJobCount + 1);
260
280
  }
281
+ });
282
+ })
283
+ // If the job request throws an error:
284
+ .on('error', async error => {
285
+ // If it was a refusal to connect:
286
+ const {message} = error;
287
+ if (message.includes('ECONNREFUSED')) {
288
+ // Report this.
289
+ console.log(`${logStart}no connection`);
290
+ // Check the next server.
291
+ await checkNetJob(servers, serverIndex + 1, isForever, interval, noJobCount + 1);
261
292
  }
262
- // If the job failed:
263
- catch(error) {
293
+ // Otherwise, if it was a DNS failure:
294
+ else if (message.includes('ENOTFOUND')) {
295
+ // Report this.
296
+ console.log(`${logStart}no domain name resolution`);
297
+ // Check the next server.
298
+ await checkNetJob(servers, serverIndex + 1, isForever, interval, noJobCount + 1);
299
+ }
300
+ // Otherwise, i.e. if it was any other error:
301
+ else {
264
302
  // Report this.
265
- console.log(`ERROR: ${error.message}\n${error.stack}`);
303
+ console.log(`ERROR: ${logStart}no response, but got error message ${error.message}`);
304
+ // Check the next server.
305
+ await checkNetJob(servers, serverIndex + 1, isForever, interval, noJobCount + 1);
266
306
  }
307
+ })
308
+ // Finish sending the request.
309
+ .end();
310
+ };
311
+ // Composes an interval description.
312
+ const intervalSpec = interval => {
313
+ if (interval > -1) {
314
+ return `repeatedly, with ${interval}-second intervals `;
267
315
  }
268
- // Otherwise, i.e. if the job has no ID:
269
316
  else {
270
- // Report this.
271
- console.log('ERROR: Job has no id');
317
+ return '';
272
318
  }
273
319
  };
274
- // Checks for a job, performs it, and submits a report, once or repeatedly.
275
- exports.cycle = async (isDirWatch, isForever, interval = 300, watchee = null) => {
276
- let statusOK = true;
277
- // Prevent a wait before the first iteration.
278
- let empty = false;
279
- const intervalMS = 1000 * Number.parseInt(interval);
280
- const intervalSpec = isForever ? `with intervals of ${interval} seconds when idle ` : '';
281
- console.log(`Watching started ${intervalSpec}(${nowString()})\n`);
282
- while (statusOK) {
283
- if (empty) {
284
- await wait(intervalMS);
285
- }
286
- // Check for a job.
287
- let job;
288
- if (isDirWatch) {
289
- job = await checkDirJob(watchee);
290
- }
291
- else {
292
- job = await checkNetJob(watchee);
293
- }
294
- // If there was one:
295
- if (job.id) {
296
- // Run it, save a report, and if applicable send the report to the job source.
297
- console.log(`Running job ${job.id} (${nowString()})`);
298
- await runJob(JSON.parse(JSON.stringify(job)), isDirWatch);
299
- console.log(`Job ${job.id} finished (${nowString()})`);
300
- // If a directory was watched:
301
- if (isDirWatch) {
302
- // Archive the job.
303
- await archiveJob(job, watchee);
304
- console.log(`Job ${job.id} archived (${nowString()})`);
305
- }
306
- // If watching was specified for only 1 job, quit.
307
- statusOK = isForever;
308
- // Prevent a wait before the next iteration.
309
- empty = false;
310
- }
311
- // Otherwise, i.e. if no job was found:
312
- else {
313
- // Cause a wait before the next check.
314
- empty = true;
315
- }
320
+ // Checks for a directory job, performs it, and submits a report, once or repeatedly.
321
+ exports.dirWatch = async (isForever, interval = 300) => {
322
+ console.log(`Directory watching started ${intervalSpec(interval)}(${nowString()})\n`);
323
+ // Start the checking.
324
+ await checkDirJob(isForever, interval);
325
+ };
326
+ // Checks for a network job, performs it, and submits a report, once or repeatedly.
327
+ exports.netWatch = async (isForever, interval = 300) => {
328
+ // If the servers to be checked are valid:
329
+ const servers = jobURLs
330
+ .split('+')
331
+ .map(url => [Math.random(), url])
332
+ .sort((a, b) => a[0] - b[0])
333
+ .map(pair => pair[1]);
334
+ if (
335
+ servers
336
+ && servers.length
337
+ && servers
338
+ .every(server => ['http://', 'https://'].some(prefix => server.startsWith(prefix)))
339
+ ) {
340
+ console.log(`Network watching started ${intervalSpec(interval)}(${nowString()})\n`);
341
+ // Start checking.
342
+ await checkNetJob(servers, 0, isForever, interval, 0);
343
+ }
344
+ else {
345
+ console.log('ERROR: List of job URLs invalid');
316
346
  }
317
- console.log(`Watching ended (${nowString()})`);
318
347
  };