chore(testing): add lerna-smoke-tests (#14347)

This commit is contained in:
James Henry 2023-01-17 23:50:01 +04:00 committed by GitHub
parent da4959a324
commit 965c638d8d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 334 additions and 23 deletions

View File

@ -147,6 +147,11 @@ jobs:
NX_E2E_RUN_CYPRESS: 'false' NX_E2E_RUN_CYPRESS: 'false'
NX_VERBOSE_LOGGING: 'false' NX_VERBOSE_LOGGING: 'false'
steps: steps:
- run:
name: Configure git metadata (needed for lerna smoke tests)
command: |
git config --global user.email test@test.com
git config --global user.name "Test Test"
- run: - run:
name: Set dynamic nx run variable name: Set dynamic nx run variable
command: | command: |
@ -192,7 +197,7 @@ jobs:
pids+=($!) pids+=($!)
(yarn nx affected --target=build --base=$NX_BASE --head=$NX_HEAD --parallel=3 && (yarn nx affected --target=build --base=$NX_BASE --head=$NX_HEAD --parallel=3 &&
npx nx affected --target=e2e --base=$NX_BASE --head=$NX_HEAD --exclude=e2e-storybook,e2e-storybook-angular,e2e-react-native,e2e-detox --parallel=1) & npx nx affected --target=e2e --base=$NX_BASE --head=$NX_HEAD --exclude=e2e-storybook,e2e-storybook-angular --parallel=1) &
pids+=($!) pids+=($!)
for pid in "${pids[@]}"; do for pid in "${pids[@]}"; do
@ -213,27 +218,11 @@ jobs:
echo "export NX_RUN_GROUP=\"run-group-macos-$CIRCLE_WORKFLOW_ID\";" >> $BASH_ENV echo "export NX_RUN_GROUP=\"run-group-macos-$CIRCLE_WORKFLOW_ID\";" >> $BASH_ENV
- setup: - setup:
os: macos os: macos
- nx/set-shas:
main-branch-name: 'master'
- run: - run:
name: Check if "detox" or "react-native" were modified directly name: Run E2E Tests for macOS
# FIXME: remove --exclude=e2e-detox once we have a fix for the detox tests
command: | command: |
COUNT=`git diff --name-only $NX_BASE $NX_HEAD | (grep -E 'packages/detox|packages/react-native|e2e/detox|e2e/react-native' || true) | wc -l` npx nx run-many -t e2e-macos --parallel=1 --exclude=e2e-detox
if [[ $COUNT -gt 0 ]]; then
echo "React Native and Detox were touched directly"
echo "export E2E_AFFECTED=true;" >> $BASH_ENV
else
echo "React Native and Detox were not touched directly"
echo "export E2E_AFFECTED=false;" >> $BASH_ENV
fi
- run:
name: Run E2E Tests
command: |
if $E2E_AFFECTED; then
npx nx affected --target=e2e --base=$NX_BASE --head=$NX_HEAD --exclude=e2e-make-angular-cli-faster,e2e-detox,e2e-js,e2e-next,e2e-workspace-create,e2e-nx-run,e2e-nx-misc,e2e-react,e2e-web,e2e-webpack,e2e-rollup,e2e-esbuild,e2e-angular-extensions,e2e-angular-core,e2e-nx-plugin,e2e-cypress,e2e-node,e2e-linter,e2e-jest,e2e-add-nx-to-monorepo,nx-dev-e2e,e2e-nx-init,e2e-graph-client,e2e-vite,e2e-cra-to-nx,e2e-storybook,e2e-storybook-angular --parallel=1;
else
echo "Skipping E2E tests";
fi
no_output_timeout: 45m no_output_timeout: 45m
# ------------------------- # -------------------------

View File

@ -4,7 +4,7 @@
"sourceRoot": "e2e/detox", "sourceRoot": "e2e/detox",
"projectType": "application", "projectType": "application",
"targets": { "targets": {
"e2e": {}, "e2e-macos": {},
"run-e2e-tests": {} "run-e2e-tests": {}
}, },
"implicitDependencies": ["detox"] "implicitDependencies": ["detox"]

View File

@ -0,0 +1,11 @@
/* eslint-disable */
export default {
transform: {
'^.+\\.[tj]sx?$': 'ts-jest',
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'html'],
maxWorkers: 1,
globals: { 'ts-jest': { tsconfig: '<rootDir>/tsconfig.spec.json' } },
displayName: 'e2e-lerna-smoke-tests',
preset: '../../jest.preset.js',
};

View File

@ -0,0 +1,11 @@
{
"name": "e2e-lerna-smoke-tests",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "e2e/lerna-smoke-tests",
"projectType": "application",
"targets": {
"e2e": {},
"run-e2e-tests": {}
},
"implicitDependencies": ["nx", "devkit"]
}

View File

@ -0,0 +1,79 @@
/**
* These minimal smoke tests are here to ensure that we do not break assumptions on the lerna side
* when making updates to nx or @nrwl/devkit.
*/
import {
cleanupLernaWorkspace,
newLernaWorkspace,
runLernaCLI,
tmpProjPath,
updateJson,
} from '@nrwl/e2e/utils';
expect.addSnapshotSerializer({
serialize(str: string) {
return (
str
// Not all package managers print the package.json path in the output
.replace(tmpProjPath(), '')
.replace('/private', '')
.replace('/packages/package-1', '')
// We trim each line to reduce the chances of snapshot flakiness
.split('\n')
.map((r) => r.trim())
.join('\n')
);
},
test(val: string) {
return val != null && typeof val === 'string';
},
});
describe('Lerna Smoke Tests', () => {
beforeAll(() => newLernaWorkspace());
afterAll(() => cleanupLernaWorkspace());
// `lerna repair` builds on top of `nx repair` and runs all of its generators
describe('lerna repair', () => {
// If this snapshot fails it means that nx repair generators are making assumptions which don't hold true for lerna workspaces
it('should complete successfully on a new lerna workspace', async () => {
expect(runLernaCLI(`repair`)).toMatchInlineSnapshot(`
> Lerna No changes were necessary. This workspace is up to date!
`);
}, 1000000);
});
// `lerna run` delegates to the nx task runner behind the scenes
describe('lerna run', () => {
it('should complete successfully on a new lerna workspace', async () => {
runLernaCLI('create package-1 -y');
updateJson('packages/package-1/package.json', (json) => ({
...json,
scripts: {
...(json.scripts || {}),
'print-name': 'echo test-package-1',
},
}));
expect(runLernaCLI(`run print-name`)).toMatchInlineSnapshot(`
> package-1:print-name
> package-1@0.0.0 print-name
> echo test-package-1
test-package-1
> Lerna (powered by Nx) Successfully ran target print-name for project package-1
`);
}, 1000000);
});
});

View File

@ -0,0 +1,13 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"types": ["node", "jest"]
},
"include": [],
"files": [],
"references": [
{
"path": "./tsconfig.spec.json"
}
]
}

