From b536539bc16754009abeb30468f4d6035618c171 Mon Sep 17 00:00:00 2001 From: digital88 Date: Wed, 19 Mar 2025 23:37:53 +0300 Subject: [PATCH 1/5] #830 --- docs/configuration.md | 25 ++++++++-------- .../testcontainers/src/reaper/reaper.test.ts | 30 +++++++++++++++++++ packages/testcontainers/src/reaper/reaper.ts | 25 ++++++++++++---- packages/testcontainers/src/utils/labels.ts | 1 + 4 files changed, 63 insertions(+), 18 deletions(-) create mode 100644 packages/testcontainers/src/reaper/reaper.test.ts diff --git a/docs/configuration.md b/docs/configuration.md index 0974f3692..33dbf2e6d 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -32,15 +32,16 @@ Configuration of the Docker daemon: Configuration of Testcontainers and its behaviours: -| Variable | Example | Description | -| ------------------------------------- | -------------------------- | ---------------------------------------- | -| TESTCONTAINERS_HOST_OVERRIDE | tcp://docker:2375 | Docker's host on which ports are exposed | -| TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE | /var/run/docker.sock | Path to Docker's socket used by ryuk | -| TESTCONTAINERS_RYUK_PRIVILEGED | true | Run ryuk as a privileged container | -| TESTCONTAINERS_RYUK_DISABLED | true | Disable ryuk | -| TESTCONTAINERS_RYUK_PORT | 65515 | Set ryuk host port (not recommended) | -| TESTCONTAINERS_SSHD_PORT | 65515 | Set SSHd host port (not recommended) | -| TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX | mycompany.com/registry | Set default image registry | -| RYUK_CONTAINER_IMAGE | testcontainers/ryuk:0.11.0 | Custom image for ryuk | -| SSHD_CONTAINER_IMAGE | testcontainers/sshd:1.1.0 | Custom image for SSHd | -| TESTCONTAINERS_REUSE_ENABLE | true | Enable reusable containers | +| Variable | Example | Description | +| ------------------------------------- | -------------------------- | -------------------------------------------- | +| TESTCONTAINERS_HOST_OVERRIDE | tcp://docker:2375 | Docker's host on which ports are exposed | +| TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE | /var/run/docker.sock | Path to Docker's socket used by ryuk | +| TESTCONTAINERS_RYUK_PRIVILEGED | true | Run ryuk as a privileged container | +| TESTCONTAINERS_RYUK_DISABLED | true | Disable ryuk | +| TESTCONTAINERS_RYUK_PORT | 65515 | Set ryuk host port (not recommended) | +| TESTCONTAINERS_SSHD_PORT | 65515 | Set SSHd host port (not recommended) | +| TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX | mycompany.com/registry | Set default image registry | +| RYUK_CONTAINER_IMAGE | testcontainers/ryuk:0.11.0 | Custom image for ryuk | +| SSHD_CONTAINER_IMAGE | testcontainers/sshd:1.1.0 | Custom image for SSHd | +| TESTCONTAINERS_REUSE_ENABLE | true | Enable reusable containers | +| TESTCONTAINERS_RYUK_VERBOSE | true | Sets RYUK_VERBOSE env var in ryuk container | diff --git a/packages/testcontainers/src/reaper/reaper.test.ts b/packages/testcontainers/src/reaper/reaper.test.ts new file mode 100644 index 000000000..c45e6169b --- /dev/null +++ b/packages/testcontainers/src/reaper/reaper.test.ts @@ -0,0 +1,30 @@ +import { getContainerRuntimeClient } from "../container-runtime"; +import { getReaper } from "./reaper"; + +// ryuk container is created as global variable +// so it's impossible to test against it in isolation. +// both of this tests work if run manually one after another +// and ensuring that ryuk container does not exist before either test. + +describe("Reaper", { timeout: 120_000 }, () => { + it.skip("should create Reaper container without RYUK_VERBOSE env var by default", async () => { + const client = await getContainerRuntimeClient(); + const reaper = await getReaper(client); + const reaperContainer = client.container.getById(reaper.reaperContainerId); + const reaperContainerEnv = (await reaperContainer.inspect()).Config.Env; + expect(reaperContainerEnv).not.toContain("RYUK_VERBOSE=true"); + expect(reaperContainerEnv).not.toContain("RYUK_VERBOSE=false"); + }); + + it.skip("should propagate TESTCONTAINERS_RYUK_VERBOSE into Reaper container", async () => { + vitest.stubEnv("TESTCONTAINERS_RYUK_VERBOSE", "true"); + try { + const client = await getContainerRuntimeClient(); + const reaper = await getReaper(client); + const reaperContainer = client.container.getById(reaper.reaperContainerId); + expect((await reaperContainer.inspect()).Config.Env).toContain("RYUK_VERBOSE=true"); + } finally { + vitest.unstubAllEnvs(); + } + }); +}); diff --git a/packages/testcontainers/src/reaper/reaper.ts b/packages/testcontainers/src/reaper/reaper.ts index 0405c133b..cf362d080 100644 --- a/packages/testcontainers/src/reaper/reaper.ts +++ b/packages/testcontainers/src/reaper/reaper.ts @@ -3,7 +3,7 @@ import { Socket } from "net"; import { IntervalRetry, log, RandomUuid, withFileLock } from "../common"; import { ContainerRuntimeClient, ImageName } from "../container-runtime"; import { GenericContainer } from "../generic-container/generic-container"; -import { LABEL_TESTCONTAINERS_SESSION_ID } from "../utils/labels"; +import { LABEL_TESTCONTAINERS_RYUK, LABEL_TESTCONTAINERS_SESSION_ID } from "../utils/labels"; import { Wait } from "../wait-strategies/wait"; export const REAPER_IMAGE = process.env["RYUK_CONTAINER_IMAGE"] @@ -16,6 +16,8 @@ export interface Reaper { addSession(sessionId: string): void; addComposeProject(projectName: string): void; + + get reaperContainerId(): string; } let reaper: Reaper; @@ -28,7 +30,7 @@ export async function getReaper(client: ContainerRuntimeClient): Promise reaper = await withFileLock("testcontainers-node.lock", async () => { const reaperContainer = await findReaperContainer(client); - sessionId = reaperContainer?.Labels["org.testcontainers.session-id"] ?? new RandomUuid().nextUuid(); + sessionId = reaperContainer?.Labels[LABEL_TESTCONTAINERS_SESSION_ID] ?? new RandomUuid().nextUuid(); if (process.env.TESTCONTAINERS_RYUK_DISABLED === "true") { return new DisabledReaper(sessionId); @@ -46,7 +48,7 @@ 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["org.testcontainers.ryuk"] === "true" + (container) => container.State === "running" && container.Labels[LABEL_TESTCONTAINERS_RYUK] === "true" ); } @@ -60,7 +62,7 @@ async function useExistingReaper(reaperContainer: ContainerInfo, sessionId: stri const socket = await connectToReaperSocket(host, reaperPort, reaperContainer.Id); - return new RyukReaper(sessionId, socket); + return new RyukReaper(sessionId, socket, reaperContainer.Id); } async function createNewReaper(sessionId: string, remoteSocketPath: string): Promise { @@ -76,6 +78,8 @@ 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"]) + container.withEnvironment({ RYUK_VERBOSE: process.env["TESTCONTAINERS_RYUK_VERBOSE"] }); if (process.env.TESTCONTAINERS_RYUK_PRIVILEGED === "true") { container.withPrivilegedMode(); @@ -89,7 +93,7 @@ async function createNewReaper(sessionId: string, remoteSocketPath: string): Pro startedContainer.getId() ); - return new RyukReaper(sessionId, socket); + return new RyukReaper(sessionId, socket, startedContainer.getId()); } async function connectToReaperSocket(host: string, port: number, containerId: string): Promise { @@ -135,9 +139,14 @@ async function connectToReaperSocket(host: string, port: number, containerId: st class RyukReaper implements Reaper { constructor( public readonly sessionId: string, - private readonly socket: Socket + private readonly socket: Socket, + private readonly containerId: string ) {} + get reaperContainerId() { + return this.containerId; + } + addComposeProject(projectName: string): void { this.socket.write(`label=com.docker.compose.project=${projectName}\r\n`); } @@ -153,4 +162,8 @@ class DisabledReaper implements Reaper { addComposeProject(): void {} addSession(): void {} + + get reaperContainerId() { + return ""; + } } diff --git a/packages/testcontainers/src/utils/labels.ts b/packages/testcontainers/src/utils/labels.ts index 7a2b73e87..b2ef06e3c 100644 --- a/packages/testcontainers/src/utils/labels.ts +++ b/packages/testcontainers/src/utils/labels.ts @@ -6,6 +6,7 @@ export const LABEL_TESTCONTAINERS_VERSION = "org.testcontainers.version"; export const LABEL_TESTCONTAINERS_SESSION_ID = "org.testcontainers.session-id"; export const LABEL_TESTCONTAINERS_SSHD = "org.testcontainers.sshd"; export const LABEL_TESTCONTAINERS_CONTAINER_HASH = "org.testcontainers.container-hash"; +export const LABEL_TESTCONTAINERS_RYUK = "org.testcontainers.ryuk"; export function createLabels(): Record { return { From 7fd13e56d94b729693e8e6280f43c84bed8eb10a Mon Sep 17 00:00:00 2001 From: digital88 Date: Thu, 20 Mar 2025 21:06:04 +0300 Subject: [PATCH 2/5] fix tests --- .../testcontainers/src/reaper/reaper.test.ts | 39 +++++++++++++------ packages/testcontainers/src/reaper/reaper.ts | 23 +++++++---- 2 files changed, 42 insertions(+), 20 deletions(-) diff --git a/packages/testcontainers/src/reaper/reaper.test.ts b/packages/testcontainers/src/reaper/reaper.test.ts index c45e6169b..c611adcef 100644 --- a/packages/testcontainers/src/reaper/reaper.test.ts +++ b/packages/testcontainers/src/reaper/reaper.test.ts @@ -1,27 +1,42 @@ -import { getContainerRuntimeClient } from "../container-runtime"; -import { getReaper } from "./reaper"; +import { ContainerRuntimeClient, getContainerRuntimeClient } from "../container-runtime"; +import { FindReaperContainerFn, Reaper } from "./reaper"; -// ryuk container is created as global variable -// so it's impossible to test against it in isolation. -// both of this tests work if run manually one after another -// and ensuring that ryuk container does not exist before either test. +// FindReaperContainerFn is used in each test to intentionally 'fail' to find reaper container. +// Failing this check will trigger re-creation of new reaper container, which is needed for testing. +// Otherwise `getReaperFn` will always get a reference to already running reaper container, +// which is not desired in the context of unit testing. + +let getReaperFn: (client: ContainerRuntimeClient, findReaperContainerFn?: FindReaperContainerFn) => Promise; describe("Reaper", { timeout: 120_000 }, () => { - it.skip("should create Reaper container without RYUK_VERBOSE env var by default", async () => { + beforeEach(async () => { + // This code will reset global variable `reaper` before each test. + // The variable is used to store reference to reaper container, + // it must be 'fresh' instance of reaper before any test is run. + getReaperFn = (await import("./reaper")).getReaper; + }); + + afterEach(() => { + vi.resetModules(); + }); + + it("should create Reaper container without RYUK_VERBOSE env var by default", async () => { const client = await getContainerRuntimeClient(); - const reaper = await getReaper(client); - const reaperContainer = client.container.getById(reaper.reaperContainerId); + const reaper = await getReaperFn(client, async () => undefined); + expect(reaper.containerId).toBeTruthy(); // will fail if TESTCONTAINERS_RYUK_DISABLED=true + const reaperContainer = client.container.getById(reaper.containerId); const reaperContainerEnv = (await reaperContainer.inspect()).Config.Env; expect(reaperContainerEnv).not.toContain("RYUK_VERBOSE=true"); expect(reaperContainerEnv).not.toContain("RYUK_VERBOSE=false"); }); - it.skip("should propagate TESTCONTAINERS_RYUK_VERBOSE into Reaper container", async () => { + it("should propagate TESTCONTAINERS_RYUK_VERBOSE into Reaper container", async () => { vitest.stubEnv("TESTCONTAINERS_RYUK_VERBOSE", "true"); try { const client = await getContainerRuntimeClient(); - const reaper = await getReaper(client); - const reaperContainer = client.container.getById(reaper.reaperContainerId); + const reaper = await getReaperFn(client, async () => undefined); + expect(reaper.containerId).toBeTruthy(); // will fail if TESTCONTAINERS_RYUK_DISABLED=true + const reaperContainer = client.container.getById(reaper.containerId); expect((await reaperContainer.inspect()).Config.Env).toContain("RYUK_VERBOSE=true"); } finally { vitest.unstubAllEnvs(); diff --git a/packages/testcontainers/src/reaper/reaper.ts b/packages/testcontainers/src/reaper/reaper.ts index cf362d080..b3b1dde38 100644 --- a/packages/testcontainers/src/reaper/reaper.ts +++ b/packages/testcontainers/src/reaper/reaper.ts @@ -17,19 +17,26 @@ export interface Reaper { addComposeProject(projectName: string): void; - get reaperContainerId(): string; + get containerId(): string; } +// see reaper.test.ts for context of this export usage. +export type FindReaperContainerFn = (client: ContainerRuntimeClient) => Promise; + let reaper: Reaper; let sessionId: string; -export async function getReaper(client: ContainerRuntimeClient): Promise { +export async function getReaper( + client: ContainerRuntimeClient, + findReaperContainerFn?: FindReaperContainerFn +): Promise { if (reaper) { return reaper; } reaper = await withFileLock("testcontainers-node.lock", async () => { - const reaperContainer = await findReaperContainer(client); + const findReaperFn = findReaperContainerFn ?? findReaperContainerDefault; + const reaperContainer = await findReaperFn(client); sessionId = reaperContainer?.Labels[LABEL_TESTCONTAINERS_SESSION_ID] ?? new RandomUuid().nextUuid(); if (process.env.TESTCONTAINERS_RYUK_DISABLED === "true") { @@ -45,7 +52,7 @@ export async function getReaper(client: ContainerRuntimeClient): Promise return reaper; } -async function findReaperContainer(client: ContainerRuntimeClient): Promise { +async function findReaperContainerDefault(client: ContainerRuntimeClient): Promise { const containers = await client.container.list(); return containers.find( (container) => container.State === "running" && container.Labels[LABEL_TESTCONTAINERS_RYUK] === "true" @@ -140,11 +147,11 @@ class RyukReaper implements Reaper { constructor( public readonly sessionId: string, private readonly socket: Socket, - private readonly containerId: string + private readonly id: string ) {} - get reaperContainerId() { - return this.containerId; + get containerId() { + return this.id; } addComposeProject(projectName: string): void { @@ -163,7 +170,7 @@ class DisabledReaper implements Reaper { addSession(): void {} - get reaperContainerId() { + get containerId() { return ""; } } From 9cc75a979bfd15f2e824253b57ef663c96642bfe Mon Sep 17 00:00:00 2001 From: digital88 Date: Thu, 20 Mar 2025 22:06:24 +0300 Subject: [PATCH 3/5] fix tests (again) --- .../testcontainers/src/reaper/reaper.test.ts | 25 ++++++++---- packages/testcontainers/src/reaper/reaper.ts | 40 +++++++------------ 2 files changed, 31 insertions(+), 34 deletions(-) diff --git a/packages/testcontainers/src/reaper/reaper.test.ts b/packages/testcontainers/src/reaper/reaper.test.ts index c611adcef..6f8f8a70b 100644 --- a/packages/testcontainers/src/reaper/reaper.test.ts +++ b/packages/testcontainers/src/reaper/reaper.test.ts @@ -1,12 +1,19 @@ import { ContainerRuntimeClient, getContainerRuntimeClient } from "../container-runtime"; -import { FindReaperContainerFn, Reaper } from "./reaper"; +import { Reaper } from "./reaper"; -// FindReaperContainerFn is used in each test to intentionally 'fail' to find reaper container. -// Failing this check will trigger re-creation of new reaper container, which is needed for testing. -// Otherwise `getReaperFn` will always get a reference to already running reaper container, -// which is not desired in the context of unit testing. +let getReaperFn: (client: ContainerRuntimeClient) => Promise; -let getReaperFn: (client: ContainerRuntimeClient, findReaperContainerFn?: FindReaperContainerFn) => Promise; +async function mockGetContainerRuntimeClient(realClient: ContainerRuntimeClient) { + const ContainerRuntimeClient = vi.fn(); + ContainerRuntimeClient.prototype.info = vi.fn(); + vi.spyOn(ContainerRuntimeClient.prototype, "info", "get").mockReturnValue(realClient.info); + ContainerRuntimeClient.prototype.container = vi.fn(); + ContainerRuntimeClient.prototype.container.list = vi.fn().mockReturnValue([]); + const getContainerRuntimeClientMock = vi + .fn(getContainerRuntimeClient) + .mockImplementation(async () => new ContainerRuntimeClient()); + return getContainerRuntimeClientMock; +} describe("Reaper", { timeout: 120_000 }, () => { beforeEach(async () => { @@ -22,7 +29,8 @@ describe("Reaper", { timeout: 120_000 }, () => { it("should create Reaper container without RYUK_VERBOSE env var by default", async () => { const client = await getContainerRuntimeClient(); - const reaper = await getReaperFn(client, async () => undefined); + const mockGetClientFn = await mockGetContainerRuntimeClient(client); + const reaper = await getReaperFn(await mockGetClientFn()); expect(reaper.containerId).toBeTruthy(); // will fail if TESTCONTAINERS_RYUK_DISABLED=true const reaperContainer = client.container.getById(reaper.containerId); const reaperContainerEnv = (await reaperContainer.inspect()).Config.Env; @@ -34,7 +42,8 @@ describe("Reaper", { timeout: 120_000 }, () => { vitest.stubEnv("TESTCONTAINERS_RYUK_VERBOSE", "true"); try { const client = await getContainerRuntimeClient(); - const reaper = await getReaperFn(client, async () => undefined); + const mockGetClientFn = await mockGetContainerRuntimeClient(client); + const reaper = await getReaperFn(await mockGetClientFn()); expect(reaper.containerId).toBeTruthy(); // will fail if TESTCONTAINERS_RYUK_DISABLED=true const reaperContainer = client.container.getById(reaper.containerId); expect((await reaperContainer.inspect()).Config.Env).toContain("RYUK_VERBOSE=true"); diff --git a/packages/testcontainers/src/reaper/reaper.ts b/packages/testcontainers/src/reaper/reaper.ts index b3b1dde38..a2af584bc 100644 --- a/packages/testcontainers/src/reaper/reaper.ts +++ b/packages/testcontainers/src/reaper/reaper.ts @@ -13,34 +13,27 @@ export const REAPER_IMAGE = process.env["RYUK_CONTAINER_IMAGE"] export interface Reaper { sessionId: string; + containerId: string; + addSession(sessionId: string): void; addComposeProject(projectName: string): void; - - get containerId(): string; } -// see reaper.test.ts for context of this export usage. -export type FindReaperContainerFn = (client: ContainerRuntimeClient) => Promise; - let reaper: Reaper; let sessionId: string; -export async function getReaper( - client: ContainerRuntimeClient, - findReaperContainerFn?: FindReaperContainerFn -): Promise { +export async function getReaper(client: ContainerRuntimeClient): Promise { if (reaper) { return reaper; } reaper = await withFileLock("testcontainers-node.lock", async () => { - const findReaperFn = findReaperContainerFn ?? findReaperContainerDefault; - const reaperContainer = await findReaperFn(client); + const reaperContainer = await findReaperContainer(client); sessionId = reaperContainer?.Labels[LABEL_TESTCONTAINERS_SESSION_ID] ?? new RandomUuid().nextUuid(); if (process.env.TESTCONTAINERS_RYUK_DISABLED === "true") { - return new DisabledReaper(sessionId); + return new DisabledReaper(sessionId, ""); } else if (reaperContainer) { return await useExistingReaper(reaperContainer, sessionId, client.info.containerRuntime.host); } else { @@ -52,7 +45,7 @@ export async function getReaper( return reaper; } -async function findReaperContainerDefault(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" @@ -69,7 +62,7 @@ async function useExistingReaper(reaperContainer: ContainerInfo, sessionId: stri const socket = await connectToReaperSocket(host, reaperPort, reaperContainer.Id); - return new RyukReaper(sessionId, socket, reaperContainer.Id); + return new RyukReaper(sessionId, reaperContainer.Id, socket); } async function createNewReaper(sessionId: string, remoteSocketPath: string): Promise { @@ -100,7 +93,7 @@ async function createNewReaper(sessionId: string, remoteSocketPath: string): Pro startedContainer.getId() ); - return new RyukReaper(sessionId, socket, startedContainer.getId()); + return new RyukReaper(sessionId, startedContainer.getId(), socket); } async function connectToReaperSocket(host: string, port: number, containerId: string): Promise { @@ -146,14 +139,10 @@ async function connectToReaperSocket(host: string, port: number, containerId: st class RyukReaper implements Reaper { constructor( public readonly sessionId: string, - private readonly socket: Socket, - private readonly id: string + public readonly containerId: string, + private readonly socket: Socket ) {} - get containerId() { - return this.id; - } - addComposeProject(projectName: string): void { this.socket.write(`label=com.docker.compose.project=${projectName}\r\n`); } @@ -164,13 +153,12 @@ class RyukReaper implements Reaper { } class DisabledReaper implements Reaper { - constructor(public readonly sessionId: string) {} + constructor( + public readonly sessionId: string, + public readonly containerId: string + ) {} addComposeProject(): void {} addSession(): void {} - - get containerId() { - return ""; - } } From 623f490b42f20bd8a99d9062a3faa0e14061c572 Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Thu, 20 Mar 2025 20:42:53 +0000 Subject: [PATCH 4/5] Refactor reaper tests --- .../testcontainers/src/reaper/reaper.test.ts | 52 ++++++------------- 1 file changed, 17 insertions(+), 35 deletions(-) diff --git a/packages/testcontainers/src/reaper/reaper.test.ts b/packages/testcontainers/src/reaper/reaper.test.ts index 6f8f8a70b..372fbd597 100644 --- a/packages/testcontainers/src/reaper/reaper.test.ts +++ b/packages/testcontainers/src/reaper/reaper.test.ts @@ -1,37 +1,23 @@ import { ContainerRuntimeClient, getContainerRuntimeClient } from "../container-runtime"; import { Reaper } from "./reaper"; -let getReaperFn: (client: ContainerRuntimeClient) => Promise; - -async function mockGetContainerRuntimeClient(realClient: ContainerRuntimeClient) { - const ContainerRuntimeClient = vi.fn(); - ContainerRuntimeClient.prototype.info = vi.fn(); - vi.spyOn(ContainerRuntimeClient.prototype, "info", "get").mockReturnValue(realClient.info); - ContainerRuntimeClient.prototype.container = vi.fn(); - ContainerRuntimeClient.prototype.container.list = vi.fn().mockReturnValue([]); - const getContainerRuntimeClientMock = vi - .fn(getContainerRuntimeClient) - .mockImplementation(async () => new ContainerRuntimeClient()); - return getContainerRuntimeClientMock; -} - describe("Reaper", { timeout: 120_000 }, () => { - beforeEach(async () => { - // This code will reset global variable `reaper` before each test. - // The variable is used to store reference to reaper container, - // it must be 'fresh' instance of reaper before any test is run. - getReaperFn = (await import("./reaper")).getReaper; - }); + let reaper: Reaper; + let client: ContainerRuntimeClient; - afterEach(() => { + const getReaper = async () => await (await import("./reaper")).getReaper(client); + + beforeEach(async () => { vi.resetModules(); + vitest.unstubAllEnvs(); + + client = await getContainerRuntimeClient(); }); it("should create Reaper container without RYUK_VERBOSE env var by default", async () => { - const client = await getContainerRuntimeClient(); - const mockGetClientFn = await mockGetContainerRuntimeClient(client); - const reaper = await getReaperFn(await mockGetClientFn()); - expect(reaper.containerId).toBeTruthy(); // will fail if TESTCONTAINERS_RYUK_DISABLED=true + vi.spyOn(client.container, "list").mockResolvedValue([]); + reaper = await getReaper(); + const reaperContainer = client.container.getById(reaper.containerId); const reaperContainerEnv = (await reaperContainer.inspect()).Config.Env; expect(reaperContainerEnv).not.toContain("RYUK_VERBOSE=true"); @@ -40,15 +26,11 @@ describe("Reaper", { timeout: 120_000 }, () => { it("should propagate TESTCONTAINERS_RYUK_VERBOSE into Reaper container", async () => { vitest.stubEnv("TESTCONTAINERS_RYUK_VERBOSE", "true"); - try { - const client = await getContainerRuntimeClient(); - const mockGetClientFn = await mockGetContainerRuntimeClient(client); - const reaper = await getReaperFn(await mockGetClientFn()); - expect(reaper.containerId).toBeTruthy(); // will fail if TESTCONTAINERS_RYUK_DISABLED=true - const reaperContainer = client.container.getById(reaper.containerId); - expect((await reaperContainer.inspect()).Config.Env).toContain("RYUK_VERBOSE=true"); - } finally { - vitest.unstubAllEnvs(); - } + + vi.spyOn(client.container, "list").mockResolvedValue([]); + reaper = await getReaper(); + + const reaperContainer = client.container.getById(reaper.containerId); + expect((await reaperContainer.inspect()).Config.Env).toContain("RYUK_VERBOSE=true"); }); }); From a80dd5f6855b2196ecb23ad0cb7953d53a984c26 Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Thu, 20 Mar 2025 20:46:13 +0000 Subject: [PATCH 5/5] Remove global reaper --- packages/testcontainers/src/reaper/reaper.test.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/testcontainers/src/reaper/reaper.test.ts b/packages/testcontainers/src/reaper/reaper.test.ts index 372fbd597..4421c4c56 100644 --- a/packages/testcontainers/src/reaper/reaper.test.ts +++ b/packages/testcontainers/src/reaper/reaper.test.ts @@ -1,8 +1,6 @@ import { ContainerRuntimeClient, getContainerRuntimeClient } from "../container-runtime"; -import { Reaper } from "./reaper"; describe("Reaper", { timeout: 120_000 }, () => { - let reaper: Reaper; let client: ContainerRuntimeClient; const getReaper = async () => await (await import("./reaper")).getReaper(client); @@ -16,7 +14,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([]); - reaper = await getReaper(); + const reaper = await getReaper(); const reaperContainer = client.container.getById(reaper.containerId); const reaperContainerEnv = (await reaperContainer.inspect()).Config.Env; @@ -28,7 +26,7 @@ describe("Reaper", { timeout: 120_000 }, () => { vitest.stubEnv("TESTCONTAINERS_RYUK_VERBOSE", "true"); vi.spyOn(client.container, "list").mockResolvedValue([]); - reaper = await getReaper(); + const reaper = await getReaper(); const reaperContainer = client.container.getById(reaper.containerId); expect((await reaperContainer.inspect()).Config.Env).toContain("RYUK_VERBOSE=true");