跳转到内容
本站为社区维护的非官方中文翻译,与 Cloudflare, Inc. 无关联。本站不提供账户登录,请勿在任何非官方站点输入您的 Cloudflare 账户信息。官方文档:developers.cloudflare.com

运行 Git 仓库中的测试

最后更新 查看 MarkdownAgent 设置

在全新 sandbox 中运行仓库的测试。本例中,Durable Object 会克隆仓库、安装依赖、运行测试脚本、返回结果,然后停止实例。

前提条件

当您运行 wrangler deploy 时,必须在本地运行 Docker。对于大多数人来说,安装 Docker 的最佳方式是按照安装 Docker Desktop 的文档 ↗进行操作。其他工具(如 Colima ↗)也可能有效。

您可以通过在终端中运行 docker info 命令来检查 Docker 是否正常运行。如果 Docker 正在运行,该命令将成功执行。如果 Docker 未运行,docker info 命令将挂起或返回包含 "Cannot connect to the Docker daemon" 消息的错误。

运行测试套件

  1. 在项目根目录创建 Dockerfile,包含 Git 以及测试所需的运行时。这个镜像会在带 Node.js 24 的 Debian Trixie 中添加 Git:

    Dockerfiledockerfile
    FROM node:24-trixie-slim
    
    RUN apt-get update \
        && apt-get install --yes --no-install-recommends ca-certificates git \
        && rm -rf /var/lib/apt/lists/*
    
    CMD ["sleep", "infinity"]

    sleep infinity 会让 container 保持运行,以便接受更多 exec() 调用。

  2. 在 wrangler.jsonc 中,将 Dockerfile 构建为 containers 条目中的具名镜像:

    {
        "containers": [
            {
                "class_name": "MyContainer",
                "scheduling_policy": "durable_object",
                "images": {
                    "tests": {
                        "dockerfile": "./Dockerfile",
                    },
                },
            },
        ],
    }
    [[containers]]
    class_name = "MyContainer"
    scheduling_policy = "durable_object"
    
    [containers.images.tests]
    dockerfile = "./Dockerfile"
  3. 向 Durable Object 添加方法,用于克隆仓库、安装依赖并运行测试:

    src/index.tsts
    export class MyContainer extends DurableObject<Env> {
        // ...
    
        async runTests(repository: string, revision: string) {
            const container = this.ctx.container;
    
            if (!container) {
                throw new Error("No container is configured");
            }
    
            container.start({
                image: container.images.tests,
                // `git` and `npm` need the Internet. Do not pass credentials into this sandbox
                enableInternet: true,
            });
    
            const decoder = new TextDecoder();
            const run = async (step: string, argv: string[], cwd?: string) => {
                // Limit each step to 10 minutes.
                const process = await container.exec(
                    ["timeout", "--kill-after=5", "600", ...argv],
                    { cwd },
                );
                const output = await process.output();
    
                return {
                    step,
                    stdout: decoder.decode(output.stdout),
                    stderr: decoder.decode(output.stderr),
                    exitCode: output.exitCode,
                };
            };
    
            try {
                const clone = await run("clone", [
                    "git",
                    "clone",
                    "--depth=1",
                    `--branch=${revision}`,
                    "--",
                    repository,
                    "/workspace",
                ]);
    
                if (clone.exitCode !== 0) {
                    return clone;
                }
    
                const install = await run(
                    "install",
                    ["npm", "ci", "--no-audit", "--no-fund"],
                    "/workspace",
                );
    
                if (install.exitCode !== 0) {
                    return install;
                }
    
                return await run("test", ["npm", "test", "--", "--silent"], "/workspace");
            } finally {
                if (container.running) {
                    await container.destroy();
                }
            }
        }
    }

    -- 会阻止 git 将以 - 开头的仓库值当作选项读取。

    运行时间超过 10 分钟的步骤会返回退出代码 124;如果 timeout 也发送了 SIGKILL,则返回 137。timeout 会停止该步骤及其启动且仍处于同一进程组中的进程。更多信息请参阅停止命令启动的进程。finally 代码块会在运行后停止实例。

    output() 会将步骤的全部输出保存在 Durable Object 的内存中。对于输出很多的测试套件,请改为 流式输出。

    enableInternet: true 允许 git 和 npm 下载仓库及其软件包。它也允许仓库的安装和测试脚本访问互联网,因此不要将凭据传入此 sandbox。更多信息请参阅 Sandbox 安全。

  4. 向 Worker 添加路由,在新 sandbox 中运行测试:

    src/index.jsjs
    export default {
    	async fetch(request, env) {
    		if (request.method !== "POST") {
    			return new Response("Not found", { status: 404 });
    		}
    
    		const sandbox = env.MY_CONTAINER.getByName(crypto.randomUUID());
    		const result = await sandbox.runTests(
    			"https://github.com/cloudflare/workers-oauth-provider.git",
    			"v0.0.6",
    		);
    
    		return Response.json(result, {
    			status: result.exitCode === 0 ? 200 : 500,
    		});
    	},
    };
    src/index.tsts
    export default {
        async fetch(request: Request, env: Env): Promise<Response> {
            if (request.method !== "POST") {
                return new Response("Not found", { status: 404 });
            }
    
            const sandbox = env.MY_CONTAINER.getByName(crypto.randomUUID());
            const result = await sandbox.runTests(
                "https://github.com/cloudflare/workers-oauth-provider.git",
                "v0.0.6",
            );
    
            return Response.json(result, {
                status: result.exitCode === 0 ? 200 : 500,
            });
        },
    } satisfies ExportedHandler<Env>;

    随机 sandbox 名称会为每次运行提供新实例,因此一次运行看不到另一次运行的文件。

    本例中,Worker 运行公开 workers-oauth-provider ↗ 仓库版本 v0.0.6 的测试。请用你自己的仓库、修订版本和 npm 命令替换它们。exec() 不会启动 shell,因此请将每个参数放在独立的数组项中。

  5. 部署你的 Worker:

    npx wrangler deploy
  6. 向 Wrangler 打印的 workers.dev URL 发送 POST 请求:

    curl https://<YOUR_WORKER>.<YOUR_SUBDOMAIN>.workers.dev --request POST

    响应包含测试输出和退出码:

    {
        "step": "test",
        "stdout": "...\n Test Files  1 passed (1)\n      Tests  37 passed (37)\n...",
        "stderr": "",
        "exitCode": 0
    }

    当某个步骤失败时,Worker 会以 500 响应,step 会标出失败的步骤。

相关资源

这篇文档对您有帮助吗?