testem 3.19.0 → 3.20.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.
@@ -1,9 +1,9 @@
1
1
  const execa = require('execa').execa;
2
- const log = require('npmlog');
3
- const Bluebird = require('bluebird');
2
+ const log = require('../log');
4
3
  const EventEmitter = require('events').EventEmitter;
5
4
 
6
5
  const isWin = require('./is-win')();
6
+ const closeTimeoutAfterExit = 1000;
7
7
 
8
8
  module.exports = class Process extends EventEmitter {
9
9
  constructor(name, options, process) {
@@ -14,6 +14,9 @@ module.exports = class Process extends EventEmitter {
14
14
  this.process = process;
15
15
  this.stdout = '';
16
16
  this.stderr = '';
17
+ this.exitAt = null;
18
+ this._killResolve = null;
19
+ this._closeFallbackTimer = null;
17
20
  this.process.stdout.on('data', chunk => {
18
21
  this.stdout += chunk;
19
22
  this.emit('out');
@@ -26,6 +29,11 @@ module.exports = class Process extends EventEmitter {
26
29
  });
27
30
 
28
31
  this.process.on('close', this.onClose.bind(this));
32
+ this.process.on('exit', code => {
33
+ this.exitAt = Date.now();
34
+ this.exitCode = code;
35
+ this.scheduleCloseFallback();
36
+ });
29
37
  this.process.on('error', this.onError.bind(this));
30
38
 
31
39
  this.process.then(result => {
@@ -38,9 +46,35 @@ module.exports = class Process extends EventEmitter {
38
46
  });
39
47
  }
40
48
 
49
+ scheduleCloseFallback() {
50
+ if (!this._killResolve || !this.exitAt || this._closeFallbackTimer || !this._killTimer) {
51
+ return;
52
+ }
53
+
54
+ this._closeFallbackTimer = setTimeout(() => {
55
+ this._closeFallbackTimer = null;
56
+
57
+ if (!this._killResolve || !this.exitAt) {
58
+ return;
59
+ }
60
+
61
+ if (this._killTimer) {
62
+ clearTimeout(this._killTimer);
63
+ this._killTimer = null;
64
+ }
65
+
66
+ this.process = null;
67
+
68
+ const resolve = this._killResolve;
69
+ this._killResolve = null;
70
+ resolve(this.exitCode);
71
+ }, closeTimeoutAfterExit);
72
+ }
73
+
41
74
  onKillTimeout() {
42
75
  log.warn('Process ' + this.name + ' not terminated in ' + this.killTimeout + 'ms.');
43
76
  kill(this.process, 'SIGKILL');
77
+ this.scheduleCloseFallback();
44
78
  }
45
79
 
46
80
  onClose(code) {
@@ -95,15 +129,21 @@ module.exports = class Process extends EventEmitter {
95
129
  if (!this.process) {
96
130
  log.info('Process ' + this.name + ' already killed.');
97
131
 
98
- return Bluebird.resolve(this.exitCode);
132
+ return Promise.resolve(this.exitCode);
99
133
  }
100
134
 
101
135
  sig = sig || 'SIGTERM';
102
136
 
103
137
  let self = this;
104
138
 
105
- return new Bluebird.Promise(resolve => {
139
+ return new Promise(resolve => {
140
+ self._killResolve = resolve;
106
141
  self.process.once('close', (code, sig) => {
142
+ if (self._closeFallbackTimer) {
143
+ clearTimeout(self._closeFallbackTimer);
144
+ self._closeFallbackTimer = null;
145
+ }
146
+ self._killResolve = null;
107
147
  self.process = null;
108
148
  if (self._killTimer) {
109
149
  clearTimeout(self._killTimer);
@@ -123,6 +163,7 @@ module.exports = class Process extends EventEmitter {
123
163
  // See https://github.com/testem/testem/pull/1335 for more information.
124
164
  if (self.process) {
125
165
  kill(self.process, sig);
166
+ self.scheduleCloseFallback();
126
167
  } else {
127
168
  log.info('Process ' + this.name + ' already killed.');
128
169
  }
@@ -0,0 +1,149 @@
1
+ function fromCallback(fn) {
2
+ return new Promise((resolve, reject) => {
3
+ fn((err, result) => {
4
+ if (err) {
5
+ reject(err);
6
+ } else {
7
+ resolve(result);
8
+ }
9
+ });
10
+ });
11
+ }
12
+
13
+ async function filter(arr, predicate) {
14
+ const results = await Promise.all(arr.map(item => predicate(item)));
15
+ return arr.filter((_, i) => results[i]);
16
+ }
17
+
18
+ function reduce(arr, reducer, initialValue) {
19
+ return arr.reduce(
20
+ (acc, item) => acc.then(a => reducer(a, item)),
21
+ Promise.resolve(initialValue)
22
+ );
23
+ }
24
+
25
+ function each(arr, fn) {
26
+ return arr.reduce(
27
+ (p, item) => p.then(() => fn(item)),
28
+ Promise.resolve()
29
+ ).then(() => arr);
30
+ }
31
+
32
+ class Disposer {
33
+ constructor(promise, cleanup) {
34
+ this.promise = Promise.resolve(promise);
35
+ this.cleanup = cleanup;
36
+ }
37
+ }
38
+
39
+ function disposer(promise, cleanup) {
40
+ return new Disposer(promise, cleanup);
41
+ }
42
+
43
+ async function using(resource, fn) {
44
+ const isDisposer = resource instanceof Disposer;
45
+ let value;
46
+
47
+ if (isDisposer) {
48
+ try {
49
+ value = await resource.promise;
50
+ } catch (setupErr) {
51
+ const inspection = {
52
+ isRejected: () => true,
53
+ reason: () => setupErr,
54
+ };
55
+ try {
56
+ await resource.cleanup(undefined, inspection);
57
+ } catch {
58
+ // Prefer the setup error below
59
+ }
60
+ throw setupErr;
61
+ }
62
+ } else {
63
+ value = await resource;
64
+ }
65
+
66
+ let result, rejected = false, error;
67
+
68
+ try {
69
+ result = await fn(value);
70
+ } catch (err) {
71
+ rejected = true;
72
+ error = err;
73
+ }
74
+
75
+ if (isDisposer) {
76
+ const inspection = {
77
+ isRejected: () => rejected,
78
+ reason: () => error,
79
+ };
80
+
81
+ try {
82
+ await resource.cleanup(value, inspection);
83
+ } catch (cleanupErr) {
84
+ if (!rejected) {
85
+ // Callback succeeded but cleanup failed — surface cleanup error
86
+ throw cleanupErr;
87
+ }
88
+ // Both callback and cleanup failed — suppress cleanup error, re-throw original below
89
+ }
90
+ }
91
+
92
+ if (rejected) {
93
+ throw error;
94
+ }
95
+ return result;
96
+ }
97
+
98
+ function mapLimit(arr, concurrency, fn) {
99
+ if (!isFinite(concurrency) || concurrency >= arr.length) {
100
+ return Promise.all(arr.map(fn));
101
+ }
102
+
103
+ let index = 0;
104
+ let results = new Array(arr.length);
105
+
106
+ function next() {
107
+ let i = index++;
108
+ if (i >= arr.length) {
109
+ return Promise.resolve();
110
+ }
111
+
112
+ return Promise.resolve(fn(arr[i])).then(result => {
113
+ results[i] = result;
114
+ return next();
115
+ });
116
+ }
117
+
118
+ let workers = [];
119
+ for (let w = 0; w < Math.min(concurrency, arr.length); w++) {
120
+ workers.push(next());
121
+ }
122
+
123
+ return Promise.all(workers).then(() => results);
124
+ }
125
+
126
+ async function retry(fn, { max_tries = 3 } = {}) {
127
+ let lastErr;
128
+ for (let i = 0; i < max_tries; i++) {
129
+ try {
130
+ return await fn();
131
+ } catch (e) {
132
+ lastErr = e;
133
+ }
134
+ }
135
+ throw lastErr;
136
+ }
137
+
138
+ function delay(ms) {
139
+ return new Promise(resolve => setTimeout(resolve, ms));
140
+ }
141
+
142
+ function asCallback(cb) {
143
+ return [
144
+ result => { if (cb) { cb(null, result); } return result; },
145
+ err => { if (cb) { cb(err); } throw err; }
146
+ ];
147
+ }
148
+
149
+ module.exports = { fromCallback, filter, reduce, each, mapLimit, retry, delay, asCallback, Disposer, disposer, using };
@@ -4,8 +4,6 @@ const fs = require('fs');
4
4
  const path = require('path');
5
5
  const mkdirp = require('mkdirp');
6
6
  const PassThrough = require('stream').PassThrough;
7
- const Bluebird = require('bluebird');
8
-
9
7
  module.exports = class ReportFile {
10
8
  constructor(reportFile) {
11
9
  this.file = reportFile;
@@ -27,7 +25,7 @@ module.exports = class ReportFile {
27
25
  this.outputStream.on('end', finish);
28
26
  this.outputStream.on('error', finish);
29
27
 
30
- this.closePromise = new Bluebird.Promise((resolve, reject) => {
28
+ this.closePromise = new Promise((resolve, reject) => {
31
29
  this.outputStream.on('finish', resolve);
32
30
  this.outputStream.on('error', reject);
33
31
  });
@@ -1,7 +1,7 @@
1
1
 
2
2
 
3
- const Bluebird = require('bluebird');
4
- const log = require('npmlog');
3
+ const { disposer } = require('./promises');
4
+ const log = require('../log');
5
5
 
6
6
  const reporters = require('../reporters');
7
7
  const isa = require('./isa');
@@ -107,11 +107,11 @@ class Reporter {
107
107
  }
108
108
  }
109
109
 
110
- Reporter.with = (app, stdout, path) => Bluebird.try(() => new Reporter(app, stdout, path)).disposer((reporter, promise) => {
110
+ Reporter.with = (app, stdout, path) => disposer(Promise.resolve(new Reporter(app, stdout, path)), (reporter, promise) => {
111
111
  if (promise.isRejected()) {
112
112
  let err = promise.reason();
113
113
 
114
- if (!err.hideFromReporter) {
114
+ if (reporter && !err.hideFromReporter) {
115
115
  reporter.report(null, {
116
116
  passed: false,
117
117
  name: err.name || 'unknown error',
@@ -122,7 +122,7 @@ Reporter.with = (app, stdout, path) => Bluebird.try(() => new Reporter(app, stdo
122
122
  }
123
123
  }
124
124
 
125
- return reporter.close();
125
+ return reporter ? reporter.close() : Promise.resolve();
126
126
  });
127
127
 
128
128
  function forwardToReporters(fn) {
@@ -1,6 +1,6 @@
1
1
 
2
2
 
3
- const Bluebird = require('bluebird');
3
+ const { disposer } = require('./promises');
4
4
  const EventEmitter = require('events').EventEmitter;
5
5
 
6
6
  class RunTimeout {
@@ -11,7 +11,7 @@ class RunTimeout {
11
11
  static with(timeout) {
12
12
  let runTimeout = new RunTimeout(timeout);
13
13
 
14
- return runTimeout.start().disposer(() => runTimeout.stop());
14
+ return disposer(runTimeout.start(), () => runTimeout.stop());
15
15
  }
16
16
 
17
17
  start() {
@@ -23,7 +23,7 @@ class RunTimeout {
23
23
  }, this.timeout * 1000);
24
24
  }
25
25
 
26
- return Bluebird.resolve(this);
26
+ return Promise.resolve(this);
27
27
  }
28
28
 
29
29
  setTimedOut() {
@@ -39,7 +39,7 @@ class RunTimeout {
39
39
 
40
40
  try(fn) {
41
41
  if (this.timedOut) {
42
- return Bluebird.reject(new Error('Run timed out.'));
42
+ return Promise.reject(new Error('Run timed out.'));
43
43
  }
44
44
 
45
45
  return fn();
@@ -1,17 +1,17 @@
1
1
 
2
2
 
3
- const Bluebird = require('bluebird');
3
+ const { disposer } = require('./promises');
4
4
  const EventEmitter = require('events').EventEmitter;
5
5
 
6
6
  class SignalListeners extends EventEmitter {
7
7
  static with() {
8
8
  let signalListeners = new this();
9
9
 
10
- return signalListeners.add().disposer(() => signalListeners.remove());
10
+ return disposer(signalListeners.add(), () => signalListeners.remove());
11
11
  }
12
12
  }
13
13
 
14
- SignalListeners.prototype.add = Bluebird.method(function() {
14
+ SignalListeners.prototype.add = function() {
15
15
  this._boundSigInterrupt = () => {
16
16
  this.emit('signal', new Error('Received SIGINT signal'));
17
17
  };
@@ -23,16 +23,18 @@ SignalListeners.prototype.add = Bluebird.method(function() {
23
23
  };
24
24
  process.on('SIGTERM', this._boundSigTerminate);
25
25
 
26
- return this;
27
- });
26
+ return Promise.resolve(this);
27
+ };
28
28
 
29
- SignalListeners.prototype.remove = Bluebird.method(function() {
29
+ SignalListeners.prototype.remove = function() {
30
30
  if (this._boundSigInterrupt) {
31
31
  process.removeListener('SIGINT', this._boundSigInterrupt);
32
32
  }
33
33
  if (this._boundSigTerminate) {
34
34
  process.removeListener('SIGTERM', this._boundSigTerminate);
35
35
  }
36
- });
36
+
37
+ return Promise.resolve();
38
+ };
37
39
 
38
40
  module.exports = SignalListeners;
@@ -0,0 +1,26 @@
1
+
2
+
3
+ const fs = require('fs');
4
+
5
+ const dirsToClean = new Set();
6
+
7
+ process.on('exit', () => {
8
+ for (const dir of dirsToClean) {
9
+ try {
10
+ fs.rmSync(dir, { recursive: true, force: true });
11
+ } catch {
12
+ // Best-effort cleanup: ignore errors during process exit
13
+ }
14
+ }
15
+ });
16
+
17
+ // Allow graceful cleanup when the process is terminated by a signal.
18
+ // Calling process.exit() ensures the 'exit' event fires and cleanup runs.
19
+ process.on('SIGTERM', () => process.exit(0));
20
+ process.on('SIGINT', () => process.exit(130));
21
+
22
+ function registerCleanup(dir) {
23
+ dirsToClean.add(dir);
24
+ }
25
+
26
+ module.exports = { registerCleanup, _registeredDirs: dirsToClean };
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "unittest",
8
8
  "browser"
9
9
  ],
10
- "version": "3.19.0",
10
+ "version": "3.20.0",
11
11
  "author": "Toby Ho <airportyh@gmail.com>",
12
12
  "repository": {
13
13
  "type": "git",
@@ -17,35 +17,34 @@
17
17
  "url": "http://github.com/testem/testem/issues"
18
18
  },
19
19
  "engines": {
20
- "node": ">= 7.*"
20
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
21
21
  },
22
22
  "license": "MIT",
23
23
  "dependencies": {
24
- "@xmldom/xmldom": "^0.8.0",
25
- "backbone": "^1.1.2",
26
- "bluebird": "^3.4.6",
27
- "charm": "^1.0.0",
28
- "commander": "^2.6.0",
29
- "compression": "^1.7.4",
30
- "consolidate": "^0.16.0",
24
+ "@xmldom/xmldom": "^0.9.9",
25
+ "backbone": "^1.6.1",
26
+ "charm": "^1.0.2",
27
+ "chokidar": "^5.0.0",
28
+ "commander": "^14.0.3",
29
+ "compression": "^1.8.1",
30
+ "consolidate": "^1.0.4",
31
31
  "execa": "^9.6.1",
32
- "express": "^4.10.7",
33
- "fireworm": "^0.7.2",
32
+ "express": "^5.2.1",
34
33
  "glob": "^13.0.6",
35
- "http-proxy": "^1.13.1",
36
- "js-yaml": "^3.2.5",
37
- "lodash": "^4.17.21",
34
+ "http-proxy": "^1.18.1",
35
+ "js-yaml": "^4.1.1",
36
+ "lodash": "^4.18.1",
37
+ "minimatch": "^10.2.5",
38
38
  "mkdirp": "^3.0.1",
39
39
  "mustache": "^4.2.0",
40
- "node-notifier": "^10.0.0",
41
- "npmlog": "^6.0.0",
40
+ "node-notifier": "^10.0.1",
42
41
  "printf": "^0.6.1",
42
+ "proc-log": "^6.1.0",
43
43
  "rimraf": "^6.1.3",
44
- "socket.io": "^4.5.4",
44
+ "socket.io": "^4.8.3",
45
45
  "spawn-args": "^0.2.0",
46
46
  "styled_string": "0.0.1",
47
- "tap-parser": "^7.0.0",
48
- "tmp": "^0.2.5"
47
+ "tap-parser": "^18.3.0"
49
48
  },
50
49
  "files": [
51
50
  "lib",
@@ -59,30 +58,41 @@
59
58
  "scripts": {
60
59
  "install:all": "npm install && npm install phantomjs-prebuilt",
61
60
  "test": "mocha tests/*_tests.js tests/**/*_tests.js",
61
+ "test:verbose": "TESTEM_VERBOSE=1 mocha tests/*_tests.js tests/**/*_tests.js",
62
62
  "cover": "cover run ./node_modules/.bin/_mocha tests/*_tests.js tests/**/*_tests.js; cover report html; open cover_html/index.html",
63
63
  "integration": "node ./bin/run-integration.js",
64
+ "ci:safari-smoke": "node ./scripts/safari-ci-smoke.js",
64
65
  "browser-tests": "cd examples/saucelabs/ && ../../testem.js ci -d",
65
66
  "lint": "eslint ."
66
67
  },
67
68
  "devDependencies": {
68
- "bluebird-retry": "^0.11.0",
69
- "browserify": "^17.0.0",
69
+ "@cypress/request": "^3.0.10",
70
+ "@eslint/eslintrc": "^3.3.5",
71
+ "@eslint/js": "^10.0.1",
70
72
  "chai": "^6.2.2",
71
- "chai-files": "^1.2.0",
72
- "chai-shallow-deep-equal": "^1.4.4",
73
- "cheerio": "1.0.0-rc.12",
73
+ "chai-files": "^1.4.0",
74
+ "chai-shallow-deep-equal": "^1.4.6",
75
+ "cheerio": "^1.2.0",
74
76
  "dirty-chai": "^3.0.0",
75
- "eslint": "^9.39.4",
76
- "eslint-plugin-chai-expect": "^3.1.0",
77
+ "esbuild": "^0.27.4",
78
+ "esbuild-plugins-node-modules-polyfill": "^1.8.1",
79
+ "eslint": "^10.2.0",
80
+ "eslint-config-prettier": "^10.1.8",
81
+ "eslint-plugin-chai-expect": "^4.0.0",
77
82
  "eslint-plugin-mocha": "^11.2.0",
83
+ "fresh-tape": "^5.9.1",
78
84
  "mocha": "^11.7.5",
79
- "request": "^2.51.0",
80
- "saucie": "^3.3.3",
81
- "shelljs": "^0.8.2",
82
- "sinon": "10.0.0",
85
+ "saucie": "^4.0.1",
86
+ "sinon": "^21.0.3",
83
87
  "sinon-chai": "^4.0.1",
84
- "socket.io-client": "^4.1.2",
85
- "tape": "^5.2.2"
88
+ "socket.io-client": "^4.8.3"
89
+ },
90
+ "overrides": {
91
+ "mocha": {
92
+ "diff": "^8.0.3",
93
+ "minimatch": "^9.0.7",
94
+ "serialize-javascript": "^7.0.5"
95
+ }
86
96
  },
87
97
  "bin": {
88
98
  "testem": "./testem.js"