diff --git a/.github/workflows/test-main.yml b/.github/workflows/test-main.yml index 37dbad12a..91d794407 100644 --- a/.github/workflows/test-main.yml +++ b/.github/workflows/test-main.yml @@ -17,6 +17,7 @@ jobs: os-version: [ ubuntu-latest ] node-version: [ 14.x, 16.x, 18.x ] steps: + - name: Code checkout - name: Code checkout uses: actions/checkout@v3 with: @@ -100,6 +101,36 @@ jobs: CI_ROOTLESS: true CI_PODMAN: true + colima: + runs-on: ${{ matrix.os-version }} + strategy: + fail-fast: false + matrix: + os-version: [ macos-latest ] + node-version: [ 14.x, 16.x, 18.x ] + steps: + - name: Code checkout + uses: actions/checkout@v3 + - name: Setup Colima + run: | + brew install docker docker-compose + colima start --cpu 3 --memory 14 --disk 14 --runtime docker + colima status + colima --version + - name: Set environment + run: | + echo "DOCKER_HOST=unix://${HOME}/.colima/default/docker.sock" >> $GITHUB_ENV + echo "TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE=/var/run/docker.sock" >> $GITHUB_ENV + echo "NODE_OPTIONS=--dns-result-order=ipv4first" >> $GITHUB_ENV + - name: Install NodeJS ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + - name: Install dependencies + run: npm ci --omit=optional + - name: Run tests + run: npm run test:ci + smoke-test: runs-on: ${{ matrix.os-version }} strategy: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1cc0a03c8..016e94749 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -94,6 +94,36 @@ jobs: CI_ROOTLESS: true CI_PODMAN: true + colima: + runs-on: ${{ matrix.os-version }} + strategy: + fail-fast: false + matrix: + os-version: [ macos-latest ] + node-version: [ 14.x, 16.x, 18.x ] + steps: + - name: Code checkout + uses: actions/checkout@v3 + - name: Setup Colima + run: | + brew install docker docker-compose + colima start --cpu 3 --memory 14 --disk 14 --runtime docker + colima status + colima --version + - name: Set environment + run: | + echo "DOCKER_HOST=unix://${HOME}/.colima/default/docker.sock" >> $GITHUB_ENV + echo "TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE=/var/run/docker.sock" >> $GITHUB_ENV + echo "NODE_OPTIONS=--dns-result-order=ipv4first" >> $GITHUB_ENV + - name: Install NodeJS ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + - name: Install dependencies + run: npm ci --omit=optional + - name: Run tests + run: npm run test:ci + smoke-test: runs-on: ${{ matrix.os-version }} strategy: diff --git a/src/docker-compose-environment/docker-compose-environment.test.ts b/src/docker-compose-environment/docker-compose-environment.test.ts index 0173a4676..df1b18e56 100644 --- a/src/docker-compose-environment/docker-compose-environment.test.ts +++ b/src/docker-compose-environment/docker-compose-environment.test.ts @@ -46,7 +46,7 @@ describe("DockerComposeEnvironment", () => { const startedEnv1 = await env.up(); const dockerEventStream = await getDockerEventStream(); - const dockerPullEventPromise = waitForDockerEvent(dockerEventStream, "pull", 2); + const dockerPullEventPromise = waitForDockerEvent(dockerEventStream, "pull"); const startedEnv2 = await env.withPullPolicy(new AlwaysPullPolicy()).up(); await dockerPullEventPromise; @@ -89,8 +89,14 @@ describe("DockerComposeEnvironment", () => { it("should support log message wait strategy", async () => { const startedEnvironment = await new DockerComposeEnvironment(fixtures, "docker-compose.yml") - .withWaitStrategy(await composeContainerName("container"), Wait.forLogMessage("Listening on port 8080")) - .withWaitStrategy(await composeContainerName("another_container"), Wait.forLogMessage("Listening on port 8080")) + .withWaitStrategy( + await composeContainerName("container"), + Wait.forAll([Wait.forListeningPorts(), Wait.forLogMessage("Listening on port 8080")]) + ) + .withWaitStrategy( + await composeContainerName("another_container"), + Wait.forAll([Wait.forListeningPorts(), Wait.forLogMessage("Listening on port 8080")]) + ) .up(); await Promise.all( @@ -115,7 +121,10 @@ describe("DockerComposeEnvironment", () => { it("should support health check wait strategy", async () => { const startedEnvironment = await new DockerComposeEnvironment(fixtures, "docker-compose-with-healthcheck.yml") - .withWaitStrategy(await composeContainerName("container"), Wait.forHealthCheck()) + .withWaitStrategy( + await composeContainerName("container"), + Wait.forAll([Wait.forListeningPorts(), Wait.forHealthCheck()]) + ) .up(); await checkEnvironmentContainerIsHealthy(startedEnvironment, await composeContainerName("container")); diff --git a/src/docker-compose-environment/docker-compose-environment.ts b/src/docker-compose-environment/docker-compose-environment.ts index 869dbc09b..31ca99bf6 100644 --- a/src/docker-compose-environment/docker-compose-environment.ts +++ b/src/docker-compose-environment/docker-compose-environment.ts @@ -16,9 +16,9 @@ import { StartedDockerComposeEnvironment } from "./started-docker-compose-enviro import { dockerComposeDown } from "../docker-compose/functions/docker-compose-down"; import { dockerComposeUp } from "../docker-compose/functions/docker-compose-up"; import { waitForContainer } from "../wait-for-container"; +import { Wait } from "../wait-strategy/wait"; import { DefaultPullPolicy, PullPolicy } from "../pull-policy"; import { dockerComposePull } from "../docker-compose/functions/docker-compose-pull"; -import { Wait } from "../wait-strategy/wait"; export class DockerComposeEnvironment { private readonly composeFilePath: string; diff --git a/src/modules/arangodb/arangodb-container.ts b/src/modules/arangodb/arangodb-container.ts index 9e82d3bee..7373b7d73 100755 --- a/src/modules/arangodb/arangodb-container.ts +++ b/src/modules/arangodb/arangodb-container.ts @@ -17,7 +17,7 @@ export class ArangoDBContainer extends GenericContainer { public override async start(): Promise<StartedArangoContainer> { this.withExposedPorts(...(this.hasExposedPorts ? this.opts.exposedPorts : [ARANGODB_PORT])) - .withWaitStrategy(Wait.forLogMessage("Have fun!")) + .withWaitStrategy(Wait.forAll([Wait.forListeningPorts(), Wait.forLogMessage("Have fun!")])) .withEnvironment({ ARANGO_ROOT_PASSWORD: this.password }) .withStartupTimeout(120_000); diff --git a/src/modules/elasticsearch/elasticsearch-container.test.ts b/src/modules/elasticsearch/elasticsearch-container.test.ts index a74b6f1c8..0a2d05c16 100644 --- a/src/modules/elasticsearch/elasticsearch-container.test.ts +++ b/src/modules/elasticsearch/elasticsearch-container.test.ts @@ -2,7 +2,7 @@ import { ElasticsearchContainer } from "./elasticsearch-container"; import { Client } from "@elastic/elasticsearch"; describe("ElasticsearchContainer", () => { - jest.setTimeout(180_000); + jest.setTimeout(240_000); // createIndex { it("should create an index", async () => { diff --git a/src/modules/elasticsearch/elasticsearch-container.ts b/src/modules/elasticsearch/elasticsearch-container.ts index ed6400e9f..d74bc2b26 100644 --- a/src/modules/elasticsearch/elasticsearch-container.ts +++ b/src/modules/elasticsearch/elasticsearch-container.ts @@ -17,7 +17,7 @@ export class ElasticsearchContainer extends GenericContainer { target: "/usr/share/elasticsearch/config/jvm.options.d/elasticsearch-default-memory-vm.options", }, ]) - .withStartupTimeout(120_000); + .withStartupTimeout(180_000); return new StartedElasticsearchContainer(await super.start()); } diff --git a/src/modules/kafka/kafka-container.test.ts b/src/modules/kafka/kafka-container.test.ts index ea3264680..20886a5a9 100644 --- a/src/modules/kafka/kafka-container.test.ts +++ b/src/modules/kafka/kafka-container.test.ts @@ -7,7 +7,7 @@ import * as fs from "fs"; import * as path from "path"; describe("KafkaContainer", () => { - jest.setTimeout(240_000); + jest.setTimeout(360_000); // connectBuiltInZK { it("should connect using in-built zoo-keeper", async () => { diff --git a/src/modules/kafka/kafka-container.ts b/src/modules/kafka/kafka-container.ts index d19c46779..8c49ad052 100644 --- a/src/modules/kafka/kafka-container.ts +++ b/src/modules/kafka/kafka-container.ts @@ -44,7 +44,7 @@ export class KafkaContainer extends GenericContainer { constructor(image = KAFKA_IMAGE) { super(image); - this.withExposedPorts(KAFKA_PORT).withStartupTimeout(180_000).withEnvironment({ + this.withExposedPorts(KAFKA_PORT).withStartupTimeout(300_000).withEnvironment({ KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: "BROKER:PLAINTEXT,PLAINTEXT:PLAINTEXT", KAFKA_INTER_BROKER_LISTENER_NAME: "BROKER", KAFKA_BROKER_ID: "1", diff --git a/src/modules/mongodb/mongodb-container.test.ts b/src/modules/mongodb/mongodb-container.test.ts index 8025be079..4056564b6 100644 --- a/src/modules/mongodb/mongodb-container.test.ts +++ b/src/modules/mongodb/mongodb-container.test.ts @@ -2,7 +2,7 @@ import { MongoDBContainer, StartedMongoDBContainer } from "./mongodb-container"; import mongoose from "mongoose"; describe("MongodbContainer", () => { - jest.setTimeout(240_000); + jest.setTimeout(180_000); // connect4 { it("should work using default version 4.0.1", async () => { diff --git a/src/modules/mongodb/mongodb-container.ts b/src/modules/mongodb/mongodb-container.ts index 76f4ffc5a..ae8907f5f 100644 --- a/src/modules/mongodb/mongodb-container.ts +++ b/src/modules/mongodb/mongodb-container.ts @@ -17,7 +17,7 @@ export class MongoDBContainer extends GenericContainer { public override async start(): Promise<StartedMongoDBContainer> { this.withExposedPorts(MONGODB_PORT) .withCommand(["--replSet", "rs0"]) - .withWaitStrategy(Wait.forLogMessage(/.*waiting for connections.*/i)) + .withWaitStrategy(Wait.forAll([Wait.forListeningPorts(), Wait.forLogMessage(/.*waiting for connections.*/i)])) .withStartupTimeout(120_000); return new StartedMongoDBContainer(await super.start()); } diff --git a/src/modules/mysql/mysql-container.ts b/src/modules/mysql/mysql-container.ts index 834c74fb7..085b8de24 100644 --- a/src/modules/mysql/mysql-container.ts +++ b/src/modules/mysql/mysql-container.ts @@ -42,7 +42,7 @@ export class MySqlContainer extends GenericContainer { MYSQL_USER: this.username, MYSQL_PASSWORD: this.userPassword, }) - .withStartupTimeout(120_000); + .withStartupTimeout(180_000); return new StartedMySqlContainer( await super.start(), diff --git a/src/modules/nats/nats-container.ts b/src/modules/nats/nats-container.ts index ada4641b4..da532600b 100755 --- a/src/modules/nats/nats-container.ts +++ b/src/modules/nats/nats-container.ts @@ -4,8 +4,6 @@ import { AbstractStartedContainer } from "../abstract-started-container"; import { Wait } from "../../wait-strategy/wait"; const CLIENT_PORT = 4222; -const ROUTING_PORT_FOR_CLUSTERING = 6222; -const HTTP_MANAGEMENT_PORT = 8222; const USER_ARGUMENT_KEY = "--user"; const PASS_ARGUMENT_KEY = "--pass"; @@ -35,18 +33,6 @@ export class NatsContainer extends GenericContainer { return this; } - private static ensureDashInFrontOfArgumentName(name: string): string { - if (name.startsWith("--") || name.startsWith("-")) { - return name; - } - - if (name.length == 1) { - return "-" + name; - } else { - return "--" + name; - } - } - public override async start(): Promise<StartedNatsContainer> { function buildCmdsFromArgs(args: { [p: string]: string }): string[] { const result: string[] = []; @@ -60,17 +46,25 @@ export class NatsContainer extends GenericContainer { } this.withCommand(buildCmdsFromArgs(this.args)) - .withExposedPorts( - ...(this.hasExposedPorts - ? this.opts.exposedPorts - : [CLIENT_PORT, ROUTING_PORT_FOR_CLUSTERING, HTTP_MANAGEMENT_PORT]) - ) - .withWaitStrategy(Wait.forLogMessage(/.*Server is ready.*/)) + .withExposedPorts(...(this.hasExposedPorts ? this.opts.exposedPorts : [CLIENT_PORT])) + .withWaitStrategy(Wait.forAll([Wait.forListeningPorts(), Wait.forLogMessage(/.*Server is ready.*/)])) .withStartupTimeout(120_000); return new StartedNatsContainer(await super.start(), this.getUser(), this.getPass()); } + private static ensureDashInFrontOfArgumentName(name: string): string { + if (name.startsWith("--") || name.startsWith("-")) { + return name; + } + + if (name.length == 1) { + return "-" + name; + } else { + return "--" + name; + } + } + private getUser(): string { return this.args[USER_ARGUMENT_KEY]; } diff --git a/src/modules/neo4j/neo4j-container.test.ts b/src/modules/neo4j/neo4j-container.test.ts index b7f9fc092..99269cff3 100755 --- a/src/modules/neo4j/neo4j-container.test.ts +++ b/src/modules/neo4j/neo4j-container.test.ts @@ -2,7 +2,7 @@ import neo4j from "neo4j-driver"; import { Neo4jContainer } from "./neo4j-container"; describe("Neo4jContainer", () => { - jest.setTimeout(180_000); + jest.setTimeout(240_000); // createNode { it("should create a person node", async () => { @@ -48,7 +48,7 @@ describe("Neo4jContainer", () => { // apoc { it("should have APOC plugin installed", async () => { - const container = await new Neo4jContainer().withApoc().withStartupTimeout(120_000).start(); + const container = await new Neo4jContainer().withApoc().start(); const driver = neo4j.driver( container.getBoltUri(), neo4j.auth.basic(container.getUsername(), container.getPassword()) diff --git a/src/modules/neo4j/neo4j-container.ts b/src/modules/neo4j/neo4j-container.ts index 0b182a42b..b279214de 100755 --- a/src/modules/neo4j/neo4j-container.ts +++ b/src/modules/neo4j/neo4j-container.ts @@ -32,9 +32,9 @@ export class Neo4jContainer extends GenericContainer { public override async start(): Promise<StartedNeo4jContainer> { this.withExposedPorts(...(this.hasExposedPorts ? this.opts.exposedPorts : [BOLT_PORT, HTTP_PORT])) - .withWaitStrategy(Wait.forLogMessage("Started.")) + .withWaitStrategy(Wait.forAll([Wait.forListeningPorts(), Wait.forLogMessage("Started.")])) .withEnvironment({ NEO4J_AUTH: `${USERNAME}/${this.password}` }) - .withStartupTimeout(120_000); + .withStartupTimeout(180_000); if (this.apoc) { this.withEnvironment({ diff --git a/src/modules/postgresql/postgresql-container.ts b/src/modules/postgresql/postgresql-container.ts index f51817840..f8f80ba26 100755 --- a/src/modules/postgresql/postgresql-container.ts +++ b/src/modules/postgresql/postgresql-container.ts @@ -36,7 +36,12 @@ export class PostgreSqlContainer extends GenericContainer { POSTGRES_USER: this.username, POSTGRES_PASSWORD: this.password, }) - .withWaitStrategy(Wait.forLogMessage(/.*database system is ready to accept connections.*/, 2)) + .withWaitStrategy( + Wait.forAll([ + Wait.forListeningPorts(), + Wait.forLogMessage(/.*database system is ready to accept connections.*/, 2), + ]) + ) .withStartupTimeout(120_000); return new StartedPostgreSqlContainer(await super.start(), this.database, this.username, this.password); diff --git a/src/wait-strategy/health-check-wait-strategy.test.ts b/src/wait-strategy/health-check-wait-strategy.test.ts index 47fa4b5e8..ff4c7411e 100644 --- a/src/wait-strategy/health-check-wait-strategy.test.ts +++ b/src/wait-strategy/health-check-wait-strategy.test.ts @@ -14,7 +14,7 @@ describe("HealthCheckWaitStrategy", () => { const customGenericContainer = await GenericContainer.fromDockerfile(context).build(); const container = await customGenericContainer .withExposedPorts(8080) - .withWaitStrategy(Wait.forHealthCheck()) + .withWaitStrategy(Wait.forAll([Wait.forListeningPorts(), Wait.forHealthCheck()])) .start(); await checkContainerIsHealthy(container); @@ -32,7 +32,7 @@ describe("HealthCheckWaitStrategy", () => { retries: 5, startPeriod: 1000, }) - .withWaitStrategy(Wait.forHealthCheck()) + .withWaitStrategy(Wait.forAll([Wait.forListeningPorts(), Wait.forHealthCheck()])) .start(); await checkContainerIsHealthy(container); @@ -84,7 +84,7 @@ describe("HealthCheckWaitStrategy", () => { retries: 5, startPeriod: 1000, }) - .withWaitStrategy(Wait.forHealthCheck()) + .withWaitStrategy(Wait.forAll([Wait.forListeningPorts(), Wait.forHealthCheck()])) .start(); await checkContainerIsHealthy(container); diff --git a/src/wait-strategy/host-port-wait-strategy.test.ts b/src/wait-strategy/host-port-wait-strategy.test.ts index 9e7bbada2..5f78088a1 100644 --- a/src/wait-strategy/host-port-wait-strategy.test.ts +++ b/src/wait-strategy/host-port-wait-strategy.test.ts @@ -22,7 +22,7 @@ describe("HostPortWaitStrategy", () => { .withExposedPorts(8081) .withStartupTimeout(0) .start() - ).rejects.toThrowError("Port 8081 not bound after 0ms"); + ).rejects.toThrowError(/Port \d+ not bound after 0ms/); expect(await getRunningContainerNames()).not.toContain(containerName); }); diff --git a/src/wait-strategy/log-wait-strategy.test.ts b/src/wait-strategy/log-wait-strategy.test.ts index 2b6be720c..ea5ee33e0 100644 --- a/src/wait-strategy/log-wait-strategy.test.ts +++ b/src/wait-strategy/log-wait-strategy.test.ts @@ -9,7 +9,7 @@ describe("LogWaitStrategy", () => { it("should wait for log", async () => { const container = await new GenericContainer("cristianrgreco/testcontainer:1.1.14") .withExposedPorts(8080) - .withWaitStrategy(Wait.forLogMessage("Listening on port 8080")) + .withWaitStrategy(Wait.forAll([Wait.forListeningPorts(), Wait.forLogMessage("Listening on port 8080")])) .start(); await checkContainerIsHealthy(container); @@ -20,7 +20,7 @@ describe("LogWaitStrategy", () => { it("should wait for log with regex", async () => { const container = await new GenericContainer("cristianrgreco/testcontainer:1.1.14") .withExposedPorts(8080) - .withWaitStrategy(Wait.forLogMessage(/Listening on port \d+/)) + .withWaitStrategy(Wait.forAll([Wait.forListeningPorts(), Wait.forLogMessage(/Listening on port \d+/)])) .start(); await checkContainerIsHealthy(container);