View File

@ -0,0 +1,20 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node"]
},
"include": [
"**/*.test.ts",
"**/*.spec.ts",
"**/*.spec.tsx",
"**/*.test.tsx",
"**/*.spec.js",
"**/*.test.js",
"**/*.spec.jsx",
"**/*.test.jsx",
"**/*.d.ts",
"jest.config.ts"
]
}

View File

@ -4,7 +4,7 @@
"sourceRoot": "e2e/react-native", "sourceRoot": "e2e/react-native",
"projectType": "application", "projectType": "application",
"targets": { "targets": {
"e2e": {}, "e2e-macos": {},
"run-e2e-tests": {} "run-e2e-tests": {}
}, },
"implicitDependencies": ["react-native"] "implicitDependencies": ["react-native"]

View File

@ -7,6 +7,7 @@ import {
workspaceRoot, workspaceRoot,
} from '@nrwl/devkit'; } from '@nrwl/devkit';
import { angularCliVersion } from '@nrwl/workspace/src/utils/versions'; import { angularCliVersion } from '@nrwl/workspace/src/utils/versions';
import { dump } from '@zkochan/js-yaml';
import { ChildProcess, exec, execSync, ExecSyncOptions } from 'child_process'; import { ChildProcess, exec, execSync, ExecSyncOptions } from 'child_process';
import { import {
copySync, copySync,
@ -46,7 +47,8 @@ export function getPublishedVersion(): string {
export function detectPackageManager(dir: string = ''): PackageManager { export function detectPackageManager(dir: string = ''): PackageManager {
return existsSync(join(dir, 'yarn.lock')) return existsSync(join(dir, 'yarn.lock'))
? 'yarn' ? 'yarn'
: existsSync(join(dir, 'pnpm-lock.yaml')) : existsSync(join(dir, 'pnpm-lock.yaml')) ||
existsSync(join(dir, 'pnpm-workspace.yaml'))
? 'pnpm' ? 'pnpm'
: 'npm'; : 'npm';
} }
@ -374,6 +376,106 @@ export function newProject({
} }
} }
export function newLernaWorkspace({
name = uniq('lerna-proj'),
packageManager = getSelectedPackageManager(),
} = {}): string {
try {
const projScope = name;
projName = name;
const pm = getPackageManagerCommand({ packageManager });
createNonNxProjectDirectory(projScope, packageManager !== 'pnpm');
if (packageManager === 'pnpm') {
updateFile(
'pnpm-workspace.yaml',
dump({
packages: ['packages/*'],
})
);
updateFile(
'.npmrc',
'prefer-frozen-lockfile=false\nstrict-peer-dependencies=false\nauto-install-peers=true'
);
}
if (process.env.NX_VERBOSE_LOGGING == 'true') {
logInfo(`NX`, `E2E test has created a lerna workspace: ${tmpProjPath()}`);
}
// We need to force the real latest version of lerna to depend on our locally published version of nx
updateJson(`package.json`, (json) => {
// yarn workspaces can only be enabled in private projects
json.private = true;
const nxVersion = getPublishedVersion();
const overrides = {
...json.overrides,
nx: nxVersion,
'@nrwl/devkit': nxVersion,
};
if (packageManager === 'pnpm') {
json.pnpm = {
...json.pnpm,
overrides: {
...json.pnpm?.overrides,
...overrides,
},
};
} else if (packageManager === 'yarn') {
json.resolutions = {
...json.resolutions,
...overrides,
};
} else {
json.overrides = overrides;
}
return json;
});
/**
* Again, in order to ensure we override the required version relationships, we first install lerna as a devDep
* before running `lerna init`.
*/
execSync(
`${pm.addDev} lerna@${getLatestLernaVersion()}${
packageManager === 'pnpm'
? ' --workspace-root'
: packageManager === 'yarn'
? ' -W'
: ''
}`,
{
cwd: tmpProjPath(),
stdio: isVerbose() ? 'inherit' : 'pipe',
env: { CI: 'true', ...process.env },
encoding: 'utf-8',
}
);
execSync(`${pm.runLerna} init`, {
cwd: tmpProjPath(),
stdio: isVerbose() ? 'inherit' : 'pipe',
env: { CI: 'true', ...process.env },
encoding: 'utf-8',
});
execSync(pm.install, {
cwd: tmpProjPath(),
stdio: isVerbose() ? 'inherit' : 'pipe',
env: { CI: 'true', ...process.env },
encoding: 'utf-8',
});
return projScope;
} catch (e) {
logError(`Failed to set up lerna workspace for e2e tests.`, e.message);
throw e;
}
}
const KILL_PORT_DELAY = 5000; const KILL_PORT_DELAY = 5000;
export async function killPort(port: number): Promise<boolean> { export async function killPort(port: number): Promise<boolean> {
@ -416,6 +518,14 @@ export async function cleanupProject(opts?: RunCmdOpts) {
} }
} }
export function cleanupLernaWorkspace() {
if (isCI) {
try {
removeSync(tmpProjPath());
} catch (e) {}
}
}
export function runCypressTests() { export function runCypressTests() {
if (process.env.NX_E2E_RUN_CYPRESS === 'true') { if (process.env.NX_E2E_RUN_CYPRESS === 'true') {
ensureCypressInstallation(); ensureCypressInstallation();
@ -629,6 +739,42 @@ export function runCLI(
} }
} }
export function runLernaCLI(
command: string,
opts: RunCmdOpts = {
silenceError: false,
env: undefined,
}
): string {
try {
const pm = getPackageManagerCommand();
const logs = execSync(`${pm.runLerna} ${command}`, {
cwd: opts.cwd || tmpProjPath(),
env: { CI: 'true', ...(opts.env || getStrippedEnvironmentVariables()) },
encoding: 'utf-8',
stdio: 'pipe',
maxBuffer: 50 * 1024 * 1024,
});
const r = stripConsoleColors(logs);
if (isVerbose()) {
console.log(logs);
}
return r;
} catch (e) {
if (opts.silenceError) {
return stripConsoleColors(e.stdout?.toString() + e.stderr?.toString());
} else {
logError(
`Original command: ${command}`,
`${e.stdout?.toString()}\n\n${e.stderr?.toString()}`
);
throw e;
}
}
}
/** /**
* Remove log colors for fail proof string search * Remove log colors for fail proof string search
* @param log * @param log
@ -882,6 +1028,7 @@ export function getPackageManagerCommand({
addProd: string; addProd: string;
addDev: string; addDev: string;
list: string; list: string;
runLerna: string;
} { } {
const npmMajorVersion = getNpmMajorVersion(); const npmMajorVersion = getNpmMajorVersion();
const publishedVersion = getPublishedVersion(); const publishedVersion = getPublishedVersion();
@ -899,6 +1046,7 @@ export function getPackageManagerCommand({
addProd: `npm install --legacy-peer-deps`, addProd: `npm install --legacy-peer-deps`,
addDev: `npm install --legacy-peer-deps -D`, addDev: `npm install --legacy-peer-deps -D`,
list: 'npm ls --depth 10', list: 'npm ls --depth 10',
runLerna: `npx lerna`,
}, },
yarn: { yarn: {
// `yarn create nx-workspace` is failing due to wrong global path // `yarn create nx-workspace` is failing due to wrong global path
@ -911,6 +1059,7 @@ export function getPackageManagerCommand({
addProd: `yarn add`, addProd: `yarn add`,
addDev: `yarn add -D`, addDev: `yarn add -D`,
list: 'npm ls --depth 10', list: 'npm ls --depth 10',
runLerna: `yarn lerna`,
}, },
// Pnpm 3.5+ adds nx to // Pnpm 3.5+ adds nx to
pnpm: { pnpm: {
@ -923,6 +1072,7 @@ export function getPackageManagerCommand({
addProd: `pnpm add`, addProd: `pnpm add`,
addDev: `pnpm add -D`, addDev: `pnpm add -D`,
list: 'npm ls --depth 10', list: 'npm ls --depth 10',
runLerna: `pnpm exec lerna`,
}, },
}[packageManager.trim() as PackageManager]; }[packageManager.trim() as PackageManager];
} }
@ -932,6 +1082,11 @@ function getNpmMajorVersion(): string {
return npmMajorVersion; return npmMajorVersion;
} }
function getLatestLernaVersion(): string {
const lernaVersion = execSync(`npm view lerna version`).toString().trim();
return lernaVersion;
}
export const packageManagerLockFile = { export const packageManagerLockFile = {
npm: 'package-lock.json', npm: 'package-lock.json',
yarn: 'yarn.lock', yarn: 'yarn.lock',

33
nx.json
View File

@ -95,6 +95,39 @@
{ {
"env": "SELECTED_CLI" "env": "SELECTED_CLI"
}, },
{
"env": "SELECTED_PM"
},
{
"env": "NX_E2E_CI_CACHE_KEY"
}
],
"options": {
"commands": [
{
"command": "yarn e2e-start-local-registry"
},
{
"command": "yarn e2e-build-package-publish"
},
{
"command": "nx run-e2e-tests {projectName}"
}
],
"parallel": false
}
},
"e2e-macos": {
"executor": "nx:run-commands",
"inputs": [
"default",
"^production",
{
"env": "SELECTED_CLI"
},
{
"env": "SELECTED_PM"
},
{ {
"env": "NX_E2E_CI_CACHE_KEY" "env": "NX_E2E_CI_CACHE_KEY"
} }