chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:01:57 +08:00
commit 9dda3e2451
399 changed files with 118131 additions and 0 deletions
+11
View File
@@ -0,0 +1,11 @@
#!/usr/bin/env bun
/**
* Delete the RTK test VM.
* Usage: bun run scripts/benchmark/cleanup.ts
*/
import { vmDelete } from "./lib/vm";
console.log("Deleting rtk-test VM...");
await vmDelete();
console.log("Done.");
+315
View File
@@ -0,0 +1,315 @@
#cloud-config
# RTK Integration Test VM — Ubuntu 24.04
# Installs all tools needed for comprehensive RTK testing (~200 commands)
# Usage: multipass launch --name rtk-test --cloud-init scripts/benchmark/cloud-init.yaml --cpus 2 --memory 4G --disk 20G 24.04
package_update: true
package_upgrade: false
packages:
# System tools
- curl
- wget
- jq
- git
- make
- cmake
- rsync
- sqlite3
- shellcheck
- yamllint
- postgresql-client
- docker.io
- containerd
- python3
- python3-pip
- python3-venv
- pipx
# Build essentials (for Rust compilation)
- build-essential
- pkg-config
- libssl-dev
- libsqlite3-dev
# Misc
- hyperfine
- unzip
- tree
runcmd:
# ── Rust toolchain ──
- su - ubuntu -c 'curl --proto "=https" --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y'
# ── Node.js 22 + package managers ──
- curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
- apt-get install -y nodejs
- npm install -g pnpm yarn
- npm install -g eslint prettier typescript
- npm install -g markdownlint-cli
# ── Go 1.22 ──
- curl -fsSL https://go.dev/dl/go1.22.5.linux-amd64.tar.gz | tar -C /usr/local -xz
- echo 'export PATH=$PATH:/usr/local/go/bin:/home/ubuntu/go/bin' >> /home/ubuntu/.bashrc
- su - ubuntu -c 'export PATH=$PATH:/usr/local/go/bin && go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest'
# ── Python tools ──
- pipx install ruff
- pipx install mypy
- pipx install poetry
- pip3 install --break-system-packages pytest uv pre-commit
# ── .NET 8 SDK ──
- |
wget https://dot.net/v1/dotnet-install.sh -O /tmp/dotnet-install.sh
chmod +x /tmp/dotnet-install.sh
/tmp/dotnet-install.sh --channel 8.0 --install-dir /usr/local/share/dotnet
ln -sf /usr/local/share/dotnet/dotnet /usr/local/bin/dotnet
echo 'export DOTNET_ROOT=/usr/local/share/dotnet' >> /home/ubuntu/.bashrc
# ── Terraform ──
- |
wget -qO- https://apt.releases.hashicorp.com/gpg | gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" > /etc/apt/sources.list.d/hashicorp.list
apt-get update && apt-get install -y terraform
# ── Helm ──
- curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
# ── Hadolint ──
- |
wget -qO /usr/local/bin/hadolint https://github.com/hadolint/hadolint/releases/latest/download/hadolint-Linux-x86_64
chmod +x /usr/local/bin/hadolint
# ── Docker setup ──
- usermod -aG docker ubuntu
- systemctl enable docker
- systemctl start docker
# ── kubectl (standalone binary) ──
- |
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
rm kubectl
# ── ansible ──
- pip3 install --break-system-packages ansible-core
# ── Mock tools (too heavy to install) ──
- |
cat > /usr/local/bin/gcloud << 'MOCK'
#!/bin/bash
if [ "$1" = "version" ] || [ "$1" = "--version" ]; then
echo "Google Cloud SDK 400.0.0"
echo "bq 2.0.80"
echo "core 2023.01.01"
echo "gsutil 5.17"
else
echo "gcloud mock: $*"
fi
MOCK
chmod +x /usr/local/bin/gcloud
- |
cat > /usr/local/bin/shopify << 'MOCK'
#!/bin/bash
echo "Shopify CLI 3.0.0 (mock)"
if [ "$1" = "theme" ] && [ "$2" = "check" ]; then
echo "Running theme check..."
echo " 1 issue found"
echo " [warn] Missing alt text on image"
fi
MOCK
chmod +x /usr/local/bin/shopify
- |
cat > /usr/local/bin/pio << 'MOCK'
#!/bin/bash
if [ "$1" = "--version" ]; then echo "PlatformIO Core, version 6.1.0"
elif [ "$1" = "run" ]; then
echo "Processing esp32dev (platform: espressif32; board: esp32dev)"
echo "Linking .pio/build/esp32dev/firmware.elf"
echo "========================= [SUCCESS] ========================="
fi
MOCK
chmod +x /usr/local/bin/pio
- |
cat > /usr/local/bin/quarto << 'MOCK'
#!/bin/bash
if [ "$1" = "--version" ]; then echo "1.3.450"
elif [ "$1" = "render" ]; then echo "Rendering document..."; echo "Output created: document.html"
fi
MOCK
chmod +x /usr/local/bin/quarto
- |
cat > /usr/local/bin/sops << 'MOCK'
#!/bin/bash
if [ "$1" = "--version" ]; then echo "sops 3.7.3"; fi
MOCK
chmod +x /usr/local/bin/sops
- |
cat > /usr/local/bin/swift << 'MOCK'
#!/bin/bash
if [ "$1" = "--version" ]; then echo "Swift version 5.9.2 (swift-5.9.2-RELEASE)"
elif [ "$1" = "build" ]; then echo "Compiling Swift module..."; echo "Build complete! (0.42s)"
fi
MOCK
chmod +x /usr/local/bin/swift
# ── Fake test projects ──
# Node.js project with errors
- |
su - ubuntu -c '
mkdir -p /tmp/test-node/src && cd /tmp/test-node
npm init -y >/dev/null 2>&1
echo "{\"compilerOptions\":{\"strict\":true,\"noEmit\":true,\"target\":\"ES2020\",\"module\":\"ESNext\",\"moduleResolution\":\"node\"},\"include\":[\"src\"]}" > tsconfig.json
echo "const x: number = \"not a number\";\nconst unused = 42;\nfunction greet(name: string): string { return name }\ngreet(123);" > src/index.ts
echo "{\"rules\":{\"no-unused-vars\":\"error\",\"semi\":[\"error\",\"always\"]}}" > .eslintrc.json
echo "const x = 1;const y=2; const z =3" > src/ugly.ts
'
# Python project with errors
- |
su - ubuntu -c '
mkdir -p /tmp/test-python && cd /tmp/test-python
cat > main.py << "PYEOF"
import os
import sys
unused_import = 1
def add(a: int, b: int) -> str:
return a + b
x: int = "hello"
PYEOF
cat > test_main.py << "PYEOF"
def test_pass():
assert 1 + 1 == 2
def test_fail():
assert 1 + 1 == 3, "math is broken"
PYEOF
cat > pyproject.toml << "PYEOF"
[tool.ruff]
line-length = 80
select = ["E", "F", "W"]
[tool.mypy]
strict = true
[tool.pytest.ini_options]
testpaths = ["."]
PYEOF
'
# Go project with errors
- |
su - ubuntu -c '
export PATH=$PATH:/usr/local/go/bin
mkdir -p /tmp/test-go && cd /tmp/test-go
go mod init test-go 2>/dev/null
cat > main.go << "GOEOF"
package main
import "fmt"
func main() { fmt.Println("hello") }
func unused() { var x int; _ = x }
GOEOF
cat > main_test.go << "GOEOF"
package main
import "testing"
func TestPass(t *testing.T) { if 1+1 != 2 { t.Fatal("math") } }
func TestFail(t *testing.T) { t.Fatal("expected failure") }
GOEOF
'
# Rust project with errors
- |
su - ubuntu -c '
export PATH=$HOME/.cargo/bin:$PATH
mkdir -p /tmp/test-rust && cd /tmp/test-rust
cargo init --name test-rust 2>/dev/null
cat > src/main.rs << "RSEOF"
fn main() {
let x = vec![1, 2, 3];
let _y = x.iter().map(|i| i.clone()).collect::<Vec<_>>();
println!("hello");
}
#[cfg(test)]
mod tests {
#[test] fn test_pass() { assert_eq!(1 + 1, 2); }
#[test] fn test_fail() { assert_eq!(1 + 1, 3); }
}
RSEOF
'
# Dockerfiles for hadolint
- |
su - ubuntu -c '
cat > /tmp/Dockerfile.bad << "DEOF"
FROM ubuntu:latest
RUN apt-get update && apt-get install -y curl wget git
RUN cd /tmp && wget http://example.com/script.sh && bash script.sh
EXPOSE 80 443 8080
DEOF
'
# Shell/YAML/Markdown test files
- |
su - ubuntu -c '
printf "#!/bin/bash\necho \$foo\nls *.txt\ncd \$(pwd)\n[ -f file ] && rm file\n" > /tmp/test.sh
printf "foo: bar\nbaz: qux\nlist:\n - item1\n - item2\ntruthy: yes\n" > /tmp/test.yaml
printf "#Header without space\nSome text\n\n* List item\n+ Mixed markers\n" > /tmp/test.md
'
# Git repo for testing
- |
su - ubuntu -c '
mkdir -p /tmp/test-git && cd /tmp/test-git
git init && git config user.email "test@rtk.dev" && git config user.name "RTK Test"
for i in $(seq 1 20); do echo "line $i" >> file.txt && git add file.txt && git commit -m "feat: commit number $i"; done
echo "modified" >> file.txt && echo "new file" > new.txt
'
# Large log file for dedup testing
- |
su - ubuntu -c '
for i in $(seq 1 500); do
printf "[2026-03-25 10:00:00] INFO Starting service...\n[2026-03-25 10:00:01] WARN Connection timeout\n[2026-03-25 10:00:01] ERROR Failed to connect: refused\n"
done > /tmp/large.log
for i in $(seq 1 50); do echo "[2026-03-25 10:05:00] FATAL Out of memory"; done >> /tmp/large.log
'
# .env file
- |
su - ubuntu -c '
printf "DATABASE_URL=postgres://user:pass@localhost:5432/db\nAPI_KEY=sk-1234567890abcdef\nSECRET_TOKEN=ghp_xxxx\nNODE_ENV=production\nPORT=3000\n" > /tmp/.env
'
# Makefile
- |
su - ubuntu -c '
printf ".PHONY: all test\nall:\n\t@echo Building...\n\t@echo Build complete\ntest:\n\t@echo Running tests...\n\t@echo 2 tests passed\n" > /tmp/Makefile
'
# Terraform project
- |
su - ubuntu -c '
mkdir -p /tmp/test-terraform && cd /tmp/test-terraform
printf "terraform {\n required_version = \">= 1.0\"\n}\nresource \"null_resource\" \"test\" {\n triggers = { always = timestamp() }\n}\noutput \"test\" { value = \"hello\" }\n" > main.tf
'
# Helm chart
- su - ubuntu -c 'mkdir -p /tmp/test-helm && cd /tmp/test-helm && helm create test-chart 2>/dev/null || true'
# .NET project
- |
export DOTNET_ROOT=/usr/local/share/dotnet
su - ubuntu -c '
export DOTNET_ROOT=/usr/local/share/dotnet && export PATH=$PATH:$DOTNET_ROOT
mkdir -p /tmp/test-dotnet && cd /tmp/test-dotnet
dotnet new console -n TestApp --force 2>/dev/null || true
'
# Signal completion
- touch /home/ubuntu/.cloud-init-complete
- chown ubuntu:ubuntu /home/ubuntu/.cloud-init-complete
- echo "RTK cloud-init setup complete" | tee /var/log/rtk-setup.log
final_message: "RTK test VM ready in $UPTIME seconds"
+113
View File
@@ -0,0 +1,113 @@
/**
* Report generation for RTK integration test results.
*/
import type { TestResult } from "./test";
import { getCounts, getResults } from "./test";
interface BuildInfo {
buildTime: number;
binarySize: number;
version: string;
branch: string;
commit: string;
}
export function generateReport(buildInfo: BuildInfo): string {
const { total, passed, failed, skipped } = getCounts();
const results = getResults();
const passRate = total > 0 ? Math.round((passed * 100) / total) : 0;
const lines: string[] = [];
lines.push("======================================================");
lines.push(" RTK INTEGRATION TEST REPORT");
lines.push("======================================================");
lines.push("");
lines.push(`Date: ${new Date().toISOString()}`);
lines.push(`Branch: ${buildInfo.branch}`);
lines.push(`Commit: ${buildInfo.commit}`);
lines.push(`Version: ${buildInfo.version}`);
lines.push(`Binary: ${buildInfo.binarySize} bytes`);
lines.push(`Build: ${buildInfo.buildTime}s`);
lines.push("");
// Summary
lines.push("--- Summary ---");
lines.push(`Total: ${total}`);
lines.push(`Passed: ${passed} (${passRate}%)`);
lines.push(`Failed: ${failed}`);
lines.push(`Skipped: ${skipped}`);
lines.push("");
// Group results by phase (name prefix before ":")
const phases = new Map<string, TestResult[]>();
for (const r of results) {
const colonIdx = r.name.indexOf(":");
const phase = colonIdx > 0 ? r.name.slice(0, colonIdx) : "misc";
if (!phases.has(phase)) phases.set(phase, []);
phases.get(phase)!.push(r);
}
for (const [phase, phaseResults] of phases) {
const pPassed = phaseResults.filter((r) => r.status === "PASS").length;
const pTotal = phaseResults.length;
lines.push(`--- ${phase} (${pPassed}/${pTotal}) ---`);
for (const r of phaseResults) {
const shortName = r.name.includes(":") ? r.name.split(":")[1] : r.name;
lines.push(` ${r.status.padEnd(4)} | ${shortName} | ${r.detail}`);
}
lines.push("");
}
// Failures detail
const failures = results.filter((r) => r.status === "FAIL");
if (failures.length > 0) {
lines.push("--- Failures ---");
for (const f of failures) {
lines.push(` ${f.name}: ${f.detail}`);
}
lines.push("");
}
// Token savings summary
const savingsResults = results.filter((r) => r.savings !== undefined);
if (savingsResults.length > 0) {
const avgSavings = Math.round(
savingsResults.reduce((sum, r) => sum + (r.savings ?? 0), 0) /
savingsResults.length
);
const minSavings = Math.min(
...savingsResults.map((r) => r.savings ?? 100)
);
const maxSavings = Math.max(...savingsResults.map((r) => r.savings ?? 0));
lines.push("--- Token Savings ---");
lines.push(`Average: ${avgSavings}%`);
lines.push(`Min: ${minSavings}%`);
lines.push(`Max: ${maxSavings}%`);
lines.push("");
}
// Verdict
lines.push("======================================================");
if (failed === 0) {
lines.push(" Verdict: READY FOR RELEASE");
} else {
lines.push(` Verdict: NOT READY (${failed} failures)`);
}
lines.push("======================================================");
return lines.join("\n");
}
/** Save report to file */
export async function saveReport(
buildInfo: BuildInfo,
outPath: string
): Promise<string> {
const report = generateReport(buildInfo);
await Bun.write(outPath, report);
console.log(`\nReport saved to: ${outPath}`);
return report;
}
+167
View File
@@ -0,0 +1,167 @@
/**
* Test helpers for RTK integration testing.
*/
import { vmExec, RTK_BIN } from "./vm";
export type TestStatus = "PASS" | "FAIL" | "SKIP";
export interface TestResult {
name: string;
status: TestStatus;
detail: string;
exitCode?: number;
outputSize?: number;
savings?: number;
duration?: number;
}
const results: TestResult[] = [];
export function getResults(): TestResult[] {
return results;
}
export function getCounts() {
const total = results.length;
const passed = results.filter((r) => r.status === "PASS").length;
const failed = results.filter((r) => r.status === "FAIL").length;
const skipped = results.filter((r) => r.status === "SKIP").length;
return { total, passed, failed, skipped };
}
function record(result: TestResult) {
results.push(result);
const icon =
result.status === "PASS"
? "\x1b[32mPASS\x1b[0m"
: result.status === "FAIL"
? "\x1b[31mFAIL\x1b[0m"
: "\x1b[33mSKIP\x1b[0m";
console.log(` ${icon} | ${result.name} | ${result.detail}`);
}
/**
* Test a command exits with expected code and doesn't crash.
* expectedExit: number or "any" (just checks no signal death)
*/
export async function testCmd(
name: string,
cmd: string,
expectedExit: number | "any" = 0
): Promise<TestResult> {
const start = Date.now();
const { stdout, stderr, exitCode } = await vmExec(cmd);
const duration = Date.now() - start;
const outputSize = stdout.length + stderr.length;
let status: TestStatus;
let detail: string;
if (expectedExit === "any") {
// Just check it didn't die from signal (exit >= 128)
if (exitCode < 128) {
status = "PASS";
detail = `exit=${exitCode} | ${outputSize}b | ${duration}ms`;
} else {
status = "FAIL";
detail = `SIGNAL exit=${exitCode} | ${outputSize}b`;
}
} else if (exitCode === expectedExit) {
status = "PASS";
detail = `exit=${exitCode} | ${outputSize}b | ${duration}ms`;
} else {
status = "FAIL";
detail = `expected exit=${expectedExit}, got ${exitCode} | ${outputSize}b`;
}
const result: TestResult = {
name,
status,
detail,
exitCode,
outputSize,
duration,
};
record(result);
return result;
}
/**
* Test token savings: compare raw command output vs RTK filtered output.
*/
export async function testSavings(
name: string,
rawCmd: string,
rtkCmd: string,
targetPct: number
): Promise<TestResult> {
const raw = await vmExec(rawCmd);
const rtk = await vmExec(rtkCmd);
const rawSize = raw.stdout.length;
const rtkSize = rtk.stdout.length;
if (rawSize === 0) {
const result: TestResult = {
name,
status: "SKIP",
detail: "raw output empty",
};
record(result);
return result;
}
const savings = Math.round(100 - (rtkSize * 100) / rawSize);
let status: TestStatus;
let detail: string;
if (savings >= targetPct) {
status = "PASS";
detail = `raw=${rawSize}b filtered=${rtkSize}b savings=${savings}% (target: >=${targetPct}%)`;
} else {
status = "FAIL";
detail = `savings=${savings}% < target ${targetPct}% (raw=${rawSize}b filtered=${rtkSize}b)`;
}
const result: TestResult = { name, status, detail, savings };
record(result);
return result;
}
/**
* Test rewrite engine: input -> expected output.
*/
export async function testRewrite(
input: string,
expected: string
): Promise<TestResult> {
const escaped = input.replace(/'/g, "'\\''");
const { stdout } = await vmExec(`${RTK_BIN} rewrite '${escaped}'`);
const actual = stdout.trim();
let status: TestStatus;
let detail: string;
if (actual === expected) {
status = "PASS";
detail = `'${input}' -> '${actual}'`;
} else {
status = "FAIL";
detail = `'${input}' -> expected '${expected}', got '${actual}'`;
}
const result: TestResult = { name: `rewrite: ${input}`, status, detail };
record(result);
return result;
}
/**
* Skip a test with a reason.
*/
export function skipTest(name: string, reason: string): TestResult {
const result: TestResult = { name, status: "SKIP", detail: reason };
record(result);
return result;
}
+181
View File
@@ -0,0 +1,181 @@
/**
* Multipass VM management for RTK integration testing.
*/
import { $ } from "bun";
const VM_NAME = "rtk-test";
const CLOUD_INIT = "scripts/benchmark/cloud-init.yaml";
export interface VmInfo {
name: string;
state: string;
ipv4: string;
}
/** Check if VM exists and is running */
export async function vmExists(): Promise<boolean> {
const result = await $`multipass list --format json`.quiet();
const data = JSON.parse(result.stdout.toString());
return data.list?.some((vm: VmInfo) => vm.name === VM_NAME) ?? false;
}
/** Check if VM is running */
export async function vmRunning(): Promise<boolean> {
const result = await $`multipass list --format json`.quiet();
const data = JSON.parse(result.stdout.toString());
const vm = data.list?.find((v: VmInfo) => v.name === VM_NAME);
return vm?.state === "Running";
}
/** Create a new VM with cloud-init (20 min timeout for full provisioning) */
export async function vmCreate(): Promise<void> {
console.log(`[vm] Creating ${VM_NAME} with cloud-init (this takes ~10-15 min)...`);
// --timeout 1200 = 20 min for cloud-init to finish installing Rust, Go, Node, .NET, etc.
await $`multipass launch --name ${VM_NAME} --cpus 2 --memory 4G --disk 20G --timeout 1200 --cloud-init ${CLOUD_INIT} 24.04`;
}
/** Start existing VM */
export async function vmStart(): Promise<void> {
console.log(`[vm] Starting ${VM_NAME}...`);
await $`multipass start ${VM_NAME}`;
}
/** Execute a command in the VM, returns stdout (60s timeout per test by default) */
export async function vmExec(
cmd: string,
timeoutMs = 60_000
): Promise<{
stdout: string;
stderr: string;
exitCode: number;
}> {
const exec = $`multipass exec ${VM_NAME} -- bash -c ${cmd}`
.quiet()
.nothrow()
.then((r) => ({
stdout: r.stdout.toString(),
stderr: r.stderr.toString(),
exitCode: r.exitCode,
}));
const timeout = new Promise<{ stdout: string; stderr: string; exitCode: number }>((_, reject) =>
setTimeout(() => reject(new Error(`vmExec timed out after ${timeoutMs}ms: ${cmd}`)), timeoutMs)
);
return Promise.race([exec, timeout]);
}
/** Transfer a file to the VM */
export async function vmTransfer(
localPath: string,
remotePath: string
): Promise<void> {
await $`multipass transfer ${localPath} ${VM_NAME}:${remotePath}`;
}
/** Wait for cloud-init to complete (max 40 min — installs Rust, Go, Node, .NET, etc.) */
export async function vmWaitReady(maxWaitSec = 2400): Promise<boolean> {
console.log("[vm] Waiting for cloud-init...");
const start = Date.now();
while ((Date.now() - start) / 1000 < maxWaitSec) {
const { exitCode } = await vmExec(
"test -f /home/ubuntu/.cloud-init-complete"
);
if (exitCode === 0) {
const elapsed = Math.round((Date.now() - start) / 1000);
console.log(`[vm] Cloud-init complete after ${elapsed}s`);
return true;
}
await Bun.sleep(10_000);
}
console.error("[vm] Cloud-init timed out!");
return false;
}
/** Transfer RTK source and build in release mode */
export async function vmBuildRtk(projectRoot: string): Promise<{
buildTime: number;
binarySize: number;
version: string;
}> {
console.log("[vm] Transferring RTK source...");
// Create tarball excluding heavy dirs and macOS resource forks (._*)
await $`COPYFILE_DISABLE=1 tar czf /tmp/rtk-src.tar.gz --exclude target --exclude .git --exclude node_modules --exclude "index.html*" --exclude "._*" -C ${projectRoot} .`;
await vmTransfer("/tmp/rtk-src.tar.gz", "/tmp/rtk-src.tar.gz");
await vmExec(
"mkdir -p /home/ubuntu/rtk && cd /home/ubuntu/rtk && tar xzf /tmp/rtk-src.tar.gz"
);
console.log("[vm] Building RTK (release)...");
const start = Date.now();
const { stdout, exitCode } = await vmExec(
"export PATH=$HOME/.cargo/bin:$PATH && cd /home/ubuntu/rtk && cargo build --release 2>&1 | tail -5"
);
const buildTime = Math.round((Date.now() - start) / 1000);
if (exitCode !== 0) {
throw new Error(`Build failed:\n${stdout}`);
}
const { stdout: sizeStr } = await vmExec(
"stat -c%s /home/ubuntu/rtk/target/release/rtk"
);
const binarySize = parseInt(sizeStr.trim(), 10);
const { stdout: version } = await vmExec(
"/home/ubuntu/rtk/target/release/rtk --version"
);
console.log(
`[vm] Build OK in ${buildTime}s — ${binarySize} bytes — ${version.trim()}`
);
return { buildTime, binarySize, version: version.trim() };
}
/** Delete the VM */
export async function vmDelete(): Promise<void> {
console.log(`[vm] Deleting ${VM_NAME}...`);
await $`multipass delete ${VM_NAME} --purge`.nothrow();
}
/** Ensure VM is ready (create or reuse) */
export async function vmEnsureReady(): Promise<void> {
if (await vmExists()) {
if (!(await vmRunning())) {
await vmStart();
}
console.log(`[vm] Reusing existing VM ${VM_NAME}`);
// Check if cloud-init is still running
const { exitCode } = await vmExec(
"test -f /home/ubuntu/.cloud-init-complete"
);
if (exitCode !== 0) {
console.log("[vm] Cloud-init still running, waiting...");
const ready = await vmWaitReady();
if (!ready) {
throw new Error(
"Cloud-init timed out. Check: multipass exec rtk-test -- cat /var/log/cloud-init-output.log"
);
}
}
} else {
await vmCreate();
// multipass launch --timeout should wait, but double-check
const { exitCode } = await vmExec(
"test -f /home/ubuntu/.cloud-init-complete"
);
if (exitCode !== 0) {
const ready = await vmWaitReady();
if (!ready) {
throw new Error(
"Cloud-init timed out. Check: multipass exec rtk-test -- cat /var/log/cloud-init-output.log"
);
}
}
}
}
export const RTK_BIN = "/home/ubuntu/rtk/target/release/rtk";
+17
View File
@@ -0,0 +1,17 @@
#!/usr/bin/env bun
/**
* Fast rebuild: reuse existing VM, just transfer source and recompile.
* Usage: bun run scripts/benchmark/rebuild.ts
*/
import { vmEnsureReady, vmBuildRtk } from "./lib/vm";
const PROJECT_ROOT = new URL("../../", import.meta.url).pathname.replace(/\/$/, "");
await vmEnsureReady();
const info = await vmBuildRtk(PROJECT_ROOT);
console.log(`\nRebuild complete:`);
console.log(` Version: ${info.version}`);
console.log(` Binary: ${info.binarySize} bytes`);
console.log(` Time: ${info.buildTime}s`);
+425
View File
@@ -0,0 +1,425 @@
#!/usr/bin/env bun
/**
* RTK Full Integration Test Suite — Multipass VM
*
* Usage:
* bun run scripts/benchmark/run.ts # Full suite
* bun run scripts/benchmark/run.ts --quick # Skip slow phases (perf, concurrency)
* bun run scripts/benchmark/run.ts --phase 3 # Run specific phase only
*
* Prerequisites:
* brew install multipass
*/
import { $ } from "bun";
import { vmEnsureReady, vmBuildRtk, vmExec, RTK_BIN } from "./lib/vm";
import { testCmd, testSavings, testRewrite, skipTest, getCounts } from "./lib/test";
import { saveReport } from "./lib/report";
const args = process.argv.slice(2);
const quick = args.includes("--quick");
const phaseArg = args.includes("--phase")
? parseInt(args[args.indexOf("--phase") + 1], 10)
: null;
const phaseOnly = phaseArg !== null && !Number.isNaN(phaseArg) ? phaseArg : null;
if (args.includes("--phase") && phaseOnly === null) {
console.error("Error: --phase requires a number (e.g. --phase 3)");
process.exit(1);
}
const reportPath = args.includes("--report")
? args[args.indexOf("--report") + 1]
: `${new URL("../../", import.meta.url).pathname.replace(/\/$/, "")}/benchmark-report.txt`;
const PROJECT_ROOT = new URL("../../", import.meta.url).pathname.replace(/\/$/, "");
const RTK = RTK_BIN;
function shouldRun(phase: number): boolean {
return phaseOnly === null || phaseOnly === phase;
}
function heading(phase: number, title: string) {
console.log(`\n\x1b[34m[Phase ${phase}] ${title}\x1b[0m`);
}
// ══════════════════════════════════════════════════════════════
// Phase 0: VM Setup
// ══════════════════════════════════════════════════════════════
console.log("\x1b[34m[rtk-test] RTK Full Integration Test Suite\x1b[0m");
console.log(`Project: ${PROJECT_ROOT}`);
await vmEnsureReady();
// ══════════════════════════════════════════════════════════════
// Phase 1: Transfer & Build
// ══════════════════════════════════════════════════════════════
heading(1, "Transfer & Build");
const branch = (await $`git -C ${PROJECT_ROOT} branch --show-current`.text()).trim();
const commit = (await $`git -C ${PROJECT_ROOT} log --oneline -1`.text()).trim();
const buildInfo = await vmBuildRtk(PROJECT_ROOT);
// Binary size check
// ARM Linux release binaries are ~6.5MB (vs ~4MB x86 stripped).
// CLAUDE.md target is <5MB for stripped x86 release builds.
// VM builds are ARM + not fully stripped, so we use a relaxed 8MB limit here.
const sizeLimit = 8_388_608; // 8MB (relaxed for ARM Linux VM)
if (buildInfo.binarySize < sizeLimit) {
console.log(` \x1b[32mPASS\x1b[0m | binary size | ${buildInfo.binarySize} bytes < 8MB`);
} else {
console.log(` \x1b[31mFAIL\x1b[0m | binary size | ${buildInfo.binarySize} bytes >= 8MB`);
}
// ══════════════════════════════════════════════════════════════
// Phase 2: Cargo Quality (fmt, clippy, test)
// ══════════════════════════════════════════════════════════════
if (shouldRun(2)) {
heading(2, "Cargo Quality");
await testCmd(
"quality:cargo fmt",
"export PATH=$HOME/.cargo/bin:$PATH && cd /home/ubuntu/rtk && cargo fmt --all --check 2>&1"
);
await testCmd(
"quality:cargo clippy",
"export PATH=$HOME/.cargo/bin:$PATH && cd /home/ubuntu/rtk && cargo clippy --all-targets -- -D warnings 2>&1"
);
await testCmd(
"quality:cargo test",
"export PATH=$HOME/.cargo/bin:$PATH && cd /home/ubuntu/rtk && cargo test --all 2>&1"
);
}
// ══════════════════════════════════════════════════════════════
// Phase 3: Rust Built-in Commands
// ══════════════════════════════════════════════════════════════
if (shouldRun(3)) {
heading(3, "Rust Built-in Commands");
// Git
await testCmd("git:status", `cd /tmp/test-git && ${RTK} git status`);
await testCmd("git:log", `cd /tmp/test-git && ${RTK} git log -5`);
await testCmd("git:log --oneline", `cd /tmp/test-git && ${RTK} git log --oneline -10`);
await testCmd("git:diff", `cd /tmp/test-git && ${RTK} git diff`, "any");
await testCmd("git:branch", `cd /tmp/test-git && ${RTK} git branch`);
await testCmd("git:add --dry-run", `cd /tmp/test-git && ${RTK} git add --dry-run .`, "any");
// Files
await testCmd("files:ls", `${RTK} ls /home/ubuntu/rtk`);
await testCmd("files:ls src/", `${RTK} ls /home/ubuntu/rtk/src/`);
await testCmd("files:ls -R", `${RTK} ls -R /home/ubuntu/rtk/src/`);
await testCmd("files:read", `${RTK} read /home/ubuntu/rtk/src/main.rs`);
await testCmd("files:read aggressive", `${RTK} read /home/ubuntu/rtk/src/main.rs -l aggressive`);
await testCmd("files:smart", `${RTK} smart /home/ubuntu/rtk/src/main.rs`);
await testCmd("files:find *.rs", `${RTK} find '*.rs' /home/ubuntu/rtk/src/`);
await testCmd("files:wc", `${RTK} wc /home/ubuntu/rtk/src/main.rs`);
await testCmd("files:diff", `${RTK} diff /home/ubuntu/rtk/src/main.rs /home/ubuntu/rtk/src/utils.rs`);
// Search
await testCmd("search:grep", `${RTK} grep 'fn main' /home/ubuntu/rtk/src/`);
// Data
await testCmd("data:json", `${RTK} json /tmp/test-node/package.json`);
await testCmd("data:deps", `cd /home/ubuntu/rtk && ${RTK} deps`);
await testCmd("data:env", `${RTK} env`);
// Runners
await testCmd("runner:summary", `${RTK} summary 'echo hello world'`);
// BUG: rtk err swallows exit code — tracked in #846
await testCmd("runner:err", `${RTK} err false`, "any");
await testCmd("runner:test", `${RTK} test 'echo ok'`, "any");
// Logs
await testCmd("log:large", `${RTK} log /tmp/large.log`);
// Network
await testCmd("net:curl", `${RTK} curl https://mockhttp.org/get`, "any");
// GitHub
await testCmd("gh:pr list", `cd /home/ubuntu/rtk && ${RTK} gh pr list`, "any");
// Cargo (test project has intentional test failure → exit 101)
await testCmd("cargo:build", `export PATH=$HOME/.cargo/bin:$PATH && cd /tmp/test-rust && ${RTK} cargo build`);
await testCmd("cargo:test", `export PATH=$HOME/.cargo/bin:$PATH && cd /tmp/test-rust && ${RTK} cargo test`, 101);
await testCmd("cargo:clippy", `export PATH=$HOME/.cargo/bin:$PATH && cd /tmp/test-rust && ${RTK} cargo clippy`);
// Python (test project has intentional failures)
await testCmd("python:pytest", `cd /tmp/test-python && ${RTK} pytest`, 1);
await testCmd("python:ruff check", `cd /tmp/test-python && ${RTK} ruff check .`, 1);
await testCmd("python:mypy", `cd /tmp/test-python && ${RTK} mypy .`, 1);
await testCmd("python:pip list", `${RTK} pip list`);
// Go (test project has intentional test failure)
await testCmd("go:test", `export PATH=$PATH:/usr/local/go/bin && cd /tmp/test-go && ${RTK} go test ./...`, 1);
await testCmd("go:build", `export PATH=$PATH:/usr/local/go/bin && cd /tmp/test-go && ${RTK} go build .`, 1);
await testCmd("go:vet", `export PATH=$PATH:/usr/local/go/bin && cd /tmp/test-go && ${RTK} go vet ./...`, 1);
await testCmd("go:golangci-lint", `export PATH=$PATH:/usr/local/go/bin:$HOME/go/bin && cd /tmp/test-go && ${RTK} golangci-lint run`, 1);
// TypeScript
await testCmd("ts:tsc", `cd /tmp/test-node && ${RTK} tsc --noEmit`, "any");
// Linters
await testCmd("lint:eslint", `cd /tmp/test-node && ${RTK} lint 'eslint src/'`, "any");
await testCmd("lint:prettier", `cd /tmp/test-node && ${RTK} prettier --check src/`, "any");
// Docker
await testCmd("docker:ps", `${RTK} docker ps`, "any");
await testCmd("docker:images", `${RTK} docker images`, "any");
// Kubernetes
await testCmd("k8s:pods", `${RTK} kubectl pods`, "any");
// .NET
await testCmd("dotnet:build", `export DOTNET_ROOT=/usr/local/share/dotnet && export PATH=$PATH:$DOTNET_ROOT && cd /tmp/test-dotnet/TestApp 2>/dev/null && ${RTK} dotnet build || echo 'dotnet skip'`, "any");
// Meta
await testCmd("meta:gain", `${RTK} gain`);
await testCmd("meta:gain --history", `${RTK} gain --history`);
await testCmd("meta:proxy", `${RTK} proxy echo 'proxy test'`);
await testCmd("meta:verify", `${RTK} verify`, "any");
}
// ══════════════════════════════════════════════════════════════
// Phase 4: TOML Filter Commands
// ══════════════════════════════════════════════════════════════
if (shouldRun(4)) {
heading(4, "TOML Filter Commands");
// System
await testCmd("toml:df", `${RTK} df -h`);
await testCmd("toml:du", `${RTK} du -sh /tmp`, "any");
await testCmd("toml:ps", `${RTK} ps aux`);
await testCmd("toml:ping", `${RTK} ping -c 2 127.0.0.1`);
// Build tools
await testCmd("toml:make", `cd /tmp && ${RTK} make -f Makefile`, "any");
await testCmd("toml:rsync", `${RTK} rsync --version`);
// Linters
await testCmd("toml:shellcheck", `${RTK} shellcheck /tmp/test.sh`, "any");
await testCmd("toml:hadolint", `${RTK} hadolint /tmp/Dockerfile.bad`, "any");
await testCmd("toml:yamllint", `${RTK} yamllint /tmp/test.yaml`, "any");
await testCmd("toml:markdownlint", `${RTK} markdownlint /tmp/test.md`, "any");
// Cloud/Infra
await testCmd("toml:terraform", `${RTK} terraform --version`, "any");
await testCmd("toml:helm", `${RTK} helm version`, "any");
await testCmd("toml:ansible", `${RTK} ansible-playbook --version`, "any");
// Mocked tools
await testCmd("toml:gcloud", `${RTK} gcloud version`);
await testCmd("toml:shopify", `${RTK} shopify theme check`, "any");
await testCmd("toml:pio", `${RTK} pio run`, "any");
await testCmd("toml:quarto", `${RTK} quarto render`, "any");
await testCmd("toml:sops", `${RTK} sops --version`);
// Swift ecosystem
await testCmd("toml:swift build", `${RTK} swift build`, "any");
await testCmd("toml:swift test", `${RTK} swift test`, "any");
await testCmd("toml:swift run", `${RTK} swift run`, "any");
await testCmd("toml:swift package", `${RTK} swift package resolve`, "any");
await testCmd("toml:swiftlint", `${RTK} swiftlint`, "any");
await testCmd("toml:swiftformat", `${RTK} swiftformat`, "any");
await testCmd("toml:kubectl", `${RTK} kubectl version --client`, "any");
}
// ══════════════════════════════════════════════════════════════
// Phase 5: Hook Rewrite Engine
// ══════════════════════════════════════════════════════════════
if (shouldRun(5)) {
heading(5, "Hook Rewrite Engine");
// Basic rewrites
await testRewrite("git status", "rtk git status");
await testRewrite("git log --oneline -10", "rtk git log --oneline -10");
await testRewrite("cargo test", "rtk cargo test");
await testRewrite("cargo build --release", "rtk cargo build --release");
await testRewrite("docker ps", "rtk docker ps");
// NOTE: rtk rewrites "kubectl get pods" to "rtk kubectl get pods" (preserves get)
await testRewrite("kubectl get pods", "rtk kubectl get pods");
await testRewrite("ruff check", "rtk ruff check");
await testRewrite("pytest", "rtk pytest");
await testRewrite("go test", "rtk go test");
await testRewrite("pnpm list", "rtk pnpm list");
await testRewrite("gh pr list", "rtk gh pr list");
await testRewrite("df -h", "rtk df -h");
await testRewrite("ps aux", "rtk ps aux");
// Compound
await testRewrite("cargo test && git status", "rtk cargo test && rtk git status");
// NOTE: shell strips single quotes in vmExec, so 'msg' becomes msg
await testRewrite("git add . && git commit -m msg", "rtk git add . && rtk git commit -m msg");
// No rewrite (shell builtins) — rtk rewrite returns empty string + exit 1
// We test via testCmd since testRewrite expects non-empty output
await testCmd("rewrite:cd (no rewrite)", `${RTK} rewrite 'cd /tmp'`, 1);
await testCmd("rewrite:export (no rewrite)", `${RTK} rewrite 'export FOO=bar'`, 1);
}
// ══════════════════════════════════════════════════════════════
// Phase 6: Exit Code Preservation
// ══════════════════════════════════════════════════════════════
if (shouldRun(6)) {
heading(6, "Exit Code Preservation");
// Success
await testCmd("exit:git status=0", `cd /tmp/test-git && ${RTK} git status`, 0);
await testCmd("exit:ls=0", `${RTK} ls /tmp`, 0);
await testCmd("exit:gain=0", `${RTK} gain`, 0);
// Failures
// rg returns exit 1 (no match) or 2 (error) — accept both
await testCmd("exit:grep NOTFOUND", `${RTK} grep NOTFOUND_XYZ_123 /tmp`, "any");
}
// ══════════════════════════════════════════════════════════════
// Phase 7: Token Savings
// ══════════════════════════════════════════════════════════════
if (shouldRun(7)) {
heading(7, "Token Savings");
await testSavings(
"savings:git log",
"cd /tmp/test-git && git log -20",
`cd /tmp/test-git && ${RTK} git log -20`,
60
);
await testSavings(
"savings:ls",
"ls -la /home/ubuntu/rtk/src/",
`${RTK} ls /home/ubuntu/rtk/src/`,
60
);
await testSavings(
"savings:log dedup",
"cat /tmp/large.log",
`${RTK} log /tmp/large.log`,
80
);
await testSavings(
"savings:read aggressive",
"cat /home/ubuntu/rtk/src/main.rs",
`${RTK} read /home/ubuntu/rtk/src/main.rs -l aggressive`,
50
);
await testSavings(
"savings:swift test",
"swift test",
`${RTK} swift test`,
60
);
await testSavings(
"savings:swiftlint",
"swiftlint",
`${RTK} swiftlint`,
20
);
}
// ══════════════════════════════════════════════════════════════
// Phase 8: Pipe Compatibility
// ══════════════════════════════════════════════════════════════
if (shouldRun(8)) {
heading(8, "Pipe Compatibility");
await testCmd("pipe:git status|wc", `cd /tmp/test-git && ${RTK} git status | wc -l`);
await testCmd("pipe:ls|wc", `${RTK} ls /home/ubuntu/rtk/src/ | wc -l`);
await testCmd("pipe:grep|head", `${RTK} grep 'fn' /home/ubuntu/rtk/src/ | head -5`);
}
// ══════════════════════════════════════════════════════════════
// Phase 9: Edge Cases
// ══════════════════════════════════════════════════════════════
if (shouldRun(9)) {
heading(9, "Edge Cases");
await testCmd("edge:summary true", `${RTK} summary 'true'`, "any");
await testCmd("edge:grep NOTFOUND", `${RTK} grep NOTFOUND_XYZ /home/ubuntu/rtk/src/`, 1);
await testCmd("edge:unicode", `echo 'hello world' > /tmp/uni.txt && ${RTK} grep 'hello' /tmp`, "any");
}
// ══════════════════════════════════════════════════════════════
// Phase 10: Performance (skip with --quick)
// ══════════════════════════════════════════════════════════════
if (shouldRun(10) && !quick) {
heading(10, "Performance");
// hyperfine
const { exitCode: hfExist } = await vmExec("command -v hyperfine");
if (hfExist === 0) {
const { stdout: hfOut } = await vmExec(
`cd /tmp/test-git && hyperfine --warmup 3 --min-runs 5 '${RTK} git status' 'git status' --export-json /dev/stdout 2>/dev/null`
);
try {
const hf = JSON.parse(hfOut);
const rtkMean = (hf.results?.[0]?.mean * 1000).toFixed(1);
const rawMean = (hf.results?.[1]?.mean * 1000).toFixed(1);
console.log(` Startup: rtk=${rtkMean}ms raw=${rawMean}ms`);
} catch {
console.log(" hyperfine output parse failed");
}
} else {
skipTest("perf:hyperfine", "not installed");
}
// Memory
const { stdout: memOut } = await vmExec(
`cd /tmp/test-git && /usr/bin/time -v ${RTK} git status 2>&1 | grep 'Maximum resident'`
);
const memKb = parseInt(memOut.match(/(\d+)/)?.[1] ?? "0", 10);
if (memKb > 0 && memKb < 20000) {
await testCmd("perf:memory", `echo '${memKb} KB < 20MB'`);
} else if (memKb > 0) {
await testCmd("perf:memory", `echo '${memKb} KB >= 20MB' && exit 1`, 0);
}
} else if (quick && shouldRun(10)) {
skipTest("perf:hyperfine", "--quick mode");
skipTest("perf:memory", "--quick mode");
}
// ══════════════════════════════════════════════════════════════
// Phase 11: Concurrency (skip with --quick)
// ══════════════════════════════════════════════════════════════
if (shouldRun(11) && !quick) {
heading(11, "Concurrency");
await testCmd(
"concurrency:10x git status",
`cd /tmp/test-git && for i in $(seq 1 10); do ${RTK} git status >/dev/null & done; wait`
);
} else if (quick && shouldRun(11)) {
skipTest("concurrency:10x", "--quick mode");
}
// ══════════════════════════════════════════════════════════════
// Report
// ══════════════════════════════════════════════════════════════
const report = await saveReport(
{ ...buildInfo, branch, commit },
reportPath
);
console.log("\n" + report);
const { total, passed, failed, skipped } = getCounts();
const passRate = total > 0 ? Math.round((passed * 100) / total) : 0;
if (failed === 0) {
console.log(`\n\x1b[32m READY FOR RELEASE — ${passed}/${total} (${passRate}%)\x1b[0m\n`);
process.exit(0);
} else {
console.log(`\n\x1b[31m NOT READY — ${failed} failures — ${passed}/${total} (${passRate}%)\x1b[0m\n`);
process.exit(1);
}