From 29f222049297f063ed3fb15813d0bc40d9510c70 Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Fri, 21 Mar 2025 09:46:18 +0000 Subject: [PATCH 01/14] Add additional reaper tests --- .../testcontainers/src/reaper/reaper.test.ts | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/packages/testcontainers/src/reaper/reaper.test.ts b/packages/testcontainers/src/reaper/reaper.test.ts index 4421c4c56..e3b8aad94 100644 --- a/packages/testcontainers/src/reaper/reaper.test.ts +++ b/packages/testcontainers/src/reaper/reaper.test.ts @@ -1,4 +1,5 @@ import { ContainerRuntimeClient, getContainerRuntimeClient } from "../container-runtime"; +import { RandomUniquePortGenerator } from "../utils/port-generator"; describe("Reaper", { timeout: 120_000 }, () => { let client: ContainerRuntimeClient; @@ -12,6 +13,42 @@ describe("Reaper", { timeout: 120_000 }, () => { client = await getContainerRuntimeClient(); }); + it("should create disabled reaper when TESTCONTAINERS_RYUK_DISABLED=true", async () => { + vitest.stubEnv("TESTCONTAINERS_RYUK_DISABLED", "true"); + + vi.spyOn(client.container, "list").mockResolvedValue([]); + const reaper = await getReaper(); + + // DisabledReaper has an empty containerId + expect(reaper.containerId).toBe(""); + + // Should not throw exceptions when methods are called + expect(() => reaper.addSession("test-session")).not.toThrow(); + expect(() => reaper.addComposeProject("test-project")).not.toThrow(); + }); + + it("should use custom port when TESTCONTAINERS_RYUK_PORT is set", async () => { + const customPort = (await new RandomUniquePortGenerator().generatePort()).toString(); + vitest.stubEnv("TESTCONTAINERS_RYUK_PORT", customPort); + + vi.spyOn(client.container, "list").mockResolvedValue([]); + const reaper = await getReaper(); + + const reaperContainer = client.container.getById(reaper.containerId); + const ports = (await reaperContainer.inspect()).HostConfig.PortBindings; + expect(ports["8080"][0].HostPort).toBe(customPort); + }); + + it("should reuse existing reaper container if one is already running", async () => { + const reaper = await getReaper(); + const reaperContainerInfo = (await client.container.list()).filter((c) => c.Id === reaper.containerId)[0]; + vi.spyOn(client.container, "list").mockResolvedValue([reaperContainerInfo]); + + const reaper2 = await getReaper(); + + expect(reaper2.containerId).toBe(reaper.containerId); + }); + it("should create Reaper container without RYUK_VERBOSE env var by default", async () => { vi.spyOn(client.container, "list").mockResolvedValue([]); const reaper = await getReaper(); From 0fb5e9aee2639da5de2bbf425ff18c17493235f7 Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Fri, 21 Mar 2025 09:50:38 +0000 Subject: [PATCH 02/14] Reactor setting env vars in tests --- .../auth/get-auth-config.test.ts | 21 ++++++----- .../src/container-runtime/image-name.test.ts | 37 ++++++------------- .../generic-container-reuse.test.ts | 6 +-- .../testcontainers/src/reaper/reaper.test.ts | 8 ++-- 4 files changed, 30 insertions(+), 42 deletions(-) diff --git a/packages/testcontainers/src/container-runtime/auth/get-auth-config.test.ts b/packages/testcontainers/src/container-runtime/auth/get-auth-config.test.ts index 3ea88ffe1..fd45721ce 100644 --- a/packages/testcontainers/src/container-runtime/auth/get-auth-config.test.ts +++ b/packages/testcontainers/src/container-runtime/auth/get-auth-config.test.ts @@ -14,20 +14,23 @@ describe("get auth config", () => { }); afterEach(() => { - delete process.env.DOCKER_AUTH_CONFIG; + vi.unstubAllEnvs(); vi.resetModules(); }); it("should use DOCKER_AUTH_CONFIG environment variable as Docker config", async () => { - process.env.DOCKER_AUTH_CONFIG = JSON.stringify({ - auths: { - "https://registry.example.com": { - email: "user@example.com", - username: "user", - password: "pass", + vi.stubEnv( + "DOCKER_AUTH_CONFIG", + JSON.stringify({ + auths: { + "https://registry.example.com": { + email: "user@example.com", + username: "user", + password: "pass", + }, }, - }, - }); + }) + ); const { getAuthConfig } = await import("./get-auth-config"); expect(await getAuthConfig("https://registry.example.com")).toEqual({ username: "user", diff --git a/packages/testcontainers/src/container-runtime/image-name.test.ts b/packages/testcontainers/src/container-runtime/image-name.test.ts index fe919ae62..d6f0a66ee 100644 --- a/packages/testcontainers/src/container-runtime/image-name.test.ts +++ b/packages/testcontainers/src/container-runtime/image-name.test.ts @@ -10,6 +10,10 @@ describe("ContainerImage", () => { expect(imageName.equals(new ImageName("anotherRegistry", "image", "tag"))).toBe(false); }); + afterEach(() => { + vi.unstubAllEnvs(); + }); + describe("string", () => { it("should work with registry", () => { const imageName = new ImageName("registry", "image", "tag"); @@ -57,21 +61,12 @@ describe("ContainerImage", () => { it.each(["custom.com/registry", "custom.com/registry/"])( "should substitute no registry with the one provided via TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX when provided registry is %s", (customRegistry: string) => { - const oldEnvValue = process.env.TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX; - try { - process.env.TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX = customRegistry; - const imageName = new ImageName(undefined, "image", "tag"); - expect(imageName.registry).toBe("custom.com"); - expect(imageName.image).toBe("registry/image"); - expect(imageName.tag).toBe("tag"); - expect(imageName.string).toBe("custom.com/registry/image:tag"); - } finally { - if (oldEnvValue === undefined) { - delete process.env.TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX; - } else { - process.env.TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX = oldEnvValue; - } - } + vi.stubEnv("TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX", customRegistry); + const imageName = new ImageName(undefined, "image", "tag"); + expect(imageName.registry).toBe("custom.com"); + expect(imageName.image).toBe("registry/image"); + expect(imageName.tag).toBe("tag"); + expect(imageName.string).toBe("custom.com/registry/image:tag"); } ); }); @@ -176,18 +171,8 @@ describe("ContainerImage", () => { ])( "fromString with TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX set to $customRegistry", ({ customRegistry, expectedRegistry, expectedImagePrefix }) => { - let oldEnvValue: string | undefined; beforeEach(() => { - oldEnvValue = process.env.TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX; - process.env.TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX = customRegistry; - }); - - afterEach(() => { - if (oldEnvValue === undefined) { - delete process.env.TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX; - } else { - process.env.TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX = oldEnvValue; - } + vi.stubEnv("TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX", customRegistry); }); it("should work", () => { diff --git a/packages/testcontainers/src/generic-container/generic-container-reuse.test.ts b/packages/testcontainers/src/generic-container/generic-container-reuse.test.ts index 6d53b7cbc..4bedca2ba 100644 --- a/packages/testcontainers/src/generic-container/generic-container-reuse.test.ts +++ b/packages/testcontainers/src/generic-container/generic-container-reuse.test.ts @@ -4,7 +4,7 @@ import { GenericContainer } from "./generic-container"; describe("GenericContainer reuse", { timeout: 180_000 }, () => { afterEach(() => { - process.env.TESTCONTAINERS_REUSE_ENABLE = undefined; + vi.unstubAllEnvs(); }); it("should not reuse the container by default", async () => { @@ -64,7 +64,7 @@ describe("GenericContainer reuse", { timeout: 180_000 }, () => { }); it("should not reuse the container when TESTCONTAINERS_REUSE_ENABLE is set to false", async () => { - process.env.TESTCONTAINERS_REUSE_ENABLE = "false"; + vi.stubEnv("TESTCONTAINERS_REUSE_ENABLE", "false"); const name = `there_can_only_be_one_${randomUuid()}`; const container = await new GenericContainer("cristianrgreco/testcontainer:1.1.14") @@ -90,7 +90,7 @@ describe("GenericContainer reuse", { timeout: 180_000 }, () => { it.each(["true", undefined])( "should reuse the container when TESTCONTAINERS_REUSE_ENABLE is set to %s", async (reuseEnable: string | undefined) => { - process.env.TESTCONTAINERS_REUSE_ENABLE = reuseEnable; + vi.stubEnv("TESTCONTAINERS_REUSE_ENABLE", reuseEnable); const name = `there_can_only_be_one_${randomUuid()}`; const container1 = await new GenericContainer("cristianrgreco/testcontainer:1.1.14") diff --git a/packages/testcontainers/src/reaper/reaper.test.ts b/packages/testcontainers/src/reaper/reaper.test.ts index e3b8aad94..998116f71 100644 --- a/packages/testcontainers/src/reaper/reaper.test.ts +++ b/packages/testcontainers/src/reaper/reaper.test.ts @@ -8,13 +8,13 @@ describe("Reaper", { timeout: 120_000 }, () => { beforeEach(async () => { vi.resetModules(); - vitest.unstubAllEnvs(); + vi.unstubAllEnvs(); client = await getContainerRuntimeClient(); }); it("should create disabled reaper when TESTCONTAINERS_RYUK_DISABLED=true", async () => { - vitest.stubEnv("TESTCONTAINERS_RYUK_DISABLED", "true"); + vi.stubEnv("TESTCONTAINERS_RYUK_DISABLED", "true"); vi.spyOn(client.container, "list").mockResolvedValue([]); const reaper = await getReaper(); @@ -29,7 +29,7 @@ describe("Reaper", { timeout: 120_000 }, () => { it("should use custom port when TESTCONTAINERS_RYUK_PORT is set", async () => { const customPort = (await new RandomUniquePortGenerator().generatePort()).toString(); - vitest.stubEnv("TESTCONTAINERS_RYUK_PORT", customPort); + vi.stubEnv("TESTCONTAINERS_RYUK_PORT", customPort); vi.spyOn(client.container, "list").mockResolvedValue([]); const reaper = await getReaper(); @@ -60,7 +60,7 @@ describe("Reaper", { timeout: 120_000 }, () => { }); it("should propagate TESTCONTAINERS_RYUK_VERBOSE into Reaper container", async () => { - vitest.stubEnv("TESTCONTAINERS_RYUK_VERBOSE", "true"); + vi.stubEnv("TESTCONTAINERS_RYUK_VERBOSE", "true"); vi.spyOn(client.container, "list").mockResolvedValue([]); const reaper = await getReaper(); From 72a0e92560135bba174c413950aaf627f8123a98 Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Fri, 21 Mar 2025 10:01:54 +0000 Subject: [PATCH 03/14] Always unstub envs --- .../src/container-runtime/auth/get-auth-config.test.ts | 1 - .../testcontainers/src/container-runtime/image-name.test.ts | 4 ---- .../src/generic-container/generic-container-reuse.test.ts | 4 ---- packages/testcontainers/src/reaper/reaper.test.ts | 2 -- vitest.config.ts | 1 + 5 files changed, 1 insertion(+), 11 deletions(-) diff --git a/packages/testcontainers/src/container-runtime/auth/get-auth-config.test.ts b/packages/testcontainers/src/container-runtime/auth/get-auth-config.test.ts index fd45721ce..bb9e8887d 100644 --- a/packages/testcontainers/src/container-runtime/auth/get-auth-config.test.ts +++ b/packages/testcontainers/src/container-runtime/auth/get-auth-config.test.ts @@ -14,7 +14,6 @@ describe("get auth config", () => { }); afterEach(() => { - vi.unstubAllEnvs(); vi.resetModules(); }); diff --git a/packages/testcontainers/src/container-runtime/image-name.test.ts b/packages/testcontainers/src/container-runtime/image-name.test.ts index d6f0a66ee..4f87519cc 100644 --- a/packages/testcontainers/src/container-runtime/image-name.test.ts +++ b/packages/testcontainers/src/container-runtime/image-name.test.ts @@ -10,10 +10,6 @@ describe("ContainerImage", () => { expect(imageName.equals(new ImageName("anotherRegistry", "image", "tag"))).toBe(false); }); - afterEach(() => { - vi.unstubAllEnvs(); - }); - describe("string", () => { it("should work with registry", () => { const imageName = new ImageName("registry", "image", "tag"); diff --git a/packages/testcontainers/src/generic-container/generic-container-reuse.test.ts b/packages/testcontainers/src/generic-container/generic-container-reuse.test.ts index 4bedca2ba..785d92733 100644 --- a/packages/testcontainers/src/generic-container/generic-container-reuse.test.ts +++ b/packages/testcontainers/src/generic-container/generic-container-reuse.test.ts @@ -3,10 +3,6 @@ import { checkContainerIsHealthy } from "../utils/test-helper"; import { GenericContainer } from "./generic-container"; describe("GenericContainer reuse", { timeout: 180_000 }, () => { - afterEach(() => { - vi.unstubAllEnvs(); - }); - it("should not reuse the container by default", async () => { const name = `there_can_only_be_one_${randomUuid()}`; const container = await new GenericContainer("cristianrgreco/testcontainer:1.1.14") diff --git a/packages/testcontainers/src/reaper/reaper.test.ts b/packages/testcontainers/src/reaper/reaper.test.ts index 998116f71..593b06610 100644 --- a/packages/testcontainers/src/reaper/reaper.test.ts +++ b/packages/testcontainers/src/reaper/reaper.test.ts @@ -8,8 +8,6 @@ describe("Reaper", { timeout: 120_000 }, () => { beforeEach(async () => { vi.resetModules(); - vi.unstubAllEnvs(); - client = await getContainerRuntimeClient(); }); diff --git a/vitest.config.ts b/vitest.config.ts index 0c166ea7b..fb3d8d26d 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -7,6 +7,7 @@ export default defineConfig({ silent: "passed-only", mockReset: true, restoreMocks: true, + unstubEnvs: true, alias: { testcontainers: path.resolve(__dirname, "packages/testcontainers/src"), }, From 827d4258b04a4991925e753844442e524c735db6 Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Fri, 21 Mar 2025 17:40:20 +0000 Subject: [PATCH 04/14] Cleanup --- packages/testcontainers/src/reaper/reaper.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/testcontainers/src/reaper/reaper.ts b/packages/testcontainers/src/reaper/reaper.ts index a2af584bc..1b3394eec 100644 --- a/packages/testcontainers/src/reaper/reaper.ts +++ b/packages/testcontainers/src/reaper/reaper.ts @@ -78,9 +78,9 @@ async function createNewReaper(sessionId: string, remoteSocketPath: string): Pro .withBindMounts([{ source: remoteSocketPath, target: "/var/run/docker.sock" }]) .withLabels({ [LABEL_TESTCONTAINERS_SESSION_ID]: sessionId }) .withWaitStrategy(Wait.forLogMessage(/.*Started.*/)); - if (process.env["TESTCONTAINERS_RYUK_VERBOSE"]) + if (process.env["TESTCONTAINERS_RYUK_VERBOSE"]) { container.withEnvironment({ RYUK_VERBOSE: process.env["TESTCONTAINERS_RYUK_VERBOSE"] }); - + } if (process.env.TESTCONTAINERS_RYUK_PRIVILEGED === "true") { container.withPrivilegedMode(); } From 71d3edb3fe5469828327915266d05116a8f5047f Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Fri, 21 Mar 2025 18:20:19 +0000 Subject: [PATCH 05/14] Ensure we do not connect to reapers created in test --- packages/testcontainers/src/reaper/reaper.test.ts | 1 + packages/testcontainers/src/reaper/reaper.ts | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/testcontainers/src/reaper/reaper.test.ts b/packages/testcontainers/src/reaper/reaper.test.ts index 593b06610..49127b9d1 100644 --- a/packages/testcontainers/src/reaper/reaper.test.ts +++ b/packages/testcontainers/src/reaper/reaper.test.ts @@ -8,6 +8,7 @@ describe("Reaper", { timeout: 120_000 }, () => { beforeEach(async () => { vi.resetModules(); + vi.stubEnv("TESTCONTAINERS_RYUK_TEST_LABEL", "true"); client = await getContainerRuntimeClient(); }); diff --git a/packages/testcontainers/src/reaper/reaper.ts b/packages/testcontainers/src/reaper/reaper.ts index 1b3394eec..1158838c6 100644 --- a/packages/testcontainers/src/reaper/reaper.ts +++ b/packages/testcontainers/src/reaper/reaper.ts @@ -48,7 +48,10 @@ export async function getReaper(client: ContainerRuntimeClient): Promise async function findReaperContainer(client: ContainerRuntimeClient): Promise { const containers = await client.container.list(); return containers.find( - (container) => container.State === "running" && container.Labels[LABEL_TESTCONTAINERS_RYUK] === "true" + (container) => + container.State === "running" && + container.Labels[LABEL_TESTCONTAINERS_RYUK] === "true" && + container.Labels["TESTCONTAINERS_RYUK_TEST_LABEL"] !== "true" ); } @@ -81,9 +84,12 @@ async function createNewReaper(sessionId: string, remoteSocketPath: string): Pro if (process.env["TESTCONTAINERS_RYUK_VERBOSE"]) { container.withEnvironment({ RYUK_VERBOSE: process.env["TESTCONTAINERS_RYUK_VERBOSE"] }); } - if (process.env.TESTCONTAINERS_RYUK_PRIVILEGED === "true") { + if (process.env["TESTCONTAINERS_RYUK_PRIVILEGED"] === "true") { container.withPrivilegedMode(); } + if (process.env["TESTCONTAINERS_RYUK_TEST_LABEL"] === "true") { + container.withLabels({ TESTCONTAINERS_RYUK_TEST_LABEL: "true" }); + } const startedContainer = await container.start(); From d81dc7d7971af1b068b6598017254b0f7764aed1 Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Fri, 21 Mar 2025 18:21:11 +0000 Subject: [PATCH 06/14] Move test around --- .../testcontainers/src/reaper/reaper.test.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/testcontainers/src/reaper/reaper.test.ts b/packages/testcontainers/src/reaper/reaper.test.ts index 49127b9d1..bd6438191 100644 --- a/packages/testcontainers/src/reaper/reaper.test.ts +++ b/packages/testcontainers/src/reaper/reaper.test.ts @@ -26,6 +26,16 @@ describe("Reaper", { timeout: 120_000 }, () => { expect(() => reaper.addComposeProject("test-project")).not.toThrow(); }); + it("should reuse existing reaper container if one is already running", async () => { + const reaper = await getReaper(); + const reaperContainerInfo = (await client.container.list()).filter((c) => c.Id === reaper.containerId)[0]; + vi.spyOn(client.container, "list").mockResolvedValue([reaperContainerInfo]); + + const reaper2 = await getReaper(); + + expect(reaper2.containerId).toBe(reaper.containerId); + }); + it("should use custom port when TESTCONTAINERS_RYUK_PORT is set", async () => { const customPort = (await new RandomUniquePortGenerator().generatePort()).toString(); vi.stubEnv("TESTCONTAINERS_RYUK_PORT", customPort); @@ -38,16 +48,6 @@ describe("Reaper", { timeout: 120_000 }, () => { expect(ports["8080"][0].HostPort).toBe(customPort); }); - it("should reuse existing reaper container if one is already running", async () => { - const reaper = await getReaper(); - const reaperContainerInfo = (await client.container.list()).filter((c) => c.Id === reaper.containerId)[0]; - vi.spyOn(client.container, "list").mockResolvedValue([reaperContainerInfo]); - - const reaper2 = await getReaper(); - - expect(reaper2.containerId).toBe(reaper.containerId); - }); - it("should create Reaper container without RYUK_VERBOSE env var by default", async () => { vi.spyOn(client.container, "list").mockResolvedValue([]); const reaper = await getReaper(); From 9bad277f65e939d04a1aac270fd10c00912d8ace Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Fri, 21 Mar 2025 18:32:01 +0000 Subject: [PATCH 07/14] Add more tests --- .../testcontainers/src/reaper/reaper.test.ts | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/packages/testcontainers/src/reaper/reaper.test.ts b/packages/testcontainers/src/reaper/reaper.test.ts index bd6438191..3743898c7 100644 --- a/packages/testcontainers/src/reaper/reaper.test.ts +++ b/packages/testcontainers/src/reaper/reaper.test.ts @@ -14,21 +14,36 @@ describe("Reaper", { timeout: 120_000 }, () => { it("should create disabled reaper when TESTCONTAINERS_RYUK_DISABLED=true", async () => { vi.stubEnv("TESTCONTAINERS_RYUK_DISABLED", "true"); - vi.spyOn(client.container, "list").mockResolvedValue([]); - const reaper = await getReaper(); - // DisabledReaper has an empty containerId - expect(reaper.containerId).toBe(""); + const reaper = await getReaper(); - // Should not throw exceptions when methods are called expect(() => reaper.addSession("test-session")).not.toThrow(); expect(() => reaper.addComposeProject("test-project")).not.toThrow(); }); + it("should return cached reaper instance", async () => { + const reaper = await getReaper(); + const reaper2 = await getReaper(); + + expect(reaper2.containerId).toBe(reaper.containerId); + }); + + it("should create new reaper container if one is not running", async () => { + const reaper = await getReaper(); + vi.resetModules(); + vi.spyOn(client.container, "list").mockResolvedValue([]); + + const reaper2 = await getReaper(); + + expect(reaper2.containerId).not.toBe(reaper.containerId); + }); + it("should reuse existing reaper container if one is already running", async () => { const reaper = await getReaper(); + vi.resetModules(); const reaperContainerInfo = (await client.container.list()).filter((c) => c.Id === reaper.containerId)[0]; + reaperContainerInfo.Labels["TESTCONTAINERS_RYUK_TEST_LABEL"] = "false"; vi.spyOn(client.container, "list").mockResolvedValue([reaperContainerInfo]); const reaper2 = await getReaper(); @@ -39,8 +54,8 @@ describe("Reaper", { timeout: 120_000 }, () => { it("should use custom port when TESTCONTAINERS_RYUK_PORT is set", async () => { const customPort = (await new RandomUniquePortGenerator().generatePort()).toString(); vi.stubEnv("TESTCONTAINERS_RYUK_PORT", customPort); - vi.spyOn(client.container, "list").mockResolvedValue([]); + const reaper = await getReaper(); const reaperContainer = client.container.getById(reaper.containerId); @@ -50,6 +65,7 @@ describe("Reaper", { timeout: 120_000 }, () => { it("should create Reaper container without RYUK_VERBOSE env var by default", async () => { vi.spyOn(client.container, "list").mockResolvedValue([]); + const reaper = await getReaper(); const reaperContainer = client.container.getById(reaper.containerId); @@ -60,8 +76,8 @@ describe("Reaper", { timeout: 120_000 }, () => { it("should propagate TESTCONTAINERS_RYUK_VERBOSE into Reaper container", async () => { vi.stubEnv("TESTCONTAINERS_RYUK_VERBOSE", "true"); - vi.spyOn(client.container, "list").mockResolvedValue([]); + const reaper = await getReaper(); const reaperContainer = client.container.getById(reaper.containerId); From 82c3ef84a2a5a915244be76a881c7bf0907c2e29 Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Fri, 21 Mar 2025 20:00:51 +0000 Subject: [PATCH 08/14] Fix tests for podman + update podman docs --- docs/supported-container-runtimes.md | 33 ++++++++++++++----- .../testcontainers/src/reaper/reaper.test.ts | 3 +- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/docs/supported-container-runtimes.md b/docs/supported-container-runtimes.md index ffc477a46..eaf4181ff 100644 --- a/docs/supported-container-runtimes.md +++ b/docs/supported-container-runtimes.md @@ -8,7 +8,8 @@ Works out of the box. ### Usage -MacOS: +#### MacOS: + ```bash {% raw %} export DOCKER_HOST=unix://$(podman machine inspect --format '{{.ConnectionInfo.PodmanSocket.Path}}') @@ -16,10 +17,27 @@ export TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE=/var/run/docker.sock {% endraw %} ``` -Linux: -```bash -export DOCKER_HOST=unix://${XDG_RUNTIME_DIR}/podman/podman.sock -``` +#### Linux: + +1. Ensure the Podman socket is exposed: + + Rootless: + + ```bash + systemctl --user status podman.socket + ``` + + Rootful: + + ```bash + sudo systemctl enable --now podman.socket + ``` + +2. Export the `DOCKER_HOST`: + + ```bash + export DOCKER_HOST="unix://$(podman info --format '{{.Host.RemoteSocket.Path}}')" + ``` ### Known issues @@ -71,10 +89,7 @@ You can use a composite wait strategy to additionally wait for a port to be boun const { GenericContainer, Wait } = require("testcontainers"); const container = await new GenericContainer("redis") - .withWaitStrategy(Wait.forAll([ - Wait.forListeningPorts(), - Wait.forLogMessage("Ready to accept connections") - ])) + .withWaitStrategy(Wait.forAll([Wait.forListeningPorts(), Wait.forLogMessage("Ready to accept connections")])) .start(); ``` diff --git a/packages/testcontainers/src/reaper/reaper.test.ts b/packages/testcontainers/src/reaper/reaper.test.ts index 3743898c7..e73be4268 100644 --- a/packages/testcontainers/src/reaper/reaper.test.ts +++ b/packages/testcontainers/src/reaper/reaper.test.ts @@ -60,7 +60,8 @@ describe("Reaper", { timeout: 120_000 }, () => { const reaperContainer = client.container.getById(reaper.containerId); const ports = (await reaperContainer.inspect()).HostConfig.PortBindings; - expect(ports["8080"][0].HostPort).toBe(customPort); + const port = ports["8080"] || ports["8080/tcp"]; + expect(port[0].HostPort).toBe(customPort); }); it("should create Reaper container without RYUK_VERBOSE env var by default", async () => { From d6b0eabe9c712880fe5c2dfaf5c934cacd32f4e6 Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Fri, 21 Mar 2025 20:03:46 +0000 Subject: [PATCH 09/14] Fix docs --- docs/supported-container-runtimes.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/supported-container-runtimes.md b/docs/supported-container-runtimes.md index eaf4181ff..050b8e153 100644 --- a/docs/supported-container-runtimes.md +++ b/docs/supported-container-runtimes.md @@ -36,7 +36,9 @@ export TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE=/var/run/docker.sock 2. Export the `DOCKER_HOST`: ```bash + {% raw %} export DOCKER_HOST="unix://$(podman info --format '{{.Host.RemoteSocket.Path}}')" + {% endraw %} ``` ### Known issues From cb3a58897452d00a1a23ba25088634caf1180bd7 Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Fri, 21 Mar 2025 20:07:16 +0000 Subject: [PATCH 10/14] Remove unnecessary mocks --- packages/testcontainers/src/reaper/reaper.test.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/packages/testcontainers/src/reaper/reaper.test.ts b/packages/testcontainers/src/reaper/reaper.test.ts index e73be4268..f7254f2ab 100644 --- a/packages/testcontainers/src/reaper/reaper.test.ts +++ b/packages/testcontainers/src/reaper/reaper.test.ts @@ -14,7 +14,6 @@ describe("Reaper", { timeout: 120_000 }, () => { it("should create disabled reaper when TESTCONTAINERS_RYUK_DISABLED=true", async () => { vi.stubEnv("TESTCONTAINERS_RYUK_DISABLED", "true"); - vi.spyOn(client.container, "list").mockResolvedValue([]); const reaper = await getReaper(); @@ -32,7 +31,6 @@ describe("Reaper", { timeout: 120_000 }, () => { it("should create new reaper container if one is not running", async () => { const reaper = await getReaper(); vi.resetModules(); - vi.spyOn(client.container, "list").mockResolvedValue([]); const reaper2 = await getReaper(); @@ -54,7 +52,6 @@ describe("Reaper", { timeout: 120_000 }, () => { it("should use custom port when TESTCONTAINERS_RYUK_PORT is set", async () => { const customPort = (await new RandomUniquePortGenerator().generatePort()).toString(); vi.stubEnv("TESTCONTAINERS_RYUK_PORT", customPort); - vi.spyOn(client.container, "list").mockResolvedValue([]); const reaper = await getReaper(); @@ -65,8 +62,6 @@ describe("Reaper", { timeout: 120_000 }, () => { }); it("should create Reaper container without RYUK_VERBOSE env var by default", async () => { - vi.spyOn(client.container, "list").mockResolvedValue([]); - const reaper = await getReaper(); const reaperContainer = client.container.getById(reaper.containerId); @@ -77,8 +72,6 @@ describe("Reaper", { timeout: 120_000 }, () => { it("should propagate TESTCONTAINERS_RYUK_VERBOSE into Reaper container", async () => { vi.stubEnv("TESTCONTAINERS_RYUK_VERBOSE", "true"); - vi.spyOn(client.container, "list").mockResolvedValue([]); - const reaper = await getReaper(); const reaperContainer = client.container.getById(reaper.containerId); From 1267024a9d8c4d613b89e6cbb0a6efa75379a6b1 Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Fri, 21 Mar 2025 20:38:55 +0000 Subject: [PATCH 11/14] Fix docs indentation --- docs/supported-container-runtimes.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/supported-container-runtimes.md b/docs/supported-container-runtimes.md index 050b8e153..de278ef57 100644 --- a/docs/supported-container-runtimes.md +++ b/docs/supported-container-runtimes.md @@ -21,17 +21,17 @@ export TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE=/var/run/docker.sock 1. Ensure the Podman socket is exposed: - Rootless: - - ```bash - systemctl --user status podman.socket - ``` - - Rootful: - - ```bash - sudo systemctl enable --now podman.socket - ``` + Rootless: + + ```bash + systemctl --user status podman.socket + ``` + + Rootful: + + ```bash + sudo systemctl enable --now podman.socket + ``` 2. Export the `DOCKER_HOST`: From 30c33d7196e5bcf0d8dc3ebe4c07ddc703ebf44f Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Fri, 21 Mar 2025 20:41:18 +0000 Subject: [PATCH 12/14] Fix doc structure --- docs/supported-container-runtimes.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/supported-container-runtimes.md b/docs/supported-container-runtimes.md index de278ef57..7305dd28b 100644 --- a/docs/supported-container-runtimes.md +++ b/docs/supported-container-runtimes.md @@ -35,11 +35,11 @@ export TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE=/var/run/docker.sock 2. Export the `DOCKER_HOST`: - ```bash - {% raw %} - export DOCKER_HOST="unix://$(podman info --format '{{.Host.RemoteSocket.Path}}')" - {% endraw %} - ``` + ```bash + {% raw %} + export DOCKER_HOST="unix://$(podman info --format '{{.Host.RemoteSocket.Path}}')" + {% endraw %} + ``` ### Known issues From a788e084aff72921bee590a75a77bc97ceb31472 Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Sat, 22 Mar 2025 09:35:47 +0000 Subject: [PATCH 13/14] Update reaper tests --- packages/testcontainers/src/reaper/reaper.test.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/testcontainers/src/reaper/reaper.test.ts b/packages/testcontainers/src/reaper/reaper.test.ts index f7254f2ab..357ace7c6 100644 --- a/packages/testcontainers/src/reaper/reaper.test.ts +++ b/packages/testcontainers/src/reaper/reaper.test.ts @@ -14,6 +14,7 @@ describe("Reaper", { timeout: 120_000 }, () => { it("should create disabled reaper when TESTCONTAINERS_RYUK_DISABLED=true", async () => { vi.stubEnv("TESTCONTAINERS_RYUK_DISABLED", "true"); + vi.spyOn(client.container, "list").mockResolvedValue([]); const reaper = await getReaper(); @@ -22,6 +23,8 @@ describe("Reaper", { timeout: 120_000 }, () => { }); it("should return cached reaper instance", async () => { + vi.spyOn(client.container, "list").mockResolvedValue([]); + const reaper = await getReaper(); const reaper2 = await getReaper(); @@ -29,6 +32,7 @@ describe("Reaper", { timeout: 120_000 }, () => { }); it("should create new reaper container if one is not running", async () => { + vi.spyOn(client.container, "list").mockResolvedValue([]); const reaper = await getReaper(); vi.resetModules(); @@ -52,6 +56,7 @@ describe("Reaper", { timeout: 120_000 }, () => { it("should use custom port when TESTCONTAINERS_RYUK_PORT is set", async () => { const customPort = (await new RandomUniquePortGenerator().generatePort()).toString(); vi.stubEnv("TESTCONTAINERS_RYUK_PORT", customPort); + vi.spyOn(client.container, "list").mockResolvedValue([]); const reaper = await getReaper(); @@ -62,6 +67,7 @@ describe("Reaper", { timeout: 120_000 }, () => { }); it("should create Reaper container without RYUK_VERBOSE env var by default", async () => { + vi.spyOn(client.container, "list").mockResolvedValue([]); const reaper = await getReaper(); const reaperContainer = client.container.getById(reaper.containerId); @@ -72,6 +78,8 @@ describe("Reaper", { timeout: 120_000 }, () => { it("should propagate TESTCONTAINERS_RYUK_VERBOSE into Reaper container", async () => { vi.stubEnv("TESTCONTAINERS_RYUK_VERBOSE", "true"); + vi.spyOn(client.container, "list").mockResolvedValue([]); + const reaper = await getReaper(); const reaperContainer = client.container.getById(reaper.containerId); From 87bcfbd2749ed046b8fcd19022ba63f488b616b4 Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Sat, 22 Mar 2025 12:46:00 +0000 Subject: [PATCH 14/14] Add port forwarder ready timeout --- .../testcontainers/src/port-forwarder/port-forwarder.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/testcontainers/src/port-forwarder/port-forwarder.ts b/packages/testcontainers/src/port-forwarder/port-forwarder.ts index 93d03c09c..cdd0a834e 100644 --- a/packages/testcontainers/src/port-forwarder/port-forwarder.ts +++ b/packages/testcontainers/src/port-forwarder/port-forwarder.ts @@ -96,7 +96,13 @@ export class PortForwarderInstance { } log.debug(`Connecting to Port Forwarder on "${host}:${port}"...`); - const connection = await createSshConnection({ host, port, username: "root", password: "root" }); + const connection = await createSshConnection({ + host, + port, + username: "root", + password: "root", + readyTimeout: 100_000, + }); log.debug(`Connected to Port Forwarder on "${host}:${port}"`); connection.unref();