From fb9e00a54a68402e25754799e40056c08493bd61 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 10 Sep 2022 14:37:49 +0200 Subject: [PATCH 1/5] create-app: move mock-fs calls into describe blocks Signed-off-by: Patrik Oldsberg --- packages/create-app/src/createApp.test.ts | 22 +- packages/create-app/src/lib/tasks.test.ts | 352 +++++++++++----------- 2 files changed, 190 insertions(+), 184 deletions(-) diff --git a/packages/create-app/src/createApp.test.ts b/packages/create-app/src/createApp.test.ts index d339460243..8e7b3bae7d 100644 --- a/packages/create-app/src/createApp.test.ts +++ b/packages/create-app/src/createApp.test.ts @@ -28,17 +28,6 @@ jest.mock('./lib/versions', () => ({ packageVersions: { root: '1.0.0' }, })); -beforeAll(() => { - mockFs({ - [`${__dirname}/package.json`]: '', // required by `findPaths(__dirname)` - 'templates/': mockFs.load(path.resolve(__dirname, '../templates/')), - }); -}); - -afterAll(() => { - mockFs.restore(); -}); - const promptMock = jest.spyOn(inquirer, 'prompt'); const checkPathExistsMock = jest.spyOn(tasks, 'checkPathExistsTask'); const templatingMock = jest.spyOn(tasks, 'templatingTask'); @@ -51,6 +40,17 @@ const moveAppMock = jest.spyOn(tasks, 'moveAppTask'); const buildAppMock = jest.spyOn(tasks, 'buildAppTask'); describe('command entrypoint', () => { + beforeAll(() => { + mockFs({ + [`${__dirname}/package.json`]: '', // required by `findPaths(__dirname)` + 'templates/': mockFs.load(path.resolve(__dirname, '../templates/')), + }); + }); + + afterAll(() => { + mockFs.restore(); + }); + beforeEach(() => { promptMock.mockResolvedValueOnce({ name: 'MyApp', diff --git a/packages/create-app/src/lib/tasks.test.ts b/packages/create-app/src/lib/tasks.test.ts index d2efe81b90..decc89fbd4 100644 --- a/packages/create-app/src/lib/tasks.test.ts +++ b/packages/create-app/src/lib/tasks.test.ts @@ -86,185 +86,191 @@ jest.mock('./versions', () => ({ }, })); -beforeEach(() => { - mockFs({ - 'projects/my-module.ts': '', - 'projects/dir/my-file.txt': '', - 'tmp/mockApp/.gitignore': '', - 'tmp/mockApp/package.json': '', - 'tmp/mockApp/packages/app/package.json': '', - // load templates into mock filesystem - 'templates/': mockFs.load(path.resolve(__dirname, '../../templates/')), - }); -}); - -afterEach(() => { - mockFs.restore(); -}); - -describe('checkAppExistsTask', () => { - it('should do nothing if the directory does not exist', async () => { - const dir = 'projects/'; - const name = 'MyNewApp'; - await expect(checkAppExistsTask(dir, name)).resolves.not.toThrow(); +describe('tasks', () => { + beforeEach(() => { + mockFs({ + 'projects/my-module.ts': '', + 'projects/dir/my-file.txt': '', + 'tmp/mockApp/.gitignore': '', + 'tmp/mockApp/package.json': '', + 'tmp/mockApp/packages/app/package.json': '', + // load templates into mock filesystem + 'templates/': mockFs.load(path.resolve(__dirname, '../../templates/')), + }); }); - it('should throw an error when a file of the same name exists', async () => { - const dir = 'projects/'; - const name = 'my-module.ts'; - await expect(checkAppExistsTask(dir, name)).rejects.toThrow( - 'already exists', - ); + afterEach(() => { + mockFs.restore(); }); - it('should throw an error when a directory of the same name exists', async () => { - const dir = 'projects/'; - const name = 'dir'; - await expect(checkAppExistsTask(dir, name)).rejects.toThrow( - 'already exists', - ); - }); -}); - -describe('checkPathExistsTask', () => { - it('should create a directory at the given path', async () => { - const appDir = 'projects/newProject'; - await expect(checkPathExistsTask(appDir)).resolves.not.toThrow(); - expect(fs.existsSync(appDir)).toBe(true); - }); - - it('should do nothing if a directory of the same name exists', async () => { - const appDir = 'projects/dir'; - await expect(checkPathExistsTask(appDir)).resolves.not.toThrow(); - expect(fs.existsSync(appDir)).toBe(true); - }); - - it('should fail if a file of the same name exists', async () => { - await expect(checkPathExistsTask('projects/my-module.ts')).rejects.toThrow( - 'already exists', - ); - }); -}); - -describe('createTemporaryAppFolderTask', () => { - it('should create a directory at a given path', async () => { - const tempDir = 'projects/tmpFolder'; - await expect(createTemporaryAppFolderTask(tempDir)).resolves.not.toThrow(); - expect(fs.existsSync(tempDir)).toBe(true); - }); - - it('should fail if a directory of the same name exists', async () => { - const tempDir = 'projects/dir'; - await expect(createTemporaryAppFolderTask(tempDir)).rejects.toThrow( - 'file already exists', - ); - }); - - it('should fail if a file of the same name exists', async () => { - const tempDir = 'projects/dir/my-file.txt'; - await expect(createTemporaryAppFolderTask(tempDir)).rejects.toThrow( - 'file already exists', - ); - }); -}); - -describe('buildAppTask', () => { - it('should change to `appDir` and run `yarn install` and `yarn tsc`', async () => { - const mockChdir = jest.spyOn(process, 'chdir'); - const mockExec = child_process.exec as unknown as jest.MockedFunction< - ( - command: string, - callback: (error: null, stdout: string, stderr: string) => void, - ) => void - >; - - // requires callback implementation to support `promisify` wrapper - // https://stackoverflow.com/a/60579617/10044859 - mockExec.mockImplementation((_command, callback) => { - callback(null, 'standard out', 'standard error'); + describe('checkAppExistsTask', () => { + it('should do nothing if the directory does not exist', async () => { + const dir = 'projects/'; + const name = 'MyNewApp'; + await expect(checkAppExistsTask(dir, name)).resolves.not.toThrow(); }); - const appDir = 'projects/dir'; - await expect(buildAppTask(appDir)).resolves.not.toThrow(); - expect(mockChdir).toHaveBeenCalledTimes(2); - expect(mockChdir).toHaveBeenNthCalledWith(1, appDir); - expect(mockChdir).toHaveBeenNthCalledWith(2, appDir); - expect(mockExec).toHaveBeenCalledTimes(2); - expect(mockExec).toHaveBeenNthCalledWith( - 1, - 'yarn install', - expect.any(Function), - ); - expect(mockExec).toHaveBeenNthCalledWith( - 2, - 'yarn tsc', - expect.any(Function), - ); - }); - - it('should fail if project directory does not exist', async () => { - const appDir = 'projects/missingProject'; - await expect(buildAppTask(appDir)).rejects.toThrow( - 'no such file or directory', - ); - }); -}); - -describe('moveAppTask', () => { - const tempDir = 'tmp/mockApp/'; - const id = 'myApp'; - - it('should move all files in the temp dir to the target dir', async () => { - const destination = 'projects/mockApp'; - await moveAppTask(tempDir, destination, id); - expect(fs.existsSync('projects/mockApp/.gitignore')).toBe(true); - expect(fs.existsSync('projects/mockApp/package.json')).toBe(true); - expect(fs.existsSync('projects/mockApp/packages/app/package.json')).toBe( - true, - ); - }); - - it('should fail to move files if destination already exists', async () => { - const destination = 'projects'; - await expect(moveAppTask(tempDir, destination, id)).rejects.toThrow( - 'dest already exists', - ); - }); - - it('should remove temporary files if move succeeded', async () => { - const destination = 'projects/mockApp'; - await moveAppTask(tempDir, destination, id); - expect(fs.existsSync('tmp/mockApp')).toBe(false); - }); - - it('should remove temporary files if move failed', async () => { - const destination = 'projects'; - await expect(moveAppTask(tempDir, destination, id)).rejects.toThrow(); - expect(fs.existsSync('tmp/mockApp')).toBe(false); - }); -}); - -describe('templatingTask', () => { - it('should generate a project populating context parameters', async () => { - const templateDir = 'templates/default-app'; - const destinationDir = 'templatedApp'; - const context = { - name: 'SuperCoolBackstageInstance', - dbTypeSqlite: true, - }; - await templatingTask(templateDir, destinationDir, context); - expect(fs.existsSync('templatedApp/package.json')).toBe(true); - expect(fs.existsSync('templatedApp/.dockerignore')).toBe(true); - await expect(fs.readJson('templatedApp/backstage.json')).resolves.toEqual({ - version: '1.2.3', + it('should throw an error when a file of the same name exists', async () => { + const dir = 'projects/'; + const name = 'my-module.ts'; + await expect(checkAppExistsTask(dir, name)).rejects.toThrow( + 'already exists', + ); + }); + + it('should throw an error when a directory of the same name exists', async () => { + const dir = 'projects/'; + const name = 'dir'; + await expect(checkAppExistsTask(dir, name)).rejects.toThrow( + 'already exists', + ); + }); + }); + + describe('checkPathExistsTask', () => { + it('should create a directory at the given path', async () => { + const appDir = 'projects/newProject'; + await expect(checkPathExistsTask(appDir)).resolves.not.toThrow(); + expect(fs.existsSync(appDir)).toBe(true); + }); + + it('should do nothing if a directory of the same name exists', async () => { + const appDir = 'projects/dir'; + await expect(checkPathExistsTask(appDir)).resolves.not.toThrow(); + expect(fs.existsSync(appDir)).toBe(true); + }); + + it('should fail if a file of the same name exists', async () => { + await expect( + checkPathExistsTask('projects/my-module.ts'), + ).rejects.toThrow('already exists'); + }); + }); + + describe('createTemporaryAppFolderTask', () => { + it('should create a directory at a given path', async () => { + const tempDir = 'projects/tmpFolder'; + await expect( + createTemporaryAppFolderTask(tempDir), + ).resolves.not.toThrow(); + expect(fs.existsSync(tempDir)).toBe(true); + }); + + it('should fail if a directory of the same name exists', async () => { + const tempDir = 'projects/dir'; + await expect(createTemporaryAppFolderTask(tempDir)).rejects.toThrow( + 'file already exists', + ); + }); + + it('should fail if a file of the same name exists', async () => { + const tempDir = 'projects/dir/my-file.txt'; + await expect(createTemporaryAppFolderTask(tempDir)).rejects.toThrow( + 'file already exists', + ); + }); + }); + + describe('buildAppTask', () => { + it('should change to `appDir` and run `yarn install` and `yarn tsc`', async () => { + const mockChdir = jest.spyOn(process, 'chdir'); + const mockExec = child_process.exec as unknown as jest.MockedFunction< + ( + command: string, + callback: (error: null, stdout: string, stderr: string) => void, + ) => void + >; + + // requires callback implementation to support `promisify` wrapper + // https://stackoverflow.com/a/60579617/10044859 + mockExec.mockImplementation((_command, callback) => { + callback(null, 'standard out', 'standard error'); + }); + + const appDir = 'projects/dir'; + await expect(buildAppTask(appDir)).resolves.not.toThrow(); + expect(mockChdir).toHaveBeenCalledTimes(2); + expect(mockChdir).toHaveBeenNthCalledWith(1, appDir); + expect(mockChdir).toHaveBeenNthCalledWith(2, appDir); + expect(mockExec).toHaveBeenCalledTimes(2); + expect(mockExec).toHaveBeenNthCalledWith( + 1, + 'yarn install', + expect.any(Function), + ); + expect(mockExec).toHaveBeenNthCalledWith( + 2, + 'yarn tsc', + expect.any(Function), + ); + }); + + it('should fail if project directory does not exist', async () => { + const appDir = 'projects/missingProject'; + await expect(buildAppTask(appDir)).rejects.toThrow( + 'no such file or directory', + ); + }); + }); + + describe('moveAppTask', () => { + const tempDir = 'tmp/mockApp/'; + const id = 'myApp'; + + it('should move all files in the temp dir to the target dir', async () => { + const destination = 'projects/mockApp'; + await moveAppTask(tempDir, destination, id); + expect(fs.existsSync('projects/mockApp/.gitignore')).toBe(true); + expect(fs.existsSync('projects/mockApp/package.json')).toBe(true); + expect(fs.existsSync('projects/mockApp/packages/app/package.json')).toBe( + true, + ); + }); + + it('should fail to move files if destination already exists', async () => { + const destination = 'projects'; + await expect(moveAppTask(tempDir, destination, id)).rejects.toThrow( + 'dest already exists', + ); + }); + + it('should remove temporary files if move succeeded', async () => { + const destination = 'projects/mockApp'; + await moveAppTask(tempDir, destination, id); + expect(fs.existsSync('tmp/mockApp')).toBe(false); + }); + + it('should remove temporary files if move failed', async () => { + const destination = 'projects'; + await expect(moveAppTask(tempDir, destination, id)).rejects.toThrow(); + expect(fs.existsSync('tmp/mockApp')).toBe(false); + }); + }); + + describe('templatingTask', () => { + it('should generate a project populating context parameters', async () => { + const templateDir = 'templates/default-app'; + const destinationDir = 'templatedApp'; + const context = { + name: 'SuperCoolBackstageInstance', + dbTypeSqlite: true, + }; + await templatingTask(templateDir, destinationDir, context); + expect(fs.existsSync('templatedApp/package.json')).toBe(true); + expect(fs.existsSync('templatedApp/.dockerignore')).toBe(true); + await expect(fs.readJson('templatedApp/backstage.json')).resolves.toEqual( + { + version: '1.2.3', + }, + ); + // catalog was populated with `context.name` + expect( + fs.readFileSync('templatedApp/catalog-info.yaml', 'utf-8'), + ).toContain('name: SuperCoolBackstageInstance'); + // backend dependencies include `sqlite3` from `context.SQLite` + expect( + fs.readFileSync('templatedApp/packages/backend/package.json', 'utf-8'), + ).toContain('sqlite3"'); }); - // catalog was populated with `context.name` - expect( - fs.readFileSync('templatedApp/catalog-info.yaml', 'utf-8'), - ).toContain('name: SuperCoolBackstageInstance'); - // backend dependencies include `sqlite3` from `context.SQLite` - expect( - fs.readFileSync('templatedApp/packages/backend/package.json', 'utf-8'), - ).toContain('sqlite3"'); }); }); From 292a08880752f5b8520df8940d08b883e9cd4201 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 10 Sep 2022 16:50:09 +0200 Subject: [PATCH 2/5] cli: add `repo test` command Signed-off-by: Patrik Oldsberg --- .changeset/little-roses-rule.md | 5 + packages/cli/cli-report.md | 110 +++++++++++++++++++++ packages/cli/src/commands/index.ts | 11 +++ packages/cli/src/commands/repo/test.ts | 126 +++++++++++++++++++++++++ packages/cli/src/commands/test.ts | 1 + 5 files changed, 253 insertions(+) create mode 100644 .changeset/little-roses-rule.md create mode 100644 packages/cli/src/commands/repo/test.ts diff --git a/.changeset/little-roses-rule.md b/.changeset/little-roses-rule.md new file mode 100644 index 0000000000..4acec131e6 --- /dev/null +++ b/.changeset/little-roses-rule.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Added a new `repo test` command. diff --git a/packages/cli/cli-report.md b/packages/cli/cli-report.md index 4b72748c40..b5d9421bdf 100644 --- a/packages/cli/cli-report.md +++ b/packages/cli/cli-report.md @@ -390,6 +390,7 @@ Commands: build [options] lint [options] clean + test [options] help [command] ``` @@ -425,6 +426,115 @@ Options: -h, --help ``` +### `backstage-cli repo test` + +``` +Usage: backstage-cli [--config=] [TestPathPattern] + +Options: + -h, --help + --version + --all + --automock + -b, --bail + --cache + --cacheDirectory + --changedFilesWithAncestor + --changedSince + --ci + --clearCache + --clearMocks + --collectCoverage + --collectCoverageFrom + --color + --colors + -c, --config + --coverage + --coverageDirectory + --coveragePathIgnorePatterns + --coverageProvider + --coverageReporters + --coverageThreshold + --debug + --detectLeaks + --detectOpenHandles + --env + --errorOnDeprecated + -e, --expand + --filter + --findRelatedTests + --forceExit + --globalSetup + --globalTeardown + --globals + --haste + --ignoreProjects + --init + --injectGlobals + --json + --lastCommit + --listTests + --logHeapUsage + --maxConcurrency + -w, --maxWorkers + --moduleDirectories + --moduleFileExtensions + --moduleNameMapper + --modulePathIgnorePatterns + --modulePaths + --noStackTrace + --notify + --notifyMode + -o, --onlyChanged + -f, --onlyFailures + --outputFile + --passWithNoTests + --preset + --prettierPath + --projects + --reporters + --resetMocks + --resetModules + --resolver + --restoreMocks + --rootDir + --roots + -i, --runInBand + --runTestsByPath + --runner + --selectProjects + --setupFiles + --setupFilesAfterEnv + --shard + --showConfig + --silent + --skipFilter + --snapshotSerializers + --testEnvironment + --testEnvironmentOptions + --testFailureExitCode + --testLocationInResults + --testMatch + -t, --testNamePattern + --testPathIgnorePatterns + --testPathPattern + --testRegex + --testResultsProcessor + --testRunner + --testSequencer + --testTimeout + --transform + --transformIgnorePatterns + --unmockedModulePathPatterns + -u, --updateSnapshot + --useStderr + --verbose + --watch + --watchAll + --watchPathIgnorePatterns + --watchman +``` + ### `backstage-cli test` ``` diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index 8f3317c437..dfa53096bd 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -72,6 +72,17 @@ export function registerRepoCommand(program: Command) { .action( lazy(() => import('./repo/list-deprecations').then(m => m.command)), ); + + command + .command('test') + .allowUnknownOption(true) // Allows the command to run, but we still need to parse raw args + .option( + '--since ', + 'Only test packages that changed since the specified ref', + ) + .helpOption(', --backstage-cli-help') // Let Jest handle help + .description('Run tests, forwarding args to Jest, defaulting to watch mode') + .action(lazy(() => import('./repo/test').then(m => m.command))); } export function registerScriptCommand(program: Command) { diff --git a/packages/cli/src/commands/repo/test.ts b/packages/cli/src/commands/repo/test.ts new file mode 100644 index 0000000000..0726d10777 --- /dev/null +++ b/packages/cli/src/commands/repo/test.ts @@ -0,0 +1,126 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Command, OptionValues } from 'commander'; +import { PackageGraph } from '../../lib/monorepo'; +import { paths } from '../../lib/paths'; +import { runCheck } from '../../lib/run'; + +function includesAnyOf(hayStack: string[], ...needles: string[]) { + for (const needle of needles) { + if (hayStack.includes(needle)) { + return true; + } + } + return false; +} + +function removeOptionArg(args: string[], option: string) { + let changed = false; + do { + changed = false; + + const index = args.indexOf(option); + if (index >= 0) { + changed = true; + args.splice(index, 2); + } + const indexEq = args.findIndex(arg => arg.startsWith(`${option}=`)); + if (indexEq >= 0) { + changed = true; + args.splice(indexEq, 1); + } + } while (changed); +} + +export async function command(opts: OptionValues, cmd: Command): Promise { + // all args are forwarded to jest + let parent = cmd; + while (parent.parent) { + parent = parent.parent; + } + const allArgs = parent.args as string[]; + const args = allArgs.slice(allArgs.indexOf('test') + 1); + + // Only include our config if caller isn't passing their own config + if (!includesAnyOf(args, '-c', '--config')) { + args.push('--config', paths.resolveOwn('config/jest.js')); + } + + if (!includesAnyOf(args, '--no-passWithNoTests', '--passWithNoTests=false')) { + args.push('--passWithNoTests'); + } + + // Run in watch mode unless in CI, coverage mode, or running all tests + if ( + !process.env.CI && + !args.includes('--coverage') && + // explicitly no watching + !includesAnyOf(args, '--no-watch', '--watch=false', '--watchAll=false') && + // already watching + !includesAnyOf(args, '--watch', '--watchAll') + ) { + const isGitRepo = () => + runCheck('git', 'rev-parse', '--is-inside-work-tree'); + const isMercurialRepo = () => runCheck('hg', '--cwd', '.', 'root'); + + if ((await isGitRepo()) || (await isMercurialRepo())) { + args.push('--watch'); + } else { + args.push('--watchAll'); + } + } + + if (opts.since) { + removeOptionArg(args, '--since'); + } + + if (opts.since && !args.some(arg => arg.startsWith('--selectProjects'))) { + const packages = await PackageGraph.listTargetPackages(); + const graph = PackageGraph.fromPackages(packages); + const changedPackages = await graph.listChangedPackages({ + ref: opts.since, + }); + + const packageNames = Array.from( + graph.collectPackageNames( + changedPackages.map(pkg => pkg.name), + pkg => pkg.allLocalDependents.keys(), + ), + ); + args.push('--selectProjects', ...packageNames); + } + + // This is the only thing that is not implemented by jest.run(), so we do it here instead + // https://github.com/facebook/jest/blob/cd8828f7bbec6e55b4df5e41e853a5133c4a3ee1/packages/jest-cli/bin/jest.js#L12 + if (!process.env.NODE_ENV) { + (process.env as any).NODE_ENV = 'test'; + } + + // This is to have a consistent timezone for when running tests that involve checking + // the formatting of date/times. + // https://stackoverflow.com/questions/56261381/how-do-i-set-a-timezone-in-my-jest-config + if (!process.env.TZ) { + process.env.TZ = 'UTC'; + } + + // This ensures that the process doesn't exit too early before stdout is flushed + if (args.includes('--help')) { + (process.stdout as any)._handle.setBlocking(true); + } + + await require('jest').run(args); +} diff --git a/packages/cli/src/commands/test.ts b/packages/cli/src/commands/test.ts index ec1bf8a7a0..b55919b2ce 100644 --- a/packages/cli/src/commands/test.ts +++ b/packages/cli/src/commands/test.ts @@ -78,6 +78,7 @@ export default async (_opts: OptionValues, cmd: Command) => { process.env.TZ = 'UTC'; } + // This ensures that the process doesn't exit too early before stdout is flushed if (args.includes('--help')) { (process.stdout as any)._handle.setBlocking(true); } From a7659ae4743ed0cacdc0094220c83514da1e4610 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 10 Sep 2022 18:36:23 +0200 Subject: [PATCH 3/5] github/workflows: update to use repo test and memory limit Signed-off-by: Patrik Oldsberg --- .github/workflows/ci.yml | 4 ++-- .github/workflows/deploy_packages.yml | 2 +- .github/workflows/verify_windows.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d63a8b6d77..3c6ac696e9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -197,7 +197,7 @@ jobs: - name: test changed packages if: ${{ steps.yarn-lock.outcome == 'success' }} - run: yarn lerna run test --since origin/master -- --coverage --runInBand + run: yarn backstage-cli repo test --maxWorkers=2 --workerIdleMemoryLimit=1300M --since origin/master env: BACKSTAGE_NEXT_TESTS: 1 BACKSTAGE_TEST_DISABLE_DOCKER: 1 @@ -208,7 +208,7 @@ jobs: - name: test all packages (and upload coverage) if: ${{ steps.yarn-lock.outcome == 'failure' }} run: | - yarn lerna run test -- --coverage --runInBand + yarn backstage-cli repo test --maxWorkers=2 --workerIdleMemoryLimit=1300M --coverage bash <(curl -s https://codecov.io/bash) -N $(git rev-parse FETCH_HEAD) env: BACKSTAGE_NEXT_TESTS: 1 diff --git a/.github/workflows/deploy_packages.yml b/.github/workflows/deploy_packages.yml index 6fa34e408f..f2e4cbeb4c 100644 --- a/.github/workflows/deploy_packages.yml +++ b/.github/workflows/deploy_packages.yml @@ -97,7 +97,7 @@ jobs: - name: test (and upload coverage) run: | - yarn lerna run test -- --coverage --runInBand + yarn backstage-cli repo test --maxWorkers=2 --workerIdleMemoryLimit=1300M --coverage bash <(curl -s https://codecov.io/bash) # Upload code coverage for some specific flags. Also see .codecov.yml bash <(curl -s https://codecov.io/bash) -f packages/core-app-api/coverage/* -F core-app-api diff --git a/.github/workflows/verify_windows.yml b/.github/workflows/verify_windows.yml index 0b12caf2fc..8bbaa645bf 100644 --- a/.github/workflows/verify_windows.yml +++ b/.github/workflows/verify_windows.yml @@ -46,7 +46,7 @@ jobs: run: yarn lint:type-deps - name: test - run: yarn lerna run test + run: yarn backstage-cli repo test --maxWorkers=2 --workerIdleMemoryLimit=1300M env: BACKSTAGE_NEXT_TESTS: 1 BACKSTAGE_TEST_DISABLE_DOCKER: 1 From 6d00e801460fefa515a93a8a6851f6e56799101c Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 10 Sep 2022 18:39:38 +0200 Subject: [PATCH 4/5] update package.json to use repo test Signed-off-by: Patrik Oldsberg --- .changeset/tiny-mails-bathe.md | 14 ++++++++++++++ package.json | 4 ++-- .../templates/default-app/package.json.hbs | 4 ++-- 3 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 .changeset/tiny-mails-bathe.md diff --git a/.changeset/tiny-mails-bathe.md b/.changeset/tiny-mails-bathe.md new file mode 100644 index 0000000000..868c2a8d36 --- /dev/null +++ b/.changeset/tiny-mails-bathe.md @@ -0,0 +1,14 @@ +--- +'@backstage/create-app': patch +--- + +Updated the root `test` scripts to use `backstage-cli repo test`. + +To apply this change to an existing app, make the following change to the root `package.json`: + +```diff +- "test": "backstage-cli test", +- "test:all": "lerna run test -- --coverage", ++ "test": "backstage-cli repo test", ++ "test:all": "backstage-cli repo test --coverage", +``` diff --git a/package.json b/package.json index 6603958253..95c2fa73da 100644 --- a/package.json +++ b/package.json @@ -15,8 +15,8 @@ "tsc": "tsc", "tsc:full": "backstage-cli repo clean && tsc --skipLibCheck false --incremental false", "clean": "backstage-cli repo clean", - "test": "backstage-cli test", - "test:all": "lerna run test -- --coverage", + "test": "backstage-cli repo test", + "test:all": "backstage-cli repo test --coverage", "lint": "backstage-cli repo lint --since origin/master", "lint:docs": "node ./scripts/check-docs-quality", "lint:all": "backstage-cli repo lint", diff --git a/packages/create-app/templates/default-app/package.json.hbs b/packages/create-app/templates/default-app/package.json.hbs index a4f8815822..dd4d937db0 100644 --- a/packages/create-app/templates/default-app/package.json.hbs +++ b/packages/create-app/templates/default-app/package.json.hbs @@ -14,8 +14,8 @@ "tsc": "tsc", "tsc:full": "tsc --skipLibCheck false --incremental false", "clean": "backstage-cli repo clean", - "test": "backstage-cli test", - "test:all": "lerna run test -- --coverage", + "test": "backstage-cli repo test", + "test:all": "backstage-cli repo test --coverage", "lint": "backstage-cli repo lint --since origin/master", "lint:all": "backstage-cli repo lint", "prettier:check": "prettier --check .", From 947a9f216557875a64e2bf15d7f58022dc39e6ae Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 20 Sep 2022 13:54:49 +0200 Subject: [PATCH 5/5] cli: flip repo command help options to use --jest-help instead Signed-off-by: Patrik Oldsberg --- packages/cli/cli-report.md | 104 +------------------------ packages/cli/src/commands/index.ts | 5 +- packages/cli/src/commands/repo/test.ts | 4 +- 3 files changed, 10 insertions(+), 103 deletions(-) diff --git a/packages/cli/cli-report.md b/packages/cli/cli-report.md index b5d9421bdf..9afd12fb48 100644 --- a/packages/cli/cli-report.md +++ b/packages/cli/cli-report.md @@ -429,110 +429,12 @@ Options: ### `backstage-cli repo test` ``` -Usage: backstage-cli [--config=] [TestPathPattern] +Usage: backstage-cli repo test [options] Options: + --since + --jest-help -h, --help - --version - --all - --automock - -b, --bail - --cache - --cacheDirectory - --changedFilesWithAncestor - --changedSince - --ci - --clearCache - --clearMocks - --collectCoverage - --collectCoverageFrom - --color - --colors - -c, --config - --coverage - --coverageDirectory - --coveragePathIgnorePatterns - --coverageProvider - --coverageReporters - --coverageThreshold - --debug - --detectLeaks - --detectOpenHandles - --env - --errorOnDeprecated - -e, --expand - --filter - --findRelatedTests - --forceExit - --globalSetup - --globalTeardown - --globals - --haste - --ignoreProjects - --init - --injectGlobals - --json - --lastCommit - --listTests - --logHeapUsage - --maxConcurrency - -w, --maxWorkers - --moduleDirectories - --moduleFileExtensions - --moduleNameMapper - --modulePathIgnorePatterns - --modulePaths - --noStackTrace - --notify - --notifyMode - -o, --onlyChanged - -f, --onlyFailures - --outputFile - --passWithNoTests - --preset - --prettierPath - --projects - --reporters - --resetMocks - --resetModules - --resolver - --restoreMocks - --rootDir - --roots - -i, --runInBand - --runTestsByPath - --runner - --selectProjects - --setupFiles - --setupFilesAfterEnv - --shard - --showConfig - --silent - --skipFilter - --snapshotSerializers - --testEnvironment - --testEnvironmentOptions - --testFailureExitCode - --testLocationInResults - --testMatch - -t, --testNamePattern - --testPathIgnorePatterns - --testPathPattern - --testRegex - --testResultsProcessor - --testRunner - --testSequencer - --testTimeout - --transform - --transformIgnorePatterns - --unmockedModulePathPatterns - -u, --updateSnapshot - --useStderr - --verbose - --watch - --watchAll - --watchPathIgnorePatterns - --watchman ``` ### `backstage-cli test` diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index dfa53096bd..266b4fd9f1 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -80,7 +80,10 @@ export function registerRepoCommand(program: Command) { '--since ', 'Only test packages that changed since the specified ref', ) - .helpOption(', --backstage-cli-help') // Let Jest handle help + .option( + '--jest-help', + 'Show help for Jest CLI options, which are passed through', + ) .description('Run tests, forwarding args to Jest, defaulting to watch mode') .action(lazy(() => import('./repo/test').then(m => m.command))); } diff --git a/packages/cli/src/commands/repo/test.ts b/packages/cli/src/commands/repo/test.ts index 0726d10777..e873ac2b9b 100644 --- a/packages/cli/src/commands/repo/test.ts +++ b/packages/cli/src/commands/repo/test.ts @@ -118,7 +118,9 @@ export async function command(opts: OptionValues, cmd: Command): Promise { } // This ensures that the process doesn't exit too early before stdout is flushed - if (args.includes('--help')) { + if (args.includes('--jest-help')) { + removeOptionArg(args, '--jest-help'); + args.push('--help'); (process.stdout as any)._handle.setBlocking(true); }