Skip to content

Commit 2171a03

Browse files
committed
Add osls to the tests suite
Also: - improve packaging when using Yarn. Before, the `file:<repo>` was packing all the repo. Now we are generating a proper package using `npm pack`, it's faster (at least when using yarn) - improve error reporting
1 parent 6d8ab79 commit 2171a03

10 files changed

Lines changed: 140 additions & 45 deletions

File tree

.github/workflows/tests.yml

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,3 +168,65 @@ jobs:
168168
AWS_ACCESS_KEY_ID: ${{secrets.AWS_ACCESS_KEY_ID}}
169169
AWS_SECRET_ACCESS_KEY: ${{secrets.AWS_SECRET_ACCESS_KEY}}
170170
SERVERLESS_ACCESS_KEY: ${{secrets.SERVERLESS_ACCESS_KEY}}
171+
172+
osls-v3:
173+
runs-on: ${{ matrix.os }}
174+
name: Node.js ${{ matrix.node }} on ${{ matrix.os }} with osls v3
175+
176+
strategy:
177+
matrix:
178+
os:
179+
- ubuntu-latest
180+
node:
181+
- 20
182+
183+
steps:
184+
- name: 'Checkout'
185+
uses: actions/checkout@v7
186+
with:
187+
fetch-depth: 2
188+
189+
- name: 'Install Node.js'
190+
uses: actions/setup-node@v6
191+
with:
192+
node-version: '${{ matrix.node }}'
193+
194+
- name: 'Install dependencies'
195+
run: npm ci
196+
197+
- name: "Install osls v3"
198+
run: npm install --no-save --package-lock=false serverless@npm:osls@3
199+
200+
- name: 'Run tests'
201+
run: 'npm run test'
202+
203+
osls-v4:
204+
runs-on: ${{ matrix.os }}
205+
name: Node.js ${{ matrix.node }} on ${{ matrix.os }} with osls v4
206+
207+
strategy:
208+
matrix:
209+
os:
210+
- ubuntu-latest
211+
node:
212+
- 20
213+
214+
steps:
215+
- name: 'Checkout'
216+
uses: actions/checkout@v7
217+
with:
218+
fetch-depth: 2
219+
220+
- name: 'Install Node.js'
221+
uses: actions/setup-node@v6
222+
with:
223+
node-version: '${{ matrix.node }}'
224+
225+
- name: 'Install dependencies'
226+
run: npm ci
227+
228+
- name: "Install osls v4"
229+
run: npm install --no-save --package-lock=false serverless@npm:osls@4
230+
231+
- name: 'Run tests'
232+
run: 'npm run test'

