Skip to content

Commit b9d3449

Browse files
authored
test(utils): fix flaky git helper tests (#531)
1 parent a16d86c commit b9d3449

2 files changed

Lines changed: 130 additions & 118 deletions

File tree

packages/plugin-coverage/src/lib/runner/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export async function executeRunner(): Promise<void> {
3636
}
3737
}
3838

39-
// Caculate coverage from LCOV results
39+
// Calculate coverage from LCOV results
4040
const auditOutputs = await lcovResultsToAuditOutputs(reports, coverageTypes);
4141

4242
await ensureDirectoryExists(dirname(RUNNER_OUTPUT_PATH));
Lines changed: 129 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { mkdir, rm, writeFile } from 'node:fs/promises';
1+
import { mkdir, rm, stat, writeFile } from 'node:fs/promises';
22
import { join } from 'node:path';
33
import { type SimpleGit, simpleGit } from 'simple-git';
4-
import { expect } from 'vitest';
4+
import { afterAll, beforeAll, beforeEach, expect } from 'vitest';
55
import {
66
getCurrentBranchOrTag,
77
getGitRoot,
@@ -12,149 +12,161 @@ import {
1212
} from './git';
1313
import { toUnixPath } from './transform';
1414

15-
describe('git utils in a git repo with a branch and commits', () => {
16-
const baseDir = join(process.cwd(), 'tmp', 'testing-git-repo');
17-
const changesDir = join(baseDir, 'changes-dir');
18-
let git: SimpleGit;
15+
// we need a separate folder that is not cleaned in `global-setup.ts`, otherwise the tests can't execute in parallel
16+
const gitTestFolder = 'git-test';
17+
describe('git utils in a git repo', () => {
18+
const baseDir = join(process.cwd(), gitTestFolder);
19+
let emptyGit: SimpleGit;
1920

2021
beforeAll(async () => {
2122
await mkdir(baseDir, { recursive: true });
22-
await writeFile(join(baseDir, 'README.md'), '# hello-world\n');
23-
24-
git = simpleGit(baseDir);
25-
await git.init();
26-
27-
await git.addConfig('user.name', 'John Doe');
28-
await git.addConfig('user.email', 'john.doe@example.com');
29-
30-
await git.add('README.md');
31-
await git.commit('Create README');
32-
33-
await git.checkout(['master']);
23+
emptyGit = simpleGit(baseDir);
24+
await emptyGit.init();
25+
await emptyGit.addConfig('user.name', 'John Doe');
26+
await emptyGit.addConfig('user.email', 'john.doe@example.com');
3427
});
3528

3629
afterAll(async () => {
3730
await rm(baseDir, { recursive: true, force: true });
3831
});
3932

40-
beforeEach(async () => {
41-
await git.checkout(['-b', 'feature-branch']);
42-
await git.checkout(['master']);
33+
describe('without a branch and commits', () => {
34+
it('getCurrentBranchOrTag should throw if no branch or tag is given', async () => {
35+
await expect(getCurrentBranchOrTag(emptyGit)).rejects.toThrow(
36+
'Could not get current tag or branch.',
37+
);
38+
});
4339
});
4440

45-
afterEach(async () => {
46-
// @TODO try why restore/stash/clean/reset hard etc does not work
47-
await rm(changesDir, { recursive: true, force: true });
48-
await git.checkout(['master']);
49-
await git.deleteLocalBranch('feature-branch');
50-
});
41+
describe('with a branch and commits clean', () => {
42+
beforeAll(async () => {
43+
await writeFile(join(baseDir, 'README.md'), '# hello-world\n');
44+
await emptyGit.add('README.md');
45+
await emptyGit.commit('Create README');
5146

52-
it('should log latest commit', async () => {
53-
const gitCommitDateRegex =
54-
/^(Mon|Tue|Wed|Thu|Fri|Sat|Sun) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d{1,2} \d{2}:\d{2}:\d{2} \d{4} [+|-]\d{4}$/;
47+
await emptyGit.branch(['feature-branch']);
48+
await emptyGit.checkout(['master']);
49+
});
5550

56-
await expect(getLatestCommit(git)).resolves.toEqual({
57-
hash: expect.stringMatching(/^[\da-f]{40}$/),
58-
message: 'Create README',
59-
author: 'John Doe',
60-
date: expect.stringMatching(gitCommitDateRegex),
51+
afterAll(async () => {
52+
await emptyGit.checkout(['master']);
53+
await emptyGit.deleteLocalBranch('feature-branch');
6154
});
62-
});
6355

64-
it('should find Git root', async () => {
65-
await expect(getGitRoot(git)).resolves.toBe(toUnixPath(baseDir));
66-
});
56+
it('should log latest commit', async () => {
57+
const gitCommitDateRegex =
58+
/^(Mon|Tue|Wed|Thu|Fri|Sat|Sun) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d{1,2} \d{2}:\d{2}:\d{2} \d{4} [+|-]\d{4}$/;
6759

68-
it('should convert absolute path to relative Git path', async () => {
69-
await expect(
70-
toGitPath(join(process.cwd(), 'src', 'utils.ts')),
71-
).resolves.toBe('src/utils.ts');
72-
});
60+
await expect(getLatestCommit(emptyGit)).resolves.toEqual({
61+
hash: expect.stringMatching(/^[\da-f]{40}$/),
62+
message: 'Create README',
63+
author: 'John Doe',
64+
date: expect.stringMatching(gitCommitDateRegex),
65+
});
66+
});
7367

74-
it('should convert relative Windows path to relative Git path', async () => {
75-
await expect(toGitPath('Backend\\API\\Startup.cs')).resolves.toBe(
76-
'Backend/API/Startup.cs',
77-
);
78-
});
68+
it('should find Git root', async () => {
69+
await expect(getGitRoot(emptyGit)).resolves.toBe(toUnixPath(baseDir));
70+
});
7971

80-
it('should keep relative Unix path as is (already a Git path)', async () => {
81-
await expect(toGitPath('Backend/API/Startup.cs')).resolves.toBe(
82-
'Backend/API/Startup.cs',
83-
);
84-
});
72+
it('should convert absolute path to relative Git path', async () => {
73+
await expect(
74+
toGitPath(join(baseDir, 'src', 'utils.ts'), emptyGit),
75+
).resolves.toBe('src/utils.ts');
76+
});
8577

86-
it('guardAgainstLocalChanges should throw if history is dirty', async () => {
87-
await mkdir(changesDir, { recursive: true });
88-
await writeFile(join(changesDir, 'change.md'), '# hello-change\n');
89-
await expect(guardAgainstLocalChanges(git)).rejects.toThrow(
90-
'Working directory needs to be clean before we you can proceed. Commit your local changes or stash them.',
91-
);
92-
});
78+
it('should convert relative Windows path to relative Git path', async () => {
79+
await expect(
80+
toGitPath('Backend\\API\\Startup.cs', emptyGit),
81+
).resolves.toBe('../Backend/API/Startup.cs');
82+
});
9383

94-
it('guardAgainstLocalChanges should not throw if history is clean', async () => {
95-
await expect(guardAgainstLocalChanges(git)).resolves.toBeUndefined();
96-
});
84+
it('should keep relative Unix path as is (already a Git path)', async () => {
85+
await expect(toGitPath('Backend/API/Startup.cs', emptyGit)).resolves.toBe(
86+
'../Backend/API/Startup.cs',
87+
);
88+
});
9789

98-
it('safeCheckout should checkout target branch in clean state', async () => {
99-
await expect(git.branch()).resolves.toEqual(
100-
expect.objectContaining({ current: 'master' }),
101-
);
102-
await expect(
103-
safeCheckout('feature-branch', {}, git),
104-
).resolves.toBeUndefined();
105-
await expect(git.branch()).resolves.toEqual(
106-
expect.objectContaining({ current: 'feature-branch' }),
107-
);
108-
});
90+
it('getCurrentBranchOrTag should log current branch', async () => {
91+
await expect(getCurrentBranchOrTag(emptyGit)).resolves.toBe('master');
92+
});
10993

110-
it('safeCheckout should throw if history is dirty', async () => {
111-
await mkdir(changesDir, { recursive: true });
112-
await writeFile(join(changesDir, 'change.md'), '# hello-change\n');
113-
await expect(safeCheckout('master', {}, git)).rejects.toThrow(
114-
'Working directory needs to be clean before we you can proceed. Commit your local changes or stash them.',
115-
);
116-
});
94+
it('guardAgainstLocalChanges should not throw if history is clean', async () => {
95+
await expect(guardAgainstLocalChanges(emptyGit)).resolves.toBeUndefined();
96+
});
11797

118-
it('safeCheckout should clean local changes and check out to feature-branch', async () => {
119-
// needs to get reset to be clean
120-
await mkdir(changesDir, { recursive: true });
121-
await writeFile(join(changesDir, 'change.md'), '# hello-change\n');
122-
// needs to get cleaned to be clean
123-
await writeFile(join(baseDir, 'README.md'), '# hello-world-2\n');
124-
125-
await expect(
126-
safeCheckout('feature-branch', { forceCleanStatus: true }, git),
127-
).resolves.toBeUndefined();
128-
await expect(git.branch()).resolves.toEqual(
129-
expect.objectContaining({ current: 'feature-branch' }),
130-
);
131-
await expect(git.status()).resolves.toEqual(
132-
expect.objectContaining({ files: [] }),
133-
);
134-
});
98+
it('safeCheckout should checkout feature-branch in clean state', async () => {
99+
await expect(
100+
safeCheckout('feature-branch', {}, emptyGit),
101+
).resolves.toBeUndefined();
102+
await expect(emptyGit.branch()).resolves.toEqual(
103+
expect.objectContaining({ current: 'feature-branch' }),
104+
);
105+
});
135106

136-
it('getCurrentBranchOrTag should log current branch', async () => {
137-
await expect(getCurrentBranchOrTag(git)).resolves.toBe('master');
107+
it('safeCheckout should throw if a given branch does not exist', async () => {
108+
await expect(
109+
safeCheckout('non-existing-branch', {}, emptyGit),
110+
).rejects.toThrow(
111+
"pathspec 'non-existing-branch' did not match any file(s) known to git",
112+
);
113+
});
138114
});
139-
});
140115

141-
describe('git utils in a git repo without a branch and commits', () => {
142-
const baseDir = join(process.cwd(), 'tmp', 'testing-git-repo');
143-
let git: SimpleGit;
116+
describe('with a branch and commits dirty', () => {
117+
const newFilePath = join(baseDir, 'new-file.md');
144118

145-
beforeAll(async () => {
146-
await mkdir(baseDir, { recursive: true });
147-
git = simpleGit(baseDir);
148-
await git.init();
149-
});
119+
beforeAll(async () => {
120+
await writeFile(join(baseDir, 'README.md'), '# hello-world\n');
121+
await emptyGit.add('README.md');
122+
await emptyGit.commit('Create README');
150123

151-
afterAll(async () => {
152-
await rm(baseDir, { recursive: true, force: true });
153-
});
124+
await emptyGit.branch(['feature-branch']);
125+
await emptyGit.checkout(['master']);
126+
});
154127

155-
it('getCurrentBranchOrTag should throw if no branch is given', async () => {
156-
await expect(getCurrentBranchOrTag(git)).rejects.toThrow(
157-
'Could not get current tag or branch.',
158-
);
128+
beforeEach(async () => {
129+
await writeFile(newFilePath, '# New File\n');
130+
});
131+
132+
afterEach(async () => {
133+
try {
134+
const s = await stat(newFilePath);
135+
if (s.isFile()) {
136+
await rm(newFilePath);
137+
}
138+
} catch {
139+
// file not present (already cleaned)
140+
}
141+
});
142+
143+
afterAll(async () => {
144+
await emptyGit.checkout(['master']);
145+
await emptyGit.deleteLocalBranch('feature-branch');
146+
});
147+
148+
it('safeCheckout should clean local changes and check out to feature-branch', async () => {
149+
await expect(
150+
safeCheckout('feature-branch', { forceCleanStatus: true }, emptyGit),
151+
).resolves.toBeUndefined();
152+
await expect(emptyGit.branch()).resolves.toEqual(
153+
expect.objectContaining({ current: 'feature-branch' }),
154+
);
155+
await expect(emptyGit.status()).resolves.toEqual(
156+
expect.objectContaining({ files: [] }),
157+
);
158+
});
159+
160+
it('safeCheckout should throw if history is dirty', async () => {
161+
await expect(safeCheckout('master', {}, emptyGit)).rejects.toThrow(
162+
'Working directory needs to be clean before we you can proceed. Commit your local changes or stash them.',
163+
);
164+
});
165+
166+
it('guardAgainstLocalChanges should throw if history is dirty', async () => {
167+
await expect(guardAgainstLocalChanges(emptyGit)).rejects.toThrow(
168+
'Working directory needs to be clean before we you can proceed. Commit your local changes or stash them.',
169+
);
170+
});
159171
});
160172
});

0 commit comments

Comments
 (0)