Skip to content

Commit 58ca821

Browse files
committed
[server] Add more "prebuild selection" tests to ContextService
1 parent 766b421 commit 58ca821

File tree

1 file changed

+183
-23
lines changed

1 file changed

+183
-23
lines changed

components/server/src/workspace/context-service.spec.db.ts

Lines changed: 183 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* See License.AGPL.txt in the project root for license information.
55
*/
66

7-
import { TypeORM } from "@gitpod/gitpod-db/lib";
7+
import { TypeORM, WorkspaceDB } from "@gitpod/gitpod-db/lib";
88
import { resetDB } from "@gitpod/gitpod-db/lib/test/reset-db";
99
import {
1010
CommitContext,
@@ -21,6 +21,7 @@ import {
2121
CommitInfo,
2222
Repository,
2323
RepositoryInfo,
24+
WorkspaceConfig,
2425
} from "@gitpod/gitpod-protocol";
2526
import * as chai from "chai";
2627
import { Container } from "inversify";
@@ -64,23 +65,36 @@ const gitpodEmptyContext = {
6465
};
6566

6667
// MockRepositoryProvider is a class implementing the RepositoryProvider interface, which allows to pass in commitHistory and commitInfo as needed
68+
69+
type NamedBranch = Omit<Branch, "commit">;
70+
function toBranch(b: { branch: NamedBranch; commits: CommitInfo[] }): Branch {
71+
return {
72+
...b.branch,
73+
commit: {
74+
sha: head(b.commits).sha,
75+
author: head(b.commits).author,
76+
commitMessage: head(b.commits).commitMessage,
77+
},
78+
};
79+
}
6780
class MockRepositoryProvider implements RepositoryProvider {
68-
branches: Map<string, { branch: Branch; commits: CommitInfo[] }> = new Map();
81+
branches: Map<string, { branch: NamedBranch; commits: CommitInfo[] }> = new Map();
6982

7083
addBranch(branch: Omit<Branch, "commit">, commits: CommitInfo[]) {
7184
this.branches.set(branch.name, {
72-
branch: {
73-
...branch,
74-
commit: {
75-
sha: head(commits).sha,
76-
author: head(commits).author,
77-
commitMessage: head(commits).commitMessage,
78-
},
79-
},
85+
branch,
8086
commits,
8187
});
8288
}
8389

90+
pushCommit(branch: string, commit: CommitInfo) {
91+
const b = this.branches.get(branch);
92+
if (!b) {
93+
throw new Error("branch not found");
94+
}
95+
b.commits.push(commit);
96+
}
97+
8498
async hasReadAccess(user: any, owner: string, repo: string): Promise<boolean> {
8599
return true;
86100
}
@@ -89,7 +103,7 @@ class MockRepositoryProvider implements RepositoryProvider {
89103
if (!branch) {
90104
throw new Error("branch not found");
91105
}
92-
return branch.branch;
106+
return toBranch(branch);
93107
}
94108
async getRepo(user: User, owner: string, repo: string): Promise<Repository> {
95109
return {
@@ -110,7 +124,7 @@ class MockRepositoryProvider implements RepositoryProvider {
110124
for (const [i, c] of b.commits.entries()) {
111125
if (c.sha === ref) {
112126
// this commit, and everything before it
113-
return b.commits.slice(0, i + 1).map((c) => c.sha);
127+
return b.commits.slice(0, i).map((c) => c.sha);
114128
}
115129
}
116130
}
@@ -121,7 +135,7 @@ class MockRepositoryProvider implements RepositoryProvider {
121135
}
122136

123137
async getBranches(user: User, owner: string, repo: string): Promise<Branch[]> {
124-
return Array.from(this.branches.values()).map((b) => b.branch);
138+
return Array.from(this.branches.values()).map((b) => toBranch(b));
125139
}
126140

127141
async getUserRepos(user: User): Promise<RepositoryInfo[]> {
@@ -173,6 +187,17 @@ describe("ContextService", async () => {
173187
},
174188
};
175189
},
190+
defaultConfig: async (organizationId?: string): Promise<WorkspaceConfig> => {
191+
return {
192+
ports: [],
193+
tasks: [],
194+
image: "gitpod/workspace-base",
195+
ideCredentials: "some-credentials",
196+
};
197+
},
198+
getDefaultImage: async (organizationId?: string): Promise<string> => {
199+
return "gitpod/workspace-base";
200+
},
176201
} as any as ConfigProvider);
177202

178203
const bindContextParser = () => {
@@ -222,16 +247,42 @@ describe("ContextService", async () => {
222247
return c();
223248
}
224249

225-
const mainBranch = await mockRepositoryProvider.getBranch(user, "gitpod-io", "empty", "main");
226-
const r: CommitContext = {
227-
title: mainBranch.commit.commitMessage,
228-
ref: mainBranch.name,
229-
refType: "branch",
230-
revision: mainBranch.commit.sha,
231-
repository: await mockRepositoryProvider.getRepo(user, "gitpod-io", "empty"),
232-
normalizedContextURL: mainBranch.htmlUrl,
233-
};
234-
return r;
250+
async function createCommitContextForBranch(branchName: string): Promise<CommitContext> {
251+
const branch = await mockRepositoryProvider.getBranch(user, "gitpod-io", "empty", branchName);
252+
const r: CommitContext = {
253+
title: branch.commit.commitMessage,
254+
ref: branch.name,
255+
refType: "branch",
256+
revision: branch.commit.sha,
257+
repository: await mockRepositoryProvider.getRepo(user, "gitpod-io", "empty"),
258+
normalizedContextURL: branch.htmlUrl,
259+
};
260+
return r;
261+
}
262+
263+
const branches = await mockRepositoryProvider.getBranches(user, "gitpod-io", "empty");
264+
for (const b of branches) {
265+
if (b.htmlUrl === url) {
266+
return createCommitContextForBranch(b.name);
267+
}
268+
}
269+
for (const [_, b] of mockRepositoryProvider.branches) {
270+
for (const commit of b.commits) {
271+
const commitContextUrl = `https://github.com/gitpod-io/empty/commit/${commit.sha}`;
272+
if (commitContextUrl === url) {
273+
const r: CommitContext = {
274+
title: commit.commitMessage,
275+
ref: commit.sha,
276+
refType: "revision",
277+
revision: commit.sha,
278+
repository: await mockRepositoryProvider.getRepo(user, "gitpod-io", "empty"),
279+
normalizedContextURL: commitContextUrl,
280+
};
281+
return r;
282+
}
283+
}
284+
}
285+
return createCommitContextForBranch(gitpodEmptyContext.repository.defaultBranch);
235286
},
236287
} as any as ContextParser);
237288
};
@@ -381,6 +432,115 @@ describe("ContextService", async () => {
381432
expect(PrebuiltWorkspaceContext.is(ctx.context)).to.equal(true);
382433
});
383434

