mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-21 02:06:43 +00:00
perf(test): unit-test crabbox provider aliases
This commit is contained in:
@@ -169,6 +169,7 @@
|
||||
"!docs/**/*.jpg",
|
||||
"!docs/**/*.png",
|
||||
"src/agents/templates/",
|
||||
"scripts/crabbox-wrapper-providers.mjs",
|
||||
"scripts/crabbox-wrapper.mjs",
|
||||
"patches/",
|
||||
"skills/",
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
const providerAliases = new Map([
|
||||
["blacksmith", "blacksmith-testbox"],
|
||||
["cf", "cloudflare"],
|
||||
["container", "local-container"],
|
||||
["docker", "local-container"],
|
||||
["exe", "exe-dev"],
|
||||
["exedev", "exe-dev"],
|
||||
["google", "gcp"],
|
||||
["google-cloud", "gcp"],
|
||||
["local-docker", "local-container"],
|
||||
["namespace", "namespace-devbox"],
|
||||
["namespace-devboxes", "namespace-devbox"],
|
||||
["rail", "railway"],
|
||||
["railwayapp", "railway"],
|
||||
["run-pod", "runpod"],
|
||||
["runpodio", "runpod"],
|
||||
["sem", "semaphore"],
|
||||
["static", "ssh"],
|
||||
["static-ssh", "ssh"],
|
||||
["tensorlake-sbx", "tensorlake"],
|
||||
["tl", "tensorlake"],
|
||||
]);
|
||||
|
||||
// Crabbox providerHelpAll can omit Tensorlake even when the binary accepts it.
|
||||
const providerHelpOmissions = new Set(["tensorlake"]);
|
||||
|
||||
export function canonicalProviderName(provider) {
|
||||
return providerAliases.get(provider) ?? provider;
|
||||
}
|
||||
|
||||
function addProviderNames(names, text) {
|
||||
for (const name of text
|
||||
.replace(/\s+\(default\b.*$/u, "")
|
||||
.split(/\s*(?:,|\||\bor\b)\s*/u)
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean)) {
|
||||
if (/^[a-z0-9][a-z0-9-]*$/u.test(name)) {
|
||||
names.add(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function providerListContinuation(line, previousText) {
|
||||
const match = line.match(
|
||||
/^\s*((?:or\s+)?[a-z0-9][a-z0-9-]*(?:\s*(?:,|\||\bor\b)\s*(?:or\s+)?[a-z0-9][a-z0-9-]*)*\s*(?:,|\|)?)(?:\s+\(default\b.*)?\s*$/u,
|
||||
);
|
||||
if (!match) {
|
||||
return "";
|
||||
}
|
||||
if (/[,|]\s*$/u.test(previousText) || /[,|]|\bor\b|\(default\b/u.test(line)) {
|
||||
return match[1];
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
export function parseProvidersFromHelp(text) {
|
||||
const names = new Set();
|
||||
const lines = text.split(/\r?\n/u);
|
||||
for (let index = 0; index < lines.length; index += 1) {
|
||||
const line = lines[index];
|
||||
const providerMatch = line.match(/provider:\s*([a-z0-9][a-z0-9, -]*)(?:\s*\(default\b|$)/u);
|
||||
if (providerMatch) {
|
||||
let providerText = providerMatch[1];
|
||||
while (!/\(default\b/u.test(lines[index]) && index + 1 < lines.length) {
|
||||
const continuation = providerListContinuation(lines[index + 1], providerText);
|
||||
if (!continuation) {
|
||||
break;
|
||||
}
|
||||
index += 1;
|
||||
providerText = `${providerText} ${continuation}`;
|
||||
}
|
||||
addProviderNames(names, providerText);
|
||||
continue;
|
||||
}
|
||||
|
||||
const flagMatch = line.match(
|
||||
/^\s+-{1,2}provider(?:[=\s]+)([a-z0-9][a-z0-9|, -]*)(?:\s{2,}|\s+\(|$)/u,
|
||||
);
|
||||
if (flagMatch && /[,|]|\bor\b/u.test(flagMatch[1])) {
|
||||
addProviderNames(names, flagMatch[1]);
|
||||
}
|
||||
}
|
||||
return [...names];
|
||||
}
|
||||
|
||||
export function isProviderAdvertised(provider, advertisedProviders) {
|
||||
const canonicalProvider = canonicalProviderName(provider);
|
||||
return (
|
||||
advertisedProviders.includes(provider) ||
|
||||
advertisedProviders.includes(canonicalProvider) ||
|
||||
providerHelpOmissions.has(canonicalProvider)
|
||||
);
|
||||
}
|
||||
@@ -19,6 +19,11 @@ import {
|
||||
import { homedir, tmpdir } from "node:os";
|
||||
import { delimiter, dirname, extname, isAbsolute, relative, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import {
|
||||
canonicalProviderName,
|
||||
isProviderAdvertised,
|
||||
parseProvidersFromHelp,
|
||||
} from "./crabbox-wrapper-providers.mjs";
|
||||
import { resolvePathEnvKey, resolveWindowsCmdExePath } from "./windows-cmd-helpers.mjs";
|
||||
|
||||
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
@@ -660,7 +665,7 @@ function shouldRequireBrokeredAws(commandArgs, providerName) {
|
||||
if (process.env.OPENCLAW_CRABBOX_ALLOW_DIRECT_AWS === "1") {
|
||||
return false;
|
||||
}
|
||||
const canonicalProvider = providerAliases.get(providerName) ?? providerName;
|
||||
const canonicalProvider = canonicalProviderName(providerName);
|
||||
if (canonicalProvider !== "aws") {
|
||||
return false;
|
||||
}
|
||||
@@ -2057,7 +2062,7 @@ function isAwsMacosRemoteTarget(commandArgs, providerName) {
|
||||
}
|
||||
|
||||
function isBrokeredWsl2RemoteTarget(commandArgs, providerName) {
|
||||
const canonicalProvider = providerAliases.get(providerName) ?? providerName;
|
||||
const canonicalProvider = canonicalProviderName(providerName);
|
||||
return (
|
||||
commandArgs[0] === "run" &&
|
||||
(canonicalProvider === "aws" || canonicalProvider === "azure") &&
|
||||
@@ -3152,99 +3157,10 @@ function injectFullCheckoutLeaseReclaim(commandArgs) {
|
||||
|
||||
const version = checkedOutput(binary, ["--version"]);
|
||||
const help = checkedOutput(binary, ["run", "--help"]);
|
||||
const providerAliases = new Map([
|
||||
["blacksmith", "blacksmith-testbox"],
|
||||
["cf", "cloudflare"],
|
||||
["container", "local-container"],
|
||||
["docker", "local-container"],
|
||||
["exe", "exe-dev"],
|
||||
["exedev", "exe-dev"],
|
||||
["google", "gcp"],
|
||||
["google-cloud", "gcp"],
|
||||
["local-docker", "local-container"],
|
||||
["namespace", "namespace-devbox"],
|
||||
["namespace-devboxes", "namespace-devbox"],
|
||||
["rail", "railway"],
|
||||
["railwayapp", "railway"],
|
||||
["run-pod", "runpod"],
|
||||
["runpodio", "runpod"],
|
||||
["sem", "semaphore"],
|
||||
["static", "ssh"],
|
||||
["static-ssh", "ssh"],
|
||||
["tensorlake-sbx", "tensorlake"],
|
||||
["tl", "tensorlake"],
|
||||
]);
|
||||
// Crabbox providerHelpAll can omit Tensorlake even when the binary accepts it.
|
||||
const providerHelpOmissions = new Set(["tensorlake"]);
|
||||
|
||||
function addProviderNames(names, text) {
|
||||
for (const name of text
|
||||
.replace(/\s+\(default\b.*$/u, "")
|
||||
.split(/\s*(?:,|\||\bor\b)\s*/u)
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean)) {
|
||||
if (/^[a-z0-9][a-z0-9-]*$/u.test(name)) {
|
||||
names.add(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function providerListContinuation(line, previousText) {
|
||||
const match = line.match(
|
||||
/^\s*((?:or\s+)?[a-z0-9][a-z0-9-]*(?:\s*(?:,|\||\bor\b)\s*(?:or\s+)?[a-z0-9][a-z0-9-]*)*\s*(?:,|\|)?)(?:\s+\(default\b.*)?\s*$/u,
|
||||
);
|
||||
if (!match) {
|
||||
return "";
|
||||
}
|
||||
if (/[,|]\s*$/u.test(previousText) || /[,|]|\bor\b|\(default\b/u.test(line)) {
|
||||
return match[1];
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function parseProvidersFromHelp(text) {
|
||||
const names = new Set();
|
||||
const lines = text.split(/\r?\n/u);
|
||||
for (let index = 0; index < lines.length; index += 1) {
|
||||
const line = lines[index];
|
||||
const providerMatch = line.match(/provider:\s*([a-z0-9][a-z0-9, -]*)(?:\s*\(default\b|$)/u);
|
||||
if (providerMatch) {
|
||||
let providerText = providerMatch[1];
|
||||
while (!/\(default\b/u.test(lines[index]) && index + 1 < lines.length) {
|
||||
const continuation = providerListContinuation(lines[index + 1], providerText);
|
||||
if (!continuation) {
|
||||
break;
|
||||
}
|
||||
index += 1;
|
||||
providerText = `${providerText} ${continuation}`;
|
||||
}
|
||||
addProviderNames(names, providerText);
|
||||
continue;
|
||||
}
|
||||
|
||||
const flagMatch = line.match(
|
||||
/^\s+-{1,2}provider(?:[=\s]+)([a-z0-9][a-z0-9|, -]*)(?:\s{2,}|\s+\(|$)/u,
|
||||
);
|
||||
if (flagMatch && /[,|]|\bor\b/u.test(flagMatch[1])) {
|
||||
addProviderNames(names, flagMatch[1]);
|
||||
}
|
||||
}
|
||||
return [...names];
|
||||
}
|
||||
|
||||
function isProviderAdvertised(provider, advertisedProviders) {
|
||||
const canonicalProvider = providerAliases.get(provider) ?? provider;
|
||||
return (
|
||||
advertisedProviders.includes(provider) ||
|
||||
advertisedProviders.includes(canonicalProvider) ||
|
||||
providerHelpOmissions.has(canonicalProvider)
|
||||
);
|
||||
}
|
||||
|
||||
const providers = parseProvidersFromHelp(help.text);
|
||||
const displayBinary = binary === "crabbox" ? "crabbox" : relative(repoRoot, binary);
|
||||
const provider = selectedProvider(args, providers);
|
||||
const canonicalProvider = providerAliases.get(provider) ?? provider;
|
||||
const canonicalProvider = canonicalProviderName(provider);
|
||||
const commandProviderValue = commandProvider(args);
|
||||
let normalizedArgs = ensureAwsMacOnDemandMarket(
|
||||
ensureNativeWindowsHydrateJob(ensureAzureWindowsProvider(args, provider, providers)),
|
||||
|
||||
@@ -671,6 +671,7 @@ const TOOLING_SOURCE_TEST_TARGETS = new Map([
|
||||
["scripts/tsconfig.json", ["test/scripts/oxlint-config.test.ts"]],
|
||||
["scripts/build-all.mjs", ["test/scripts/build-all.test.ts"]],
|
||||
["scripts/build-stamp.mjs", ["src/infra/build-stamp.test.ts"]],
|
||||
["scripts/crabbox-wrapper-providers.mjs", ["test/scripts/crabbox-wrapper.test.ts"]],
|
||||
["scripts/crabbox-wrapper.mjs", ["test/scripts/crabbox-wrapper.test.ts"]],
|
||||
["scripts/github/barnacle-auto-response.mjs", ["test/scripts/barnacle-auto-response.test.ts"]],
|
||||
["scripts/changed-lanes.mjs", ["test/scripts/changed-lanes.test.ts"]],
|
||||
|
||||
@@ -72,6 +72,17 @@ describe("package manager build policy", () => {
|
||||
expect(packageJson.files).toContain("THIRD_PARTY_NOTICES.md");
|
||||
});
|
||||
|
||||
it("includes the Crabbox wrapper runtime modules in the published root package", () => {
|
||||
const packageJson = readJson("package.json") as RootPackageJson;
|
||||
|
||||
expect(packageJson.files).toEqual(
|
||||
expect.arrayContaining([
|
||||
"scripts/crabbox-wrapper.mjs",
|
||||
"scripts/crabbox-wrapper-providers.mjs",
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps npm shrinkwrap aligned with workspace overrides", () => {
|
||||
const workspace = parse(
|
||||
fs.readFileSync("pnpm-workspace.yaml", "utf8"),
|
||||
|
||||
@@ -15,6 +15,11 @@ import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import { setTimeout as delay } from "node:timers/promises";
|
||||
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
||||
import {
|
||||
canonicalProviderName,
|
||||
isProviderAdvertised,
|
||||
parseProvidersFromHelp,
|
||||
} from "../../scripts/crabbox-wrapper-providers.mjs";
|
||||
|
||||
const tempDirs: string[] = [];
|
||||
const repoRoot = process.cwd();
|
||||
@@ -588,24 +593,24 @@ describe("scripts/crabbox-wrapper", () => {
|
||||
"",
|
||||
].join("\n");
|
||||
const advertisedProviderAliases = [
|
||||
"blacksmith",
|
||||
"cf",
|
||||
"container",
|
||||
"docker",
|
||||
"exe",
|
||||
"exedev",
|
||||
"google",
|
||||
"google-cloud",
|
||||
"local-docker",
|
||||
"namespace",
|
||||
"namespace-devboxes",
|
||||
"rail",
|
||||
"railwayapp",
|
||||
"run-pod",
|
||||
"runpodio",
|
||||
"sem",
|
||||
"static",
|
||||
"static-ssh",
|
||||
["blacksmith", "blacksmith-testbox"],
|
||||
["cf", "cloudflare"],
|
||||
["container", "local-container"],
|
||||
["docker", "local-container"],
|
||||
["exe", "exe-dev"],
|
||||
["exedev", "exe-dev"],
|
||||
["google", "gcp"],
|
||||
["google-cloud", "gcp"],
|
||||
["local-docker", "local-container"],
|
||||
["namespace", "namespace-devbox"],
|
||||
["namespace-devboxes", "namespace-devbox"],
|
||||
["rail", "railway"],
|
||||
["railwayapp", "railway"],
|
||||
["run-pod", "runpod"],
|
||||
["runpodio", "runpod"],
|
||||
["sem", "semaphore"],
|
||||
["static", "ssh"],
|
||||
["static-ssh", "ssh"],
|
||||
];
|
||||
beforeAll(() => {
|
||||
runWrapper("provider: aws\n", ["--version"]);
|
||||
@@ -3077,18 +3082,12 @@ describe("scripts/crabbox-wrapper", () => {
|
||||
});
|
||||
|
||||
it.each(advertisedProviderAliases)(
|
||||
"accepts Crabbox provider alias %s when its canonical provider is advertised",
|
||||
(alias) => {
|
||||
const result = runWrapper(advertisedProviderAliasHelp, [
|
||||
"run",
|
||||
"--provider",
|
||||
alias,
|
||||
"--",
|
||||
"echo ok",
|
||||
]);
|
||||
"canonicalizes Crabbox provider alias %s to %s",
|
||||
(alias, canonical) => {
|
||||
const advertisedProviders = parseProvidersFromHelp(advertisedProviderAliasHelp);
|
||||
|
||||
expect(result.status, alias).toBe(0);
|
||||
expect(parseFakeCrabboxOutput(result).args).toContain(alias);
|
||||
expect(canonicalProviderName(alias)).toBe(canonical);
|
||||
expect(isProviderAdvertised(alias, advertisedProviders)).toBe(true);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -3099,11 +3098,9 @@ describe("scripts/crabbox-wrapper", () => {
|
||||
"",
|
||||
].join("\n");
|
||||
|
||||
const advertisedProviders = parseProvidersFromHelp(helpText);
|
||||
for (const provider of ["tensorlake", "tl", "tensorlake-sbx"]) {
|
||||
const result = runWrapper(helpText, ["run", "--provider", provider, "--", "echo ok"]);
|
||||
|
||||
expect(result.status, provider).toBe(0);
|
||||
expect(parseFakeCrabboxOutput(result).args).toContain(provider);
|
||||
expect(isProviderAdvertised(provider, advertisedProviders), provider).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3132,15 +3129,16 @@ describe("scripts/crabbox-wrapper", () => {
|
||||
});
|
||||
|
||||
it("parses provider choices from the --provider flag help format", () => {
|
||||
const result = runWrapper(
|
||||
"Usage: crabbox run [options]\n --provider hetzner|aws|local-container|blacksmith-testbox|cloudflare\n",
|
||||
["run", "--provider", "aws", "--", "echo ok"],
|
||||
);
|
||||
const helpText =
|
||||
"Usage: crabbox run [options]\n --provider hetzner|aws|local-container|blacksmith-testbox|cloudflare\n";
|
||||
|
||||
expect(result.status).toBe(0);
|
||||
expect(result.stderr).toContain(
|
||||
"providers=hetzner,aws,local-container,blacksmith-testbox,cloudflare",
|
||||
);
|
||||
expect(parseProvidersFromHelp(helpText)).toEqual([
|
||||
"hetzner",
|
||||
"aws",
|
||||
"local-container",
|
||||
"blacksmith-testbox",
|
||||
"cloudflare",
|
||||
]);
|
||||
});
|
||||
|
||||
it("uses a temporary full checkout for clean sparse Blacksmith syncs", () => {
|
||||
|
||||
@@ -1189,10 +1189,15 @@ describe("scripts/test-projects changed-target routing", () => {
|
||||
});
|
||||
|
||||
it("keeps Crabbox runner script edits on their regression tests", () => {
|
||||
expect(resolveChangedTestTargetPlan(["scripts/crabbox-wrapper.mjs"])).toEqual({
|
||||
mode: "targets",
|
||||
targets: ["test/scripts/crabbox-wrapper.test.ts"],
|
||||
});
|
||||
for (const scriptPath of [
|
||||
"scripts/crabbox-wrapper.mjs",
|
||||
"scripts/crabbox-wrapper-providers.mjs",
|
||||
]) {
|
||||
expect(resolveChangedTestTargetPlan([scriptPath]), scriptPath).toEqual({
|
||||
mode: "targets",
|
||||
targets: ["test/scripts/crabbox-wrapper.test.ts"],
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps build stamp script edits on the build stamp regression test", () => {
|
||||
|
||||
Reference in New Issue
Block a user