examples/include-external-npm-packages-lock-file/_setup.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ module.exports = async (originalFixturePath, fixturePath, utils) => {
1515
await Promise.all([
1616
utils.replaceInFile(SLS_CONFIG_PATH, '- serverless-webpack', `- ${pluginPath}`),
1717
utils.replaceInFile(WEBPACK_CONFIG_PATH, "'serverless-webpack'", `'${pluginPath}'`),
18-
utils.replaceInFile(PACKAGE_JSON_PATH, 'file:../..', `file:${pluginPath}`),
19-
utils.replaceInFile(LOCK_PATH, '../..', `${pluginPath}`)
18+
utils.replaceInFile(PACKAGE_JSON_PATH, 'file:../..', `file:${utils.pluginPackagePath}`),
19+
utils.replaceInFile(LOCK_PATH, '../..', `${utils.pluginPackagePath}`)
2020
]);
2121

2222
const command = /^win/.test(process.platform) ? 'npm.cmd' : 'npm';

examples/include-external-npm-packages-with-yarn-workspaces/_setup.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ module.exports = async (originalFixturePath, fixturePath, utils) => {
1515
await Promise.all([
1616
utils.replaceInFile(SLS_CONFIG_PATH, '- serverless-webpack', `- ${pluginPath}`),
1717
utils.replaceInFile(WEBPACK_CONFIG_PATH, "'serverless-webpack'", `'${pluginPath}'`),
18-
utils.replaceInFile(PACKAGE_JSON_PATH, 'file:../..', `file:${pluginPath}`),
19-
utils.replaceInFile(LOCK_PATH, '../..', `${pluginPath}`)
18+
utils.replaceInFile(PACKAGE_JSON_PATH, 'file:../..', `file:${utils.pluginPackagePath}`),
19+
utils.replaceInFile(LOCK_PATH, 'file:.', `file:${utils.pluginPackagePath}`)
2020
]);
2121

2222
const command = /^win/.test(process.platform) ? 'yarn.cmd' : 'yarn';

examples/include-external-npm-packages/_setup.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ module.exports = async (originalFixturePath, fixturePath, utils) => {
1515
await Promise.all([
1616
utils.replaceInFile(SLS_CONFIG_PATH, '- serverless-webpack', `- ${pluginPath}`),
1717
utils.replaceInFile(WEBPACK_CONFIG_PATH, "'serverless-webpack'", `'${pluginPath}'`),
18-
utils.replaceInFile(PACKAGE_JSON_PATH, 'file:../..', `file:${pluginPath}`),
19-
utils.replaceInFile(LOCK_PATH, 'file:../..', `file:${pluginPath}`)
18+
utils.replaceInFile(PACKAGE_JSON_PATH, 'file:../..', `file:${utils.pluginPackagePath}`),
19+
utils.replaceInFile(LOCK_PATH, 'file:../..', `file:${utils.pluginPackagePath}`)
2020
]);
2121

2222
const command = /^win/.test(process.platform) ? 'yarn.cmd' : 'yarn';

examples/serverless-v4/_setup.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ module.exports = async (originalFixturePath, fixturePath, utils) => {
1515
await Promise.all([
1616
utils.replaceInFile(SLS_CONFIG_PATH, '- serverless-webpack', `- ${pluginPath}`),
1717
utils.replaceInFile(WEBPACK_CONFIG_PATH, "'serverless-webpack'", `'${pluginPath}'`),
18-
utils.replaceInFile(PACKAGE_JSON_PATH, 'file:../..', `file:${pluginPath}`),
19-
utils.replaceInFile(LOCK_PATH, 'file:../..', `file:${pluginPath}`)
18+
utils.replaceInFile(PACKAGE_JSON_PATH, 'file:../..', `file:${utils.pluginPackagePath}`),
19+
utils.replaceInFile(LOCK_PATH, 'file:../..', `file:${utils.pluginPackagePath}`)
2020
]);
2121

2222
const command = /^win/.test(process.platform) ? 'yarn.cmd' : 'yarn';

lib/utils.js

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,26 @@ function searchAndProcessCache(moduleName, processor) {
4444
}
4545

4646
class SpawnError extends Error {
47-
constructor(message, stdout, stderr) {
48-
super(message);
47+
constructor(message, stdout, stderr, options = {}) {
48+
const formattedMessage = [
49+
message,
50+
options.cwd && `cwd: ${options.cwd}`,
51+
stdout && `stdout:\n${stdout}`,
52+
stderr && `stderr:\n${stderr}`
53+
]
54+
.filter(Boolean)
55+
.join('\n');
56+
57+
super(formattedMessage);
4958
this.stdout = stdout;
5059
this.stderr = stderr;
60+
this.command = options.command;
61+
this.args = options.args;
62+
this.cwd = options.cwd;
5163
}
5264

5365
toString() {
54-
return `${this.message}\n${this.stderr}`;
66+
return this.message;
5567
}
5668
}
5769

@@ -85,15 +97,23 @@ function spawnProcess(command, args = [], options) {
8597
stderr += data;
8698
});
8799
child.on('error', err => {
88-
if (process.env.NODE_ENV === 'test') {
89-
console.error(err);
100+
const commandLine = [command, ...normalizedArgs].join(' ');
101+
let message = `${commandLine} failed to start`;
102+
103+
if (err.code === 'ENOENT') {
104+
message += `. Command "${command}" was not found. Make sure it is installed and available in PATH.`;
90105
}
91106

92-
reject(err);
107+
message += ` Original error: ${err.message}`;
108+
109+
reject(new SpawnError(message, stdout, stderr, { command, args: normalizedArgs, cwd: options?.cwd }));
93110
});
94111
child.on('close', exitCode => {
95112
if (exitCode !== 0) {
96-
reject(new SpawnError(`${command} ${normalizedArgs.join(' ')} failed with code ${exitCode}`, stdout, stderr));
113+
const commandLine = [command, ...normalizedArgs].join(' ');
114+
const message = `${commandLine} failed with code ${exitCode}`;
115+
116+
reject(new SpawnError(message, stdout, stderr, { command, args: normalizedArgs, cwd: options?.cwd }));
97117
} else {
98118
resolve({ stdout, stderr });
99119
}

lib/validate.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ module.exports = {
4848

4949
const getEntryExtension = fileName => {
5050
const files = glob.sync(`${fileName}.*`, {
51-
cwd: this.serverless.config.servicePath,
51+
cwd: this.serverless.config.servicePath || undefined,
5252
nodir: true,
5353
ignore: this.configuration.excludeFiles ? this.configuration.excludeFiles : undefined
5454
});

tests/e2e/e2eUtils.js

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ const originalRunServerless = require('@serverless/test/run-serverless');
66
const { spawnProcess } = require('../../lib/utils');
77

88
const FIXTURES_DIR = path.resolve(__dirname, '..', '..', 'examples');
9+
const PROJECT_DIR = path.resolve(__dirname, '..', '..');
10+
let pluginPackagePathPromise;
911

1012
async function replaceInFile(path, regex, replacement) {
1113
const content = await fse.readFile(path, 'utf8');
@@ -16,15 +18,39 @@ async function replaceInFile(path, regex, replacement) {
1618
async function setupFixture(name) {
1719
const fixturePath = path.join(FIXTURES_DIR, name);
1820
const setupFixturePath = await provisionTmpDir();
21+
const pluginPackagePath = await getPluginPackagePath();
1922
await fse.copy(fixturePath, setupFixturePath);
2023

2124
const setupScriptPath = path.resolve(setupFixturePath, '_setup.js');
22-
await require(setupScriptPath)(fixturePath, setupFixturePath, { replaceInFile, spawnProcess });
25+
await require(setupScriptPath)(fixturePath, setupFixturePath, {
26+
pluginPackagePath,
27+
replaceInFile,
28+
spawnProcess
29+
});
2330
await fse.unlink(setupScriptPath);
2431

2532
return setupFixturePath;
2633
}
2734

35+
async function getPluginPackagePath() {
36+
if (!pluginPackagePathPromise) {
37+
pluginPackagePathPromise = (async () => {
38+
const packageDir = await provisionTmpDir();
39+
const npmCommand = /^win/.test(process.platform) ? 'npm.cmd' : 'npm';
40+
const { stdout } = await spawnProcess(
41+
npmCommand,
42+
['pack', '--pack-destination', packageDir, '--cache', path.join(packageDir, '.npm-cache')],
43+
{ cwd: PROJECT_DIR }
44+
);
45+
const packageFile = stdout.trim().split(/\r?\n/).pop();
46+
47+
return path.join(packageDir, packageFile);
48+
})();
49+
}
50+
51+
return pluginPackagePathPromise;
52+
}
53+
2854
async function runServerless(options) {
2955
const runServerlessOptions = {
3056
command: 'package'

tests/utils.test.js

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,15 @@ describe('Utils', () => {
2424
describe('SpawnError', () => {
2525
it('should store stdout and stderr', () => {
2626
const err = new Utils.SpawnError('message', 'stdout', 'stderr');
27-
expect(err).toHaveProperty('message', 'message');
27+
expect(err).toHaveProperty('message', 'message\nstdout:\nstdout\nstderr:\nstderr');
2828
expect(err).toHaveProperty('stdout', 'stdout');
2929
expect(err).toHaveProperty('stderr', 'stderr');
3030
});
3131

3232
it('should print message and stderr', () => {
33-
const err = new Utils.SpawnError('message', 'stdout', 'stderr');
33+
const err = new Utils.SpawnError('message', 'stdout', 'stderr', { cwd: 'cwd' });
3434

35-
expect(err.toString()).toEqual('message\nstderr');
35+
expect(err.toString()).toEqual('message\ncwd: cwd\nstdout:\nstdout\nstderr:\nstderr');
3636
});
3737
});
3838

@@ -92,10 +92,14 @@ describe('Utils', () => {
9292
childMock.on.mockReset();
9393
childMock.on.mockImplementation((name, cb) => {
9494
if (name === 'error') {
95-
cb(new Error('spawn ENOENT'));
95+
const err = new Error('spawn ENOENT');
96+
err.code = 'ENOENT';
97+
cb(err);
9698
}
9799
});
98-
return expect(Utils.spawnProcess('cmd', [])).rejects.toThrow('spawn ENOENT');
100+
return expect(Utils.spawnProcess('cmd', [])).rejects.toThrow(
101+
'cmd failed to start. Command "cmd" was not found. Make sure it is installed and available in PATH. Original error: spawn ENOENT'
102+
);
99103
});
100104

101105
it('should reject on positive exit code', () => {

tests/validate.test.js

Lines changed: 6 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1097,29 +1097,12 @@ describe('validate', () => {
10971097

10981098
expect(lib.entries).toEqual(expectedLibEntries);
10991099

1100-
// handle different stub in case of serverless version
1101-
if (serverless.version.match(/^1/)) {
1102-
expect(globMock.sync).toHaveBeenCalledTimes(1);
1103-
expect(globMock.sync).toHaveBeenCalledWith('module1.*', {
1104-
ignore: '**/*.ts',
1105-
cwd: null,
1106-
nodir: true
1107-
});
1108-
} else if (serverless.version.match(/^3/)) {
1109-
expect(globMock.sync).toHaveBeenCalledTimes(1);
1110-
expect(globMock.sync).toHaveBeenCalledWith('module1.*', {
1111-
cwd: null,
1112-
nodir: true,
1113-
ignore: '**/*.ts'
1114-
});
1115-
} else {
1116-
expect(globMock.sync).toHaveBeenCalledTimes(1);
1117-
expect(globMock.sync).toHaveBeenCalledWith('module1.*', {
1118-
ignore: '**/*.ts',
1119-
cwd: undefined,
1120-
nodir: true
1121-
});
1122-
}
1100+
expect(globMock.sync).toHaveBeenCalledTimes(1);
1101+
expect(globMock.sync).toHaveBeenCalledWith('module1.*', {
1102+
ignore: '**/*.ts',
1103+
cwd: undefined,
1104+
nodir: true
1105+
});
11231106

11241107
expect(serverless.cli.log).toHaveBeenCalledTimes(0);
11251108
return null;

0 commit comments

Comments
 (0)