test: use random ports for local verdaccio npm servers

This commit is contained in:
Jason Bedard 2022-05-10 11:32:34 -07:00 committed by Douglas Parker
parent ba93117e78
commit aa30bb156d
4 changed files with 108 additions and 86 deletions

View File

@ -1,17 +1,24 @@
import { ChildProcess, spawn } from 'child_process'; import { spawn } from 'child_process';
import { copyFileSync, mkdtempSync, realpathSync } from 'fs'; import { mkdtempSync, realpathSync } from 'fs';
import { tmpdir } from 'os'; import { tmpdir } from 'os';
import { join } from 'path'; import { join } from 'path';
import { writeFile } from './fs'; import { getGlobalVariable } from './env';
import { writeFile, readFile } from './fs';
export function createNpmRegistry(withAuthentication = false): ChildProcess { export async function createNpmRegistry(
port: number,
httpsPort: number,
withAuthentication = false,
) {
// Setup local package registry // Setup local package registry
const registryPath = mkdtempSync(join(realpathSync(tmpdir()), 'angular-cli-e2e-registry-')); const registryPath = mkdtempSync(join(realpathSync(tmpdir()), 'angular-cli-e2e-registry-'));
copyFileSync( let configContent = await readFile(
join(__dirname, '../../', withAuthentication ? 'verdaccio_auth.yaml' : 'verdaccio.yaml'), join(__dirname, '../../', withAuthentication ? 'verdaccio_auth.yaml' : 'verdaccio.yaml'),
join(registryPath, 'verdaccio.yaml'),
); );
configContent = configContent.replace(/\$\{HTTP_PORT\}/g, String(port));
configContent = configContent.replace(/\$\{HTTPS_PORT\}/g, String(httpsPort));
await writeFile(join(registryPath, 'verdaccio.yaml'), configContent);
return spawn('node', [require.resolve('verdaccio/bin/verdaccio'), '-c', './verdaccio.yaml'], { return spawn('node', [require.resolve('verdaccio/bin/verdaccio'), '-c', './verdaccio.yaml'], {
cwd: registryPath, cwd: registryPath,
@ -21,7 +28,6 @@ export function createNpmRegistry(withAuthentication = false): ChildProcess {
// Token was generated using `echo -n 'testing:s3cret' | openssl base64`. // Token was generated using `echo -n 'testing:s3cret' | openssl base64`.
const VALID_TOKEN = `dGVzdGluZzpzM2NyZXQ=`; const VALID_TOKEN = `dGVzdGluZzpzM2NyZXQ=`;
const SECURE_REGISTRY = `//localhost:4876/`;
export function createNpmConfigForAuthentication( export function createNpmConfigForAuthentication(
/** /**
@ -42,7 +48,7 @@ export function createNpmConfigForAuthentication(
invalidToken = false, invalidToken = false,
): Promise<void> { ): Promise<void> {
const token = invalidToken ? `invalid=` : VALID_TOKEN; const token = invalidToken ? `invalid=` : VALID_TOKEN;
const registry = SECURE_REGISTRY; const registry = (getGlobalVariable('package-secure-registry') as string).replace(/^\w+:/, '');
return writeFile( return writeFile(
'.npmrc', '.npmrc',
@ -68,7 +74,7 @@ export function setNpmEnvVarsForAuthentication(
delete process.env['NPM_CONFIG_REGISTRY']; delete process.env['NPM_CONFIG_REGISTRY'];
const registryKey = useYarnEnvVariable ? 'YARN_REGISTRY' : 'NPM_CONFIG_REGISTRY'; const registryKey = useYarnEnvVariable ? 'YARN_REGISTRY' : 'NPM_CONFIG_REGISTRY';
process.env[registryKey] = `http:${SECURE_REGISTRY}`; process.env[registryKey] = getGlobalVariable('package-secure-registry');
process.env['NPM_CONFIG__AUTH'] = invalidToken ? `invalid=` : VALID_TOKEN; process.env['NPM_CONFIG__AUTH'] = invalidToken ? `invalid=` : VALID_TOKEN;

View File

@ -9,6 +9,7 @@ import * as path from 'path';
import { setGlobalVariable } from './e2e/utils/env'; import { setGlobalVariable } from './e2e/utils/env';
import { gitClean } from './e2e/utils/git'; import { gitClean } from './e2e/utils/git';
import { createNpmRegistry } from './e2e/utils/registry'; import { createNpmRegistry } from './e2e/utils/registry';
import { AddressInfo, createServer, Server } from 'net';
Error.stackTraceLimit = Infinity; Error.stackTraceLimit = Infinity;
@ -122,93 +123,99 @@ if (testsToRun.length == allTests.length) {
setGlobalVariable('argv', argv); setGlobalVariable('argv', argv);
setGlobalVariable('ci', process.env['CI']?.toLowerCase() === 'true' || process.env['CI'] === '1'); setGlobalVariable('ci', process.env['CI']?.toLowerCase() === 'true' || process.env['CI'] === '1');
setGlobalVariable('package-manager', argv.yarn ? 'yarn' : 'npm'); setGlobalVariable('package-manager', argv.yarn ? 'yarn' : 'npm');
setGlobalVariable('package-registry', 'http://localhost:4873');
const registryProcess = createNpmRegistry(); Promise.all([findFreePort(), findFreePort()])
const secureRegistryProcess = createNpmRegistry(true); .then(async ([httpPort, httpsPort]) => {
setGlobalVariable('package-registry', 'http://localhost:' + httpPort);
setGlobalVariable('package-secure-registry', 'http://localhost:' + httpsPort);
testsToRun const registryProcess = await createNpmRegistry(httpPort, httpPort);
.reduce((previous, relativeName, testIndex) => { const secureRegistryProcess = await createNpmRegistry(httpPort, httpsPort, true);
// Make sure this is a windows compatible path.
let absoluteName = path.join(e2eRoot, relativeName);
if (/^win/.test(process.platform)) {
absoluteName = absoluteName.replace(/\\/g, path.posix.sep);
}
return previous.then(() => { return testsToRun
currentFileName = relativeName.replace(/\.ts$/, ''); .reduce((previous, relativeName, testIndex) => {
const start = +new Date(); // Make sure this is a windows compatible path.
let absoluteName = path.join(e2eRoot, relativeName);
if (/^win/.test(process.platform)) {
absoluteName = absoluteName.replace(/\\/g, path.posix.sep);
}
const module = require(absoluteName); return previous.then(() => {
const originalEnvVariables = { currentFileName = relativeName.replace(/\.ts$/, '');
...process.env, const start = +new Date();
};
const fn: (skipClean?: () => void) => Promise<void> | void = const module = require(absoluteName);
typeof module == 'function' const originalEnvVariables = {
? module ...process.env,
: typeof module.default == 'function' };
? module.default
: () => {
throw new Error('Invalid test module.');
};
let clean = true; const fn: (skipClean?: () => void) => Promise<void> | void =
let previousDir = null; typeof module == 'function'
? module
: typeof module.default == 'function'
? module.default
: () => {
throw new Error('Invalid test module.');
};
return Promise.resolve() let clean = true;
.then(() => printHeader(currentFileName, testIndex)) let previousDir = null;
.then(() => (previousDir = process.cwd()))
.then(() => logStack.push(lastLogger().createChild(currentFileName)))
.then(() => fn(() => (clean = false)))
.then(
() => logStack.pop(),
(err) => {
logStack.pop();
throw err;
},
)
.then(() => console.log('----'))
.then(() => {
// If we're not in a setup, change the directory back to where it was before the test.
// This allows tests to chdir without worrying about keeping the original directory.
if (!allSetups.includes(relativeName) && previousDir) {
process.chdir(previousDir);
// Restore env variables before each test. return Promise.resolve()
console.log(' Restoring original environment variables...'); .then(() => printHeader(currentFileName, testIndex))
process.env = originalEnvVariables; .then(() => (previousDir = process.cwd()))
} .then(() => logStack.push(lastLogger().createChild(currentFileName)))
}) .then(() => fn(() => (clean = false)))
.then(() => { .then(
// Only clean after a real test, not a setup step. Also skip cleaning if the test
// requested an exception.
if (!allSetups.includes(relativeName) && clean) {
logStack.push(new logging.NullLogger());
return gitClean().then(
() => logStack.pop(), () => logStack.pop(),
(err) => { (err) => {
logStack.pop(); logStack.pop();
throw err; throw err;
}, },
)
.then(() => console.log('----'))
.then(() => {
// If we're not in a setup, change the directory back to where it was before the test.
// This allows tests to chdir without worrying about keeping the original directory.
if (!allSetups.includes(relativeName) && previousDir) {
process.chdir(previousDir);
// Restore env variables before each test.
console.log(' Restoring original environment variables...');
process.env = originalEnvVariables;
}
})
.then(() => {
// Only clean after a real test, not a setup step. Also skip cleaning if the test
// requested an exception.
if (!allSetups.includes(relativeName) && clean) {
logStack.push(new logging.NullLogger());
return gitClean().then(
() => logStack.pop(),
(err) => {
logStack.pop();
throw err;
},
);
}
})
.then(
() => printFooter(currentFileName, start),
(err) => {
printFooter(currentFileName, start);
console.error(err);
throw err;
},
); );
} });
}) }, Promise.resolve())
.then( .finally(() => {
() => printFooter(currentFileName, start), registryProcess.kill();
(err) => { secureRegistryProcess.kill();
printFooter(currentFileName, start); });
console.error(err); })
throw err;
},
);
});
}, Promise.resolve())
.then( .then(
() => { () => {
registryProcess.kill();
secureRegistryProcess.kill();
console.log(colors.green('Done.')); console.log(colors.green('Done.'));
process.exit(0); process.exit(0);
}, },
@ -218,9 +225,6 @@ testsToRun
console.error(colors.red(err.message)); console.error(colors.red(err.message));
console.error(colors.red(err.stack)); console.error(colors.red(err.stack));
registryProcess.kill();
secureRegistryProcess.kill();
if (argv.debug) { if (argv.debug) {
console.log(`Current Directory: ${process.cwd()}`); console.log(`Current Directory: ${process.cwd()}`);
console.log('Will loop forever while you debug... CTRL-C to quit.'); console.log('Will loop forever while you debug... CTRL-C to quit.');
@ -257,3 +261,15 @@ function printFooter(testName: string, startTime: number) {
console.log(colors.green('Last step took ') + colors.bold.blue('' + t) + colors.green('s...')); console.log(colors.green('Last step took ') + colors.bold.blue('' + t) + colors.green('s...'));
console.log(''); console.log('');
} }
function findFreePort() {
return new Promise<number>((resolve, reject) => {
const srv = createServer();
srv.once('listening', () => {
const port = (srv.address() as AddressInfo).port;
srv.close((e) => (e ? reject(e) : resolve(port)));
});
srv.once('error', (e) => srv.close(() => reject(e)));
srv.listen();
});
}

View File

@ -3,7 +3,7 @@ storage: ./storage
auth: auth:
auth-memory: auth-memory:
users: {} users: {}
listen: localhost:4873 listen: localhost:${HTTP_PORT}
uplinks: uplinks:
npmjs: npmjs:
url: https://registry.npmjs.org/ url: https://registry.npmjs.org/

View File

@ -5,10 +5,10 @@ auth:
testing: testing:
name: testing name: testing
password: s3cret password: s3cret
listen: localhost:4876 listen: localhost:${HTTPS_PORT}
uplinks: uplinks:
local: local:
url: http://localhost:4873 url: http://localhost:${HTTP_PORT}
cache: false cache: false
maxage: 20m maxage: 20m
max_fails: 32 max_fails: 32