| 1 | /** |
| 2 | * Push-ingest profile against the LOCAL workerd (no cloud deploy): pushes |
| 3 | * the repository at `GIT_PROFILE_REPO` and prints the server's per-phase |
| 4 | * split. Skipped unless the env var is set — it is a measurement tool, not |
| 5 | * a regression test (DESIGN §22.4). |
| 6 | * |
| 7 | * GIT_PROFILE_REPO=/path/to/checkout pnpm test test/Git/GitIngestProfile.local.test.ts |
| 8 | * |
| 9 | * Do NOT export CLOUDFLARE_ACCOUNT_ID / CLOUDFLARE_API_TOKEN for this: the |
| 10 | * profile loader treats env credentials as "configure this profile as env" |
| 11 | * and rewrites ~/.alchemy/profiles.json. |
| 12 | */ |
| 13 | import * as Alchemy from "@/index.ts"; |
| 14 | import * as Cloudflare from "@/Cloudflare"; |
| 15 | import { GitApi } from "@/Git/Api.ts"; |
| 16 | import * as Test from "@/Test/Alchemy"; |
| 17 | import { expect } from "alchemy-test"; |
| 18 | import * as Effect from "effect/Effect"; |
| 19 | import * as FileSystem from "effect/FileSystem"; |
| 20 | import * as Schedule from "effect/Schedule"; |
| 21 | import * as Stream from "effect/Stream"; |
| 22 | import * as HttpClient from "effect/http/HttpClient"; |
| 23 | import * as HttpClientRequest from "effect/http/HttpClientRequest"; |
| 24 | import * as HttpApiClient from "effect/http-api/HttpApiClient"; |
| 25 | import * as ChildProcess from "effect/process/ChildProcess"; |
| 26 | import TestGitHost, { TEST_SECRET } from "./fixtures/stack.ts"; |
| 27 | |
| 28 | const PROFILE_REPO = process.env.GIT_PROFILE_REPO; |
| 29 | |
| 30 | const { test, beforeAll, afterAll, deploy, destroy } = Test.make({ |
| 31 | providers: Cloudflare.providers(), |
| 32 | dev: true, |
| 33 | }); |
| 34 | |
| 35 | const LocalStack = Alchemy.Stack( |
| 36 | "GitIngestProfileStack", |
| 37 | { providers: Cloudflare.providers(), state: Alchemy.localState() }, |
| 38 | Effect.gen(function* () { |
| 39 | const host = yield* TestGitHost; |
| 40 | return { url: host.url.as<string>() }; |
| 41 | }), |
| 42 | ); |
| 43 | |
| 44 | const git = Effect.fn(function* (cwd: string, ...args: Array<string>) { |
| 45 | const handle = yield* ChildProcess.make("git", args, { |
| 46 | cwd, |
| 47 | env: { GIT_TERMINAL_PROMPT: "0" }, |
| 48 | extendEnv: true, |
| 49 | }); |
| 50 | const [exitCode, stderr] = yield* Effect.all( |
| 51 | [handle.exitCode, Stream.mkString(Stream.decodeText(handle.stderr))], |
| 52 | { concurrency: 2 }, |
| 53 | ); |
| 54 | return { exitCode, stderr }; |
| 55 | }, Effect.timeout("10 minutes")); |
| 56 | |
| 57 | if (PROFILE_REPO === undefined) { |
| 58 | test.skip( |
| 59 | "profile: set GIT_PROFILE_REPO=/path/to/checkout to run", |
| 60 | Effect.void, |
| 61 | { tags: ["provider:cloudflare", "local"] }, |
| 62 | ); |
| 63 | } else { |
| 64 | const stack = beforeAll(deploy(LocalStack)); |
| 65 | afterAll(destroy(LocalStack)); |
| 66 | test( |
| 67 | "profile: push GIT_PROFILE_REPO into local workerd and print the ingest split", |
| 68 | Effect.gen(function* () { |
| 69 | const { url } = yield* stack; |
| 70 | const client = yield* HttpApiClient.make(GitApi, { |
| 71 | baseUrl: url, |
| 72 | transformClient: HttpClient.mapRequest((r) => |
| 73 | r.pipe(HttpClientRequest.bearerToken(TEST_SECRET)), |
| 74 | ), |
| 75 | }); |
| 76 | yield* client.repos |
| 77 | .get({ params: { owner: "profile", repo: "repo" } }) |
| 78 | .pipe( |
| 79 | Effect.retry({ schedule: Schedule.spaced("500 millis"), times: 20 }), |
| 80 | Effect.catchTag("RepoNotFound", () => Effect.void), |
| 81 | ); |
| 82 | const created = yield* client.repos.create({ |
| 83 | payload: { owner: "profile", name: "repo" }, |
| 84 | }); |
| 85 | const parsed = new URL(url); |
| 86 | const remote = `${parsed.protocol}//x:${TEST_SECRET}@${parsed.host}/profile/repo.git`; |
| 87 | const t0 = performance.now(); |
| 88 | const push = yield* git( |
| 89 | PROFILE_REPO!, |
| 90 | "push", |
| 91 | "-q", |
| 92 | remote, |
| 93 | "HEAD:refs/heads/main", |
| 94 | ); |
| 95 | const wall = performance.now() - t0; |
| 96 | if (push.exitCode !== 0) |
| 97 | console.log( |
| 98 | "[ingest-profile] PUSH FAILED:", |
| 99 | push.stderr.slice(0, 2000), |
| 100 | ); |
| 101 | expect(push.exitCode, push.stderr).toBe(0); |
| 102 | const meta = yield* client.repos.get({ |
| 103 | params: { owner: "profile", repo: "repo" }, |
| 104 | }); |
| 105 | // Clone it back and let real git verify — a promoted push must read |
| 106 | // back byte-for-byte through the wire pack. |
| 107 | const fs = yield* FileSystem.FileSystem; |
| 108 | const tmp = yield* fs.makeTempDirectory({ |
| 109 | prefix: "git-ingest-profile-", |
| 110 | }); |
| 111 | const clone = yield* git(tmp, "clone", "-q", remote, "back"); |
| 112 | if (clone.exitCode !== 0) |
| 113 | console.log( |
| 114 | "[ingest-profile] CLONE FAILED:", |
| 115 | clone.stderr.slice(0, 2000), |
| 116 | ); |
| 117 | expect(clone.exitCode, clone.stderr).toBe(0); |
| 118 | const fsck = yield* git(`${tmp}/back`, "fsck", "--connectivity-only"); |
| 119 | if (fsck.exitCode !== 0) |
| 120 | console.log( |
| 121 | "[ingest-profile] FSCK FAILED:", |
| 122 | fsck.stderr.slice(0, 2000), |
| 123 | ); |
| 124 | expect(fsck.exitCode, fsck.stderr).toBe(0); |
| 125 | console.log( |
| 126 | `[ingest-profile] objects: ${JSON.stringify(meta.objects)}; clone back + fsck ok`, |
| 127 | ); |
| 128 | const p = meta.lastPush!; |
| 129 | // Counts (chunks, regions, unresolved) are reported alongside the |
| 130 | // wall-clock phases; label them as such. |
| 131 | const counts = new Set(["chunks", "regions", "unresolved"]); |
| 132 | const phases = Object.entries(p.phases ?? {}) |
| 133 | .sort((a, b) => b[1] - a[1]) |
| 134 | .map(([k, v]) => (counts.has(k) ? `${k}=${v}` : `${k} ${v}ms`)) |
| 135 | .join(", "); |
| 136 | console.log( |
| 137 | `[ingest-profile] ${p.objects} objects, ${(p.bytes / 1048576).toFixed(1)} MiB: ` + |
| 138 | `ingest ${p.ingestMs}ms (sql ${p.stageMs}ms, cpu ${p.ingestMs - p.stageMs}ms → ${((p.ingestMs - p.stageMs) / p.objects).toFixed(2)} ms/object) ` + |
| 139 | `connectivity ${p.connectivityMs}ms finalize ${p.finalizeMs}ms total ${p.totalMs}ms; wall ${wall.toFixed(0)}ms\n` + |
| 140 | `[ingest-profile] phases: ${phases}`, |
| 141 | ); |
| 142 | }), |
| 143 | { |
| 144 | tags: [ |
| 145 | "provider:cloudflare", |
| 146 | "provider:cloudflare:r2", |
| 147 | "provider:cloudflare:worker", |
| 148 | "local", |
| 149 | ], |
| 150 | timeout: 600_000, |
| 151 | }, |
| 152 | ); |
| 153 | } |