Skip to content

Commit f873be1

Browse files
authored
Add more tests (#1100)
1 parent f0cb892 commit f873be1

20 files changed

Lines changed: 1393 additions & 106 deletions
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "patch",
3+
"comment": "Allow writeChangeFiles to work outside a git repo for testing",
4+
"packageName": "beachball",
5+
"email": "elcraig@microsoft.com",
6+
"dependentChangeType": "patch"
7+
}

src/__fixtures__/changeFiles.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export function getChange(
3030

3131
/**
3232
* Generates and writes change files for the given packages.
33-
* Also commits if `options.commit` is true.
33+
* Also commits if `options.commit` is true and the context is a git repo.
3434
* @param changes Array of package names or partial change files (which must include `packageName`).
3535
* Default values:
3636
* - `type: 'minor'`

src/__fixtures__/createTestFileStructure.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,28 @@ export function createTestFileStructure(files: Record<string, string | object>):
1818

1919
return testFolderPath.replace(/\\/g, '/');
2020
}
21+
22+
/**
23+
* Create a test file structure for a named fixture (similar to the ones in `RepositoryFactory`).
24+
*/
25+
export function createTestFileStructureType(type: 'monorepo'): string {
26+
switch (type) {
27+
case 'monorepo':
28+
return createTestFileStructure({
29+
'package.json': {
30+
name: 'monorepo-fixture',
31+
version: '1.0.0',
32+
private: true,
33+
workspaces: ['packages/*', 'packages/grouped/*'],
34+
},
35+
'packages/foo/package.json': { name: 'foo', version: '1.0.0', dependencies: { bar: '^1.3.4' } },
36+
'packages/bar/package.json': { name: 'bar', version: '1.3.4', dependencies: { baz: '^1.3.4' } },
37+
'packages/baz/package.json': { name: 'baz', version: '1.3.4' },
38+
'packages/grouped/a/package.json': { name: 'a', version: '3.1.2' },
39+
'packages/grouped/b/package.json': { name: 'b', version: '3.1.2' },
40+
'yarn.lock': '',
41+
});
42+
default:
43+
throw new Error(`Unknown test file structure type: ${type}`);
44+
}
45+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { jest } from '@jest/globals';
2+
import type { MockLogs } from './mockLogs';
3+
4+
/**
5+
* Throw an error when `process.exit()` is called. The message will include any error logs.
6+
*/
7+
export function mockProcessExit(logs: MockLogs) {
8+
return jest.spyOn(process, 'exit').mockImplementation(code => {
9+
throw new Error(`process.exit(${code ?? ''}) called. Logged errors:\n${logs.getMockLines('error')}`);
10+
});
11+
}

src/__fixtures__/packageInfos.ts

Lines changed: 54 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
1+
import path from 'path';
2+
import type { PackageInfo as WSPackageInfo } from 'workspace-tools';
13
import type { PackageInfo, PackageInfos } from '../types/PackageInfo';
24
import { getPackageInfosWithOptions } from '../options/getPackageInfosWithOptions';
35
import type { CliOptions, RepoOptions } from '../types/BeachballOptions';
46
import { defaultRemoteBranchName } from './gitDefaults';
57

8+
export type PartialPackageInfo = Omit<Partial<PackageInfo>, 'combinedOptions' | 'packageOptions'> & {
9+
beachball?: PackageInfo['packageOptions'];
10+
};
11+
612
export type PartialPackageInfos = {
7-
[name: string]: Omit<Partial<PackageInfo>, 'combinedOptions' | 'packageOptions'> & {
8-
beachball?: PackageInfo['packageOptions'];
9-
};
13+
[name: string]: PartialPackageInfo;
1014
};
1115

1216
/**
@@ -16,31 +20,70 @@ export type PartialPackageInfos = {
1620
* name: '<key>',
1721
* version: '1.0.0',
1822
* private: false,
19-
* packageJsonPath: ''
23+
* packageJsonPath: `${cliOptions.path || ''}/packages/<basename>/package.json`,
2024
* }
2125
* ```
2226
* Other defaults and values are filled by the actual logic in `getPackageInfosWithOptions`,
2327
* including the overrides in `repoOptions` merged in realistic order.
28+
* @param repoOptions Extra repo options. A `branch` option is included automatically (to prevent lookup).
29+
* @param cliOptions CLI options. Use `path` to specify the CWD.
2430
*/
2531
export function makePackageInfos(
2632
packageInfos: PartialPackageInfos,
2733
repoOptions?: Partial<RepoOptions>,
2834
cliOptions?: Partial<CliOptions>
2935
): PackageInfos {
36+
const cwd = cliOptions?.path || '';
3037
return getPackageInfosWithOptions(
31-
Object.entries(packageInfos).map(([name, info]) => {
32-
return {
38+
Object.entries(packageInfos).map(
39+
([name, info]): WSPackageInfo => ({
3340
name,
3441
version: '1.0.0',
3542
private: false,
36-
packageOptions: {},
37-
packageJsonPath: '',
43+
packageJsonPath: path.join(cwd, 'packages', path.basename(name), 'package.json'),
3844
...info,
39-
};
40-
}),
45+
})
46+
),
4147
{
4248
repoOptions: { branch: defaultRemoteBranchName, ...repoOptions },
43-
cliOptions: { path: '', command: '', ...cliOptions },
49+
cliOptions: { path: cwd, command: '', ...cliOptions },
4450
}
4551
);
4652
}
53+
54+
/**
55+
* Makes a properly typed PackageInfos object from a partial object, filling in defaults:
56+
* ```js
57+
* {
58+
* name: '<folder basename>',
59+
* version: '1.0.0',
60+
* private: false,
61+
* packageJsonPath: `${cwd}/${key}/package.json`,
62+
* }
63+
* ```
64+
* Other defaults and values are filled by the actual logic in `getPackageInfosWithOptions`,
65+
* including the overrides in `repoOptions` merged in realistic order.
66+
*/
67+
export function makePackageInfosByFolder(params: {
68+
packages: { [folder: string]: PartialPackageInfo };
69+
cwd: string;
70+
/** Extra repo options. A `branch` option is included automatically (to prevent lookup). */
71+
repoOptions?: Partial<RepoOptions>;
72+
/** Extra CLI options */
73+
cliOptions?: Partial<Omit<CliOptions, 'path'>>;
74+
}): PackageInfos {
75+
const { packages, cwd, repoOptions, cliOptions } = params;
76+
return makePackageInfos(
77+
Object.fromEntries(
78+
Object.entries(packages).map(([folder, info]) => [
79+
path.basename(folder),
80+
{
81+
packageJsonPath: path.join(cwd, folder, 'package.json'),
82+
...info,
83+
},
84+
])
85+
),
86+
repoOptions,
87+
{ ...cliOptions, path: cwd }
88+
);
89+
}

src/__fixtures__/repository.ts

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import path from 'path';
22
import * as fs from 'fs-extra';
3-
import { tmpdir } from './tmpdir';
3+
import { removeTempDir, tmpdir } from './tmpdir';
44
import { git, type GitProcessOutput } from 'workspace-tools';
55
import {
66
defaultBranchName,
@@ -9,7 +9,6 @@ import {
99
optsWithLang,
1010
setDefaultBranchName,
1111
} from './gitDefaults';
12-
import { env } from '../env';
1312

1413
/**
1514
* Clone options. See the docs for details on behavior and interaction of these options.
@@ -223,15 +222,7 @@ ${gitResult.stderr.toString()}`);
223222
* and the agents are wiped after each job, so manually deleting the files just slows things down.
224223
*/
225224
cleanUp(): void {
226-
try {
227-
// This occasionally throws on Windows with "resource busy"
228-
if (this.root && !env.isCI) {
229-
fs.removeSync(this.root);
230-
}
231-
} catch (err) {
232-
// This is non-fatal since the temp dir will eventually be cleaned up automatically
233-
console.warn(`Could not clean up repository: ${err}`);
234-
}
225+
removeTempDir(this.root);
235226
this.root = undefined;
236227
}
237228
}

src/__fixtures__/repositoryFactory.ts

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@ import path from 'path';
33
import type { PackageJson } from '../types/PackageInfo';
44
import { Repository, type RepositoryCloneOptions } from './repository';
55
import type { BeachballOptions } from '../types/BeachballOptions';
6-
import { tmpdir } from './tmpdir';
6+
import { removeTempDir, tmpdir } from './tmpdir';
77
import { gitFailFast } from 'workspace-tools';
88
import { setDefaultBranchName } from './gitDefaults';
9-
import { env } from '../env';
109
import { cloneObject } from '../object/cloneObject';
1110

1211
/**
@@ -262,16 +261,9 @@ export class RepositoryFactory {
262261
cleanUp(): void {
263262
if (!this.root) return;
264263

265-
try {
266-
// This occasionally throws on Windows with "resource busy"
267-
if (this.root && !env.isCI) {
268-
fs.removeSync(this.root);
269-
}
270-
} catch (err) {
271-
// This is non-fatal since the temp dir will eventually be cleaned up automatically
272-
console.warn(`Could not clean up factory: ${err}`);
273-
}
264+
removeTempDir(this.root);
274265
this.root = undefined;
266+
275267
for (const repo of this.childRepos) {
276268
repo.cleanUp();
277269
}

src/__fixtures__/tmpdir.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as tmp from 'tmp';
33
import { normalizedTmpdir } from 'normalized-tmpdir';
44
// import console to ensure that warnings are always logged if needed (no mocking)
55
import realConsole from 'console';
6+
import { env } from '../env';
67

78
// tmp is supposed to be able to clean up automatically, but this doesn't always work within jest.
89
// So we attempt to use its built-in cleanup mechanisms, but tests should ideally do their own cleanup too.
@@ -27,11 +28,20 @@ export function tmpdir(options?: tmp.DirOptions): string {
2728
}).name;
2829
}
2930

30-
/** Remove the temp directory, ignoring errors */
31-
export function removeTempDir(dir: string): void {
31+
/**
32+
* Clean up the folder if this is a local build.
33+
*
34+
* Doing this in CI is unnecessary because all the fixtures use unique temp directories (no collisions)
35+
* and the agents are wiped after each job, so manually deleting the files just slows things down.
36+
*/
37+
export function removeTempDir(dir: string | undefined): void {
3238
try {
33-
fs.rmSync(dir, { force: true });
34-
} catch {
35-
// ignore
39+
// This occasionally throws on Windows with "resource busy"
40+
if (dir && !env.isCI) {
41+
fs.removeSync(dir);
42+
}
43+
} catch (err) {
44+
// This is non-fatal since the temp dir will eventually be cleaned up automatically
45+
console.warn(`Could not clean up temp folder ${dir}:\n${err}`);
3646
}
3747
}

0 commit comments

Comments
 (0)