| 1 | import assert from "node:assert/strict"; |
| 2 | import { readFileSync } from "node:fs"; |
| 3 | import { test } from "node:test"; |
| 4 | |
| 5 | import { |
| 6 | INSTANCE_TYPES, |
| 7 | STANDARD_INSTANCE, |
| 8 | instanceNamed, |
| 9 | machineNamed, |
| 10 | machineSize, |
| 11 | machines, |
| 12 | priceScale, |
| 13 | runnerMinutes, |
| 14 | } from "../../../packages/contracts/src/instances.ts"; |
| 15 | |
| 16 | import { SANDBOX_BINDINGS, jobNamespaceOf } from "./egress.ts"; |
| 17 | |
| 18 | test("each label in the machine catalogue runs in its own class, bound and sized as wrangler.jsonc says", () => { |
| 19 | type Size = { vcpu: number; memory_mib: number; disk_mb: number }; |
| 20 | // wrangler.jsonc's comments are whole lines. |
| 21 | const text = readFileSync(new URL("../wrangler.jsonc", import.meta.url), "utf8").replace(/^\s*\/\/.*$/gm, ""); |
| 22 | const config = JSON.parse(text) as { |
| 23 | containers: { class_name: string; instance_type: unknown }[]; |
| 24 | durable_objects: { bindings: { name: string; class_name: string }[] }; |
| 25 | }; |
| 26 | // The named Cloudflare instance types a machine may be, at the sizes |
| 27 | // Cloudflare gives them. A custom type would be a row here once the |
| 28 | // account may use them. |
| 29 | const named: Record<string, Size> = { |
| 30 | "standard-3": { vcpu: 2, memory_mib: 8 * 1024, disk_mb: 16_000 }, |
| 31 | "standard-4": { vcpu: 4, memory_mib: 12 * 1024, disk_mb: 20_000 }, |
| 32 | }; |
| 33 | // Every class has a label, and every label a class. |
| 34 | assert.deepEqual( |
| 35 | INSTANCE_TYPES.map((instance) => instance.sandboxClass).sort(), |
| 36 | config.containers.map((container) => container.class_name).sort(), |
| 37 | ); |
| 38 | for (const instance of INSTANCE_TYPES) { |
| 39 | const type = config.containers.find((c) => c.class_name === instance.sandboxClass)?.instance_type; |
| 40 | const size = typeof type === "string" ? named[type] : undefined; |
| 41 | assert.ok(size, `${instance.sandboxClass}: ${JSON.stringify(type)}`); |
| 42 | assert.equal(type, instance.container, instance.label); |
| 43 | assert.deepEqual( |
| 44 | { vcpu: size.vcpu, memoryGib: size.memory_mib / 1024, diskGb: size.disk_mb / 1000 }, |
| 45 | { vcpu: instance.vcpu, memoryGib: instance.memoryGib, diskGb: instance.diskGb }, |
| 46 | instance.label, |
| 47 | ); |
| 48 | // Its binding, here and in wrangler.jsonc, is what a job starts in. |
| 49 | const binding = SANDBOX_BINDINGS[instance.sandboxClass]; |
| 50 | assert.ok(binding, `${instance.sandboxClass} has no binding in SANDBOX_BINDINGS`); |
| 51 | assert.ok( |
| 52 | config.durable_objects.bindings.some((b) => b.name === binding && b.class_name === instance.sandboxClass), |
| 53 | binding, |
| 54 | ); |
| 55 | assert.equal(jobNamespaceOf({ [binding]: instance.label }, instance), instance.label); |
| 56 | } |
| 57 | // A class not deployed here: the job is not started on another machine. |
| 58 | assert.equal(jobNamespaceOf({ SANDBOX: "standard" }, INSTANCE_TYPES[2]!), null); |
| 59 | assert.equal(jobNamespaceOf({ SANDBOX: "standard" }, { sandboxClass: "Sandbox64Core" }), null); |
| 60 | }); |
| 61 | |
| 62 | test("runs-on labels are the catalogue's, and a job is charged its minutes rounded up", () => { |
| 63 | assert.equal(instanceNamed("ubuntu-latest"), STANDARD_INSTANCE); |
| 64 | assert.equal(instanceNamed("G1T-4Core").machine, "4-core"); |
| 65 | assert.equal(instanceNamed("g1t-2core").machine, STANDARD_INSTANCE.machine); |
| 66 | assert.deepEqual( |
| 67 | machines().map((m) => [m.machine, m.microsPerMinute, machineSize(m)]), |
| 68 | [ |
| 69 | ["2-core", 4000, "2 vCPU, 8 GiB, 16 GB"], |
| 70 | ["4-core", 7000, "4 vCPU, 12 GiB, 20 GB"], |
| 71 | ], |
| 72 | ); |
| 73 | assert.equal(machineNamed("4-core")?.label, "g1t-4core"); |
| 74 | assert.equal(machineNamed("8-core"), null); |
| 75 | assert.equal(priceScale(instanceNamed("g1t-4core")), 1.75); |
| 76 | assert.deepEqual([0, 1, 60, 61, 3600].map(runnerMinutes), [0, 1, 1, 2, 60]); |
| 77 | }); |