Skip to content

Commit 37ea12f

Browse files
committed
feat(testlab): create unique sandbox for each instance to enable parallel testing
BREAKING CHANGE: The TestSandbox constructor changes its signature and behavior now. It used to take a `path` as the top-level directory of the sandbox. The new style is illustrated below. ```ts // Create a sandbox as a unique temporary subdirectory under the rootPath const sandbox = new TestSandbox(rootPath); // Create a sandbox in the root path directly // This is same as the old behavior const sandbox = new TestSandbox(rootPath, ''); const sandbox = new TestSandbox(rootPath, '.'); // Create a sandbox in the `test1` subdirectory of the root path const sandbox = new TestSandbox(rootPath, 'test1'); ```
1 parent d67658a commit 37ea12f

File tree

4 files changed

+75
-17
lines changed

4 files changed

+75
-17
lines changed

packages/boot/src/__tests__/acceptance/application-metadata.booter.acceptance.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {BooterApp} from '../fixtures/application';
1010

1111
describe('application metadata booter acceptance tests', () => {
1212
let app: BooterApp;
13-
const sandbox = new TestSandbox(resolve(__dirname, '../../.sandbox'));
13+
const sandbox = new TestSandbox(resolve(__dirname, '../../.sandbox'), '.');
1414

1515
beforeEach('reset sandbox', () => sandbox.reset());
1616
beforeEach(getApp);

packages/testlab/src/__tests__/integration/test-sandbox.integration.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ describe('TestSandbox integration tests', () => {
111111
beforeEach(callSandboxDelete);
112112

113113
it('throws an error when trying to call getPath()', () => {
114-
expect(() => sandbox.getPath()).to.throw(ERR);
114+
expect(() => sandbox.path).to.throw(ERR);
115115
});
116116

117117
it('throws an error when trying to call mkdir()', async () => {
@@ -146,11 +146,11 @@ describe('TestSandbox integration tests', () => {
146146
}
147147

148148
function givenPath() {
149-
path = sandbox.getPath();
149+
path = sandbox.path;
150150
}
151151

152152
async function deleteSandbox() {
153153
if (!(await pathExists(path))) return;
154-
await remove(sandbox.getPath());
154+
await remove(sandbox.path);
155155
}
156156
});
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright IBM Corp. 2020. All Rights Reserved.
2+
// Node module: @loopback/testlab
3+
// This file is licensed under the MIT License.
4+
// License text available at https://opensource.org/licenses/MIT
5+
6+
import {dirname, join, resolve} from 'path';
7+
import {expect, TestSandbox} from '../..';
8+
9+
const SANDBOX_PATH = resolve(__dirname, '../../.sandbox');
10+
11+
describe('TestSandbox', () => {
12+
it('creates a subdir by default', () => {
13+
const sandbox = new TestSandbox(SANDBOX_PATH);
14+
expect(sandbox.path).to.not.eql(SANDBOX_PATH);
15+
expect(dirname(sandbox.path)).to.eql(SANDBOX_PATH);
16+
});
17+
18+
it('creates unique subdir by default', () => {
19+
const sandbox1 = new TestSandbox(SANDBOX_PATH);
20+
const sandbox2 = new TestSandbox(SANDBOX_PATH);
21+
expect(sandbox1.path).to.not.eql(sandbox2.path);
22+
});
23+
24+
it('creates a named subdir', () => {
25+
const sandbox = new TestSandbox(SANDBOX_PATH, 'd1');
26+
expect(sandbox.path).to.not.eql(SANDBOX_PATH);
27+
expect(dirname(sandbox.path)).to.eql(SANDBOX_PATH);
28+
expect(sandbox.path).to.eql(join(SANDBOX_PATH, 'd1'));
29+
});
30+
31+
it('does not creates a subdir if it is empty string', () => {
32+
const sandbox = new TestSandbox(SANDBOX_PATH, '');
33+
expect(sandbox.path).to.eql(SANDBOX_PATH);
34+
});
35+
36+
it("does not creates a subdir if it is '.'", () => {
37+
const sandbox = new TestSandbox(SANDBOX_PATH, '.');
38+
expect(sandbox.path).to.eql(SANDBOX_PATH);
39+
});
40+
});

packages/testlab/src/test-sandbox.ts

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,13 @@ import {
99
emptyDir,
1010
ensureDir,
1111
ensureDirSync,
12+
mkdtempSync,
1213
pathExists,
1314
remove,
1415
writeFile,
1516
writeJson,
1617
} from 'fs-extra';
17-
import {parse, resolve} from 'path';
18+
import {join, parse, resolve} from 'path';
1819

1920
/**
2021
* TestSandbox class provides a convenient way to get a reference to a
@@ -37,19 +38,36 @@ export class TestSandbox {
3738
* Will create a directory if it doesn't already exist. If it exists, you
3839
* still get an instance of the TestSandbox.
3940
*
40-
* @param path - Path of the TestSandbox. If relative (it will be resolved relative to cwd()).
41-
*/
42-
constructor(path: string) {
43-
// resolve ensures path is absolute / makes it absolute (relative to cwd())
44-
this._path = resolve(path);
45-
ensureDirSync(this.path);
46-
}
47-
48-
/**
49-
* Returns the path of the TestSandbox
41+
* @example
42+
* ```ts
43+
* // Create a sandbox as a unique temporary subdirectory under the rootPath
44+
* const sandbox = new TestSandbox(rootPath);
45+
*
46+
* // Create a sandbox in the root path directly
47+
* // This is same as the old behavior
48+
* const sandbox = new TestSandbox(rootPath, '');
49+
* const sandbox = new TestSandbox(rootPath, '.');
50+
*
51+
* // Create a sandbox in the `test1` subdirectory of the root path
52+
* const sandbox = new TestSandbox(rootPath, 'test1');
53+
* ```
54+
*
55+
* @param rootPath - Root path of the TestSandbox. If relative it will be
56+
* resolved against the current directory.
57+
* @param subdir - Subdirectory for the sandbox. If not provided, the sandbox
58+
* will automatically creates a unique temporary subdirectory. This allows
59+
* sandboxes with the same root path can be used in parallel during testing.
5060
*/
51-
getPath(): string {
52-
return this.path;
61+
constructor(rootPath: string, subdir?: string) {
62+
rootPath = resolve(rootPath);
63+
ensureDirSync(rootPath);
64+
if (subdir != null) {
65+
this._path = resolve(rootPath, subdir);
66+
} else {
67+
// Create a unique temporary directory under the root path
68+
// See https://nodejs.org/api/fs.html#fs_fs_mkdtempsync_prefix_options
69+
this._path = mkdtempSync(join(rootPath, `/${process.pid}`));
70+
}
5371
}
5472

5573
/**

0 commit comments

Comments
 (0)