435+
it("should ignore unfinished prebuild", async () => {
436+
// prepare test scenario: two prebuilds
437+
const revision1 = "000000";
438+
mockRepositoryProvider.addBranch(
439+
{ name: "branch-with-history", htmlUrl: "https://github.com/gitpod-io/empty/tree/branch-with-history" },
440+
[
441+
{
442+
sha: revision1,
443+
author: "some-dude",
444+
commitMessage: `commit ${revision1}`,
445+
},
446+
],
447+
);
448+
449+
// start two prebuilds: await 1st, fake 2nd to be building
450+
const prebuildManager = container.get(PrebuildManager);
451+
const workspaceDb: WorkspaceDB = container.get(WorkspaceDB);
452+
const prebuild1Result = await prebuildManager.triggerPrebuild({}, owner, project.id, "branch-with-history");
453+
const prebuild1 = await workspaceDb.findPrebuildByID(prebuild1Result.prebuildId);
454+
await workspaceDb.storePrebuiltWorkspace({
455+
...prebuild1!,
456+
state: "available",
457+
});
458+
const wsAndI = await workspaceDb.findWorkspaceAndInstance(prebuild1!.buildWorkspaceId);
459+
await workspaceDb.updateInstancePartial(wsAndI!.instanceId, { status: { phase: "stopped" } });
460+
461+
mockRepositoryProvider.pushCommit("branch-with-history", {
462+
sha: "111111",
463+
author: "some-dude",
464+
commitMessage: "commit 111111",
465+
});
466+
const prebuild2Result = await prebuildManager.triggerPrebuild({}, owner, project.id, "branch-with-history");
467+
// fake prebuild2 to not be done, yet
468+
const prebuild2 = await workspaceDb.findPrebuildByID(prebuild2Result.prebuildId);
469+
await workspaceDb.storePrebuiltWorkspace({
470+
...prebuild2!,
471+
state: "building",
472+
});
473+
474+
// request a context for the branch (effectively 2nd commit)
475+
const svc = container.get(ContextService);
476+
const ctx = await svc.parseContext(owner, `https://github.com/gitpod-io/empty/tree/branch-with-history`, {
477+
projectId: project.id,
478+
organizationId: org.id,
479+
forceDefaultConfig: false,
480+
});
481+
expect(ctx.project?.id).to.equal(project.id);
482+
expect(PrebuiltWorkspaceContext.is(ctx.context)).to.equal(true);
483+
expect((ctx.context as any as PrebuiltWorkspaceContext).prebuiltWorkspace.id).to.equal(
484+
prebuild1Result.prebuildId,
485+
);
486+
expect((ctx.context as any as PrebuiltWorkspaceContext).prebuiltWorkspace.commit).to.equal(revision1);
487+
});
488+
489+
it("should prefer perfect-match prebuild", async () => {
490+
// prepare test scenario: two prebuilds
491+
const revision1 = "000000";
492+
mockRepositoryProvider.addBranch(
493+
{ name: "branch-with-history", htmlUrl: "https://github.com/gitpod-io/empty/tree/branch-with-history" },
494+
[
495+
{
496+
sha: revision1,
497+
author: "some-dude",
498+
commitMessage: `commit ${revision1}`,
499+
},
500+
],
501+
);
502+
503+
// trigger and "await" prebuilds for both commits.
504+
const prebuildManager = container.get(PrebuildManager);
505+
const workspaceDb: WorkspaceDB = container.get(WorkspaceDB);
506+
const prebuild1Result = await prebuildManager.triggerPrebuild({}, owner, project.id, "branch-with-history");
507+
const prebuild1 = await workspaceDb.findPrebuildByID(prebuild1Result.prebuildId);
508+
await workspaceDb.storePrebuiltWorkspace({
509+
...prebuild1!,
510+
state: "available",
511+
});
512+
const wsAndI1 = await workspaceDb.findWorkspaceAndInstance(prebuild1!.buildWorkspaceId);
513+
await workspaceDb.updateInstancePartial(wsAndI1!.instanceId, { status: { phase: "stopped" } });
514+
515+
mockRepositoryProvider.pushCommit("branch-with-history", {
516+
sha: "111111",
517+
author: "some-dude",
518+
commitMessage: "commit 111111",
519+
});
520+
const prebuild2Result = await prebuildManager.triggerPrebuild({}, owner, project.id, "branch-with-history");
521+
const prebuild2 = await workspaceDb.findPrebuildByID(prebuild2Result.prebuildId);
522+
await workspaceDb.storePrebuiltWorkspace({
523+
...prebuild2!,
524+
state: "available",
525+
});
526+
const wsAndI2 = await workspaceDb.findWorkspaceAndInstance(prebuild2!.buildWorkspaceId);
527+
await workspaceDb.updateInstancePartial(wsAndI2!.instanceId, { status: { phase: "stopped" } });
528+
529+
// request context for the _first_ commit
530+
const svc = container.get(ContextService);
531+
const ctx = await svc.parseContext(owner, `https://github.com/gitpod-io/empty/commit/000000`, {
532+
projectId: project.id,
533+
organizationId: org.id,
534+
forceDefaultConfig: false,
535+
});
536+
expect(ctx.project?.id).to.equal(project.id);
537+
expect(PrebuiltWorkspaceContext.is(ctx.context)).to.equal(true);
538+
expect((ctx.context as any as PrebuiltWorkspaceContext).prebuiltWorkspace.id).to.equal(
539+
prebuild1Result.prebuildId,
540+
);
541+
expect((ctx.context as any as PrebuiltWorkspaceContext).prebuiltWorkspace.commit).to.equal(revision1);
542+
});
543+
384544
it("should parse snapshot context", async () => {
385545
const svc = container.get(ContextService);
386546
const ctx = await svc.parseContext(owner, `snapshot/${snapshot.id}`, {

0 commit comments

Comments
 (0)