[
  {
    "path": ".circleci/config.yml",
    "content": "version: 2\njobs:\n  minimal_core:\n    machine:\n      enabled: true\n    steps:\n    - checkout\n    - run:\n        command: ./gradlew --no-daemon --continue --scan testcontainers:test --tests '*GenericContainerRuleTest'\n    - run:\n        name: Save test results\n        command: |\n          mkdir -p ~/junit/\n          find . -type f -regex \".*/build/test-results/.*xml\" -exec cp {} ~/junit/ \\;\n        when: always\n    - store_test_results:\n        path: ~/junit\n    - store_artifacts:\n        path: ~/junit\nworkflows:\n  version: 2\n  test_all:\n    jobs:\n    - minimal_core\n"
  },
  {
    "path": ".devcontainer/devcontainer.json",
    "content": "// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:\n// https://github.com/microsoft/vscode-dev-containers/tree/v0.241.1/containers/java-8\n{\n\t\"name\": \"Java 17\",\n    \"image\": \"mcr.microsoft.com/devcontainers/java:0-17\",\n\n\t// Configure tool-specific properties.\n\t\"customizations\": {\n\t\t// Configure properties specific to VS Code.\n\t\t\"vscode\": {\n\t\t\t// Add the IDs of extensions you want installed when the container is created.\n\t\t\t\"extensions\": [\n\t\t\t\t\"vscjava.vscode-java-pack\"\n\t\t\t]\n\t\t}\n\t},\n\n\t// Use 'forwardPorts' to make a list of ports inside the container available locally.\n\t// \"forwardPorts\": [],\n\n\t// Use 'postCreateCommand' to run commands after the container is created.\n\t// \"postCreateCommand\": \"java -version\",\n\n\t// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.\n\t\"remoteUser\": \"vscode\",\n\t\"features\": {\n        \"ghcr.io/devcontainers/features/git:1\": {},\n        \"ghcr.io/devcontainers/features/github-cli:1\": {},\n        \"ghcr.io/meaningful-ooo/devcontainer-features/homebrew:2\": {},\n        \"ghcr.io/devcontainers/features/java:1\": {\n            \"version\": \"none\",\n            \"installMaven\": \"false\",\n            \"installGradle\": \"false\"\n        },\n        \"ghcr.io/devcontainers/features/docker-outside-of-docker:1\": {},\n        \"ghcr.io/devcontainers/features/node:1\": {},\n        \"ghcr.io/devcontainers/features/sshd:1\": {\n            \"version\": \"latest\"\n        }\n\t},\n\t\"postStartCommand\": [\"./gradlew\", \"compileJava\"]\n}\n"
  },
  {
    "path": ".editorconfig",
    "content": "# EditorConfig is awesome: http://EditorConfig.org\n\n# top-most EditorConfig file\nroot = true\n\n# Unix-style newlines with a newline ending every file\n[*]\nend_of_line = lf\ninsert_final_newline = true\ncharset = utf-8\nindent_style = space\nindent_size = 4\ntrim_trailing_whitespace = true\n\n[*.md]\ntrim_trailing_whitespace = false\n\n[*.java]\nindent_style = space\nindent_size = 4\n# Never use star imports\nij_java_names_count_to_use_import_on_demand = 99\nij_java_class_count_to_use_import_on_demand = 99\nij_java_layout_static_imports_separately = true\n\n[*.{yml, yaml}]\nindent_size = 2\nij_yaml_keep_indents_on_empty_lines = false\n"
  },
  {
    "path": ".gitattributes",
    "content": "*.sh text eol=lf"
  },
  {
    "path": ".github/CODEOWNERS",
    "content": "* @testcontainers/java-team\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: [testcontainers]\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yaml",
    "content": "name: Bug Report\ndescription: File a bug report\ntitle: \"[Bug]: \"\nlabels: [\"type/bug\"]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        Thanks for taking the time to fill out this bug report! Before submitting a `bug`, please make sure there is no existing issue for the one you encountered and it has been discussed with the team via [discussions](https://github.com/testcontainers/testcontainers-java/discussions) or Slack.\n  - type: dropdown\n    id: module\n    attributes:\n      label: Module\n      description: Which Testcontainers module are you using?\n      options:\n        - Core\n        - ActiveMQ\n        - Azure\n        - Cassandra\n        - ChromaDB\n        - Clickhouse\n        - CockroachDB\n        - Consul\n        - Couchbase\n        - CrateDB\n        - Databend\n        - DB2\n        - Dynalite\n        - Elasticsearch\n        - GCloud\n        - Grafana\n        - HiveMQ\n        - InfluxDB\n        - K3S\n        - K6\n        - Kafka\n        - LDAP\n        - LocalStack\n        - MariaDB\n        - Milvus\n        - MinIO\n        - MockServer\n        - MongoDB\n        - MSSQLServer\n        - MySQL\n        - Neo4j\n        - NGINX\n        - OceanBase\n        - Ollama\n        - OpenFGA\n        - Oracle Free\n        - Oracle XE\n        - OrientDB\n        - Pinecone\n        - PostgreSQL\n        - Presto\n        - Pulsar\n        - Qdrant\n        - QuestDB\n        - RabbitMQ\n        - Redpanda\n        - ScyllaDB\n        - Selenium\n        - Solace\n        - Solr\n        - TiDB\n        - Timeplus\n        - ToxiProxy\n        - Trino\n        - Typesense\n        - Vault\n        - Weaviate\n        - YugabyteDB\n    validations:\n      required: true\n  - type: input\n    id: tc-version\n    attributes:\n      label: Testcontainers version\n      description: Which Testcontainers version are you using?\n      placeholder: ex. 1.17.2\n    validations:\n      required: true\n  - type: dropdown\n    id: latest-version\n    attributes:\n      label: Using the latest Testcontainers version?\n      description: If you are not using the latest version, can you update your project and try to reproduce the issue? Is it still happening?\n      options:\n        - 'Yes'\n        - 'No'\n    validations:\n      required: true\n  - type: input\n    id: host-os\n    attributes:\n      label: Host OS\n      description: Which Operating System are you using?\n      placeholder: e.g. Linux, Windows\n    validations:\n      required: true\n  - type: input\n    id: host-arch\n    attributes:\n      label: Host Arch\n      description: Which architecture are you using?\n      placeholder: e.g. x86, ARM\n    validations:\n      required: true\n  - type: textarea\n    id: docker-version\n    attributes:\n      label: Docker version\n      description: Please run `docker version` and copy and paste the output into this field.\n      render: shell\n    validations:\n      required: true\n  - type: textarea\n    id: what-happened\n    attributes:\n      label: What happened?\n      description: Provide the context and the expected result.\n    validations:\n      required: true\n  - type: textarea\n    id: logs\n    attributes:\n      label: Relevant log output\n      description: Please copy and paste any relevant log output. The content will be automatically formatted as code, so no need for backticks.\n      render: shell\n  - type: textarea\n    id: additional-information\n    attributes:\n      label: Additional Information\n      description: |\n        Any links or references to have more context about the issue.\n\n        Tip: You can attach a minimal sample project to reproduce the issue or provide further log files by clicking into this area to focus it and then dragging files in.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n  - name: Need help or have a question?\n    url: https://slack.testcontainers.org/\n    about: Visit our slack channel.\n  - name: Have a question or want to drive a Community conversation?\n    url: https://github.com/testcontainers/testcontainers-java/discussions/\n    about: Visit our Discussions page.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/enhancement.yaml",
    "content": "name: Enhancement\ndescription: Suggest an enhancement\ntitle: \"[Enhancement]: \"\nlabels: [\"type/enhancement\"]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        Before submitting an `enhancement`, please make sure there is no existing enhancement for the one you are requesting and it has been discussed with the team via [discussions](https://github.com/testcontainers/testcontainers-java/discussions) or Slack. If so, please provide the following information:\n  - type: dropdown\n    id: module\n    attributes:\n      label: Module\n      description: For which Testcontainers module does the enhancement proposal apply?\n      options:\n        - Core\n        - ActiveMQ\n        - Azure\n        - Cassandra\n        - ChromaDB\n        - Clickhouse\n        - CockroachDB\n        - Consul\n        - Couchbase\n        - CrateDB\n        - Databend\n        - DB2\n        - Dynalite\n        - Elasticsearch\n        - GCloud\n        - Grafana\n        - HiveMQ\n        - InfluxDB\n        - K3S\n        - K6\n        - Kafka\n        - LDAP\n        - LocalStack\n        - MariaDB\n        - Milvus\n        - MinIO\n        - MockServer\n        - MongoDB\n        - MSSQLServer\n        - MySQL\n        - Neo4j\n        - NGINX\n        - OceanBase\n        - Ollama\n        - OpenFGA\n        - Oracle Free\n        - Oracle XE\n        - OrientDB\n        - Pinecone\n        - PostgreSQL\n        - Presto\n        - Pulsar\n        - Qdrant\n        - QuestDB\n        - RabbitMQ\n        - Redpanda\n        - ScyllaDB\n        - Selenium\n        - Solace\n        - Solr\n        - TiDB\n        - Timeplus\n        - ToxiProxy\n        - Trino\n        - Typesense\n        - Vault\n        - Weaviate\n        - YugabyteDB\n    validations:\n      required: true\n  - type: textarea\n    id: proposal\n    attributes:\n      label: Proposal\n      description: What should be improved? What are the limitations of the current implications that would be solved by the proposal?\n    validations:\n      required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature.yaml",
    "content": "name: Feature\ndescription: Suggest a new feature\ntitle: \"[Feature]: \"\nlabels: [\"type/feature\"]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        Before submitting a `feature`, please make sure there is no existing feature for the one you are requesting and it has been discussed with the team via [discussions](https://github.com/testcontainers/testcontainers-java/discussions) or Slack. If so, please provide the following information:\n  - type: dropdown\n    id: module\n    attributes:\n      label: Module\n      description: Is this feature related to any of the existing modules?\n      options:\n        - Core\n        - ActiveMQ\n        - Azure\n        - Cassandra\n        - ChromaDB\n        - Clickhouse\n        - CockroachDB\n        - CrateDB\n        - Consul\n        - Couchbase\n        - Databend\n        - DB2\n        - Dynalite\n        - Elasticsearch\n        - GCloud\n        - Grafana\n        - HiveMQ\n        - InfluxDB\n        - K3S\n        - K6\n        - Kafka\n        - LDAP\n        - LocalStack\n        - MariaDB\n        - Milvus\n        - MinIO\n        - MockServer\n        - MongoDB\n        - MSSQLServer\n        - MySQL\n        - Neo4j\n        - NGINX\n        - OceanBase\n        - Ollama\n        - OpenFGA\n        - Oracle Free\n        - Oracle XE\n        - OrientDB\n        - Pinecone\n        - PostgreSQL\n        - Qdrant\n        - QuestDB\n        - Presto\n        - Pulsar\n        - RabbitMQ\n        - Redpanda\n        - ScyllaDB\n        - Selenium\n        - Solace\n        - Solr\n        - TiDB\n        - Timeplus\n        - ToxiProxy\n        - Trino\n        - Typesense\n        - Vault\n        - Weaviate\n        - YugabyteDB\n        - New Module\n  - type: textarea\n    id: problem\n    attributes:\n      label: Problem\n      description: Is this feature related to a problem? Please describe it.\n    validations:\n      required: true\n  - type: textarea\n    id: solution\n    attributes:\n      label: Solution\n      description: What's the proposed solution for this feature?\n    validations:\n      required: true\n  - type: textarea\n    id: benefit\n    attributes:\n      label: Benefit\n      description: What's the benefit of adding this feature to the project?\n    validations:\n      required: true\n  - type: textarea\n    id: alternatives\n    attributes:\n      label: Alternatives\n      description: Are there other alternatives? Please describe them.\n    validations:\n      required: true\n  - type: dropdown\n    id: contribute\n    attributes:\n      label: Would you like to help contributing this feature?\n      options:\n        - 'Yes'\n        - 'No'\n    validations:\n      required: true\n"
  },
  {
    "path": ".github/actions/setup-build/action.yml",
    "content": "name: Set up Build\ndescription: Sets up Build\ninputs:\n  java-version:\n    description: 'The Java version to set up'\n    required: true\n    default: '17'\nruns:\n  using: \"composite\"\n  steps:\n    - uses: ./.github/actions/setup-java\n      with:\n        java-version: ${{ inputs.java-version }}\n    - name: Clear existing docker image cache\n      shell: bash\n      run: docker image prune -af\n    - uses: ./.github/actions/setup-gradle\n"
  },
  {
    "path": ".github/actions/setup-gradle/action.yml",
    "content": "name: Set up Gradle Action\ndescription: Sets up Gradle Action\nruns:\n  using: \"composite\"\n  steps:\n    - name: Setup Gradle Build Action\n      uses: gradle/actions/setup-gradle@v4\n      with:\n        gradle-home-cache-includes: |\n          caches\n          notifications\n          jdks\n"
  },
  {
    "path": ".github/actions/setup-java/action.yml",
    "content": "name: Set up Java\ndescription: Sets up Java version\ninputs:\n  java-version:\n    description: 'The Java version to set up'\n    required: true\n    default: '17'\nruns:\n  using: \"composite\"\n  steps:\n    - uses: actions/setup-java@v4\n      with:\n        java-version: ${{ inputs.java-version }}\n        distribution: temurin\n"
  },
  {
    "path": ".github/actions/setup-junit-report/action.yml",
    "content": "name: Set up JUnit Report\ndescription: Sets up JUnit Report\nruns:\n  using: \"composite\"\n  steps:\n    - name: Publish Test Report\n      uses: mikepenz/action-junit-report@v4\n      if: always() # always run even if the previous step fails\n      with:\n        report_paths: '**/build/test-results/test/TEST-*.xml'\n        annotate_only: true\n"
  },
  {
    "path": ".github/bumper.yml",
    "content": "updates:\n- path: mkdocs.yml\n  pattern: 'latest_version: (.*)'\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nregistries:\n  gradle-plugin-portal:\n    type: maven-repository\n    url: https://plugins.gradle.org/m2\n    username: dummy # Required by dependabot\n    password: dummy # Required by dependabot\nupdates:\n  - package-ecosystem: \"gradle\"\n    directory: \"/core\"\n    schedule:\n      interval: \"weekly\"\n    open-pull-requests-limit: 10\n    ignore:\n      - dependency-name: \"org.slf4j:slf4j-api\"\n        update-types: [ \"version-update:semver-major\" ]\n      - dependency-name: \"org.mockito:mockito-core\"\n        update-types: [ \"version-update:semver-major\" ]\n      - dependency-name: \"com.fasterxml.jackson.datatype:jackson-datatype-jsr310\"\n        update-types: [ \"version-update:semver-minor\", \"version-update:semver-patch\" ]\n      - dependency-name: \"org.junit.jupiter:junit-jupiter\"\n        update-types: [ \"version-update:semver-major\" ]\n      - dependency-name: \"org.junit.platform:junit-platform-launcher\"\n        update-types: [ \"version-update:semver-major\" ]\n  - package-ecosystem: \"gradle\"\n    directory: \"/\"\n    allow:\n      - dependency-name: \"com.gradle*\"\n    registries:\n      - gradle-plugin-portal\n    schedule:\n      interval: \"weekly\"\n    open-pull-requests-limit: 10\n    ignore:\n      - dependency-name: \"com.gradleup.shadow\"\n        update-types: [ \"version-update:semver-major\" ]\n      - dependency-name: \"org.junit.jupiter:junit-jupiter\"\n        update-types: [ \"version-update:semver-major\" ]\n      - dependency-name: \"org.junit.platform:junit-platform-launcher\"\n        update-types: [ \"version-update:semver-major\" ]\n\n# Explicit entry for each module\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/activemq\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/azure\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/cassandra\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n    ignore:\n      - dependency-name: \"io.dropwizard.metrics:metrics-core\"\n        update-types: [ \"version-update:semver-major\" ]\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/chromadb\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/clickhouse\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/cockroachdb\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/consul\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/couchbase\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/cratedb\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/database-commons\"\n    schedule:\n      interval: \"monthly\"\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/databend\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/db2\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/dynalite\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/elasticsearch\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/gcloud\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/grafana\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/hivemq\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/influxdb\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n    ignore:\n      - dependency-name: \"com.influxdb:influxdb-java-client\"\n        update-types: [ \"version-update:semver-major\" ]\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/jdbc\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n    ignore:\n      - dependency-name: \"org.mockito:mockito-core\"\n        update-types: [ \"version-update:semver-major\" ]\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/jdbc-test\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n    ignore:\n      - dependency-name: \"org.apache.tomcat:tomcat-jdbc\"\n        update-types: [ \"version-update:semver-minor\" ]\n      - dependency-name: \"org.junit.jupiter:junit-jupiter\"\n        update-types: [ \"version-update:semver-major\" ]\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/junit-jupiter\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n    ignore:\n      - dependency-name: \"org.mockito:mockito-core\"\n        update-types: [ \"version-update:semver-major\" ]\n      - dependency-name: \"org.junit:junit-bom\"\n        update-types: [ \"version-update:semver-major\" ]\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/k3s\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n    ignore:\n      - dependency-name: \"com.fasterxml.jackson.dataformat:jackson-dataformat-yaml\"\n        update-types: [ \"version-update:semver-minor\", \"version-update:semver-patch\" ]\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/k6\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/kafka\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/ldap\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/localstack\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/mariadb\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n    ignore:\n      - dependency-name: \"org.mariadb:r2dbc-mariadb\"\n        update-types: [ \"version-update:semver-minor\" ]\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/milvus\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/minio\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/mockserver\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/mongodb\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/mssqlserver\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/mysql\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/neo4j\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n    ignore:\n      - dependency-name: \"org.neo4j.driver:neo4j-java-driver\"\n        update-types: [ \"version-update:semver-major\" ]\n      - dependency-name: \"org.neo4j:neo4j\"\n        update-types: [ \"version-update:semver-major\" ]\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/nginx\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/oceanbase\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/ollama\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/openfga\"\n    schedule:\n      interval: \"monthly\"\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/oracle-free\"\n    schedule:\n      interval: \"monthly\"\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/oracle-xe\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/orientdb\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/postgresql\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/presto\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/pinecone\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n    ignore:\n      - dependency-name: \"io.pinecone:pinecone-client\"\n        update-types: [ \"version-update:semver-major\" ]\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/pulsar\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n    ignore:\n      - dependency-name: \"org.apache.pulsar:pulsar-bom\"\n        update-types: [ \"version-update:semver-patch\" ]\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/qdrant\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/questdb\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/r2dbc\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n    ignore:\n      - dependency-name: \"io.r2dbc:r2dbc-spi\"\n        update-types: [ \"version-update:semver-major\", \"version-update:semver-minor\" ]\n      - dependency-name: \"org.junit.jupiter:junit-jupiter\"\n        update-types: [ \"version-update:semver-major\" ]\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/rabbitmq\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/redpanda\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/scylladb\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/selenium\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n    ignore:\n      - dependency-name: \"org.seleniumhq.selenium:selenium-bom\"\n        update-types: [ \"version-update:semver-minor\" ]\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/solace\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n    ignore:\n      - dependency-name: \"org.apache.qpid:qpid-jms-client\"\n        update-types: [ \"version-update:semver-major\" ]\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/solr\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n    ignore:\n      - dependency-name: \"org.apache.solr:solr-solrj\"\n        update-types: [ \"version-update:semver-major\" ]\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/spock\"\n    schedule:\n      interval: \"monthly\"\n    ignore:\n      - dependency-name: \"org.junit:junit-bom\"\n        update-types: [ \"version-update:semver-major\" ]\n    open-pull-requests-limit: 10\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/tidb\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/timeplus\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/toxiproxy\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/trino\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/typesense\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/vault\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/weaviate\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n  - package-ecosystem: \"gradle\"\n    directory: \"/modules/yugabytedb\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n\n# Examples\n  - package-ecosystem: \"gradle\"\n    directory: \"/examples\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n    ignore:\n      - dependency-name: \"ch.qos.logback:logback-classic\"\n        update-types: [ \"version-update:semver-minor\" ]\n      - dependency-name: \"org.apache.solr:solr-solrj\"\n        update-types: [ \"version-update:semver-major\" ]\n      - dependency-name: \"org.neo4j.driver:neo4j-java-driver\"\n        update-types: [ \"version-update:semver-major\" ]\n      - dependency-name: \"org.testng:testng\"\n        update-types: [ \"version-update:semver-minor\" ]\n      - dependency-name: \"org.slf4j:slf4j-api\"\n        update-types: [ \"version-update:semver-major\" ]\n      - dependency-name: \"org.springframework.boot\"\n        update-types: [ \"version-update:semver-major\" ]\n      - dependency-name: \"com.diffplug.spotless\"\n        update-types: [ \"version-update:semver-major\", \"version-update:semver-minor\" ]\n      - dependency-name: \"com.hazelcast:hazelcast\"\n        update-types: [ \"version-update:semver-minor\" ]\n      - dependency-name: \"org.junit.jupiter:junit-jupiter\"\n        update-types: [ \"version-update:semver-major\" ]\n      - dependency-name: \"org.junit.platform:junit-platform-launcher\"\n        update-types: [ \"version-update:semver-major\" ]\n      - dependency-name: \"com.gradleup.shadow\"\n        update-types: [ \"version-update:semver-major\" ]\n\n# Smoke test\n  - package-ecosystem: \"gradle\"\n    directory: \"/smoke-test\"\n    schedule:\n      interval: \"monthly\"\n    open-pull-requests-limit: 10\n    ignore:\n      - dependency-name: \"ch.qos.logback:logback-classic\"\n        update-types: [ \"version-update:semver-minor\" ]\n      - dependency-name: \"com.diffplug.spotless\"\n        update-types: [ \"version-update:semver-major\", \"version-update:semver-minor\" ]\n      - dependency-name: \"org.junit.jupiter:junit-jupiter\"\n        update-types: [ \"version-update:semver-major\" ]\n      - dependency-name: \"org.junit.platform:junit-platform-launcher\"\n        update-types: [ \"version-update:semver-major\" ]\n      - dependency-name: \"com.gradleup.shadow\"\n        update-types: [ \"version-update:semver-major\" ]\n\n# GitHub Actions\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n    open-pull-requests-limit: 10\n"
  },
  {
    "path": ".github/labeler.yml",
    "content": "\"area/docker-compose\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - core/src/main/java/org/testcontainers/containers/ComposeContainer.java\n      - core/src/main/java/org/testcontainers/containers/ComposeDelegate.java\n      - core/src/main/java/org/testcontainers/containers/DockerComposeContainer.java\n      - core/src/main/java/org/testcontainers/containers/DockerComposeFiles.java\n      - core/src/test/java/org/testcontainers/containers/Compose*Test.java\n      - core/src/test/java/org/testcontainers/containers/DockerCompose*Test.java\n      - core/src/test/java/org/testcontainers/junit/Compose*Test.java\n      - core/src/test/java/org/testcontainers/junit/DockerCompose*Test.java\n\"github_actions\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - .github/workflows/*\n\"gradle-wrapper\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - gradle/wrapper/*\n      - gradlew\n      - gradlew.bat\n\"modules/activemq\":\n  - changed-files:\n      - any-glob-to-any-file:\n          - modules/activemq/**/*\n\"modules/azure\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/azure/**/*\n\"modules/cassandra\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/cassandra/**/*\n\"modules/chromadb\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/chromadb/**/*\n\"modules/clickhouse\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/clickhouse/**/*\n\"modules/cockroachdb\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/cockroachdb/**/*\n\"modules/consul\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/consul/**/*\n\"modules/couchbase\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/couchbase/**/*\n\"modules/cratedb\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/cratedb/**/*\n\"modules/databend\":\n  - changed-files:\n      - any-glob-to-any-file:\n          - modules/databend/**/*\n\"modules/db2\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/db2/**/*\n\"modules/dynalite\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/dynalite/**/*\n\"modules/elasticsearch\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/elasticsearch/**/*\n\"modules/gcloud\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/gcloud/**/*\n\"modules/grafana\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/grafana/**/*\n\"modules/hivemq\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/hivemq/**/*\n\"modules/influx\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/influxdb/**/*\n\"modules/jdbc\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/jdbc/**/*\n\"modules/jupiter\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/junit-jupiter/**/*\n\"modules/k3s\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/k3s/**/*\n\"modules/k6\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/k6/**/*\n\"modules/kafka\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/kafka/**/*\n\"modules/ldap\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/ldap/**/*\n\"modules/localstack\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/localstack/**/*\n\"modules/mariadb\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/mariadb/**/*\n\"modules/milvus\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/milvus/**/*\n\"modules/minio\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/minio/**/*\n\"modules/mockserver\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/mockserver/**/*\n\"modules/mongodb\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/mongodb/**/*\n\"modules/sql-server\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/mssqlserver/**/*\n\"modules/mysql\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/mysql/**/*\n\"modules/neo4j\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/neo4j/**/*\n\"modules/nginx\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/nginx/**/*\n\"modules/oceanbase\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/oceanbase/**/*\n\"modules/ollama\":\n  - changed-files:\n      - any-glob-to-any-file:\n          - modules/ollama/**/*\n\"modules/openfga\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/openfga/**/*\n\"modules/oracle\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/oracle-free/**/*\n      - modules/oracle-xe/**/*\n\"modules/orientdb\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/orientdb/**/*\n\"modules/pinecone\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/pinecone/**/*\n\"modules/postgres\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/postgresql/**/*\n\"modules/presto\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/presto/**/*\n\"modules/pulsar\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/pulsar/**/*\n\"modules/qdrant\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/qdrant/**/*\n\"modules/questdb\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/questdb/**/*\n\"modules/r2dbc\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/r2dbc/**/*\n\"modules/rabbitmq\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/rabbitmq/**/*\n\"modules/redpanda\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/redpanda/**/*\n\"modules/scylladb\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/scylladb/**/*\n\"modules/selenium\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/selenium/**/*\n\"modules/solace\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/solace/**/*\n\"modules/solr\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/solr/**/*\n\"modules/spock\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/spock/**/*\n\"modules/tidb\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/tidb/**/*\n\"modules/timeplus\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/timeplus/**/*\n\"modules/toxiproxy\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/toxiproxy/**/*\n\"modules/trino\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/trino/**/*\n\"modules/typesense\":\n  - changed-files:\n      - any-glob-to-any-file:\n          - modules/typesense/**/*\n\"modules/vault\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/vault/**/*\n\"modules/weaviate\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/weaviate/**/*\n\"modules/yugabytedb\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - modules/yugabytedb/**/*\n\"type/docs\":\n  - changed-files:\n    - any-glob-to-any-file:\n      - docs/**/*.md\n"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "<!--\nThanks for contributing to Testcontainers. Before submitting a pull request, please\nreview our contributing guidelines at https://java.testcontainers.org/contributing/\n\nPlease also review the following notes before submitting a pull request.\n\nNew Modules:\n\nIf you are contributing a new module, please add your module name to the following files:\n\n* `./.github/ISSUE_TEMPLATE/bug_report.yaml`\n* `./.github/ISSUE_TEMPLATE/enhancement.yaml`\n* `./.github/ISSUE_TEMPLATE/feature.yaml`\n* `./.github/dependabot.yml`\n* `./.github/labeler.yml`\n\nAlso make sure that your new module has the appropriate documentation under `./docs/modules/`\n\nBefore committing any change, please run `./gradlew checkstyleMain checkstyleTest spotlessApply` and fix any issues that occur.\n\nDependency Upgrades:\nPlease do not open a pull request to update only a version dependency. Existing process will perform\nthe upgrade every week. However, if the upgrade involves more changes (changes for deprecated API) then\nyour pull request is very welcome.\n\nDescribing Your Changes:\n\nIf, after having reviewed the notes above, you're ready to submit your pull request, please\nprovide a brief description of the proposed changes and their context. If they fix a bug, please\ndescribe the broken behaviour and how the changes fix it. If they make an enhancement,\nplease describe the new functionality and why you believe it's useful. If your pull\nrequest relates to any existing issues, please reference them by using the issue number\nprefixed with #.\n-->\n"
  },
  {
    "path": ".github/release-drafter.yml",
    "content": "name-template: $NEXT_PATCH_VERSION\ntag-template: $NEXT_PATCH_VERSION\ntemplate: |\n    # What's Changed\n\n    $CHANGES\ncategories:\n  - title: ⚠️ Breaking API changes\n    label: type/breaking-api-change\n  - title: 🚀 Features & Enhancements\n    labels:\n      - type/feature\n      - type/enhancement\n  - title: ☠️ Deprecations\n    label: type/deprecation\n  - title: 🐛 Bug Fixes\n    label: type/bug\n  - title: 📖 Documentation\n    label: type/docs\n  - title: 🧹 Housekeeping\n    labels:\n      - type/housekeeping\n      - type/test-improvement\n  - title: 📦 Dependency updates\n    label: dependencies\n    collapse-after: 5\n"
  },
  {
    "path": ".github/settings.yml",
    "content": "# These settings are synced to GitHub by https://probot.github.io/apps/settings/\n\nrepository:\n  # See https://docs.github.com/en/rest/reference/repos#update-a-repository for all available settings.\n\n  # The name of the repository. Changing this will rename the repository\n  name: testcontainers-java\n\n  # A short description of the repository that will show up on GitHub\n  description: Testcontainers is a Java library that supports JUnit tests, providing lightweight, throwaway instances of common databases, Selenium web browsers, or anything else that can run in a Docker container.\n\n  # A URL with more information about the repository\n  homepage: https://testcontainers.org\n\n  # A comma-separated list of topics to set on the repository\n  topics: java,testing,docker,docker-compose,jvm,test-automation,junit,hacktoberfest,integration-testing\n\n  # Either `true` to make the repository private, or `false` to make it public.\n  private: false\n\n  # Either `true` to enable issues for this repository, `false` to disable them.\n  has_issues: true\n\n  # Either `true` to enable projects for this repository, or `false` to disable them.\n  # If projects are disabled for the organization, passing `true` will cause an API error.\n  has_projects: false\n\n  # Either `true` to enable the wiki for this repository, `false` to disable it.\n  has_wiki: false\n\n  # Either `true` to enable downloads for this repository, `false` to disable them.\n  has_downloads: false\n\n  # Updates the default branch for this repository.\n  default_branch: main\n\n  # Either `true` to allow squash-merging pull requests, or `false` to prevent\n  # squash-merging.\n  allow_squash_merge: true\n\n  # Either `true` to allow merging pull requests with a merge commit, or `false`\n  # to prevent merging pull requests with merge commits.\n  allow_merge_commit: false\n\n  # Either `true` to allow rebase-merging pull requests, or `false` to prevent\n  # rebase-merging.\n  allow_rebase_merge: false\n\n  # Either `true` to enable automatic deletion of branches on merge, or `false` to disable\n  delete_branch_on_merge: true\n\n# Labels: define labels for Issues and Pull Requests\n# If including a `#`, make sure to wrap it with quotes!\nlabels:\n  - name: area/bitbucket-pipelines\n    color: '#f9d0c4'\n\n  - name: area/docker-compose\n    color: '#f9d0c4'\n\n  - name: area/logging\n    color: '#f9d0c4'\n\n  - name: area/shading\n    color: '#f9d0c4'\n\n  - name: area/test frameworks\n    color: '#f9d0c4'\n\n  - name: blocker\n    color: '#b60205'\n\n  - name: client/docker-for-mac\n    color: '#c2e0c6'\n\n  - name: client/docker-for-windows\n    color: '#c2e0c6'\n\n  - name: client/docker-machine\n    color: '#c2e0c6'\n\n  - name: client/in-container\n    color: '#c2e0c6'\n\n  - name: client/podman\n    color: '#c2e0c6'\n\n  - name: dependencies\n    color: '#0025ff'\n\n  - name: github_actions\n    color: '#000000'\n\n  - name: good first issue\n    color: '#14d60a'\n\n  - name: gradle-wrapper\n    color: '#02303A'\n\n  - name: hacktoberfest\n    color: '#14d60a'\n\n  - name: hacktoberfest-accepted\n    color: '#79C259'\n\n  - name: help wanted\n    color: '#fef2c0'\n\n  - name: modules/activemq\n    color: '#006b75'\n\n  - name: modules/azure\n    color: '#006b75'\n\n  - name: modules/cassandra\n    color: '#006b75'\n\n  - name: modules/chromadb\n    color: '#006b75'\n\n  - name: modules/clickhouse\n    color: '#006b75'\n\n  - name: modules/cockroachdb\n    color: '#006b75'\n\n  - name: modules/consul\n    color: '#006b75'\n\n  - name: modules/couchbase\n    color: '#006b75'\n\n  - name: modules/cratedb\n    color: '#006b75'\n\n  - name: modules/db2\n    color: '#006b75'\n\n  - name: modules/dynalite\n    color: '#006b75'\n\n  - name: modules/elasticsearch\n    color: '#006b75'\n\n  - name: modules/gcloud\n    color: '#006b75'\n\n  - name: modules/grafana\n    color: '#006b75'\n\n  - name: modules/hivemq\n    color: '#006b75'\n\n  - name: modules/influx\n    color: '#006b75'\n\n  - name: modules/jdbc\n    color: '#006b75'\n\n  - name: modules/jupiter\n    color: '#006b75'\n\n  - name: modules/k3s\n    color: '#006b75'\n\n  - name: modules/k6\n    color: '#006b75'\n\n  - name: modules/kafka\n    color: '#006b75'\n\n  - name: modules/ldap\n    color: '#006b75'\n\n  - name: modules/localstack\n    color: '#006b75'\n\n  - name: modules/mariadb\n    color: '#006b75'\n\n  - name: modules/milvus\n    color: '#006b75'\n\n  - name: modules/minio\n    color: '#006b75'\n\n  - name: modules/mockserver\n    color: '#006b75'\n\n  - name: modules/mongodb\n    color: '#006b75'\n\n  - name: modules/mysql\n    color: '#006b75'\n\n  - name: modules/neo4j\n    color: '#006b75'\n\n  - name: modules/nginx\n    color: '#006b75'\n\n  - name: modules/oceanbase\n    color: '#006b75'\n\n  - name: modules/ollama\n    color: '#006b75'\n\n  - name: modules/openfga\n    color: '#006b75'\n\n  - name: modules/oracle\n    color: '#006b75'\n\n  - name: modules/orientdb\n    color: '#006b75'\n\n  - name: modules/pinecone\n    color: '#006b75'\n\n  - name: modules/postgres\n    color: '#006b75'\n\n  - name: modules/presto\n    color: '#006b75'\n\n  - name: modules/pulsar\n    color: '#006b75'\n\n  - name: modules/qdrant\n    color: '#006b75'\n\n  - name: modules/questdb\n    color: '#006b75'\n\n  - name: modules/r2dbc\n    color: '#006b75'\n\n  - name: modules/rabbitmq\n    color: '#006b75'\n\n  - name: modules/redpanda\n    color: '#006b75'\n\n  - name: modules/selenium\n    color: '#006b75'\n\n  - name: modules/solace\n    color: '#006b75'\n\n  - name: modules/solr\n    color: '#006b75'\n\n  - name: modules/spock\n    color: '#006b75'\n\n  - name: modules/sql-server\n    color: '#006b75'\n\n  - name: modules/tidb\n    color: '#006b75'\n\n  - name: modules/timeplus\n    color: '#006b75'\n\n  - name: modules/toxiproxy\n    color: '#006b75'\n\n  - name: modules/trino\n    color: '#006b75'\n\n  - name: modules/typesense\n    color: '#006b75'\n\n  - name: modules/vault\n    color: '#006b75'\n\n  - name: modules/weaviate\n    color: '#006b75'\n\n  - name: modules/yugabytedb\n    color: '#006b75'\n\n  - name: modules/databend\n    color: '#006b75'\n\n  - name: os/linux\n    color: '#1d76db'\n\n  - name: os/macOS\n    color: '#1d76db'\n\n  - name: os/windows\n    color: '#1d76db'\n\n  - name: resolution/acknowledged\n    color: '#fef2c0'\n\n  - name: resolution/answered\n    color: '#fef2c0'\n\n  - name: resolution/awaiting-release\n    color: '#fef2c0'\n\n  - name: resolution/duplicate\n    color: '#fef2c0'\n\n  - name: resolution/invalid\n    color: '#fef2c0'\n\n  - name: resolution/pr-submitted\n    color: '#fef2c0'\n\n  - name: resolution/somedaymaybe\n    color: '#fef2c0'\n\n  - name: resolution/waiting-for-info\n    color: '#fef2c0'\n\n  - name: resolution/wontfix\n    color: '#fef2c0'\n\n  - name: security\n    color: '#ee0701'\n\n  - name: stale\n    color: '#ffffff'\n\n  - name: type/breaking-api-change\n    color: '#d4c5f9'\n\n  - name: type/bug\n    color: '#d4c5f9'\n\n  - name: type/deprecation\n    color: '#d4c5f9'\n\n  - name: type/docs\n    color: '#d4c5f9'\n\n  - name: type/enhancement\n    color: '#d4c5f9'\n\n  - name: type/feature\n    color: '#d4c5f9'\n\n  - name: type/housekeeping\n    color: '#d4c5f9'\n\n  - name: type/new module\n    color: '#d4c5f9'\n\n  - name: type/question\n    color: '#d4c5f9'\n\n  - name: type/test-improvement\n    color: '#d4c5f9'\n\n# Collaborators: give specific users access to this repository.\n# See https://docs.github.com/en/rest/reference/repos#add-a-repository-collaborator for available options\n# collaborators:\n#   - username:\n#     permission: maintain\n\n  # Note: `permission` is only valid on organization-owned repositories.\n  # The permission to grant the collaborator. Can be one of:\n  # * `pull` - can pull, but not push to or administer this repository.\n  # * `push` - can pull and push, but not administer this repository.\n  # * `admin` - can pull, push and administer this repository.\n  # * `maintain` - Recommended for project managers who need to manage the repository without access to sensitive or destructive actions.\n  # * `triage` - Recommended for contributors who need to proactively manage issues and pull requests without write access.\n\n# See https://docs.github.com/en/rest/reference/teams#add-or-update-team-repository-permissions for available options\nteams:\n  # Please make sure the team already exist in the organization, as the repository-settings application is not creating them.\n  # See https://github.com/repository-settings/app/discussions/639 for more information about teams and settings\n  - name: java-team\n    # The permission to grant the team. Can be one of:\n    # * `pull` - can pull, but not push to or administer this repository.\n    # * `push` - can pull and push, but not administer this repository.\n    # * `admin` - can pull, push and administer this repository.\n    # * `maintain` - Recommended for project managers who need to manage the repository without access to sensitive or destructive actions.\n    # * `triage` - Recommended for contributors who need to proactively manage issues and pull requests without write access.\n    permission: admin\n  - name: oss-team\n    permission: maintain\n\nbranches:\n  - name: main\n    # https://docs.github.com/en/rest/reference/repos#update-branch-protection\n    # Branch Protection settings. Set to null to disable\n    protection:\n      # Required. Require at least one approving review on a pull request, before merging. Set to null to disable.\n      required_pull_request_reviews:\n        # The number of approvals required. (1-6)\n        required_approving_review_count: 1\n        # Dismiss approved reviews automatically when a new commit is pushed.\n        dismiss_stale_reviews: true\n        # Blocks merge until code owners have reviewed.\n        require_code_owner_reviews: true\n        # Specify which users and teams can dismiss pull request reviews. Pass an empty dismissal_restrictions object to disable. User and team dismissal_restrictions are only available for organization-owned repositories. Omit this parameter for personal repositories.\n        dismissal_restrictions:\n          users: []\n          teams: [java-team]\n      # Required. Require status checks to pass before merging. Set to null to disable\n      required_status_checks:\n        # Required. Require branches to be up to date before merging.\n        strict: true\n        # Required. The list of status checks to require in order to merge into this branch\n        contexts: [\"core (17)\", \"core (21)\", \"check_docs_examples (:docs:examples:check)\", \"in-docker_test\", \"ci/circleci: minimal_core\", \"test\"]\n      # Required. Enforce all configured restrictions for administrators. Set to true to enforce required status checks for repository administrators. Set to null to disable.\n      enforce_admins: false\n      # Prevent merge commits from being pushed to matching branches\n      required_linear_history: true\n      # Required. Restrict who can push to this branch. Team and user restrictions are only available for organization-owned repositories. Set to null to disable.\n      restrictions:\n        apps: []\n        users: []\n        teams: [java-team]\n"
  },
  {
    "path": ".github/workflows/ci-docker-wormhole.yml",
    "content": "name: CI-Docker-Wormhole\n\non:\n  pull_request:\n    paths-ignore:\n      - '.github/ISSUE_TEMPLATE/*.yaml'\n      - '.github/CODEOWNERS'\n      - '.github/pull_request_template.md'\n      - 'docs/**/*.css'\n      - 'docs/**/*.html'\n      - 'docs/**/*.ico'\n      - 'docs/**/*.md'\n      - 'docs/**/*.png'\n      - 'docs/**/*.svg'\n      - 'mkdocs.yml'\n      - 'README.md'\n      - 'RELEASING.md'\n      - '.sdkmanrc'\n  push:\n    branches: [ main ]\n    paths-ignore:\n      - '.github/ISSUE_TEMPLATE/*.yaml'\n      - '.github/CODEOWNERS'\n      - '.github/pull_request_template.md'\n      - 'docs/**/*.css'\n      - 'docs/**/*.html'\n      - 'docs/**/*.ico'\n      - 'docs/**/*.md'\n      - 'docs/**/*.png'\n      - 'docs/**/*.svg'\n      - 'mkdocs.yml'\n      - 'README.md'\n      - 'RELEASING.md'\n      - '.sdkmanrc'\n\nconcurrency:\n  group: \"${{ github.workflow }}-${{ github.head_ref || github.sha }}\"\n  cancel-in-progress: true\n\npermissions:\n  contents: read\n\njobs:\n  in-docker_test:\n    runs-on: ubuntu-22.04\n    steps:\n      - uses: actions/checkout@v5\n      - name: Build with Gradle\n        run: |\n          docker run -i --rm \\\n            -v /var/run/docker.sock:/var/run/docker.sock \\\n            -v \"$HOME:$HOME\" \\\n            -v \"$PWD:$PWD\" \\\n            -w \"$PWD\" \\\n            -e AUTO_APPLY_GIT_HOOKS=false \\\n            eclipse-temurin:17-jdk-alpine \\\n            ./gradlew --no-daemon --continue --scan testcontainers:test --tests '*GenericContainerRuleTest'\n"
  },
  {
    "path": ".github/workflows/ci-rootless.yml",
    "content": "name: CI-Docker-Rootless\n\non:\n  pull_request:\n    paths-ignore:\n      - '.github/ISSUE_TEMPLATE/*.yaml'\n      - '.github/CODEOWNERS'\n      - '.github/pull_request_template.md'\n      - 'docs/**/*.css'\n      - 'docs/**/*.html'\n      - 'docs/**/*.ico'\n      - 'docs/**/*.md'\n      - 'docs/**/*.png'\n      - 'docs/**/*.svg'\n      - 'mkdocs.yml'\n      - 'README.md'\n      - 'RELEASING.md'\n      - '.sdkmanrc'\n  push:\n    branches: [ main ]\n    paths-ignore:\n      - '.github/ISSUE_TEMPLATE/*.yaml'\n      - '.github/CODEOWNERS'\n      - '.github/pull_request_template.md'\n      - 'docs/**/*.css'\n      - 'docs/**/*.html'\n      - 'docs/**/*.ico'\n      - 'docs/**/*.md'\n      - 'docs/**/*.png'\n      - 'docs/**/*.svg'\n      - 'mkdocs.yml'\n      - 'README.md'\n      - 'RELEASING.md'\n      - '.sdkmanrc'\n\nconcurrency:\n  group: \"${{ github.workflow }}-${{ github.head_ref || github.sha }}\"\n  cancel-in-progress: true\n\npermissions:\n  contents: read\n\njobs:\n  test:\n    runs-on: ubuntu-22.04\n    permissions:\n      checks: write\n    steps:\n      - uses: actions/checkout@v5\n      - name: Setup rootless Docker\n        uses: docker/setup-docker-action@v4\n        with:\n          rootless: true\n      - name: Setup Gradle Build Action\n        uses: gradle/actions/setup-gradle@v5\n      - name: Build with Gradle\n        run: ./gradlew --no-daemon --scan testcontainers:test --tests '*GenericContainerRuleTest'\n      - uses: ./.github/actions/setup-junit-report\n"
  },
  {
    "path": ".github/workflows/ci-windows-trigger.yml",
    "content": "name: windows-test command dispatch\n\non:\n  issue_comment:\n    types: [created]\n\npermissions:\n  contents: read\n\njobs:\n  windows-test-command-trigger:\n    permissions:\n      pull-requests: write  # for peter-evans/slash-command-dispatch to create PR reaction\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Trigger windows-test command\n        uses: peter-evans/slash-command-dispatch@13bc09769d122a64f75aa5037256f6f2d78be8c4 # v4.0.0\n        with:\n          token: ${{ secrets.WINDOWS_WORKERS_TOKEN }}\n          # The command to trigger the pipeline: e.g. /windows-test\n          # The command name must match the name of the repository_dispatch.type in 'ci-windows.yml' workflow, using '-command' as suffix. E.g. 'windows-test-command'\n          commands: windows-test\n          issue-type: pull-request\n          # The user that owns the above token must belong to the elevated role of 'Maintainers'\n          permission: maintain\n          reactions: false\n"
  },
  {
    "path": ".github/workflows/ci-windows.yml",
    "content": "name: CI - Windows\n\non:\n  pull_request:\n    paths-ignore:\n      - '.github/ISSUE_TEMPLATE/*.yaml'\n      - '.github/CODEOWNERS'\n      - '.github/pull_request_template.md'\n      - 'docs/**/*.css'\n      - 'docs/**/*.html'\n      - 'docs/**/*.ico'\n      - 'docs/**/*.md'\n      - 'docs/**/*.png'\n      - 'docs/**/*.svg'\n      - 'mkdocs.yml'\n      - 'README.md'\n      - 'RELEASING.md'\n      - '.sdkmanrc'\n  push:\n    branches: [ main ]\n    paths-ignore:\n      - '.github/ISSUE_TEMPLATE/*.yaml'\n      - '.github/CODEOWNERS'\n      - '.github/pull_request_template.md'\n      - 'docs/**/*.css'\n      - 'docs/**/*.html'\n      - 'docs/**/*.ico'\n      - 'docs/**/*.md'\n      - 'docs/**/*.png'\n      - 'docs/**/*.svg'\n      - 'mkdocs.yml'\n      - 'README.md'\n      - 'RELEASING.md'\n      - '.sdkmanrc'\n  repository_dispatch:\n    types: [windows-test-command]\n\nconcurrency:\n  group: \"${{ github.workflow }}-${{ github.head_ref || github.sha }}\"\n  cancel-in-progress: true\n\npermissions:\n  contents: read\n\nenv:\n  DEVELOCITY_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }}\n\njobs:\n  main:\n    if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}\n    runs-on: self-hosted\n    permissions:\n      checks: write\n    steps:\n      - uses: actions/checkout@v5\n      - uses: ./.github/actions/setup-build\n      - name: Build with Gradle\n        run: ./gradlew.bat cleanTest testcontainers:test --no-daemon --continue --scan --no-build-cache\n      - uses: ./.github/actions/setup-junit-report\n\n  pr:\n    if: ${{ github.event.client_payload.slash_command.command == 'windows-test' }}\n    runs-on: self-hosted\n    permissions:\n      checks: write\n      statuses: write\n    steps:\n      - name: Create pending status\n        uses: actions/github-script@v8\n        with:\n          github-token: ${{ secrets.GITHUB_TOKEN }}\n          script: |\n            github.rest.repos.createCommitStatus({\n              owner: context.repo.owner,\n              repo: context.repo.repo,\n              sha: context.payload.client_payload.pull_request.head.sha,\n              state: 'pending',\n              target_url: `https://github.com/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`,\n              context: 'CI - Windows',\n            })\n      - uses: actions/checkout@v5\n        with:\n          token: ${{ secrets.GITHUB_TOKEN }}\n          repository: ${{ github.event.client_payload.pull_request.head.repo.full_name }}\n          ref: ${{ github.event.client_payload.pull_request.head.ref }}\n      - uses: ./.github/actions/setup-build\n      - name: Build with Gradle\n        run: ./gradlew.bat cleanTest testcontainers:test --no-daemon --continue --scan --no-build-cache\n      - uses: ./.github/actions/setup-junit-report\n\n      - name: Create success status\n        uses: actions/github-script@v8\n        if: success()\n        with:\n          github-token: ${{ secrets.GITHUB_TOKEN }}\n          script: |\n            github.rest.repos.createCommitStatus({\n              owner: context.repo.owner,\n              repo: context.repo.repo,\n              sha: context.payload.client_payload.pull_request.head.sha,\n              state: 'success',\n              target_url: `https://github.com/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`,\n              context: 'CI - Windows',\n            })\n\n      - name: Create failure status\n        uses: actions/github-script@v8\n        if: failure()\n        with:\n          github-token: ${{ secrets.GITHUB_TOKEN }}\n          script: |\n            github.rest.repos.createCommitStatus({\n              owner: context.repo.owner,\n              repo: context.repo.repo,\n              sha: context.payload.client_payload.pull_request.head.sha,\n              state: 'failure',\n              target_url: `https://github.com/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`,\n              context: 'CI - Windows',\n            })\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  pull_request:\n    paths-ignore:\n      - '.github/ISSUE_TEMPLATE/*.yaml'\n      - '.github/CODEOWNERS'\n      - '.github/pull_request_template.md'\n      - 'docs/**/*.css'\n      - 'docs/**/*.html'\n      - 'docs/**/*.ico'\n      - 'docs/**/*.md'\n      - 'docs/**/*.png'\n      - 'docs/**/*.svg'\n      - 'mkdocs.yml'\n      - 'README.md'\n      - 'RELEASING.md'\n      - '.sdkmanrc'\n  push:\n    branches: [ main ]\n    paths-ignore:\n      - '.github/ISSUE_TEMPLATE/*.yaml'\n      - '.github/CODEOWNERS'\n      - '.github/pull_request_template.md'\n      - 'docs/**/*.css'\n      - 'docs/**/*.html'\n      - 'docs/**/*.ico'\n      - 'docs/**/*.md'\n      - 'docs/**/*.png'\n      - 'docs/**/*.svg'\n      - 'mkdocs.yml'\n      - 'README.md'\n      - 'RELEASING.md'\n      - '.sdkmanrc'\n\nconcurrency:\n  group: \"${{ github.workflow }}-${{ github.head_ref || github.sha }}\"\n  cancel-in-progress: true\n\npermissions:\n  contents: read\n\nenv:\n  AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}\n  AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}\n  DEVELOCITY_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }}\n\njobs:\n  core:\n    runs-on: ubuntu-22.04\n    permissions:\n      checks: write\n    strategy:\n      matrix:\n        java: [ '17', '21' ]\n    steps:\n      - uses: actions/checkout@v5\n      - uses: ./.github/actions/setup-build\n        with:\n          java-version: ${{ matrix.java }}\n      - name: Build and test with Gradle\n        run: |\n          ./gradlew :testcontainers:check --no-daemon --continue --scan\n      - uses: ./.github/actions/setup-junit-report\n\n  turbo-mode:\n    if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}\n    runs-on: ubuntu-22.04\n    permissions:\n      checks: write\n    steps:\n      - uses: actions/checkout@v5\n      - uses: ./.github/actions/setup-build\n      - name: Setup Testcontainers Cloud Client\n        uses: atomicjar/testcontainers-cloud-setup-action@main\n        with:\n          token: ${{ secrets.TC_CLOUD_TOKEN }}\n          args: --max-concurrency=4\n      - name: Test using Testcontainers Cloud with Turbo Mode enabled\n        working-directory: ./smoke-test/\n        run: |\n          ./gradlew check --no-daemon --continue --scan --info\n      - uses: ./.github/actions/setup-junit-report\n\n  find_gradle_jobs:\n    needs: [core]\n    runs-on: ubuntu-22.04\n    outputs:\n      matrix: ${{ steps.set-matrix.outputs.matrix }}\n    steps:\n      - uses: actions/checkout@v5\n      - uses: ./.github/actions/setup-java\n      - name: Setup Gradle Build Action\n        uses: gradle/actions/setup-gradle@v5\n      - id: set-matrix\n        env:\n          # Since we override the tests executor,\n          # we should not push empty results to the cache\n          READ_ONLY_REMOTE_GRADLE_CACHE: true\n        run: |\n          TASKS=$(./gradlew --no-daemon --parallel -q testMatrix | jq 'del(.[] | select(. == \":testcontainers:check\" or startswith(\":docs:\")))' --compact-output)\n          echo $TASKS\n          echo \"matrix={\\\"gradle_args\\\":$TASKS}\" >> $GITHUB_OUTPUT\n  check:\n    needs: [find_gradle_jobs]\n    strategy:\n      fail-fast: false\n      matrix: ${{ fromJson(needs.find_gradle_jobs.outputs.matrix) }}\n    runs-on: ubuntu-22.04\n    permissions:\n      checks: write\n    steps:\n      - uses: actions/checkout@v5\n      - uses: ./.github/actions/setup-build\n      - name: Build and test with Gradle (${{matrix.gradle_args}})\n        run: |\n          ./gradlew --no-daemon --continue --scan ${{matrix.gradle_args}}\n      - uses: ./.github/actions/setup-junit-report\n\n  find_examples_jobs:\n    needs: [check]\n    runs-on: ubuntu-22.04\n    outputs:\n      matrix: ${{ steps.set-matrix.outputs.matrix }}\n    steps:\n      - uses: actions/checkout@v5\n      - uses: ./.github/actions/setup-java\n      - name: Setup Gradle Build Action\n        uses: gradle/actions/setup-gradle@v5\n      - id: set-matrix\n        working-directory: ./examples/\n        env:\n          # Since we override the tests executor,\n          # we should not push empty results to the cache\n          READ_ONLY_REMOTE_GRADLE_CACHE: true\n        run: |\n          TASKS=$(./gradlew --no-daemon --parallel -q testMatrix)\n          echo $TASKS\n          echo \"matrix={\\\"gradle_args\\\":$TASKS}\" >> $GITHUB_OUTPUT\n  check_examples:\n    needs: [find_examples_jobs]\n    strategy:\n      fail-fast: false\n      matrix: ${{ fromJson(needs.find_examples_jobs.outputs.matrix) }}\n    runs-on: ubuntu-22.04\n    permissions:\n      checks: write\n    steps:\n      - uses: actions/checkout@v5\n      - uses: ./.github/actions/setup-build\n      - name: Build and test Examples with Gradle (${{matrix.gradle_args}})\n        working-directory: ./examples/\n        run: |\n          ./gradlew --no-daemon --continue --scan --info ${{matrix.gradle_args}}\n      - uses: ./.github/actions/setup-junit-report\n\n  find_docs_examples_jobs:\n    needs: [check_examples]\n    runs-on: ubuntu-22.04\n    outputs:\n      matrix: ${{ steps.set-matrix.outputs.matrix }}\n    steps:\n      - uses: actions/checkout@v5\n      - uses: ./.github/actions/setup-java\n      - name: Setup Gradle Build Action\n        uses: gradle/actions/setup-gradle@v5\n      - id: set-matrix\n        env:\n          # Since we override the tests executor,\n          # we should not push empty results to the cache\n          READ_ONLY_REMOTE_GRADLE_CACHE: true\n        run: |\n          TASKS=$(./gradlew --no-daemon --parallel -q testMatrix | jq 'map(select(startswith(\":docs:\")))' --compact-output)\n          echo $TASKS\n          echo \"matrix={\\\"gradle_args\\\":$TASKS}\" >> $GITHUB_OUTPUT\n  check_docs_examples:\n    needs: [find_docs_examples_jobs]\n    strategy:\n      fail-fast: false\n      matrix: ${{ fromJson(needs.find_docs_examples_jobs.outputs.matrix) }}\n    runs-on: ubuntu-22.04\n    permissions:\n      checks: write\n    steps:\n      - uses: actions/checkout@v5\n      - uses: ./.github/actions/setup-build\n      - name: Build and test with Gradle (${{matrix.gradle_args}})\n        run: |\n          ./gradlew --no-daemon --continue --scan ${{matrix.gradle_args}}\n      - uses: ./.github/actions/setup-junit-report\n"
  },
  {
    "path": ".github/workflows/combine-prs.yml",
    "content": "name: Combine PRs\n\non:\n  workflow_dispatch:\n\njobs:\n  combine-prs:\n    permissions:\n      contents: write\n      pull-requests: write\n      checks: read\n    runs-on: ubuntu-latest\n    steps:\n      - name: combine-prs\n        id: combine-prs\n        uses: github/combine-prs@v5.2.0\n        with:\n          github_token: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/labeler.yml",
    "content": "name: \"Pull Request Labeler\"\non:\n- pull_request_target\n\njobs:\n  triage:\n    permissions:\n      contents: read\n      pull-requests: write\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/labeler@v6\n      with:\n        repo-token: \"${{ secrets.GITHUB_TOKEN }}\"\n"
  },
  {
    "path": ".github/workflows/moby-latest.yml",
    "content": "name: Tests against recent Docker engine releases\n\non:\n  workflow_dispatch:\n    inputs:\n      version:\n        description: 'Docker version'\n        required: false\n        default: 'latest'\n        type: string\n  schedule:\n    # nightly build, at 23:59 CEST\n    - cron:  '59 23 * * *'\n\nenv:\n  DEVELOCITY_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_ACCESS_KEY }}\n\njobs:\n  test_docker:\n    strategy:\n      matrix:\n        include:\n          - { install-docker-type: \"STABLE\", version: \"${{ inputs.version }}\", channel: stable, rootless: false }\n          - { install-docker-type: \"ROOTLESS\", version: \"${{ inputs.version }}\", channel: stable, rootless: true }\n          - { install-docker-type: \"ROOTFUL\", version: edge, channel: test, rootless: false }\n    name: \"Core tests using Docker ${{ matrix.install-docker-type }} (channel ${{ matrix.channel }})\"\n    runs-on: ubuntu-22.04\n    continue-on-error: true\n    steps:\n      - uses: actions/checkout@v5\n      - uses: ./.github/actions/setup-build\n\n      - name: Install Stable Docker\n        id: setup_docker\n        uses: docker/setup-docker-action@v4\n        with:\n          version: ${{ matrix.version }}\n          channel: ${{ matrix.channel }}\n          rootless: ${{ matrix.rootless }}\n\n      - name: Check Docker version\n        run: docker version\n\n      - name: Build with Gradle\n        run: ./gradlew cleanTest --no-daemon --continue --scan -Dscan.tag.DOCKER_${{ matrix.install-docker-type }} testcontainers:test -Dorg.gradle.caching=false\n        env:\n          DOCKER_HOST: ${{steps.setup_docker.outputs.sock}}\n      - uses: ./.github/actions/setup-junit-report\n\n      - name: Notify to Slack on failures\n        if: failure()\n        id: slack\n        uses: slackapi/slack-github-action@v2.1.1\n        with:\n          payload: |\n            {\n              \"tc_project\": \"testcontainers-java\",\n              \"tc_docker_install_type\": \"${{ matrix.install-docker-type }}\",\n              \"tc_github_action_url\": \"${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}/attempts/${{ github.run_attempt }}\",\n              \"tc_github_action_status\": \"FAILED\",\n              \"tc_slack_channel_id\": \"${{ secrets.SLACK_DOCKER_LATEST_CHANNEL_ID }}\"\n            }\n        env:\n          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_DOCKER_LATEST_WEBHOOK }}\n"
  },
  {
    "path": ".github/workflows/release-drafter.yml",
    "content": "name: Release Drafter\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n    types: [opened, reopened, synchronize]\n\npermissions:\n  contents: read\n\njobs:\n  update_release_draft:\n    permissions:\n      contents: write  # for release-drafter/release-drafter to create a github release\n      pull-requests: write  # for release-drafter/release-drafter to add label to PR\n    if: github.repository == 'testcontainers/testcontainers-java'\n    runs-on: ubuntu-latest\n    steps:\n      - uses: release-drafter/release-drafter@b1476f6e6eb133afa41ed8589daba6dc69b4d3f5 # v5.19.0\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release\n\non:\n  release:\n    types: [published]\n\nenv:\n  AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}\n  AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}\n\npermissions:\n  contents: read\n\njobs:\n  release:\n    runs-on: ubuntu-22.04\n    steps:\n      - uses: actions/checkout@v5\n      - uses: ./.github/actions/setup-java\n      - name: Clear existing docker image cache\n        run: docker image prune -af\n\n      - name: Setup Gradle Build Action\n        uses: gradle/actions/setup-gradle@v5\n\n      - name: Run Gradle Build\n        run: ./gradlew build --scan --no-daemon -i -x test\n\n      - name: Run Gradle Publish\n        run: |\n          ./gradlew publish \\\n            -Pversion=\"${{github.event.release.tag_name}}\" --scan --no-daemon -i\n\n      - name: Run Gradle Deploy\n        run: |\n          ./gradlew jreleaserDeploy -Pversion=\"${{github.event.release.tag_name}}\" --scan --no-daemon -i\n        env:\n          JRELEASER_GPG_PUBLIC_KEY: ${{ vars.GPG_PUBLIC_KEY }}\n          JRELEASER_GPG_SECRET_KEY: ${{ secrets.GPG_SIGNING_KEY }}\n          JRELEASER_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}\n          JRELEASER_MAVENCENTRAL_USERNAME: ${{ secrets.JRELEASER_MAVENCENTRAL_USERNAME }}\n          JRELEASER_MAVENCENTRAL_PASSWORD: ${{ secrets.JRELEASER_MAVENCENTRAL_PASSWORD }}\n"
  },
  {
    "path": ".github/workflows/scripts/check_ci_status.sh",
    "content": "#!/bin/bash\nset -o errexit\nset -o nounset\nset -o pipefail\nset -o xtrace\n\nif [ -z $1 ] ; then\n  echo \"First parameter (commit SHA) is required!\" && exit 1;\nfi\n\nSTATUS=$(curl -s https://api.github.com/repos/testcontainers/testcontainers-java/commits/$1/status | jq -r '.state')\n\n[ \"$STATUS\" == 'success' ]\n"
  },
  {
    "path": ".github/workflows/update-docs-version.yml",
    "content": "name: Update docs version\n\non:\n  release:\n    types: [ published ]\n\npermissions:\n  contents: read\n\njobs:\n  build:\n    permissions:\n      contents: write  # for peter-evans/create-pull-request to create branch\n      pull-requests: write  # for peter-evans/create-pull-request to create a PR\n    if: github.repository == 'testcontainers/testcontainers-java'\n    runs-on: ubuntu-22.04\n    steps:\n      - uses: actions/checkout@v5\n        with:\n          ref: main\n      - name: Update latest_version property in mkdocs.yml\n        run: |\n          sed -i \"s/latest_version: .*/latest_version: ${GITHUB_REF_NAME}/g\" mkdocs.yml\n          git diff\n      - name: Create Pull Request\n        uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v3.10.1\n        with:\n          title: Update docs version to ${{ github.ref_name }}\n          body: |\n            Update docs version to ${{ github.ref_name }}\n            skip-checks: true\n          branch: update-docs-version\n          delete-branch: true\n"
  },
  {
    "path": ".github/workflows/update-gradle-wrapper.yml",
    "content": "name: Update Gradle Wrapper\n\non:\n  schedule:\n    - cron: \"0 0 * * *\"\n\npermissions:\n  contents: read\n\njobs:\n  update-gradle-wrapper:\n    permissions:\n      contents: write # for gradle-update/update-gradle-wrapper-action\n      pull-requests: write # for gradle-update/update-gradle-wrapper-action\n    if: github.repository == 'testcontainers/testcontainers-java'\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v5\n\n      - name: Update Gradle Wrapper\n        uses: gradle-update/update-gradle-wrapper-action@512b1875f3b6270828abfe77b247d5895a2da1e5 # v1.0.13\n        with:\n          repo-token: ${{ secrets.GITHUB_TOKEN }}\n          labels: dependencies\n\n      - uses: gradle/actions/wrapper-validation@v5\n"
  },
  {
    "path": ".github/workflows/update-testcontainers-version.yml",
    "content": "name: Update testcontainers version\n\non:\n  release:\n    types: [ published ]\n\npermissions:\n  contents: read\n\njobs:\n  build:\n    permissions:\n      contents: write  # for peter-evans/create-pull-request to create branch\n      pull-requests: write  # for peter-evans/create-pull-request to create a PR\n    if: github.repository == 'testcontainers/testcontainers-java'\n    runs-on: ubuntu-22.04\n    steps:\n      - uses: actions/checkout@v5\n        with:\n          ref: main\n      - name: Update testcontainers.version property in gradle.properties\n        run: |\n          sed -i \"s/^testcontainers\\.version=.*/testcontainers\\.version=${GITHUB_REF_NAME}/g\" gradle.properties\n          git diff\n      - name: Create Pull Request\n        uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v3.10.1\n        with:\n          title: Update testcontainers version to ${{ github.ref_name }}\n          body: |\n            Update testcontainers version to ${{ github.ref_name }}\n          branch: update-tc-version\n          delete-branch: true\n"
  },
  {
    "path": ".gitignore",
    "content": "/.mvn/timing.properties\n# Created by .ignore support plugin (hsz.mobi)\n### Maven template\ntarget/\ndependency-reduced-pom.xml\n### JetBrains template\n# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm\n\n*.iml\n\n## Directory-based project format:\n.idea/*\n!.idea/icon.png\n# if you remove the above rule, at least ignore the following:\n\n# User-specific stuff:\n# .idea/workspace.xml\n# .idea/tasks.xml\n# .idea/dictionaries\n\n# Sensitive or high-churn files:\n# .idea/dataSources.ids\n# .idea/dataSources.xml\n# .idea/sqlDataSources.xml\n# .idea/dynamic.xml\n# .idea/uiDesigner.xml\n\n# Gradle:\n# .idea/gradle.xml\n# .idea/libraries\n\n# Mongo Explorer plugin:\n# .idea/mongoSettings.xml\n\n## File-based project format:\n## Plugin-specific files:\n\n# IntelliJ\n*.lastchange\n\n# Vagrant\n.vagrant/\n\n# Gitbook\n_book/\nnode_modules/\n\n.gradle/\nbuild/\n!buildSrc/src/main/groovy/org/testcontainers/build\nout/\n*.class\n\n# Eclipse IDE files\n**/.project\n**/.classpath\n**/.settings\n**/bin/\n**/out/\n\n# Generated docs\nsite/\n.direnv/\nsrc/mkdocs-codeinclude-plugin\nsrc/pip-delete-this-directory.txt\n\n.DS_Store\n\n# Codespaces / VSCode\n/.vscode/\n\n# Python virtual environment\n/.venv/\n"
  },
  {
    "path": ".sdkmanrc",
    "content": "# Enable auto-env through the sdkman_auto_env config\n# Add key=value pairs of SDKs to use below\njava=17.0.16-tem\n"
  },
  {
    "path": "AUTHORS",
    "content": "This file is deprecated. Please see the [contributors](https://github.com/testcontainers/testcontainers-java/graphs/contributors) listing.\n\n[Richard North](https://github.com/rnorth), [Sergei Egorov](https://github.com/bsideup) and [Kevin Wittek](https://github.com/kiview) are the core maintainers.\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Change Log\n~All notable changes to this project will be documented in this file.~\n\n# MOVED\n\n**After version 1.8.3 all future releases will _only_ be documented in the [Releases](https://github.com/testcontainers/testcontainers-java/releases) section of the GitHub repository. This changelog file will eventually be removed.**\n\n\n## [1.8.3] - 2018-08-05\n\n### Fixed\n\n- Fixed `with*` methods of `CouchbaseContainer` ([\\#810](https://github.com/testcontainers/testcontainers-java/pull/810))\n- Fix problem with gzip encoded streams (e.g. copy file from container), by adding decompression support to netty exec factory (#817, fixes #681, relates to docker-java/docker-java#1079)\n\n## [1.8.2] - 2018-07-31\n\n### Fixed\n\n- Add support for transparently using local images with docker-compose ([\\#798](https://github.com/testcontainers/testcontainers-java/pull/798), fixes [\\#674](https://github.com/testcontainers/testcontainers-java/issues/674))\n- Fix bug with Dockerfile image creation with Docker for Mac 18.06-ce ([\\#808](https://github.com/testcontainers/testcontainers-java/pull/808), fixes [\\#680](https://github.com/testcontainers/testcontainers-java/issues/680))\n\n### Changed\n\n- Update Visible Assertions to 2.1.1 ([\\#779](https://github.com/testcontainers/testcontainers-java/pull/779)).\n- KafkaContainer optimization (`group.initial.rebalance.delay.ms=0`) ([\\#782](https://github.com/testcontainers/testcontainers-java/pull/782)).\n\n## [1.8.1] - 2018-07-10\n\n### Fixed\n- Linux/Mac: Added support for docker credential helpers so that images may be pulled from private registries. See [\\#729](https://github.com/testcontainers/testcontainers-java/issues/729), [\\#647](https://github.com/testcontainers/testcontainers-java/issues/647) and [\\#567](https://github.com/testcontainers/testcontainers-java/issues/567).\n- Ensure that the `COMPOSE_FILE` environment variable is populated with all relevant compose file names when running docker-compose in local mode [\\#755](https://github.com/testcontainers/testcontainers-java/issues/755).\n- Fixed issue whereby specified command in MariaDB image was not being applied. ([\\#534](https://github.com/testcontainers/testcontainers-java/issues/534))\n- Changed Oracle thin URL to support both Oracle 11 and 12 XE ([\\#769](https://github.com/testcontainers/testcontainers-java/issues/769))\n- Ensure that full JDBC URL query string is passed to JdbcDatabaseDelegate during initscript invocation ([\\#741](https://github.com/testcontainers/testcontainers-java/issues/741); fixes [\\#727](https://github.com/testcontainers/testcontainers-java/issues/727))\n- Ensure that necessary transitive dependency inclusions are applied to generated project POMs ([\\#772](https://github.com/testcontainers/testcontainers-java/issues/772); fixes [\\#753](https://github.com/testcontainers/testcontainers-java/issues/753) and [\\#652](https://github.com/testcontainers/testcontainers-java/issues/652))\n\n### Changed\n- Update Apache Pulsar module to 2.0.1 [\\#760](https://github.com/testcontainers/testcontainers-java/issues/760).\n- Make JdbcDatabaseContainer#getDriverClassName public [\\#743](https://github.com/testcontainers/testcontainers-java/pull/743).\n- enable `copyFileToContainer` feature during container startup [\\#742](https://github.com/testcontainers/testcontainers-java/pull/742).\n- avoid using file mounting in KafkaContainer [\\#775](https://github.com/testcontainers/testcontainers-java/pull/775).\n- Added Apache Cassandra module [\\#776](https://github.com/testcontainers/testcontainers-java/pull/776).\n\n## [1.8.0] - 2018-06-14\n\n### Fixed\n- Fixed JDBC URL Regex Pattern to ensure all supported Database URL's are accepted ([\\#596](https://github.com/testcontainers/testcontainers-java/issues/596))\n- Filtered out TestContainer parameters (TC_*) from query string before passing to database ([\\#345](https://github.com/testcontainers/testcontainers-java/issues/345))\n- Use `latest` tag as default image tag ([\\#676](https://github.com/testcontainers/testcontainers-java/issues/676))\n\n### Changed\n- Allow `HttpWaitStrategy` to wait for a specific port ([\\#703](https://github.com/testcontainers/testcontainers-java/pull/703))\n- New module: Apache Pulsar ([\\#713](https://github.com/testcontainers/testcontainers-java/pull/713))\n- Add support for defining container labels ([\\#725](https://github.com/testcontainers/testcontainers-java/pull/725))\n- Use `quay.io/testcontainers/ryuk` instead of `bsideup/ryuk` ([\\#721](https://github.com/testcontainers/testcontainers-java/pull/721))\n- Added Couchbase module ([\\#688](https://github.com/testcontainers/testcontainers-java/pull/688))\n- Enhancements and Fixes for JDBC URL usage to create Containers ([\\#594](https://github.com/testcontainers/testcontainers-java/pull/594))\n    - Extracted JDBC URL manipulations to a separate class - `ConnectionUrl`. \n    - Added an overloaded method `JdbcDatabaseContainerProvider.newInstance(ConnectionUrl)`, with default implementation delegating to the existing `newInstance(tag)` method. (Relates to [\\#566](https://github.com/testcontainers/testcontainers-java/issues/566))\n    - Added an implementation of `MySQLContainerProvider.newInstance(ConnectionUrl)` that uses Database Name, User, and Password from JDBC URL while creating new MySQL Container. ([\\#566](https://github.com/testcontainers/testcontainers-java/issues/566) for MySQL Container)\n- Changed **internal** port of KafkaContainer back to 9092 ([\\#733](https://github.com/testcontainers/testcontainers-java/pull/733))\n- Add support for Dockerfile based images to OracleContainer ([\\#734](https://github.com/testcontainers/testcontainers-java/pull/734))\n- Read from both `/proc/net/tcp` and `/proc/net/tcp6` in `InternalCommandPortListeningCheck` ([\\#750](https://github.com/testcontainers/testcontainers-java/pull/750))\n- Added builder methods for timeouts in `JdbcDatabaseContainer` ([\\#748](https://github.com/testcontainers/testcontainers-java/pull/748))\n- Added an alternative experimental transport based on OkHttp. Enable it with `transport.type=okhttp` property ([\\#710](https://github.com/testcontainers/testcontainers-java/pull/710))\n- Framework-agnostic container & test lifecycle ([\\#702](https://github.com/testcontainers/testcontainers-java/pull/702))\n\n## [1.7.3] - 2018-05-16\n\n### Fixed\n- Fix for setting `ryuk.container.timeout` causes a `ClassCastException` ([\\#684](https://github.com/testcontainers/testcontainers-java/issues/684))\n- Fixed provided but shaded dependencies in modules ([\\#693](https://github.com/testcontainers/testcontainers-java/issues/693))\n\n### Changed\n- Added InfluxDB module ([\\#686](https://github.com/testcontainers/testcontainers-java/pull/686))\n- Added MockServer module ([\\#696](https://github.com/testcontainers/testcontainers-java/pull/696))\n- Changed LocalStackContainer to extend GenericContainer ([\\#695](https://github.com/testcontainers/testcontainers-java/pull/695))\n\n## [1.7.2] - 2018-04-30\n\n- Add support for private repositories using docker credential stores/helpers (fixes [\\#567](https://github.com/testcontainers/testcontainers-java/issues/567))\n\n### Fixed\n- Add support for private repositories using docker credential stores/helpers (fixes [\\#567](https://github.com/testcontainers/testcontainers-java/issues/567))\n- Retry any exceptions (not just `DockerClientException`) on image pull ([\\#662](https://github.com/testcontainers/testcontainers-java/issues/662))\n- Fixed handling of the paths with `+` in them ([\\#664](https://github.com/testcontainers/testcontainers-java/issues/664))\n\n### Changed\n- Database container images are now pinned to a specific version rather than using `latest`. The tags selected are the most recent as of the time of this change. If a JDBC URL is used with no tag specified, a WARN level log message is output, pending a future change to make tags mandatory in the JDBC URL. ([\\#671](https://github.com/testcontainers/testcontainers-java/issues/671))\n- Updated docker-java to 3.1.0-rc-3, enforced `org.jetbrains:annotations:15.0`. ([\\#672](https://github.com/testcontainers/testcontainers-java/issues/672))\n\n## [1.7.1] - 2018-04-20\n\n### Fixed\n- Fixed missing `commons-codec` dependency ([\\#642](https://github.com/testcontainers/testcontainers-java/issues/642))\n- Fixed `HostPortWaitStrategy` throws `NumberFormatException` when port is exposed but not mapped ([\\#640](https://github.com/testcontainers/testcontainers-java/issues/640))\n- Fixed log processing: multibyte unicode, linebreaks and ASCII color codes. Color codes can be turned on with `withRemoveAnsiCodes(false)` ([\\#643](https://github.com/testcontainers/testcontainers-java/pull/643))\n- Fixed Docker host IP detection within docker container (detect only if not explicitly set) ([\\#648](https://github.com/testcontainers/testcontainers-java/pull/648))\n- Add support for private repositories using docker credential stores/helpers ([PR \\#647](https://github.com/testcontainers/testcontainers-java/pull/647), fixes [\\#567](https://github.com/testcontainers/testcontainers-java/issues/567))\n\n### Changed\n- Support multiple HTTP status codes for HttpWaitStrategy ([\\#630](https://github.com/testcontainers/testcontainers-java/issues/630))\n- Mark all long-living threads started by Testcontainers as daemons and group them. ([\\#646](https://github.com/testcontainers/testcontainers-java/issues/646))\n- Remove noisy `DEBUG` logging of Netty packets ([\\#646](https://github.com/testcontainers/testcontainers-java/issues/646))\n- Updated docker-java to 3.1.0-rc-2 ([\\#646](https://github.com/testcontainers/testcontainers-java/issues/646))\n\n## [1.7.0] - 2018-04-07\n\n### Fixed\n- Fixed extraneous insertion of `useSSL=false` in all JDBC URL strings, even for DBs that do not understand it. Usage is now restricted to MySQL by default and can be overridden by authors of `JdbcDatabaseContainer` subclasses ([\\#568](https://github.com/testcontainers/testcontainers-java/issues/568))\n- Fixed `getServicePort` on `DockerComposeContainer` throws NullPointerException if service instance number in not used. ([\\#619](https://github.com/testcontainers/testcontainers-java/issues/619))\n- Increase Ryuk's timeout and make it configurable with `ryuk.container.timeout`. ([\\#621](https://github.com/testcontainers/testcontainers-java/issues/621)[\\#635](https://github.com/testcontainers/testcontainers-java/issues/635))\n\n### Changed\n- Added compatibility with selenium greater than 3.X ([\\#611](https://github.com/testcontainers/testcontainers-java/issues/611))\n- Abstracted and changed database init script functionality to support use of SQL-like scripts with non-JDBC connections. ([\\#551](https://github.com/testcontainers/testcontainers-java/pull/551))\n- Added `JdbcDatabaseContainer(Future)` constructor. ([\\#543](https://github.com/testcontainers/testcontainers-java/issues/543))\n- Mark DockerMachineClientProviderStrategy as not persistable ([\\#593](https://github.com/testcontainers/testcontainers-java/pull/593))\n- Added `waitingFor(String serviceName, WaitStrategy waitStrategy)` and overloaded `withExposedService()` methods to `DockerComposeContainer` to allow user to define `WaitStrategy` for compose containers. ([\\#174](https://github.com/testcontainers/testcontainers-java/issues/174), [\\#515](https://github.com/testcontainers/testcontainers-java/issues/515) and ([\\#600](https://github.com/testcontainers/testcontainers-java/pull/600)))\n- Deprecated `WaitStrategy` and implementations in favour of classes with same names in `org.testcontainers.containers.strategy` ([\\#600](https://github.com/testcontainers/testcontainers-java/pull/600))\n- Added `ContainerState` interface representing the state of a started container ([\\#600](https://github.com/testcontainers/testcontainers-java/pull/600))\n- Added `WaitStrategyTarget` interface which is the target of the new `WaitStrategy` ([\\#600](https://github.com/testcontainers/testcontainers-java/pull/600))\n- *Breaking:* Removed hard-coded `wnameless` Oracle database image name. Users should instead place a file on the classpath named `testcontainers.properties` containing `oracle.container.image=IMAGE`, where IMAGE is a suitable image name and tag/SHA hash. For information, the approach recommended by Oracle for creating an Oracle XE docker image is described [here](https://blogs.oracle.com/oraclewebcentersuite/implement-oracle-database-xe-as-docker-containers).\n- Added `DockerHealthcheckWaitStrategy` that is based on Docker's built-in [healthcheck](https://docs.docker.com/engine/reference/builder/#healthcheck) ([\\#618](https://github.com/testcontainers/testcontainers-java/pull/618)).\n- Added `withLogConsumer(String serviceName, Consumer<OutputFrame> consumer)` method to `DockerComposeContainer` ([\\#605](https://github.com/testcontainers/testcontainers-java/issues/605))\n- Added `withFixedExposedPort(int hostPort, int containerPort, InternetProtocol protocol)` method to `FixedHostPortGenericContainer` and `addFixedExposedPort(int hostPort, int containerPort, InternetProtocol protocol)` to `GenericContainer` ([\\#586](https://github.com/testcontainers/testcontainers-java/pull/586))\n\n## [1.6.0] - 2018-01-28\n\n### Fixed\n- Fixed incompatibility of Docker-Compose container with JDK9. ([\\#562](https://github.com/testcontainers/testcontainers-java/pull/562))\n- Fixed retrieval of Docker host IP when running inside Docker. ([\\#479](https://github.com/testcontainers/testcontainers-java/issues/479))\n- Compose is now able to pull images from private repositories. ([\\#536](https://github.com/testcontainers/testcontainers-java/issues/536))\n- Fixed overriding MySQL image command. ([\\#534](https://github.com/testcontainers/testcontainers-java/issues/534))\n- Fixed shading for javax.annotation.CheckForNull ([\\#563](https://github.com/testcontainers/testcontainers-java/issues/563) and [testcontainers/testcontainers-scala\\#11](https://github.com/testcontainers/testcontainers-scala/issues/11)).\n\n### Changed\n- Added JDK9 build and tests to Travis-CI. ([\\#562](https://github.com/testcontainers/testcontainers-java/pull/562))\n- Added Kafka module ([\\#546](https://github.com/testcontainers/testcontainers-java/pull/546))\n- Added \"Death Note\" to track & kill spawned containers even if the JVM was \"kill -9\"ed ([\\#545](https://github.com/testcontainers/testcontainers-java/pull/545))\n- Environment variables are now stored as Map instead of List ([\\#550](https://github.com/testcontainers/testcontainers-java/pull/550))\n- Added `withEnv(String name, Function<Optional<String>, String> mapper)` with optional previous value ([\\#550](https://github.com/testcontainers/testcontainers-java/pull/550))\n- Added `withFileSystemBind` overloaded method with `READ_WRITE` file mode by default ([\\#550](https://github.com/testcontainers/testcontainers-java/pull/550))\n- All connections to JDBC containers (e.g. MySQL) don't use SSL anymore. ([\\#374](https://github.com/testcontainers/testcontainers-java/issues/374))\n\n## [1.5.1] - 2017-12-19\n\n### Fixed\n- Fixed problem with case-sensitivity when checking internal port. ([\\#524](https://github.com/testcontainers/testcontainers-java/pull/524))\n- Add retry logic around checkExposedPort pre-flight check for improved robustness ([\\#513](https://github.com/testcontainers/testcontainers-java/issues/513))\n\n### Changed\n- Added `getDatabaseName` method to JdbcDatabaseContainer, MySQLContainer, PostgreSQLContainer ([\\#473](https://github.com/testcontainers/testcontainers-java/issues/473))\n- Added `VncRecordingContainer` - Network-based, attachable re-implementation of `VncRecordingSidekickContainer` ([\\#526](https://github.com/testcontainers/testcontainers-java/pull/526))\n\n## [1.5.0] - 2017-12-12\n### Fixed\n- Fixed problems with using container based docker-compose on Windows ([\\#514](https://github.com/testcontainers/testcontainers-java/pull/514))\n- Fixed problems with copying files on Windows ([\\#514](https://github.com/testcontainers/testcontainers-java/pull/514))\n- Fixed regression in 1.4.3 when using Docker Compose on Windows ([\\#439](https://github.com/testcontainers/testcontainers-java/issues/439))\n- Fixed local Docker Compose executable name resolution on Windows ([\\#416](https://github.com/testcontainers/testcontainers-java/issues/416))\n- Fixed TAR composition on Windows ([\\#444](https://github.com/testcontainers/testcontainers-java/issues/444))\n- Allowing `addExposedPort` to be used after ports have been specified with `withExposedPorts` ([\\#453](https://github.com/testcontainers/testcontainers-java/issues/453))\n- Stopping creation of temporary directory prior to creating temporary file ([\\#443](https://github.com/testcontainers/testcontainers-java/issues/443))\n- Ensure that temp files are created in a temp directory ([\\#423](https://github.com/testcontainers/testcontainers-java/issues/423))\n- Added `WaitAllStrategy` as a mechanism for composing multiple startup `WaitStrategy` objects together\n- Changed `BrowserWebDriverContainer` to use improved wait strategies, to eliminate race conditions when starting VNC recording containers. This should lead to far fewer 'error' messages logged when starting up selenium containers, and less exposure to race related bugs (fixes [\\#466](https://github.com/testcontainers/testcontainers-java/issues/466)).\n\n### Changed\n- Make Network instances reusable (i.e. work with `@ClassRule`) ([\\#469](https://github.com/testcontainers/testcontainers-java/issues/469))\n- Added support for explicitly setting file mode when copying file into container ([\\#446](https://github.com/testcontainers/testcontainers-java/issues/446), [\\#467](https://github.com/testcontainers/testcontainers-java/issues/467))\n- Use Visible Assertions 2.1.0 for pre-flight test output (eliminating Jansi/JNR-POSIX dependencies for lower likelihood of conflict. JNA is now used internally by Visible Assertions instead).\n- Mark all links functionality as deprecated. This is pending removal in a later release. Please see [\\#465](https://github.com/testcontainers/testcontainers-java/issues/465). `Network` features should be used instead.\n- Added support for copying files to/from running containers ([\\#378](https://github.com/testcontainers/testcontainers-java/issues/378))\n- Add `getLivenessCheckPorts` as an eventual replacement for `getLivenessCheckPort`; this allows multiple ports to be included in post-startup wait strategies.\n- Refactor wait strategy port checking and improve test coverage.\n- Added support for customising the recording file name ([\\#500](https://github.com/testcontainers/testcontainers-java/issues/500))\n\n## [1.4.3] - 2017-10-14\n### Fixed\n- Fixed local Docker Compose executable name resolution on Windows ([\\#416](https://github.com/testcontainers/testcontainers-java/issues/416), [\\#460](https://github.com/testcontainers/testcontainers-java/issues/460))\n- Fixed TAR composition on Windows ([\\#444](https://github.com/testcontainers/testcontainers-java/issues/444))\n- Allowing `addExposedPort` to be used after ports have been specified with `withExposedPorts` ([\\#453](https://github.com/testcontainers/testcontainers-java/issues/453))\n- Stopping creation of temporary directory prior to creating temporary file ([\\#443](https://github.com/testcontainers/testcontainers-java/issues/443))\n\n### Changed\n- Added `forResponsePredicate` method to HttpWaitStrategy to test response body ([\\#441](https://github.com/testcontainers/testcontainers-java/issues/441))\n- Changed `DockerClientProviderStrategy` to be loaded via Service Loader ([\\#434](https://github.com/testcontainers/testcontainers-java/issues/434), [\\#435](https://github.com/testcontainers/testcontainers-java/issues/435))\n- Made it possible to specify docker compose container in configuration ([\\#422](https://github.com/testcontainers/testcontainers-java/issues/422), [\\#425](https://github.com/testcontainers/testcontainers-java/issues/425))\n- Clarified wording of pre-flight check messages ([\\#457](https://github.com/testcontainers/testcontainers-java/issues/457), [\\#436](https://github.com/testcontainers/testcontainers-java/issues/436))\n- Added caching of failure to find a docker daemon, so that subsequent tests fail fast. This is likely to be a significant improvement in situations where there is no docker daemon available, dramatically reducing run time and log output when further attempts to find the docker daemon cannot succeed.\n- Allowing JDBC containers' username, password and DB name to be customized ([\\#400](https://github.com/testcontainers/testcontainers-java/issues/400), [\\#354](https://github.com/testcontainers/testcontainers-java/issues/354))\n\n## [1.4.2] - 2017-07-25\n### Fixed\n- Worked around incompatibility between Netty's Unix socket support and OS X 10.11. Reinstated use of TCP-Unix Socket proxy when running on OS X prior to v10.12. (Fixes [\\#402](https://github.com/testcontainers/testcontainers-java/issues/402))\n- Changed to use version 2.0 of the Visible Assertions library for startup pre-flight checks. This no longer has a dependency on Jansi, and is intended to resolve a JVM crash issue apparently caused by native lib version conflicts ([\\#395](https://github.com/testcontainers/testcontainers-java/issues/395)). Please note that the newer ANSI code is less mature and thus has had less testing, particularly in interesting terminal environments such as Windows. If issues are encountered, coloured assertion output may be disabled by setting the system property `visibleassertions.ansi.enabled` to `true`.\n- Fixed NullPointerException when calling GenericContainer#isRunning on not started container ([\\#411](https://github.com/testcontainers/testcontainers-java/issues/411))\n\n### Changed\n- Removed Guava usage from `jdbc` module ([\\#401](https://github.com/testcontainers/testcontainers-java/issues/401))\n\n## [1.4.1] - 2017-07-10\n### Fixed\n- Fixed Guava shading in `jdbc` module\n\n## [1.4.0] - 2017-07-09\n### Fixed\n- Fixed the case when disk's size is bigger than Integer's max value ([\\#379](https://github.com/testcontainers/testcontainers-java/issues/379), [\\#380](https://github.com/testcontainers/testcontainers-java/issues/380))\n- Fixed erroneous version reference used during CI testing of shaded dependencies\n- Fixed leakage of Vibur and Tomcat JDBC test dependencies in `jdbc-test` and `mysql` modules ([\\#382](https://github.com/testcontainers/testcontainers-java/issues/382))\n- Added timeout and retries for creation of `RemoteWebDriver` ([\\#381](https://github.com/testcontainers/testcontainers-java/issues/381), [\\#373](https://github.com/testcontainers/testcontainers-java/issues/373), [\\#257](https://github.com/testcontainers/testcontainers-java/issues/257))\n- Fixed various shading issues\n- Improved removal of containers/networks when using Docker Compose, eliminating irrelevant errors during cleanup ([\\#342](https://github.com/testcontainers/testcontainers-java/issues/342), [\\#394](https://github.com/testcontainers/testcontainers-java/issues/394))\n\n### Changed\n- Added support for Docker networks ([\\#372](https://github.com/testcontainers/testcontainers-java/issues/372))\n- Added `getFirstMappedPort` method ([\\#377](https://github.com/testcontainers/testcontainers-java/issues/377))\n- Extracted Oracle XE container into a separate repository ([testcontainers/testcontainers-java-module-oracle-xe](https://github.com/testcontainers/testcontainers-java-module-oracle-xe))\n- Added shading tests\n- Updated docker-java to 3.0.12 ([\\#393](https://github.com/testcontainers/testcontainers-java/issues/393))\n\n## [1.3.1] - 2017-06-22\n### Fixed\n- Fixed non-POSIX fallback for file attribute reading ([\\#371](https://github.com/testcontainers/testcontainers-java/issues/371))\n- Fixed NullPointerException in AuditLogger when running using slf4j-log4j12 bridge ([\\#375](https://github.com/testcontainers/testcontainers-java/issues/375))\n- Improved cleanup of JDBC connections during database container startup checks\n\n### Changed\n- Extracted MariaDB into a separate repository ([\\#337](https://github.com/testcontainers/testcontainers-java/issues/337))\n- Added `TC_DAEMON` JDBC URL flag to prevent `ContainerDatabaseDriver` from shutting down containers at the time all connections are closed. ([\\#359](https://github.com/testcontainers/testcontainers-java/issues/359), [\\#360](https://github.com/testcontainers/testcontainers-java/issues/360))\n- Added pre-flight checks (can be disabled with `checks.disable` configuration property) ([\\#363](https://github.com/testcontainers/testcontainers-java/issues/363))\n- Improved startup time by adding dynamic priorities to DockerClientProviderStrategy ([\\#362](https://github.com/testcontainers/testcontainers-java/issues/362))\n- Added global configuration file `~/.testcontainers.properties` ([\\#362](https://github.com/testcontainers/testcontainers-java/issues/362))\n- Added container arguments to specify SELinux contexts for mounts ([\\#334](https://github.com/testcontainers/testcontainers-java/issues/334))\n- Removed unused Jersey dependencies ([\\#361](https://github.com/testcontainers/testcontainers-java/issues/361))\n- Removed deprecated, wrongly-generated setters from `GenericContainer`\n\n## [1.3.0] - 2017-06-05\n### Fixed\n- Improved container cleanup if startup failed ([\\#336](https://github.com/testcontainers/testcontainers-java/issues/336), [\\#335](https://github.com/testcontainers/testcontainers-java/issues/335))\n\n### Changed\n- Upgraded docker-java library to 3.0.10 ([\\#349](https://github.com/testcontainers/testcontainers-java/issues/349))\n- Added basic audit logging of Testcontainers' actions via a specific SLF4J logger name with metadata captured via MDC. Intended for use in highly shared Docker environments.\n- Use string-based detection of Selenium container startup ([\\#328](https://github.com/testcontainers/testcontainers-java/issues/328), [\\#351](https://github.com/testcontainers/testcontainers-java/issues/351))\n- Use string-based detection of PostgreSQL container startup ([\\#327](https://github.com/testcontainers/testcontainers-java/issues/327), [\\#317](https://github.com/testcontainers/testcontainers-java/issues/317))\n- Update libraries to recent versions ([\\#333](https://github.com/testcontainers/testcontainers-java/issues/333))\n- Introduce abstraction over files and classpath resources, allowing recursive copying of directories ([\\#313](https://github.com/testcontainers/testcontainers-java/issues/313))\n\n## [1.2.1] - 2017-04-06\n### Fixed\n- Fix bug in space detection when `alpine:3.5` image has not yet been pulled ([\\#323](https://github.com/testcontainers/testcontainers-java/issues/323), [\\#324](https://github.com/testcontainers/testcontainers-java/issues/324))\n- Minor documentation fixes\n\n### Changed\n- Add AOP Alliance dependencies to shaded deps to reduce chance of conflicts ([\\#315](https://github.com/testcontainers/testcontainers-java/issues/315))\n\n## [1.2.0] - 2017-03-12\n### Fixed\n- Fix various escaping issues that may arise when paths contain spaces ([\\#263](https://github.com/testcontainers/testcontainers-java/issues/263), [\\#279](https://github.com/testcontainers/testcontainers-java/issues/279))\n- General documentation fixes/improvements ([\\#300](https://github.com/testcontainers/testcontainers-java/issues/300), [\\#303](https://github.com/testcontainers/testcontainers-java/issues/303), [\\#304](https://github.com/testcontainers/testcontainers-java/issues/304))\n- Improve reliability of `ResourceReaper` when there are a large number of containers returned by `docker ps -a` ([\\#295](https://github.com/testcontainers/testcontainers-java/issues/295))\n\n### Changed\n- Support Docker for Windows via TCP socket connection ([\\#291](https://github.com/testcontainers/testcontainers-java/issues/291), [\\#297](https://github.com/testcontainers/testcontainers-java/issues/297), [\\#309](https://github.com/testcontainers/testcontainers-java/issues/309)). _Note that Docker Compose is not yet supported under Docker for Windows (see [\\#306](https://github.com/testcontainers/testcontainers-java/issues/306))\n- Expose `docker-java`'s `CreateContainerCmd` API for low-level container tweaking ([\\#301](https://github.com/testcontainers/testcontainers-java/issues/301))\n- Shade `org.newsclub` and Guava dependencies ([\\#299](https://github.com/testcontainers/testcontainers-java/issues/299), [\\#292](https://github.com/testcontainers/testcontainers-java/issues/292))\n- Add `org.testcontainers` label to all containers created by Testcontainers ([\\#294](https://github.com/testcontainers/testcontainers-java/issues/294))\n\n## [1.1.9] - 2017-02-12\n### Fixed\n- Fix inability to run Testcontainers on Alpine linux. Unix-socket-over-TCP is now used in linux environments where netty fails due to lack of glibc libraries ([\\#290](https://github.com/testcontainers/testcontainers-java/issues/290))\n- Fix slow feedback in the case of missing JDBC drivers by failing-fast if the required driver cannot be found ([\\#280](https://github.com/testcontainers/testcontainers-java/issues/280), [\\#230](https://github.com/testcontainers/testcontainers-java/issues/230))\n\n### Changed\n- Add ability to change 'tiny image' used for disk space checks ([\\#287](https://github.com/testcontainers/testcontainers-java/issues/287))\n- Add ability to attach volumes to a container using 'volumes from' ([\\#244](https://github.com/testcontainers/testcontainers-java/issues/244), [\\#289](https://github.com/testcontainers/testcontainers-java/issues/289))\n\n## [1.1.8] - 2017-01-22\n### Fixed\n- Compatibility fixes for Docker for Mac v1.13.0 ([\\#272](https://github.com/testcontainers/testcontainers-java/issues/272))\n- Relax docker environment disk space check to accommodate unusual empty `df` output observed on Docker for Mac with OverlayFS ([\\#273](https://github.com/testcontainers/testcontainers-java/issues/273), [\\#278](https://github.com/testcontainers/testcontainers-java/issues/278))\n- Fix inadvertent private-scoping of startup checks' `StartupStatus`, which made implementation of custom startup checks impossible ([\\#266](https://github.com/testcontainers/testcontainers-java/issues/266))\n- Fix potential resource lead/deadlock when errors are encountered building images from a Dockerfile ([\\#274](https://github.com/testcontainers/testcontainers-java/issues/274))\n\n### Changed\n- Add support for execution within a Docker container ([\\#267](https://github.com/testcontainers/testcontainers-java/issues/267)), correcting resolution of container addresses\n- Add support for version 2 of private docker registries, configured via `$HOME/.docker/config.json` ([\\#270](https://github.com/testcontainers/testcontainers-java/issues/270))\n- Use current classloader instead of system classloader for loading JDBC drivers ([\\#261](https://github.com/testcontainers/testcontainers-java/issues/261))\n- Allow hardcoded container image names for Ambassador and VNC recorder containers to be changed via a configuration file ([\\#277](https://github.com/testcontainers/testcontainers-java/issues/277), [\\#259](https://github.com/testcontainers/testcontainers-java/issues/259))\n- Allow Selenium Webdriver container image name to be specified as a constructor parameter ([\\#249](https://github.com/testcontainers/testcontainers-java/issues/249), [\\#171](https://github.com/testcontainers/testcontainers-java/issues/171))\n\n\n## [1.1.7] - 2016-11-19\n### Fixed\n- Compensate for premature TCP socket opening in Docker for Mac ([\\#160](https://github.com/testcontainers/testcontainers-java/issues/160), [\\#236](https://github.com/testcontainers/testcontainers-java/issues/236))\n- (Internal) Stabilise various parts of Testcontainers' self test suite ([\\#241](https://github.com/testcontainers/testcontainers-java/issues/241))\n- Fix mounting of classpath resources when those resources are in a JAR file ([\\#213](https://github.com/testcontainers/testcontainers-java/issues/213))\n- Reduce misleading error messages caused mainly by trying to perform operations on stopped containers ([\\#243](https://github.com/testcontainers/testcontainers-java/issues/243))\n\n### Changed\n- Uses a default MySQL and MariaDB configuration to reduce memory footprint ([\\#209](https://github.com/testcontainers/testcontainers-java/issues/209), [\\#243](https://github.com/testcontainers/testcontainers-java/issues/243))\n- Docker Compose can optionally now use a local `docker-compose` executable rather than running inside a container ([\\#200](https://github.com/testcontainers/testcontainers-java/issues/200))\n- Add support for privileged mode containers ([\\#234](https://github.com/testcontainers/testcontainers-java/issues/234), [\\#235](https://github.com/testcontainers/testcontainers-java/issues/235))\n- Allow container/network cleanup (ResourceReaper) to be triggered programmatically ([\\#231](https://github.com/testcontainers/testcontainers-java/issues/231))\n- Add optional tailing of logs for containers spawned by Docker Compose ([\\#233](https://github.com/testcontainers/testcontainers-java/issues/233))\n- (Internal) Relocate non-proprietary database container tests to a single module\n\n## [1.1.6] - 2016-09-22\n### Fixed\n- Fix logging of discovered Docker environment variables ([\\#218](https://github.com/testcontainers/testcontainers-java/issues/218))\n- Adopt longer timeout periods for testing docker client configurations, and allow these to be further customised through system properties ([\\#217](https://github.com/testcontainers/testcontainers-java/issues/217), see *ClientProviderStrategy classes)\n- Fix docker compose directory mounting on windows ([\\#224](https://github.com/testcontainers/testcontainers-java/issues/224))\n- Handle and ignore further categories of failure in retrieval of docker environment disk space ([\\#225](https://github.com/testcontainers/testcontainers-java/issues/225))\n\n### Changed\n- Add extra configurability options (database name, username, password) for PostgreSQL DB containers ([\\#220](https://github.com/testcontainers/testcontainers-java/issues/220))\n- Add MariaDB container type ([\\#215](https://github.com/testcontainers/testcontainers-java/issues/215))\n- Use Docker Compose `down` action for more robust teardown of compose environments\n- Ensure that Docker Compose operations run sequentially rather than concurrently if JUnit tests are parallelized ([\\#226](https://github.com/testcontainers/testcontainers-java/issues/226))\n- Allow multiple Docker Compose files to be specified, to allow for extension/composition of services ([\\#227](https://github.com/testcontainers/testcontainers-java/issues/227))\n\n## [1.1.5] - 2016-08-22\n### Fixed\n- Fix Docker Compose environment variable passthrough ([\\#208](https://github.com/testcontainers/testcontainers-java/issues/208))\n\n### Changed\n- Remove Docker Compose networks when containers are shut down ([\\#211](https://github.com/testcontainers/testcontainers-java/issues/211)) as well as at JVM shutdown\n\n## [1.1.4] - 2016-08-16\n### Fixed\n- Fix JDBC proxy driver behaviour when used with Tomcat connection pool to avoid spawning excessive numbers of containers ([\\#195](https://github.com/testcontainers/testcontainers-java/issues/195))\n- Shade Jersey dependencies in JDBC module to avoid classpath conflicts ([\\#202](https://github.com/testcontainers/testcontainers-java/issues/202))\n- Fix NullPointerException when docker host has untagged images ([\\#201](https://github.com/testcontainers/testcontainers-java/issues/201))\n- Fix relative paths for volumes mounted in docker-compose containers ([\\#189](https://github.com/testcontainers/testcontainers-java/issues/189))\n\n### Changed\n- Update to v3.0.2 of docker-java library\n- Switch to a shared, single instance docker client rather than a separate client instance per container rule ([\\#193](https://github.com/testcontainers/testcontainers-java/issues/193))\n- Ensure that docker-compose pulls images (with no timeout), prior to trying to start ([\\#188](https://github.com/testcontainers/testcontainers-java/issues/188))\n- Use official `docker/compose` image for running docker-compose ([\\#190](https://github.com/testcontainers/testcontainers-java/issues/190))\n\n## [1.1.3] - 2016-07-27\n### Fixed\n- Further fix for shading of netty Linux native libs, specifically when run using Docker Compose support\n- Ensure that file mode permissions are retained for Dockerfile builder\n\n### Changed\n- Add support for specifying container working directory, and set this to match the `/compose` directory for Docker Compose\n- Improve resilience of Selenium container startup\n- Add `withLogConsumer(...)` to allow a log consumer to be attached to a container from the moment of startup\n\n## [1.1.2] - 2016-07-19\n### Fixed\n- Fix shading of netty Linux native libs\n\n### Changed\n- Shade guava artifacts to prevent classloader conflicts\n\n## [1.1.1] - 2016-07-17\n### Fixed\n- Improve shutdown of unnecessary docker clients ([\\#170](https://github.com/testcontainers/testcontainers-java/issues/170))\n- Shade `io.netty` dependencies into the testcontainers core JAR to reduce conflicts ([\\#170](https://github.com/testcontainers/testcontainers-java/issues/170) and [\\#157](https://github.com/testcontainers/testcontainers-java/issues/157))\n- Remove timeouts for docker compose execution, particularly useful when image pulls are involved\n- Improve output logging from docker-compose, pausing to log output in case of failure rather than letting logs intermingle.\n\n### Changed\n- Reinstate container startup retry (removed in v1.1.0) as an optional setting, only used by default for Selenium webdriver containers\n\n## [1.1.0] - 2016-07-05\n### Fixed\n- Apply shade relocation to Jersey repackaged Guava libs\n- General logging and stability improvements to Docker Compose support\n- Fix liveness checks to use specific IP address obtained using `getContainerIpAddress()`\n\n### Changed\n- Integrate interim support for Docker for Mac beta and Docker Machine for Windows. See [docs](docs/index.md) for known limitations.\n- Add support for Docker Compose v2 and scaling of compose containers\n- Add support for attaching containers to specific networks.\n- Allow container environment variables to be set using a Map\n\n## [1.0.5] - 2016-05-02\n### Fixed\n- Fix problems associated with changes to `tenforce/virtuoso:latest` container, and replace with a pinned version.\n- Fix build-time dependency on visible-assertions library, which had downstream dependencies that started to break the Testcontainers build.\n\n### Changed\n- Add support for pluggable wait strategies, i.e. overriding the default TCP connect wait strategy with HTTP ping or any user-defined approach.\n- Add 'self-typing' to allow easy use of fluent-style options even when `GenericContainer` is subclassed.\n- Add support for defining extra entries for containers' `/etc/hosts` files.\n- Add fluent setter for setting file-system file/directory binding\n\n## [1.0.4] - 2016-04-17\n### Fixed\n- Prevent unnecessary and erroneous reconfiguration of container if startup needs to be retried\n- Consolidate container cleanup to ensure that ambassador containers used for Docker Compose are cleaned up appropriately\n- Fix container liveness check port lookup for FixedHostPortGenericContainer.\n- Upgrade docker-compose container to dduportal/docker-compose:1.6.0 for compatibility with docker compose file format v2.\n\n### Changed\n- Add `docker exec` support for running commands against running containers\n- Add support for building container images on the fly from Dockerfiles, including optional Dockerfile builder DSL\n- Add container name as prefix for container logs that are streamed to SLF4J\n- Improve container startup failure detection, including adding the option to specify a minimum up time that the container should achieve before being considered started successfully\n\n## [1.0.3] - 2016-03-31\n### Fixed\n- Resolve issues where containers would not be cleaned up on JVM shutdown if they failed to start correctly\n- Fix validation problem where docker image names that contained private registry URLs with port number would be rejected\n- Resolve bug where `docker pull` would try infinitely for a non-existent image name\n\n### Changed\n- Set startup free disk space check to ensure that the Docker environment has a minimum of 2GB available rather than 10%\n- Add streaming of container logs to SLF4J loggers, capture as Strings, and also the ability to wait for container log content to satisfy an expected predicate\n- Allow configuration of docker container startup timeout\n- Add detection of classpath Selenium version, and automatic selection of correct Selenium docker containers for compatibility\n\n## [1.0.2] - 2016-02-27\n### Fixed\n- If a container fail to start up correctly, startup will now be retried up to a limit of 3 times\n- Add resilience around `getMappedPort` method to fail fast when a port is not yet mapped, rather than generate misleading errors\n\n### Changed\n- Add JDBC container module for OpenLink Virtuoso\n- Add additional debug level logging to aid with diagnosis of docker daemon discovery problems\n- Add support for using a local Unix socket to connect to the Docker daemon\n\n## [1.0.1] - 2016-02-18\n### Fixed\n- Remove extraneous service loader entries in the shaded JAR\n- Upgrade to v2.2.0 of docker-java client library to take advantage of unix socket fixes (see https://github.com/docker-java/docker-java/issues/456)\n- Validate that docker image names include a tag on creation\n\n### Changed\n- By default, use docker machine name from `DOCKER_MACHINE_NAME` environment, or `default` if it exists\n- Allow container ports to map to a fixed port on the host through use of the `FixedHostPortGenericContainer` subclass of `GenericContainer`\n\n## [1.0.0] - 2016-02-07\n### Fixed\n- Resolve Jersey/Jackson dependency clashes by shading (relocating) a version of these libraries into the core Testcontainers JAR\n- Improve documentation and logging concerning discovery of Docker daemon\n\n### Changed\n- Rename container `getIpAddress()` method to `getContainerIpAddress()` and deprecate original method name.\n- Rename container `getHostIpAddress()` method to `getTestHostIpAddress()`\n\n## [0.9.9] - 2016-01-12\n### Fixed\n- Resolve thread safety issues associated with use of a singleton docker client\n- Resolve disk space check problems when running on a Debian-based docker host\n- Fix CircleCI problems where the build could hit memory limits\n\n### Changed\n- Remove bundled logback.xml to allow users more control over logging\n- Add Travis CI support for improved breadth of testing\n\n## [0.9.8] - 2015-08-12\n### Changed\n- Change from Spotify docker client library to docker-java, for improved compatibility with latest versions of Docker\n- Change from JDK 1.7 minimum requirement to JDK 1.8\n- Replace boot2docker support with docker-machine support\n- Docker images are now prefetched when a @Rule is instantiated\n- Combined Rule and Container classes throughout, for a reduced set of public classes and removal of some duplication\n- Improvements to container cleanup, especially removal of data volumes\n- General improvements to error handling, logging etc throughout\n\n### Added\n- Docker Compose support\n- Automatic docker environment disk space check\n\n## [0.9.7] - 2015-08-07\n### Added\n- Support for overriding MySQL container configuration (my.cnf file overrides)\n\n### Changed\n- Replace dependency on org.testpackage with org.rnorth.visible-assertions\n\n## [0.9.6] - 2015-07-22\n### Added\n- Generic container support (allows use of any docker image) using a GenericContainerRule.\n\n### Changed\n- Renamed from org.rnorth.test-containers to org.testcontainers\n- Explicit support for usage on linux and use with older versions of Docker (v1.2.0 tested)\n\n## [0.9.5] - 2015-06-28\n### Added\n- Oracle XE container support\n\n### Changed\n- Support for JDK 1.7 (previously was JDK 1.8+)\n\n## [0.9.4] and 0.9.3 - 2015-06-23\n### Changed\n- Refactored for better modularization\n\n## [0.9.2] - 2015-06-13\n### Added\n- 'Sidekick' VNC recording container to record video of Selenium test sessions\n\n### Changed\n- Alter timezone used for time display inside Selenium containers\n\n## [0.9.1] - 2015-06-07\n### Added\n- Support for Selenium webdriver containers\n- Recording of Selenium test sessions using vnc2flv\n\n## [0.9] - 2015-04-29\nInitial release\n\n[1.5.1]: https://github.com/testcontainers/testcontainers-java/releases/tag/1.5.1\n[1.5.0]: https://github.com/testcontainers/testcontainers-java/releases/tag/1.5.0\n[1.4.2]: https://github.com/testcontainers/testcontainers-java/releases/tag/1.4.3\n[1.4.2]: https://github.com/testcontainers/testcontainers-java/releases/tag/1.4.2\n[1.4.1]: https://github.com/testcontainers/testcontainers-java/releases/tag/1.4.1\n[1.4.0]: https://github.com/testcontainers/testcontainers-java/releases/tag/1.4.0\n[1.2.0]: https://github.com/testcontainers/testcontainers-java/releases/tag/testcontainers-1.2.0\n[1.1.9]: https://github.com/testcontainers/testcontainers-java/releases/tag/testcontainers-1.1.9\n[1.1.8]: https://github.com/testcontainers/testcontainers-java/releases/tag/testcontainers-1.1.8\n[1.1.7]: https://github.com/testcontainers/testcontainers-java/releases/tag/testcontainers-1.1.7\n[1.1.6]: https://github.com/testcontainers/testcontainers-java/releases/tag/testcontainers-1.1.6\n[1.1.5]: https://github.com/testcontainers/testcontainers-java/releases/tag/testcontainers-1.1.5\n[1.1.4]: https://github.com/testcontainers/testcontainers-java/releases/tag/testcontainers-1.1.4\n[1.1.3]: https://github.com/testcontainers/testcontainers-java/releases/tag/testcontainers-1.1.3\n[1.1.2]: https://github.com/testcontainers/testcontainers-java/releases/tag/testcontainers-1.1.2\n[1.1.1]: https://github.com/testcontainers/testcontainers-java/releases/tag/testcontainers-1.1.1\n[1.1.0]: https://github.com/testcontainers/testcontainers-java/releases/tag/testcontainers-1.1.0\n[1.0.5]: https://github.com/testcontainers/testcontainers-java/releases/tag/testcontainers-1.0.5\n[1.0.4]: https://github.com/testcontainers/testcontainers-java/releases/tag/testcontainers-1.0.4\n[1.0.3]: https://github.com/testcontainers/testcontainers-java/releases/tag/testcontainers-1.0.3\n[1.0.2]: https://github.com/testcontainers/testcontainers-java/releases/tag/testcontainers-1.0.2\n[1.0.1]: https://github.com/testcontainers/testcontainers-java/releases/tag/testcontainers-1.0.1\n[1.0.0]: https://github.com/testcontainers/testcontainers-java/releases/tag/testcontainers-1.0.0\n[0.9.9]: https://github.com/testcontainers/testcontainers-java/releases/tag/testcontainers-0.9.9\n[0.9.8]: https://github.com/testcontainers/testcontainers-java/releases/tag/testcontainers-0.9.8\n[0.9.7]: https://github.com/testcontainers/testcontainers-java/releases/tag/test-containers-0.9.7\n[0.9.6]: https://github.com/testcontainers/testcontainers-java/releases/tag/test-containers-0.9.6\n[0.9.5]: https://github.com/testcontainers/testcontainers-java/releases/tag/test-containers-0.9.5\n[0.9.4]: https://github.com/testcontainers/testcontainers-java/releases/tag/test-containers-0.9.4\n[0.9.3]: https://github.com/testcontainers/testcontainers-java/releases/tag/test-containers-0.9.3\n[0.9.2]: https://github.com/testcontainers/testcontainers-java/releases/tag/test-containers-0.9.2\n[0.9.1]: https://github.com/testcontainers/testcontainers-java/releases/tag/test-containers-0.9.1\n[0.9]: https://github.com/testcontainers/testcontainers-java/releases/tag/test-containers-0.9\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing\n\nPlease see the [main contributing guidelines](./docs/contributing.md).\n\nThere are additional docs describing [contributing documentation changes](./docs/contributing_docs.md).\n\n### GitHub Sponsorship\n\nTestcontainers is [in the GitHub Sponsors program](https://github.com/sponsors/testcontainers)!\n\nThis repository is supported by our sponsors, meaning that issues are eligible to have a 'bounty' attached to them by sponsors.\n\nPlease see [the bounty policy page](https://www.testcontainers.org/bounty) if you are interested, either as a sponsor or as a contributor.\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2015-2019 Richard North\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Testcontainers\n\n[![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.testcontainers/testcontainers/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.testcontainers/testcontainers)\n\n[![Netlify Status](https://api.netlify.com/api/v1/badges/189f28a2-7faa-42ff-b03c-738142079cc9/deploy-status)](https://app.netlify.com/sites/testcontainers/deploys)\n\n[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new?hide_repo_select=true&ref=main&repo=33816473&machine=standardLinux32gb&devcontainer_path=.devcontainer%2Fdevcontainer.json&location=EastUs)\n\n[![Revved up by Develocity](https://img.shields.io/badge/Revved%20up%20by-Develocity-06A0CE?logo=Gradle&labelColor=02303A)](https://ge.testcontainers.org/scans)\n\n> Testcontainers is a Java library that supports JUnit tests, providing lightweight, throwaway instances of common databases, Selenium web browsers, or anything else that can run in a Docker container.\n\n![Testcontainers logo](docs/logo.png)\n\n# [Read the documentation here](https://java.testcontainers.org)\n\n## License\n\nSee [LICENSE](LICENSE).\n\n## Copyright\n\nCopyright (c) 2015 - 2021 Richard North and other authors.\n\nMS SQL Server module is (c) 2017 - 2021 G DATA Software AG and other authors.\n\nHashicorp Vault module is (c) 2017 - 2021 Capital One Services, LLC and other authors.\n\nSee [contributors](https://github.com/testcontainers/testcontainers-java/graphs/contributors) for all contributors.\n"
  },
  {
    "path": "RELEASING.md",
    "content": "# Release process\n\nTestcontainers' release process is semi-automated through GitHub Actions. This describes the basic steps for a project member to perform a release.\n\n## Steps\n\n1. Ensure that the `main` branch is building and that tests are passing.\n1. Create a new release on GitHub. **The tag name is used as the version**, so please keep the tag name plain (e.g. 1.2.3).\n1. The release triggers a GitHub Action workflow.\n1. Log in to [Sonatype](https://oss.sonatype.org/) to check the staging repository.\n    * Getting access to Sonatype requires a Sonatype JIRA account and [raising an issue](https://issues.sonatype.org/browse/OSSRH-74229), requesting access. \n3. Get the staging URL from Sonatype after GitHub Action workflow finished. The general URL format should be `https://oss.sonatype.org/service/local/repositories/$staging-repo-id/content/`\n4. Manually test the release with the staging URL as maven repository URL (e.g. critical issues and features).\n5. Run [TinSalver](https://github.com/bsideup/tinsalver) from GitHub using `npx` to sign artifact (see [TinSalver README](https://github.com/bsideup/tinsalver/blob/main/README.md)).\n    * For TinSalver to correctly work with keybase on WSL on Windows, you might need to disable pinentry: `keybase config set -b pinentry.disabled true`.\n7. Close the release in Sonatype. This will evaluate the release based on given Sonatype rules.\n8. After successful closing, the release button needs to be clicked and afterwards it is automatically synced to Maven Central.\n9. Handcraft and polish some of the release notes (e.g. substitute combined dependency PRs and highlight certain features).\n10. Rename existing milestone corresponding to new release and close it. Then create a new `next` milestone.\n11. When available through Maven Central, poke [Richard North](https://github.com/rnorth) to announce the release on Twitter!\n12. Merge automated version update PRs in order to update the reference version in `mkdocs.yml` and `gradle.properties`.\n\n## Internal details\n\n* The process is done with GitHub Actions, TinSalver and Sonatype.\n* Sonatype will automatically promote the staging release to Maven Central.\n* Keybase needs to be installed on the developer machine.\n* GPG key of signing developer needs to be uploaded to the [Ubuntu keyserver](https://keyserver.ubuntu.com/) (or other server supported by Sonatype).\n"
  },
  {
    "path": "annotations/com/google/common/base/annotations.xml",
    "content": "<root>\n    <item name='com.google.common.base.Preconditions void checkArgument(boolean, java.lang.Object)'>\n        <annotation name='org.jetbrains.annotations.Contract'>\n            <val val=\"&quot;false, _ -&gt; fail; true, _ -&gt; _&quot;\"/>\n        </annotation>\n    </item>\n</root>"
  },
  {
    "path": "annotations/org/rnorth/ducttape/annotations.xml",
    "content": "<root>\n    <item name='org.rnorth.ducttape.Preconditions void check(java.lang.String, boolean)'>\n        <annotation name='org.jetbrains.annotations.Contract'>\n            <val val=\"&quot;_,true-&gt;_;_,false-&gt;fail&quot;\"/>\n        </annotation>\n    </item>\n</root>"
  },
  {
    "path": "azure-pipelines.yml",
    "content": "jobs:\n\n- job: core_tests\n  timeoutInMinutes: 60\n  steps:\n\n  # Run all core tests when running the Windows CI tests\n  - task: Gradle@2\n    condition: eq(variables['Agent.OS'], 'Windows_NT')\n    displayName: Build & test (Windows - core)\n    env:\n      AWS_ACCESS_KEY_ID: $(aws.accessKeyId)\n      AWS_SECRET_ACCESS_KEY: $(aws.secretAccessKey)\n    inputs:\n        gradleWrapperFile: 'gradlew'\n        jdkVersionOption: '1.8'\n        options: '--no-daemon --continue'\n        tasks: 'clean testcontainers:check'\n        publishJUnitResults: true\n        testResultsFiles: '**/TEST-*.xml'\n\n- job: other_tests\n  timeoutInMinutes: 120\n  steps:\n  # Run all non-core tests when running the Windows CI tests\n  - task: Gradle@2\n    condition: eq(variables['Agent.OS'], 'Windows_NT')\n    displayName: Build & test (Windows - all non-core modules)\n    env:\n      AWS_ACCESS_KEY_ID: $(aws.accessKeyId)\n      AWS_SECRET_ACCESS_KEY: $(aws.secretAccessKey)\n    inputs:\n        gradleWrapperFile: 'gradlew'\n        jdkVersionOption: '1.8'\n        options: '--no-daemon --continue'\n        tasks: 'clean check -x testcontainers:test'\n        publishJUnitResults: true\n        testResultsFiles: '**/TEST-*.xml'\n"
  },
  {
    "path": "bom/build.gradle",
    "content": "description = \"Testcontainers :: BOM\"\n\npublishing {\n    publications {\n        mavenJava(MavenPublication) { publication ->\n            artifactId = \"testcontainers-bom\"\n            artifacts = []\n\n            pom.withXml {\n                def dependencyManagementNode = asNode().appendNode('dependencyManagement').appendNode('dependencies')\n\n                def bomProject = project\n                rootProject.subprojects.each { subProject ->\n                    if (subProject != bomProject && subProject.plugins.findPlugin(\"maven-publish\")) {\n                        dependencyManagementNode.appendNode('dependency').with {\n                            appendNode('groupId', subProject.group)\n                            appendNode('artifactId',subProject.name)\n                            appendNode('version', subProject.version)\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "build.gradle",
    "content": "buildscript {\n    repositories {\n        mavenCentral()\n    }\n\n    dependencies {\n        // https://github.com/melix/japicmp-gradle-plugin/issues/36\n        classpath 'com.google.guava:guava:33.3.1-jre'\n        classpath 'com.github.tjni.captainhook:captain-hook:0.1.5'\n    }\n}\n\nplugins {\n    id 'io.franzbecker.gradle-lombok' version '5.0.0'\n    id 'com.gradleup.shadow' version '8.3.9'\n    id 'me.champeau.gradle.japicmp' version '0.4.3' apply false\n    id 'com.diffplug.spotless' version '6.22.0' apply false\n    id 'org.jreleaser' version '1.20.0' apply false\n}\n\napply from: \"$rootDir/gradle/ci-support.gradle\"\napply plugin: 'com.github.tjni.captainhook'\n\ncaptainHook {\n    autoApplyGitHooks = Boolean.valueOf(System.getenv(\"AUTO_APPLY_GIT_HOOKS\"))\n    preCommit = './gradlew spotlessApply'\n}\n\nsubprojects {\n    apply plugin: 'java'\n    apply plugin: 'java-library'\n    apply plugin: 'idea'\n    apply plugin: 'io.franzbecker.gradle-lombok'\n    apply from: \"$rootDir/gradle/shading.gradle\"\n    apply from: \"$rootDir/gradle/spotless.gradle\"\n    apply plugin: 'checkstyle'\n\n    group = \"org.testcontainers\"\n\n    java {\n        toolchain {\n            languageVersion = JavaLanguageVersion.of(17)\n        }\n    }\n\n    tasks.withType(JavaCompile) {\n        options.release.set(8)\n        options.encoding = 'UTF-8'\n    }\n\n    compileTestJava.options.encoding = 'UTF-8'\n    javadoc.options.encoding = 'UTF-8'\n\n    repositories {\n        mavenCentral()\n    }\n\n    configurations {\n        provided\n        api.extendsFrom(provided)\n    }\n\n    lombok {\n        version = '1.18.30'\n    }\n\n\n    task delombok(type: io.franzbecker.gradle.lombok.task.DelombokTask) {\n        outputs.cacheIf {\n            true\n        }\n        argumentProviders.addAll(\n            new org.testcontainers.build.DelombokArgumentProvider(srcDirs: project.sourceSets.main.java.srcDirs, outputDir: file(\"$buildDir/delombok\"))\n        )\n    }\n    delombok.onlyIf {\n        project.sourceSets.main.java.srcDirs.find { it.exists() }\n    }\n\n    // specific modules should be excluded from publication\n    if ( ! [\"test-support\", \"testcontainers-jdbc-test\"].contains(it.name) && !it.path.startsWith(\":docs:\") && it != project(\":docs\") ) {\n        apply from: \"$rootDir/gradle/publishing.gradle\"\n\n        if (it.name != \"bom\") {\n            apply plugin: \"me.champeau.gradle.japicmp\"\n            tasks.register('japicmp', me.champeau.gradle.japicmp.JapicmpTask)\n            apply from: \"$rootDir/gradle/japicmp.gradle\"\n        }\n    }\n\n    test {\n        useJUnitPlatform()\n\n        defaultCharacterEncoding = \"UTF-8\"\n        testLogging {\n            displayGranularity 1\n            showStackTraces = true\n            exceptionFormat = 'full'\n            events \"STARTED\", \"PASSED\", \"FAILED\", \"SKIPPED\"\n        }\n        ext.isCI = System.getenv(\"CI\") != null\n        if (isCI) {\n            develocity.testRetry {\n                maxRetries = 2\n                maxFailures = 5\n                failOnPassedAfterRetry = false\n            }\n        }\n    }\n\n    tasks.withType(Test).all {\n        reports {\n            junitXml.outputPerTestCase = true\n        }\n    }\n\n    // Ensure that Javadoc generation is always tested\n    check.dependsOn(javadoc)\n\n    def postCheckCommand = properties[\"postCheckCommand\"]\n    if (postCheckCommand) {\n        check.finalizedBy(tasks.create(\"postCheckExec\", Exec) {\n            if (org.gradle.internal.os.OperatingSystem.current().isWindows()) {\n                commandLine('cmd', '/c', postCheckCommand)\n            } else {\n                commandLine('sh', '-c', postCheckCommand)\n            }\n        })\n    }\n\n    javadoc {\n        dependsOn delombok\n        source = delombok.outputs\n    }\n\n    dependencies {\n        testImplementation 'ch.qos.logback:logback-classic:1.3.15'\n        testImplementation 'org.assertj:assertj-core:3.27.4'\n        testImplementation 'org.junit.jupiter:junit-jupiter:5.13.4'\n\n        testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.11.0'\n    }\n\n    checkstyle {\n        toolVersion = \"10.23.0\"\n        configFile = rootProject.file('config/checkstyle/checkstyle.xml')\n    }\n}\n"
  },
  {
    "path": "buildSrc/build.gradle",
    "content": "plugins {\n    id 'java-gradle-plugin'\n}\n\nrepositories {\n    mavenCentral()\n}\n"
  },
  {
    "path": "buildSrc/src/main/groovy/org/testcontainers/build/ComparePOMWithLatestReleasedTask.groovy",
    "content": "package org.testcontainers.build\n\nimport groovy.xml.XmlSlurper\nimport org.gradle.api.DefaultTask\nimport org.gradle.api.tasks.Input\nimport org.gradle.api.tasks.TaskAction\n\nclass ComparePOMWithLatestReleasedTask extends DefaultTask {\n\n    @Input\n    Set<String> ignore = []\n\n    @TaskAction\n    def doCompare() {\n        def rootNode = new XmlSlurper().parse(project.tasks.generatePomFileForMavenJavaPublication.destination)\n\n        def artifactId = rootNode.artifactId.text()\n\n        def latestRelease = new XmlSlurper()\n            .parse(\"https://repo1.maven.org/maven2/org/testcontainers/${artifactId}/maven-metadata.xml\")\n            .versioning.release.text()\n\n        def releasedRootNode = new XmlSlurper()\n            .parse(\"https://repo1.maven.org/maven2/org/testcontainers/${artifactId}/${latestRelease}/${artifactId}-${latestRelease}.pom\")\n\n        Set<String> dependencies = releasedRootNode.dependencies.children()\n            .collect { \"${it.groupId.text()}:${it.artifactId.text()}\".toString() }\n\n        for (dependency in rootNode.dependencies.children()) {\n            def coordinates = \"${dependency.groupId.text()}:${dependency.artifactId.text()}\".toString()\n            if (!dependencies.contains(coordinates) && !ignore.contains(coordinates)) {\n                throw new IllegalStateException(\"A new dependency '${coordinates}' has been added to 'org.testcontainers:${artifactId}' - if this was intentional please add it to the ignore list in ${project.buildFile}\")\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "buildSrc/src/main/groovy/org/testcontainers/build/DelombokArgumentProvider.groovy",
    "content": "package org.testcontainers.build\n\nimport org.gradle.api.tasks.InputFiles\nimport org.gradle.api.tasks.OutputDirectory\nimport org.gradle.api.tasks.PathSensitive\nimport org.gradle.api.tasks.PathSensitivity\nimport org.gradle.process.CommandLineArgumentProvider\n\n/**\n * Allows build cache relocatability for Delombok task\n */\nclass DelombokArgumentProvider implements CommandLineArgumentProvider {\n\n    @InputFiles\n    @PathSensitive(PathSensitivity.RELATIVE)\n    Set<File> srcDirs\n\n    @OutputDirectory\n    File outputDir\n\n    @Override\n    Iterable<String> asArguments() {\n        return [srcDirs.collect { it.absolutePath }.join(\" \"), \"-d\", outputDir.absolutePath, \"-f\", \"generateDelombokComment:skip\"]\n    }\n\n}\n"
  },
  {
    "path": "config/checkstyle/checkstyle.xml",
    "content": "<!DOCTYPE module PUBLIC \"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN\"\n    \"https://checkstyle.org/dtds/configuration_1_3.dtd\">\n<module name=\"Checker\">\n    <module name=\"TreeWalker\">\n        <module name=\"SuppressionCommentFilter\"/>\n        <module name=\"NeedBraces\">\n            <property name=\"tokens\"\n                      value=\"\n                      LITERAL_DO,\n                      LITERAL_ELSE,\n                      LITERAL_FOR,\n                      LITERAL_IF,\n                      LITERAL_WHILE,\n                      \"\n            />\n        </module>\n        <module name=\"NeedBraces\">\n            <property name=\"tokens\"\n                      value=\"\n                      LAMBDA,\n                      \"\n            />\n            <property name=\"allowSingleLineStatement\" value=\"true\"/>\n        </module>\n        <module name=\"AvoidStarImport\"/>\n        <module name=\"AvoidStaticImport\">\n            <property name=\"excludes\"\n                      value=\"\n                      io.restassured.RestAssured.*,\n                      org.assertj.core.api.Assertions.*,\n                      org.assertj.core.api.Assumptions.*,\n                      org.awaitility.Awaitility.*,\n                      org.junit.Assume.*,\n                      org.mockito.Mockito.*,\n                      org.mockito.ArgumentMatchers.*,\n                      org.mockserver.model.HttpRequest.*,\n                      org.mockserver.model.HttpResponse.*,\n                      org.rnorth.ducttape.unreliables.Unreliables.*,\n                      \"\n            />\n        </module>\n        <module name=\"RegexpSinglelineJava\">\n            <property name=\"maximum\" value=\"0\"/>\n            <property name=\"format\" value=\"org\\.junit\\.Assert\\.assert\" />\n            <property name=\"message\"\n                      value=\"Please use AssertJ imports.\" />\n            <property name=\"ignoreComments\" value=\"true\" />\n        </module>\n        <module name=\"RegexpSinglelineJava\">\n            <property name=\"maximum\" value=\"0\"/>\n            <property name=\"format\" value=\"org\\.junit\\.jupiter\\.api\\.Assertions\\.assert\" />\n            <property name=\"message\"\n                      value=\"Please use AssertJ imports.\" />\n            <property name=\"ignoreComments\" value=\"true\" />\n        </module>\n        <module name=\"EmptyLineSeparator\">\n            <property name=\"allowNoEmptyLineBetweenFields\" value=\"false\" />\n        </module>\n        <module name=\"OneStatementPerLineCheck\"/>\n        <module name=\"ModifierOrder\" />\n        <module name=\"SingleSpaceSeparator\" />\n    </module>\n    <module name=\"RegexpSingleline\">\n        <property name=\"format\" value=\"^\\s*\\*\\s*@author\"/>\n        <property name=\"message\" value=\"Remove author tags from source files.\"/>\n    </module>\n</module>\n"
  },
  {
    "path": "core/build.gradle",
    "content": "apply plugin: 'com.gradleup.shadow'\n\ndescription = \"Testcontainers Core\"\n\nsourceSets {\n    jarFileTest\n}\n\ntest.maxParallelForks = 4\n\nidea.module.testSourceDirs += sourceSets.jarFileTest.allSource.srcDirs\n\njar {\n    manifest {\n        attributes('Implementation-Version': project.getProperty(\"version\"))\n    }\n}\n\nshadowJar {\n    [\n        'META-INF/NOTICE',\n        'META-INF/NOTICE.txt',\n        'META-INF/LICENSE',\n        'META-INF/LICENSE.txt',\n        'META-INF/maven/',\n        'META-INF/proguard/',\n        'META-INF/versions/*/module-info.class',\n        'META-INF/services/java.security.Provider',\n    ].each { exclude(it) }\n}\n\ntask jarFileTest(type: Test) {\n    useJUnitPlatform()\n    testClassesDirs = sourceSets.jarFileTest.output.classesDirs\n    classpath = sourceSets.jarFileTest.runtimeClasspath\n\n    file(shadowJar.outputs.files.singleFile) // input for correct caching\n    systemProperty(\"jarFile\", shadowJar.outputs.files.singleFile)\n\n    dependsOn(shadowJar)\n}\nproject.tasks.check.dependsOn(jarFileTest)\n\ntasks.japicmp {\n    packageExcludes = [\n        \"com.github.dockerjava.*\",\n        \"org.testcontainers.shaded.*\",\n    ]\n\n    classExcludes = []\n\n    methodExcludes = []\n\n    fieldExcludes = []\n}\n\nconfigurations.all {\n    resolutionStrategy {\n        force 'com.fasterxml.jackson.core:jackson-databind:2.18.4'\n        force 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.18.4'\n    }\n}\n\ndependencies {\n    api 'org.slf4j:slf4j-api:1.7.36'\n    compileOnly 'org.jetbrains:annotations:26.0.2-1'\n    testCompileOnly 'org.jetbrains:annotations:26.0.2-1'\n    api 'org.apache.commons:commons-compress:1.28.0'\n    api ('org.rnorth.duct-tape:duct-tape:1.0.8') {\n        exclude(group: 'org.jetbrains', module: 'annotations')\n    }\n\n    provided('com.google.cloud.tools:jib-core:0.27.3') {\n        exclude group: 'com.google.guava', module: 'guava'\n        exclude group: 'com.fasterxml.jackson.datatype', module: 'jackson-datatype-jsr310'\n        exclude group: 'com.fasterxml.jackson.core', module: 'jackson-core'\n        exclude group: 'com.fasterxml.jackson.core', module: 'jackson-databind'\n        exclude group: 'org.apache.commons', module: 'commons-compress'\n    }\n\n    shaded 'org.awaitility:awaitility:4.3.0'\n\n    api platform('com.github.docker-java:docker-java-bom:3.7.1')\n    shaded platform('com.github.docker-java:docker-java-bom:3.7.1')\n\n    api \"com.github.docker-java:docker-java-api\"\n\n    shaded('com.github.docker-java:docker-java-core') {\n        exclude group: 'com.google.guava', module: 'guava'\n    }\n\n    api 'com.github.docker-java:docker-java-transport-zerodep'\n\n    shaded 'com.google.guava:guava:33.3.1-jre'\n    shaded \"org.yaml:snakeyaml:2.5\"\n\n    shaded 'org.glassfish.main.external:trilead-ssh2-repackaged:4.1.2'\n\n    shaded 'org.zeroturnaround:zt-exec:1.12'\n\n    testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.11.0'\n\n    testImplementation 'org.junit.jupiter:junit-jupiter:5.13.4'\n    testImplementation('com.google.cloud.tools:jib-core:0.27.3')  {\n        exclude group: 'com.google.guava', module: 'guava'\n    }\n    testImplementation 'org.apache.httpcomponents:httpclient:4.5.14'\n    testImplementation 'redis.clients:jedis:6.2.0'\n    testImplementation 'com.rabbitmq:amqp-client:5.26.0'\n    testImplementation 'org.mongodb:mongo-java-driver:3.12.14'\n\n    testImplementation ('org.mockito:mockito-core:4.11.0') {\n        exclude(module: 'hamcrest-core')\n    }\n    // Synthetic JAR used for MountableFileTest and DirectoryTarResourceTest\n    testImplementation files('testlib/repo/fakejar/fakejar/0/fakejar-0.jar')\n\n    testImplementation 'org.assertj:assertj-core:3.27.6'\n    testImplementation 'io.rest-assured:rest-assured:5.5.6'\n\n    jarFileTestCompileOnly \"org.projectlombok:lombok:${lombok.version}\"\n    jarFileTestAnnotationProcessor \"org.projectlombok:lombok:${lombok.version}\"\n    jarFileTestRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.11.0'\n    jarFileTestImplementation 'org.junit.jupiter:junit-jupiter:5.13.4'\n    jarFileTestImplementation 'org.assertj:assertj-core:3.27.6'\n    jarFileTestImplementation 'org.ow2.asm:asm-debug-all:5.2'\n}\n\ntasks.generatePomFileForMavenJavaPublication.finalizedBy(\n    tasks.register('checkPOMdependencies', org.testcontainers.build.ComparePOMWithLatestReleasedTask) {\n        ignore = [\n        ]\n    }\n)\n\ncompileTestJava {\n    javaCompiler = javaToolchains.compilerFor {\n        languageVersion = JavaLanguageVersion.of(17)\n    }\n    options.release.set(17)\n}\n\ntest {\n    useJUnitPlatform()\n}\n"
  },
  {
    "path": "core/src/jarFileTest/java/org/testcontainers/AbstractJarFileTest.java",
    "content": "package org.testcontainers;\n\nimport java.net.URI;\nimport java.net.URLDecoder;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.FileSystem;\nimport java.nio.file.FileSystems;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Collections;\n\npublic abstract class AbstractJarFileTest {\n\n    public static Path root;\n\n    static {\n        try {\n            Path jarFilePath = Paths.get(System.getProperty(\"jarFile\"));\n            String decodedPath = URLDecoder.decode(jarFilePath.toUri().toString(), StandardCharsets.UTF_8.name());\n            URI jarFileUri = new URI(\"jar\", decodedPath, null);\n            FileSystem fileSystem = FileSystems.newFileSystem(jarFileUri, Collections.emptyMap());\n            root = fileSystem.getPath(\"/\");\n        } catch (Exception e) {\n            throw new RuntimeException(e);\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/jarFileTest/java/org/testcontainers/JarFileShadingTest.java",
    "content": "package org.testcontainers;\n\nimport org.assertj.core.api.ListAssert;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass JarFileShadingTest extends AbstractJarFileTest {\n\n    @Test\n    void testPackages() throws Exception {\n        assertThatFileList(root).containsOnly(\"org\", \"META-INF\");\n\n        assertThatFileList(root.resolve(\"org\")).containsOnly(\"testcontainers\");\n    }\n\n    @Test\n    void testMetaInf() throws Exception {\n        assertThatFileList(root.resolve(\"META-INF\"))\n            .containsOnly(\n                \"MANIFEST.MF\",\n                \"services\",\n                \"versions\",\n                \"native-image\",\n                \"thirdparty-LICENSE\",\n                \"FastDoubleParser-NOTICE\",\n                \"FastDoubleParser-LICENSE\"\n            );\n    }\n\n    @Test\n    void testMetaInfServices() throws Exception {\n        assertThatFileList(root.resolve(\"META-INF\").resolve(\"services\"))\n            .allMatch(it -> it.startsWith(\"org.testcontainers.\"));\n    }\n\n    private ListAssert<String> assertThatFileList(Path path) throws IOException {\n        return (ListAssert) assertThat(Files.list(path))\n            .extracting(Path::getFileName)\n            .extracting(Path::toString)\n            .extracting(it -> it.endsWith(\"/\") ? it.substring(0, it.length() - 1) : it);\n    }\n}\n"
  },
  {
    "path": "core/src/jarFileTest/java/org/testcontainers/PublicBinaryAPITest.java",
    "content": "package org.testcontainers;\n\nimport lombok.RequiredArgsConstructor;\nimport org.assertj.core.api.Assertions;\nimport org.junit.jupiter.api.Assumptions;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.Parameter;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.objectweb.asm.ClassReader;\nimport org.objectweb.asm.Opcodes;\nimport org.objectweb.asm.Type;\nimport org.objectweb.asm.tree.ClassNode;\nimport org.objectweb.asm.tree.FieldNode;\nimport org.objectweb.asm.tree.MethodNode;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.file.FileVisitResult;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.SimpleFileVisitor;\nimport java.nio.file.attribute.BasicFileAttributes;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * This test checks that we don't expose any shaded class in our public API.\n */\n@ParameterizedClass\n@MethodSource(\"data\")\n@RequiredArgsConstructor\npublic class PublicBinaryAPITest extends AbstractJarFileTest {\n\n    private static final String SHADED_PACKAGE = \"org.testcontainers.shaded.\";\n\n    private static final String SHADED_PACKAGE_PATH = SHADED_PACKAGE.replaceAll(\"\\\\.\", \"/\");\n\n    static {\n        Assertions.registerFormatterForType(ClassNode.class, it -> it.name);\n        Assertions.registerFormatterForType(FieldNode.class, it -> it.name);\n        Assertions.registerFormatterForType(MethodNode.class, it -> it.name + it.desc);\n    }\n\n    public static List<Object[]> data() throws Exception {\n        List<Object[]> result = new ArrayList<>();\n\n        Files.walkFileTree(\n            root,\n            new SimpleFileVisitor<Path>() {\n                @Override\n                public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException {\n                    String fileName = path.toString();\n\n                    if (!fileName.endsWith(\".class\")) {\n                        return super.visitFile(path, attrs);\n                    }\n\n                    if (!fileName.startsWith(\"/org/testcontainers/\")) {\n                        return super.visitFile(path, attrs);\n                    }\n\n                    if (fileName.startsWith(\"/\" + SHADED_PACKAGE_PATH)) {\n                        return super.visitFile(path, attrs);\n                    }\n\n                    try (InputStream inputStream = Files.newInputStream(path)) {\n                        ClassReader reader = new ClassReader(inputStream);\n                        ClassNode node = new ClassNode();\n                        reader.accept(node, ClassReader.SKIP_CODE);\n                        if ((node.access & Opcodes.ACC_PUBLIC) != 0) {\n                            result.add(new Object[] { fileName, node });\n                        }\n                    }\n\n                    return super.visitFile(path, attrs);\n                }\n            }\n        );\n        return result;\n    }\n\n    @Parameter(0)\n    private String fileName;\n\n    @Parameter(1)\n    private ClassNode classNode;\n\n    @BeforeEach\n    public void setUp() {\n        switch (classNode.name) {\n            // Necessary evil\n            case \"org/testcontainers/dockerclient/UnixSocketClientProviderStrategy\":\n            case \"org/testcontainers/dockerclient/DockerClientProviderStrategy\":\n            case \"org/testcontainers/dockerclient/WindowsClientProviderStrategy\":\n            case \"org/testcontainers/utility/DynamicPollInterval\":\n                Assumptions.assumeTrue(false);\n        }\n    }\n\n    @Test\n    void testSuperClass() {\n        assertThat(classNode.superName).doesNotStartWith(SHADED_PACKAGE_PATH);\n    }\n\n    @Test\n    void testInterfaces() {\n        assertThat(classNode.interfaces).allSatisfy(it -> assertThat(it).doesNotStartWith(SHADED_PACKAGE_PATH));\n    }\n\n    @Test\n    void testMethodReturnTypes() {\n        assertThat(classNode.methods)\n            .filteredOn(it -> (it.access & (Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED)) != 0)\n            .allSatisfy(it -> assertThat(Type.getReturnType(it.desc).getClassName()).doesNotStartWith(SHADED_PACKAGE));\n    }\n\n    @Test\n    void testMethodArguments() {\n        assertThat(classNode.methods)\n            .filteredOn(it -> (it.access & (Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED)) != 0)\n            .allSatisfy(method -> {\n                assertThat(Arrays.asList(Type.getArgumentTypes(method.desc)))\n                    .extracting(Type::getClassName)\n                    .allSatisfy(it -> assertThat(it).doesNotStartWith(SHADED_PACKAGE));\n            });\n    }\n\n    @Test\n    void testFields() {\n        assertThat(classNode.fields)\n            .filteredOn(it -> (it.access & (Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED)) != 0)\n            .allSatisfy(it -> assertThat(Type.getType(it.desc).getClassName()).doesNotStartWith(SHADED_PACKAGE));\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/DelegatingDockerClient.java",
    "content": "package org.testcontainers;\n\nimport com.github.dockerjava.api.DockerClient;\nimport lombok.RequiredArgsConstructor;\nimport lombok.experimental.Delegate;\n\n@RequiredArgsConstructor\nclass DelegatingDockerClient implements DockerClient {\n\n    @Delegate\n    private final DockerClient dockerClient;\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/DockerClientFactory.java",
    "content": "package org.testcontainers;\n\nimport com.github.dockerjava.api.DockerClient;\nimport com.github.dockerjava.api.DockerClientDelegate;\nimport com.github.dockerjava.api.command.CreateContainerCmd;\nimport com.github.dockerjava.api.command.PullImageCmd;\nimport com.github.dockerjava.api.exception.DockerClientException;\nimport com.github.dockerjava.api.exception.InternalServerErrorException;\nimport com.github.dockerjava.api.exception.NotFoundException;\nimport com.github.dockerjava.api.model.AccessMode;\nimport com.github.dockerjava.api.model.Bind;\nimport com.github.dockerjava.api.model.Info;\nimport com.github.dockerjava.api.model.Version;\nimport com.github.dockerjava.api.model.Volume;\nimport com.google.common.annotations.VisibleForTesting;\nimport lombok.Getter;\nimport lombok.SneakyThrows;\nimport lombok.Synchronized;\nimport lombok.extern.slf4j.Slf4j;\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.commons.lang3.SystemUtils;\nimport org.testcontainers.dockerclient.DockerClientProviderStrategy;\nimport org.testcontainers.dockerclient.DockerMachineClientProviderStrategy;\nimport org.testcontainers.dockerclient.TransportConfig;\nimport org.testcontainers.images.RemoteDockerImage;\nimport org.testcontainers.images.TimeLimitedLoggedPullImageResultCallback;\nimport org.testcontainers.utility.ComparableVersion;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.MountableFile;\nimport org.testcontainers.utility.ResourceReaper;\nimport org.testcontainers.utility.TestcontainersConfiguration;\n\nimport java.io.InputStream;\nimport java.net.URI;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.ServiceLoader;\nimport java.util.UUID;\nimport java.util.function.BiFunction;\nimport java.util.function.Consumer;\nimport java.util.stream.Collectors;\n\n/**\n * Singleton class that provides initialized Docker clients.\n * <p>\n * The correct client configuration to use will be determined on first use, and cached thereafter.\n */\n@Slf4j\npublic class DockerClientFactory {\n\n    public static final ThreadGroup TESTCONTAINERS_THREAD_GROUP = new ThreadGroup(\"testcontainers\");\n\n    public static final String TESTCONTAINERS_LABEL = DockerClientFactory.class.getPackage().getName();\n\n    public static final String TESTCONTAINERS_SESSION_ID_LABEL = TESTCONTAINERS_LABEL + \".sessionId\";\n\n    public static final String TESTCONTAINERS_LANG_LABEL = TESTCONTAINERS_LABEL + \".lang\";\n\n    public static final String TESTCONTAINERS_VERSION_LABEL = TESTCONTAINERS_LABEL + \".version\";\n\n    public static final String SESSION_ID = UUID.randomUUID().toString();\n\n    public static final String TESTCONTAINERS_VERSION =\n        DockerClientFactory.class.getPackage().getImplementationVersion();\n\n    public static final Map<String, String> DEFAULT_LABELS = markerLabels();\n\n    static Map<String, String> markerLabels() {\n        String testcontainersVersion = TESTCONTAINERS_VERSION == null ? \"unspecified\" : TESTCONTAINERS_VERSION;\n\n        Map<String, String> labels = new HashMap<>();\n        labels.put(TESTCONTAINERS_LABEL, \"true\");\n        labels.put(TESTCONTAINERS_LANG_LABEL, \"java\");\n        labels.put(TESTCONTAINERS_VERSION_LABEL, testcontainersVersion);\n        return Collections.unmodifiableMap(labels);\n    }\n\n    private static final DockerImageName TINY_IMAGE = DockerImageName.parse(\"alpine:3.17\");\n\n    private static DockerClientFactory instance;\n\n    // Cached client configuration\n    @VisibleForTesting\n    DockerClientProviderStrategy strategy;\n\n    @VisibleForTesting\n    DockerClient client;\n\n    @VisibleForTesting\n    RuntimeException cachedClientFailure;\n\n    private String activeApiVersion;\n\n    @Getter(lazy = true)\n    private final boolean fileMountingSupported = checkMountableFile();\n\n    @VisibleForTesting\n    DockerClientFactory() {}\n\n    public static DockerClient lazyClient() {\n        return new DockerClientDelegate() {\n            @Override\n            protected DockerClient getDockerClient() {\n                return instance().client();\n            }\n\n            @Override\n            public String toString() {\n                return \"LazyDockerClient\";\n            }\n        };\n    }\n\n    /**\n     * Obtain an instance of the DockerClientFactory.\n     *\n     * @return the singleton instance of DockerClientFactory\n     */\n    public static synchronized DockerClientFactory instance() {\n        if (instance == null) {\n            instance = new DockerClientFactory();\n        }\n\n        return instance;\n    }\n\n    /**\n     * Checks whether Docker is accessible and {@link #client()} is able to produce a client.\n     *\n     * @return true if Docker is available, false if not.\n     */\n    public synchronized boolean isDockerAvailable() {\n        try {\n            client();\n            return true;\n        } catch (IllegalStateException ex) {\n            return false;\n        }\n    }\n\n    @Synchronized\n    private DockerClientProviderStrategy getOrInitializeStrategy() {\n        if (strategy != null) {\n            return strategy;\n        }\n        log.info(\"Testcontainers version: {}\", DEFAULT_LABELS.get(TESTCONTAINERS_VERSION_LABEL));\n        List<DockerClientProviderStrategy> configurationStrategies = new ArrayList<>();\n        ServiceLoader.load(DockerClientProviderStrategy.class).forEach(configurationStrategies::add);\n\n        strategy = DockerClientProviderStrategy.getFirstValidStrategy(configurationStrategies);\n        return strategy;\n    }\n\n    @UnstableAPI\n    public TransportConfig getTransportConfig() {\n        return getOrInitializeStrategy().getTransportConfig();\n    }\n\n    @UnstableAPI\n    public String getRemoteDockerUnixSocketPath() {\n        DockerClientProviderStrategy strategy = getOrInitializeStrategy();\n        if (strategy.allowUserOverrides()) {\n            String dockerSocketOverride = System.getenv(\"TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE\");\n            if (!StringUtils.isBlank(dockerSocketOverride)) {\n                return dockerSocketOverride;\n            }\n        }\n        if (strategy.getRemoteDockerUnixSocketPath() != null) {\n            return strategy.getRemoteDockerUnixSocketPath();\n        }\n\n        URI dockerHost = getTransportConfig().getDockerHost();\n        String path = \"unix\".equals(dockerHost.getScheme()) ? dockerHost.getRawPath() : \"/var/run/docker.sock\";\n        return SystemUtils.IS_OS_WINDOWS ? \"/\" + path : path;\n    }\n\n    /**\n     * @return a new initialized Docker client\n     */\n    @Synchronized\n    public DockerClient client() {\n        // fail-fast if checks have failed previously\n        if (cachedClientFailure != null) {\n            log.debug(\"There is a cached checks failure - throwing\", cachedClientFailure);\n            throw cachedClientFailure;\n        }\n\n        if (client != null) {\n            return client;\n        }\n\n        final DockerClientProviderStrategy strategy = getOrInitializeStrategy();\n\n        client =\n            new DockerClientDelegate() {\n                @Getter\n                final DockerClient dockerClient = strategy.getDockerClient();\n\n                @Override\n                public void close() {\n                    throw new IllegalStateException(\"You should never close the global DockerClient!\");\n                }\n            };\n        log.info(\"Docker host IP address is {}\", strategy.getDockerHostIpAddress());\n\n        Info dockerInfo = strategy.getInfo();\n        log.debug(\"Docker info: {}\", dockerInfo.getRawValues());\n        Version version = client.versionCmd().exec();\n        log.debug(\"Docker version: {}\", version.getRawValues());\n        activeApiVersion = version.getApiVersion();\n\n        String serverInfo =\n            \"Connected to docker: \\n\" +\n            \"  Server Version: \" +\n            dockerInfo.getServerVersion() +\n            \"\\n\" +\n            \"  API Version: \" +\n            activeApiVersion +\n            \"\\n\" +\n            \"  Operating System: \" +\n            dockerInfo.getOperatingSystem() +\n            \"\\n\" +\n            \"  Total Memory: \" +\n            dockerInfo.getMemTotal() /\n            (1024 * 1024) +\n            \" MB\";\n\n        String[] labels = dockerInfo.getLabels();\n        boolean hasLabels = labels != null && labels.length > 0;\n        if (hasLabels) {\n            String formattedLabels = Arrays\n                .stream(labels)\n                .map(label -> \"    \" + label)\n                .collect(Collectors.joining(\"\\n\"));\n            serverInfo += \"\\n  Labels: \\n\" + formattedLabels;\n        }\n        log.info(serverInfo);\n\n        try {\n            //noinspection deprecation\n            ResourceReaper.instance().init();\n        } catch (RuntimeException e) {\n            cachedClientFailure = e;\n            throw e;\n        }\n\n        boolean checksEnabled = !TestcontainersConfiguration.getInstance().isDisableChecks();\n        if (checksEnabled) {\n            log.debug(\"Checks are enabled\");\n\n            try {\n                log.info(\"Checking the system...\");\n                checkDockerVersion(version.getVersion());\n            } catch (RuntimeException e) {\n                cachedClientFailure = e;\n                throw e;\n            }\n        } else {\n            log.debug(\"Checks are disabled\");\n        }\n\n        return client;\n    }\n\n    private void checkDockerVersion(String dockerVersion) {\n        boolean versionIsSufficient = new ComparableVersion(dockerVersion).compareTo(new ComparableVersion(\"1.6.0\")) >=\n        0;\n        check(\"Docker server version should be at least 1.6.0\", versionIsSufficient);\n    }\n\n    private void check(String message, boolean isSuccessful) {\n        if (isSuccessful) {\n            log.info(\"\\u2714\\ufe0e {}\", message);\n        } else {\n            log.error(\"\\u274c {}\", message);\n            throw new IllegalStateException(\"Check failed: \" + message);\n        }\n    }\n\n    private boolean checkMountableFile() {\n        DockerClient dockerClient = client();\n\n        MountableFile mountableFile = MountableFile.forClasspathResource(\n            ResourceReaper.class.getName().replace(\".\", \"/\") + \".class\"\n        );\n\n        Volume volume = new Volume(\"/dummy\");\n        try {\n            return runInsideDocker(\n                createContainerCmd -> {\n                    createContainerCmd.withBinds(new Bind(mountableFile.getResolvedPath(), volume, AccessMode.ro));\n                },\n                (__, containerId) -> {\n                    try (\n                        InputStream stream = dockerClient\n                            .copyArchiveFromContainerCmd(containerId, volume.getPath())\n                            .exec()\n                    ) {\n                        stream.read();\n                        return true;\n                    } catch (Exception e) {\n                        return false;\n                    }\n                }\n            );\n        } catch (Exception e) {\n            log.debug(\"Failure while checking for mountable file support\", e);\n            return false;\n        }\n    }\n\n    /**\n     * Check whether the image is available locally and pull it otherwise\n     *\n     * @deprecated use {@link RemoteDockerImage}\n     */\n    @SneakyThrows\n    @Deprecated\n    public void checkAndPullImage(DockerClient client, String image) {\n        try {\n            client.inspectImageCmd(image).exec();\n        } catch (NotFoundException notFoundException) {\n            PullImageCmd pullImageCmd = client.pullImageCmd(image);\n            try {\n                pullImageCmd.exec(new TimeLimitedLoggedPullImageResultCallback(log)).awaitCompletion();\n            } catch (DockerClientException e) {\n                // Try to fallback to x86\n                pullImageCmd\n                    .withPlatform(\"linux/amd64\")\n                    .exec(new TimeLimitedLoggedPullImageResultCallback(log))\n                    .awaitCompletion();\n            }\n        }\n    }\n\n    /**\n     * @return the IP address of the host running Docker\n     */\n    public String dockerHostIpAddress() {\n        return getOrInitializeStrategy().getDockerHostIpAddress();\n    }\n\n    public <T> T runInsideDocker(\n        Consumer<CreateContainerCmd> createContainerCmdConsumer,\n        BiFunction<DockerClient, String, T> block\n    ) {\n        return runInsideDocker(TINY_IMAGE, createContainerCmdConsumer, block);\n    }\n\n    <T> T runInsideDocker(\n        DockerImageName imageName,\n        Consumer<CreateContainerCmd> createContainerCmdConsumer,\n        BiFunction<DockerClient, String, T> block\n    ) {\n        RemoteDockerImage dockerImage = new RemoteDockerImage(imageName);\n        HashMap<String, String> labels = new HashMap<>(DEFAULT_LABELS);\n        labels.putAll(ResourceReaper.instance().getLabels());\n        CreateContainerCmd createContainerCmd = client.createContainerCmd(dockerImage.get()).withLabels(labels);\n        createContainerCmdConsumer.accept(createContainerCmd);\n        String id = createContainerCmd.exec().getId();\n\n        try {\n            client.startContainerCmd(id).exec();\n            return block.apply(client, id);\n        } finally {\n            try {\n                client.removeContainerCmd(id).withRemoveVolumes(true).withForce(true).exec();\n            } catch (NotFoundException | InternalServerErrorException e) {\n                log.debug(\"Swallowed exception while removing container\", e);\n            }\n        }\n    }\n\n    /**\n     * @return the docker API version of the daemon that we have connected to\n     */\n    public String getActiveApiVersion() {\n        client();\n        return activeApiVersion;\n    }\n\n    /**\n     * @return the docker execution driver of the daemon that we have connected to\n     */\n    public String getActiveExecutionDriver() {\n        return getInfo().getExecutionDriver();\n    }\n\n    /**\n     * @param providerStrategyClass a class that extends {@link DockerMachineClientProviderStrategy}\n     * @return whether or not the currently active strategy is of the provided type\n     */\n    public boolean isUsing(Class<? extends DockerClientProviderStrategy> providerStrategyClass) {\n        return strategy != null && providerStrategyClass.isAssignableFrom(this.strategy.getClass());\n    }\n\n    @UnstableAPI\n    public Info getInfo() {\n        return getOrInitializeStrategy().getInfo();\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/Testcontainers.java",
    "content": "package org.testcontainers;\n\nimport lombok.experimental.UtilityClass;\nimport org.testcontainers.containers.PortForwardingContainer;\n\nimport java.util.Map;\nimport java.util.Map.Entry;\n\n@UtilityClass\npublic class Testcontainers {\n\n    public void exposeHostPorts(int... ports) {\n        for (int port : ports) {\n            PortForwardingContainer.INSTANCE.exposeHostPort(port);\n        }\n    }\n\n    public void exposeHostPorts(Map<Integer, Integer> ports) {\n        for (Entry<Integer, Integer> entry : ports.entrySet()) {\n            PortForwardingContainer.INSTANCE.exposeHostPort(entry.getKey(), entry.getValue());\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/UnstableAPI.java",
    "content": "package org.testcontainers;\n\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * Marks that the annotated API is a subject to change and SHOULD NOT be considered\n * a stable API.\n */\n@Retention(RetentionPolicy.SOURCE)\n@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD })\n@Documented\npublic @interface UnstableAPI {\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/BindMode.java",
    "content": "package org.testcontainers.containers;\n\nimport com.github.dockerjava.api.model.AccessMode;\n\n/**\n * Possible modes for binding storage volumes.\n */\npublic enum BindMode {\n    READ_ONLY(AccessMode.ro),\n    READ_WRITE(AccessMode.rw);\n\n    public final AccessMode accessMode;\n\n    BindMode(AccessMode accessMode) {\n        this.accessMode = accessMode;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/ComposeCommand.java",
    "content": "package org.testcontainers.containers;\n\nimport java.util.Set;\n\nclass ComposeCommand {\n\n    static String getDownCommand(ComposeDelegate.ComposeVersion composeVersion, Set<String> options) {\n        String composeOptions = optionsAsString(options);\n        if (composeOptions.isEmpty()) {\n            return composeVersion == ComposeDelegate.ComposeVersion.V1 ? \"down\" : \"compose down\";\n        }\n        String cmd = composeVersion == ComposeDelegate.ComposeVersion.V1 ? \"%s down\" : \"compose %s down\";\n        return String.format(cmd, composeOptions);\n    }\n\n    static String getUpCommand(ComposeDelegate.ComposeVersion composeVersion, Set<String> options) {\n        String composeOptions = optionsAsString(options);\n        if (composeOptions.isEmpty()) {\n            return composeVersion == ComposeDelegate.ComposeVersion.V1 ? \"up -d\" : \"compose up -d\";\n        }\n        String cmd = composeVersion == ComposeDelegate.ComposeVersion.V1 ? \"%s up -d\" : \"compose %s up -d\";\n        return String.format(cmd, composeOptions);\n    }\n\n    private static String optionsAsString(final Set<String> options) {\n        String optionsString = String.join(\" \", options);\n        if (!optionsString.isEmpty()) {\n            // ensures that there is a space between the options and 'up' if options are passed.\n            return optionsString;\n        } else {\n            // otherwise two spaces would appear between 'docker-compose' and 'up'\n            return \"\";\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/ComposeContainer.java",
    "content": "package org.testcontainers.containers;\n\nimport com.github.dockerjava.api.model.Container;\nimport com.google.common.annotations.VisibleForTesting;\nimport lombok.NonNull;\nimport lombok.extern.slf4j.Slf4j;\nimport org.apache.commons.lang3.SystemUtils;\nimport org.testcontainers.containers.output.OutputFrame;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.containers.wait.strategy.WaitStrategy;\nimport org.testcontainers.lifecycle.Startable;\nimport org.testcontainers.utility.Base58;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.File;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.function.Consumer;\n\n/**\n * Testcontainers implementation for Docker Compose V2. <br>\n * It uses either Compose V2 contained within the Docker binary, or a containerised version of Compose V2.\n */\n@Slf4j\npublic class ComposeContainer implements Startable {\n\n    private final Map<String, Integer> scalingPreferences = new HashMap<>();\n\n    private boolean localCompose;\n\n    private boolean pull = true;\n\n    private boolean build = false;\n\n    private Set<String> options = new HashSet<>();\n\n    private boolean tailChildContainers;\n\n    private static final Object MUTEX = new Object();\n\n    private List<String> services = new ArrayList<>();\n\n    /**\n     * Properties that should be passed through to all Compose and ambassador containers (not\n     * necessarily to containers that are spawned by Compose itself)\n     */\n    private Map<String, String> env = new HashMap<>();\n\n    private RemoveImages removeImages;\n\n    private boolean removeVolumes = true;\n\n    public static final String COMPOSE_EXECUTABLE = SystemUtils.IS_OS_WINDOWS ? \"docker.exe\" : \"docker\";\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"docker\");\n\n    private final ComposeDelegate composeDelegate;\n\n    private String project;\n\n    private List<String> filesInDirectory = new ArrayList<>();\n\n    /**\n     * Creates a new ComposeContainer using the specified Docker image and compose files.\n     *\n     * @param image        The Docker image to use for the container\n     * @param composeFiles One or more Docker Compose configuration files\n     */\n    public ComposeContainer(DockerImageName image, File... composeFiles) {\n        this(image, Arrays.asList(composeFiles));\n    }\n\n    /**\n     * Creates a new ComposeContainer using the specified Docker image and compose files.\n     *\n     * @param image        The Docker image to use for the container\n     * @param composeFiles A list of Docker Compose configuration files\n     */\n    public ComposeContainer(DockerImageName image, List<File> composeFiles) {\n        this(image, Base58.randomString(6).toLowerCase(), composeFiles);\n    }\n\n    /**\n     * Creates a new ComposeContainer with the specified Docker image, identifier, and compose files.\n     *\n     * @param image        The Docker image to use for the container\n     * @param identifier   A unique identifier for this compose environment\n     * @param composeFiles One or more Docker Compose configuration files\n     */\n    public ComposeContainer(DockerImageName image, String identifier, File... composeFiles) {\n        this(image, identifier, Arrays.asList(composeFiles));\n    }\n\n    /**\n     * Creates a new ComposeContainer with the specified Docker image, identifier, and a single compose file.\n     *\n     * @param image       The Docker image to use for the container\n     * @param identifier  A unique identifier for this compose environment\n     * @param composeFile A Docker Compose configuration file\n     */\n    public ComposeContainer(DockerImageName image, String identifier, File composeFile) {\n        this(image, identifier, Collections.singletonList(composeFile));\n    }\n\n    /**\n     * Creates a new ComposeContainer with the specified Docker image, identifier, and compose files.\n     *\n     * @param image        The Docker image to use for the container\n     * @param identifier   A unique identifier for this compose environment\n     * @param composeFiles A list of Docker Compose configuration files\n     */\n    public ComposeContainer(DockerImageName image, String identifier, List<File> composeFiles) {\n        image.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n        this.composeDelegate =\n            new ComposeDelegate(ComposeDelegate.ComposeVersion.V2, composeFiles, identifier, COMPOSE_EXECUTABLE, image);\n        this.project = this.composeDelegate.getProject();\n    }\n\n    /**\n     * Use the new constructor {@link #ComposeContainer(DockerImageName image, File... composeFiles)}\n     */\n    public ComposeContainer(File... composeFiles) {\n        this(DEFAULT_IMAGE_NAME, Arrays.asList(composeFiles));\n        this.localCompose = true;\n    }\n\n    /**\n     * Use the new constructor {@link #ComposeContainer(DockerImageName image, List composeFiles)}\n     */\n    public ComposeContainer(List<File> composeFiles) {\n        this(DEFAULT_IMAGE_NAME, composeFiles);\n        this.localCompose = true;\n    }\n\n    /**\n     * Use the new constructor {@link #ComposeContainer(DockerImageName image, String identifier, File... composeFile)}\n     */\n    public ComposeContainer(String identifier, File... composeFiles) {\n        this(DEFAULT_IMAGE_NAME, identifier, Arrays.asList(composeFiles));\n        this.localCompose = true;\n    }\n\n    /**\n     * Use the new constructor {@link #ComposeContainer(DockerImageName image, String identifier, List composeFiles)}\n     */\n    public ComposeContainer(String identifier, List<File> composeFiles) {\n        this(DEFAULT_IMAGE_NAME, identifier, composeFiles);\n        this.localCompose = true;\n    }\n\n    @Override\n    public void start() {\n        synchronized (MUTEX) {\n            this.composeDelegate.registerContainersForShutdown();\n            if (pull) {\n                try {\n                    this.composeDelegate.pullImages();\n                } catch (ContainerLaunchException e) {\n                    log.warn(\"Exception while pulling images, using local images if available\", e);\n                }\n            }\n            this.composeDelegate.createServices(\n                    this.localCompose,\n                    this.build,\n                    this.options,\n                    this.services,\n                    this.scalingPreferences,\n                    this.env,\n                    this.filesInDirectory\n                );\n            this.composeDelegate.startAmbassadorContainer();\n            this.composeDelegate.waitUntilServiceStarted(this.tailChildContainers);\n        }\n    }\n\n    @VisibleForTesting\n    List<Container> listChildContainers() {\n        return this.composeDelegate.listChildContainers();\n    }\n\n    public ComposeContainer withServices(@NonNull String... services) {\n        this.services = Arrays.asList(services);\n        return this;\n    }\n\n    @Override\n    public void stop() {\n        synchronized (MUTEX) {\n            try {\n                this.composeDelegate.getAmbassadorContainer().stop();\n\n                // Kill the services using docker\n                String cmd = ComposeCommand.getDownCommand(ComposeDelegate.ComposeVersion.V2, this.options);\n\n                if (removeVolumes) {\n                    cmd += \" -v\";\n                }\n                if (removeImages != null) {\n                    cmd += \" --rmi \" + removeImages.dockerRemoveImagesType();\n                }\n                this.composeDelegate.runWithCompose(this.localCompose, cmd, this.env, this.filesInDirectory);\n            } finally {\n                this.composeDelegate.clear();\n                this.project = this.composeDelegate.randomProjectId();\n            }\n        }\n    }\n\n    public ComposeContainer withExposedService(String serviceName, int servicePort) {\n        this.composeDelegate.withExposedService(serviceName, servicePort, Wait.defaultWaitStrategy());\n        return this;\n    }\n\n    public ComposeContainer withExposedService(String serviceName, int instance, int servicePort) {\n        return withExposedService(serviceName + \"-\" + instance, servicePort);\n    }\n\n    public ComposeContainer withExposedService(\n        String serviceName,\n        int instance,\n        int servicePort,\n        WaitStrategy waitStrategy\n    ) {\n        this.composeDelegate.withExposedService(serviceName + \"-\" + instance, servicePort, waitStrategy);\n        return this;\n    }\n\n    public ComposeContainer withExposedService(\n        String serviceName,\n        int servicePort,\n        @NonNull WaitStrategy waitStrategy\n    ) {\n        this.composeDelegate.withExposedService(serviceName, servicePort, waitStrategy);\n        return this;\n    }\n\n    /**\n     * Specify the {@link WaitStrategy} to use to determine if the container is ready.\n     *\n     * @param serviceName  the name of the service to wait for\n     * @param waitStrategy the WaitStrategy to use\n     * @return this\n     * @see org.testcontainers.containers.wait.strategy.Wait#defaultWaitStrategy()\n     */\n    public ComposeContainer waitingFor(String serviceName, @NonNull WaitStrategy waitStrategy) {\n        String serviceInstanceName = this.composeDelegate.getServiceInstanceName(serviceName);\n        this.composeDelegate.addWaitStrategy(serviceInstanceName, waitStrategy);\n        return this;\n    }\n\n    /**\n     * Get the host (e.g. IP address or hostname) that an exposed service can be found at, from the host machine\n     * (i.e. should be the machine that's running this Java process).\n     * <p>\n     * The service must have been declared using ComposeContainer#withExposedService.\n     *\n     * @param serviceName the name of the service as set in the docker-compose.yml file.\n     * @param servicePort the port exposed by the service container.\n     * @return a host IP address or hostname that can be used for accessing the service container.\n     */\n    public String getServiceHost(String serviceName, Integer servicePort) {\n        return this.composeDelegate.getServiceHost();\n    }\n\n    /**\n     * Get the port that an exposed service can be found at, from the host machine\n     * (i.e. should be the machine that's running this Java process).\n     * <p>\n     * The service must have been declared using ComposeContainer#withExposedService.\n     *\n     * @param serviceName the name of the service as set in the docker-compose.yml file.\n     * @param servicePort the port exposed by the service container.\n     * @return a port that can be used for accessing the service container.\n     */\n    public Integer getServicePort(String serviceName, Integer servicePort) {\n        return this.composeDelegate.getServicePort(serviceName, servicePort);\n    }\n\n    public ComposeContainer withScaledService(String serviceBaseName, int numInstances) {\n        scalingPreferences.put(serviceBaseName, numInstances);\n        return this;\n    }\n\n    public ComposeContainer withEnv(String key, String value) {\n        env.put(key, value);\n        return this;\n    }\n\n    public ComposeContainer withEnv(Map<String, String> env) {\n        env.forEach(this.env::put);\n        return this;\n    }\n\n    /**\n     * Whether to pull images first.\n     *\n     * @return this instance, for chaining\n     */\n    public ComposeContainer withPull(boolean pull) {\n        this.pull = pull;\n        return this;\n    }\n\n    /**\n     * Whether to tail child container logs.\n     *\n     * @return this instance, for chaining\n     */\n    public ComposeContainer withTailChildContainers(boolean tailChildContainers) {\n        this.tailChildContainers = tailChildContainers;\n        return this;\n    }\n\n    /**\n     * Attach an output consumer at container startup, enabling stdout and stderr to be followed, waited on, etc.\n     * <p>\n     * More than one consumer may be registered.\n     *\n     * @param serviceName the name of the service as set in the docker-compose.yml file\n     * @param consumer    consumer that output frames should be sent to\n     * @return this instance, for chaining\n     */\n    public ComposeContainer withLogConsumer(String serviceName, Consumer<OutputFrame> consumer) {\n        this.composeDelegate.withLogConsumer(serviceName, consumer);\n        return this;\n    }\n\n    /**\n     * Whether to always build images before starting containers.\n     *\n     * @return this instance, for chaining\n     */\n    public ComposeContainer withBuild(boolean build) {\n        this.build = build;\n        return this;\n    }\n\n    /**\n     * Adds options to the docker command, e.g. docker --compatibility.\n     *\n     * @return this instance, for chaining\n     */\n    public ComposeContainer withOptions(String... options) {\n        this.options = new HashSet<>(Arrays.asList(options));\n        return this;\n    }\n\n    /**\n     * Remove images after containers shutdown.\n     *\n     * @return this instance, for chaining\n     */\n    public ComposeContainer withRemoveImages(ComposeContainer.RemoveImages removeImages) {\n        this.removeImages = removeImages;\n        return this;\n    }\n\n    /**\n     * Remove volumes after containers shut down.\n     *\n     * @param removeVolumes whether volumes are to be removed.\n     * @return this instance, for chaining.\n     */\n    public ComposeContainer withRemoveVolumes(boolean removeVolumes) {\n        this.removeVolumes = removeVolumes;\n        return this;\n    }\n\n    /**\n     * Set the maximum startup timeout all the waits set are bounded to.\n     *\n     * @return this instance. for chaining\n     */\n    public ComposeContainer withStartupTimeout(Duration startupTimeout) {\n        this.composeDelegate.setStartupTimeout(startupTimeout);\n        return this;\n    }\n\n    public ComposeContainer withCopyFilesInContainer(String... fileCopyInclusions) {\n        this.filesInDirectory = Arrays.asList(fileCopyInclusions);\n        return this;\n    }\n\n    public Optional<ContainerState> getContainerByServiceName(String serviceName) {\n        return this.composeDelegate.getContainerByServiceName(serviceName);\n    }\n\n    private void followLogs(String containerId, Consumer<OutputFrame> consumer) {\n        this.followLogs(containerId, consumer);\n    }\n\n    public enum RemoveImages {\n        /**\n         * Remove all images used by any service.\n         */\n        ALL(\"all\"),\n\n        /**\n         * Remove only images that don't have a custom tag set by the `image` field.\n         */\n        LOCAL(\"local\");\n\n        private final String dockerRemoveImagesType;\n\n        RemoveImages(final String dockerRemoveImagesType) {\n            this.dockerRemoveImagesType = dockerRemoveImagesType;\n        }\n\n        public String dockerRemoveImagesType() {\n            return dockerRemoveImagesType;\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/ComposeDelegate.java",
    "content": "package org.testcontainers.containers;\n\nimport com.github.dockerjava.api.DockerClient;\nimport com.github.dockerjava.api.model.Container;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Preconditions;\nimport com.google.common.base.Strings;\nimport com.google.common.collect.Sets;\nimport lombok.Getter;\nimport lombok.NonNull;\nimport lombok.Setter;\nimport lombok.extern.slf4j.Slf4j;\nimport org.testcontainers.DockerClientFactory;\nimport org.testcontainers.containers.output.OutputFrame;\nimport org.testcontainers.containers.output.Slf4jLogConsumer;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.containers.wait.strategy.WaitAllStrategy;\nimport org.testcontainers.containers.wait.strategy.WaitStrategy;\nimport org.testcontainers.images.RemoteDockerImage;\nimport org.testcontainers.utility.Base58;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.ImageNameSubstitutor;\nimport org.testcontainers.utility.LogUtils;\nimport org.testcontainers.utility.ResourceReaper;\n\nimport java.io.File;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.function.Consumer;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\n@Slf4j\nclass ComposeDelegate {\n\n    private final ComposeVersion composeVersion;\n\n    private final String composeSeparator;\n\n    private final DockerClient dockerClient;\n\n    private final List<File> composeFiles;\n\n    private final DockerComposeFiles dockerComposeFiles;\n\n    private final String identifier;\n\n    @Getter\n    private final String project;\n\n    private final String executable;\n\n    private final DockerImageName defaultImageName;\n\n    private final AtomicInteger nextAmbassadorPort = new AtomicInteger(2000);\n\n    private final Map<String, Map<Integer, Integer>> ambassadorPortMappings = new ConcurrentHashMap<>();\n\n    private final Map<String, List<Consumer<OutputFrame>>> logConsumers = new ConcurrentHashMap<>();\n\n    @Getter\n    private final SocatContainer ambassadorContainer = new SocatContainer();\n\n    private final Map<String, ComposeServiceWaitStrategyTarget> serviceInstanceMap = new ConcurrentHashMap<>();\n\n    private final Map<String, WaitAllStrategy> waitStrategyMap = new ConcurrentHashMap<>();\n\n    @Setter\n    private Duration startupTimeout = Duration.ofMinutes(30);\n\n    ComposeDelegate(\n        ComposeVersion composeVersion,\n        List<File> composeFiles,\n        String identifier,\n        String executable,\n        DockerImageName defaultImageName\n    ) {\n        this.composeVersion = composeVersion;\n        this.composeSeparator = composeVersion.getSeparator();\n        this.dockerClient = DockerClientFactory.lazyClient();\n        this.composeFiles = composeFiles;\n        this.dockerComposeFiles = new DockerComposeFiles(this.composeFiles);\n        this.identifier = identifier.toLowerCase();\n        this.project = randomProjectId();\n        this.executable = executable;\n        this.defaultImageName = defaultImageName;\n    }\n\n    void pullImages() {\n        // Pull images using our docker client rather than compose itself,\n        // (a) as a workaround for https://github.com/docker/compose/issues/5854, which prevents authenticated image pulls being possible when credential helpers are in use\n        // (b) so that credential helper-based auth still works when compose is running from within a container\n        this.dockerComposeFiles.getDependencyImages()\n            .forEach(imageName -> {\n                try {\n                    log.info(\n                        \"Preemptively checking local images for '{}', referenced via a compose file or transitive Dockerfile. If not available, it will be pulled.\",\n                        imageName\n                    );\n                    new RemoteDockerImage(DockerImageName.parse(imageName))\n                        .withImageNameSubstitutor(ImageNameSubstitutor.noop())\n                        .get();\n                } catch (Exception e) {\n                    log.warn(\n                        \"Unable to pre-fetch an image ({}) depended upon by Docker Compose build - startup will continue but may fail. Exception message was: {}\",\n                        imageName,\n                        e.getMessage()\n                    );\n                }\n            });\n    }\n\n    void createServices(\n        boolean localCompose,\n        boolean build,\n        final Set<String> options,\n        final List<String> services,\n        final Map<String, Integer> scalingPreferences,\n        Map<String, String> env,\n        List<String> fileCopyInclusions\n    ) {\n        // services that have been explicitly requested to be started. If empty, all services should be started.\n        final String serviceNameArgs = Stream\n            .concat(\n                services.stream(), // services that have been specified with `withServices`\n                scalingPreferences.keySet().stream() // services that are implicitly needed via `withScaledService`\n            )\n            .distinct()\n            .collect(Collectors.joining(\" \"));\n\n        // Apply scaling for the services specified using `withScaledService`\n        final String scalingOptions = scalingPreferences\n            .entrySet()\n            .stream()\n            .map(entry -> \"--scale \" + entry.getKey() + \"=\" + entry.getValue())\n            .distinct()\n            .collect(Collectors.joining(\" \"));\n\n        String command = ComposeCommand.getUpCommand(this.composeVersion, options);\n\n        if (build) {\n            command += \" --build\";\n        }\n\n        if (!Strings.isNullOrEmpty(scalingOptions)) {\n            command += \" \" + scalingOptions;\n        }\n\n        if (!Strings.isNullOrEmpty(serviceNameArgs)) {\n            command += \" \" + serviceNameArgs;\n        }\n\n        // Run the docker compose container, which starts up the services\n        runWithCompose(localCompose, command, env, fileCopyInclusions);\n    }\n\n    void waitUntilServiceStarted(boolean tailChildContainers) {\n        listChildContainers().forEach(container -> createServiceInstance(container, tailChildContainers));\n\n        Set<String> servicesToWaitFor = waitStrategyMap.keySet();\n        Set<String> instantiatedServices = serviceInstanceMap.keySet();\n        Sets.SetView<String> missingServiceInstances = Sets.difference(servicesToWaitFor, instantiatedServices);\n\n        if (!missingServiceInstances.isEmpty()) {\n            throw new IllegalStateException(\n                \"Services named \" +\n                missingServiceInstances +\n                \" \" +\n                \"do not exist, but wait conditions have been defined \" +\n                \"for them. This might mean that you misspelled \" +\n                \"the service name when defining the wait condition.\"\n            );\n        }\n\n        serviceInstanceMap.forEach(this::waitUntilServiceStarted);\n    }\n\n    private void createServiceInstance(Container container, boolean tailChildContainers) {\n        String serviceName = getServiceNameFromContainer(container);\n        final ComposeServiceWaitStrategyTarget containerInstance = new ComposeServiceWaitStrategyTarget(\n            dockerClient,\n            container,\n            ambassadorContainer,\n            ambassadorPortMappings.getOrDefault(serviceName, new HashMap<>())\n        );\n\n        String containerId = containerInstance.getContainerId();\n        if (tailChildContainers) {\n            followLogs(containerId, new Slf4jLogConsumer(log).withPrefix(container.getNames()[0]));\n        }\n        //follow logs using registered consumers for this service\n        logConsumers\n            .getOrDefault(serviceName, Collections.emptyList())\n            .forEach(consumer -> followLogs(containerId, consumer));\n        serviceInstanceMap.putIfAbsent(serviceName, containerInstance);\n    }\n\n    private void waitUntilServiceStarted(String serviceName, ComposeServiceWaitStrategyTarget serviceInstance) {\n        final WaitAllStrategy waitAllStrategy = waitStrategyMap.get(serviceName);\n        if (waitAllStrategy != null) {\n            waitAllStrategy.waitUntilReady(serviceInstance);\n        }\n    }\n\n    private String getServiceNameFromContainer(com.github.dockerjava.api.model.Container container) {\n        final String containerName = container.getLabels().get(\"com.docker.compose.service\");\n        final String containerNumber = container.getLabels().get(\"com.docker.compose.container-number\");\n        return String.format(\"%s%s%s\", containerName, this.composeSeparator, containerNumber);\n    }\n\n    public void runWithCompose(boolean localCompose, String cmd) {\n        runWithCompose(localCompose, cmd, Collections.emptyMap(), Collections.emptyList());\n    }\n\n    public void runWithCompose(\n        boolean localCompose,\n        String cmd,\n        Map<String, String> env,\n        List<String> fileCopyInclusions\n    ) {\n        Preconditions.checkNotNull(composeFiles);\n        Preconditions.checkArgument(!composeFiles.isEmpty(), \"No docker compose file have been provided\");\n\n        final DockerCompose dockerCompose;\n        if (localCompose) {\n            dockerCompose = new LocalDockerCompose(this.executable, composeFiles, project);\n        } else {\n            dockerCompose =\n                new ContainerisedDockerCompose(this.defaultImageName, composeFiles, project, fileCopyInclusions);\n        }\n\n        dockerCompose.withCommand(cmd).withEnv(env).invoke();\n    }\n\n    void registerContainersForShutdown() {\n        ResourceReaper\n            .instance()\n            .registerLabelsFilterForCleanup(Collections.singletonMap(\"com.docker.compose.project\", project));\n    }\n\n    @VisibleForTesting\n    List<Container> listChildContainers() {\n        return dockerClient\n            .listContainersCmd()\n            .withShowAll(true)\n            .exec()\n            .stream()\n            .filter(container -> Arrays.stream(container.getNames()).anyMatch(name -> name.startsWith(\"/\" + project)))\n            .collect(Collectors.toList());\n    }\n\n    void startAmbassadorContainer() {\n        if (!this.ambassadorPortMappings.isEmpty()) {\n            this.ambassadorContainer.start();\n        }\n    }\n\n    public void withExposedService(String serviceName, int servicePort) {\n        withExposedService(serviceName, servicePort, Wait.defaultWaitStrategy());\n    }\n\n    public void withExposedService(String serviceName, int instance, int servicePort) {\n        withExposedService(serviceName + this.composeSeparator + instance, servicePort);\n    }\n\n    public void withExposedService(String serviceName, int instance, int servicePort, WaitStrategy waitStrategy) {\n        withExposedService(serviceName + this.composeSeparator + instance, servicePort, waitStrategy);\n    }\n\n    public void withExposedService(String serviceName, int servicePort, @NonNull WaitStrategy waitStrategy) {\n        String serviceInstanceName = getServiceInstanceName(serviceName);\n\n        /*\n         * For every service/port pair that needs to be exposed, we register a target on an 'ambassador container'.\n         *\n         * The ambassador container's role is to link (within the Docker network) to one of the\n         * compose services, and proxy TCP network I/O out to a port that the ambassador container\n         * exposes.\n         *\n         * This avoids the need for the docker compose file to explicitly expose ports on all the\n         * services.\n         *\n         * {@link GenericContainer} should ensure that the ambassador container is on the same network\n         * as the rest of the compose environment.\n         */\n\n        // Ambassador container will be started together after docker compose has started\n        int ambassadorPort = nextAmbassadorPort.getAndIncrement();\n        ambassadorPortMappings\n            .computeIfAbsent(serviceInstanceName, __ -> new ConcurrentHashMap<>())\n            .put(servicePort, ambassadorPort);\n        ambassadorContainer.withTarget(ambassadorPort, serviceInstanceName, servicePort);\n        ambassadorContainer.addLink(\n            new FutureContainer(this.project + this.composeSeparator + serviceInstanceName),\n            serviceInstanceName\n        );\n        addWaitStrategy(serviceInstanceName, waitStrategy);\n    }\n\n    String getServiceInstanceName(String serviceName) {\n        String serviceInstanceName = serviceName;\n        String regex = String.format(\".*%s[0-9]+\", this.composeSeparator);\n        if (!serviceInstanceName.matches(regex)) {\n            serviceInstanceName += String.format(\"%s1\", this.composeSeparator); // implicit first instance of this service\n        }\n        return serviceInstanceName;\n    }\n\n    /*\n     * can have multiple wait strategies for a single container, e.g. if waiting on several ports\n     * if no wait strategy is defined, the WaitAllStrategy will return immediately.\n     * The WaitAllStrategy uses the startup timeout for everything as a global maximum, but we expect timeouts to be handled by the inner strategies.\n     */\n    void addWaitStrategy(String serviceInstanceName, @NonNull WaitStrategy waitStrategy) {\n        final WaitAllStrategy waitAllStrategy = waitStrategyMap.computeIfAbsent(\n            serviceInstanceName,\n            __ -> {\n                return new WaitAllStrategy(WaitAllStrategy.Mode.WITH_MAXIMUM_OUTER_TIMEOUT)\n                    .withStartupTimeout(startupTimeout);\n            }\n        );\n        waitAllStrategy.withStrategy(waitStrategy);\n    }\n\n    /**\n     * Get the port that an exposed service can be found at, from the host machine\n     * (i.e. should be the machine that's running this Java process).\n     * <p>\n     * The service must have been declared using DockerComposeContainer#withExposedService.\n     *\n     * @param serviceName the name of the service as set in the docker-compose.yml file.\n     * @param servicePort the port exposed by the service container.\n     * @return a port that can be used for accessing the service container.\n     */\n    public Integer getServicePort(String serviceName, Integer servicePort) {\n        Map<Integer, Integer> portMap = this.ambassadorPortMappings.get(getServiceInstanceName(serviceName));\n\n        if (portMap == null) {\n            throw new IllegalArgumentException(\n                \"Could not get a port for '\" +\n                serviceName +\n                \"'. \" +\n                \"Testcontainers does not have an exposed port configured for '\" +\n                serviceName +\n                \"'. \" +\n                \"To fix, please ensure that the service '\" +\n                serviceName +\n                \"' has ports exposed using .withExposedService(...)\"\n            );\n        } else {\n            return ambassadorContainer.getMappedPort(portMap.get(servicePort));\n        }\n    }\n\n    Optional<ContainerState> getContainerByServiceName(String serviceName) {\n        String serviceInstantName = getServiceInstanceName(serviceName);\n        return Optional.ofNullable(serviceInstanceMap.get(serviceInstantName));\n    }\n\n    private void followLogs(String containerId, Consumer<OutputFrame> consumer) {\n        LogUtils.followOutput(dockerClient, containerId, consumer);\n    }\n\n    String randomProjectId() {\n        return this.identifier + Base58.randomString(6).toLowerCase();\n    }\n\n    void withLogConsumer(String serviceName, Consumer<OutputFrame> consumer) {\n        String serviceInstanceName = getServiceInstanceName(serviceName);\n        final List<Consumer<OutputFrame>> consumers =\n            this.logConsumers.getOrDefault(serviceInstanceName, new ArrayList<>());\n        consumers.add(consumer);\n        this.logConsumers.putIfAbsent(serviceInstanceName, consumers);\n    }\n\n    String getServiceHost() {\n        return this.ambassadorContainer.getHost();\n    }\n\n    void clear() {\n        this.logConsumers.clear();\n        this.ambassadorPortMappings.clear();\n        this.serviceInstanceMap.clear();\n        this.waitStrategyMap.clear();\n    }\n\n    enum ComposeVersion {\n        V1(\"_\"),\n\n        V2(\"-\");\n\n        private final String separator;\n\n        ComposeVersion(String separator) {\n            this.separator = separator;\n        }\n\n        public String getSeparator() {\n            return this.separator;\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/ComposeServiceWaitStrategyTarget.java",
    "content": "package org.testcontainers.containers;\n\nimport com.github.dockerjava.api.DockerClient;\nimport com.github.dockerjava.api.command.InspectContainerResponse;\nimport com.github.dockerjava.api.model.Container;\nimport lombok.EqualsAndHashCode;\nimport lombok.Getter;\nimport lombok.NonNull;\nimport org.testcontainers.containers.wait.strategy.WaitStrategyTarget;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * Class to provide a wait strategy target for services started through docker-compose\n */\n@EqualsAndHashCode\nclass ComposeServiceWaitStrategyTarget implements WaitStrategyTarget {\n\n    private final Container container;\n\n    private final GenericContainer proxyContainer;\n\n    private final DockerClient dockerClient;\n\n    @NonNull\n    private Map<Integer, Integer> mappedPorts;\n\n    @Getter(lazy = true)\n    private final InspectContainerResponse containerInfo = dockerClient.inspectContainerCmd(getContainerId()).exec();\n\n    ComposeServiceWaitStrategyTarget(\n        DockerClient dockerClient,\n        Container container,\n        GenericContainer proxyContainer,\n        @NonNull Map<Integer, Integer> mappedPorts\n    ) {\n        this.dockerClient = dockerClient;\n        this.container = container;\n        this.proxyContainer = proxyContainer;\n        this.mappedPorts = new HashMap<>(mappedPorts);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public List<Integer> getExposedPorts() {\n        return new ArrayList<>(this.mappedPorts.keySet());\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public Integer getMappedPort(int originalPort) {\n        return this.proxyContainer.getMappedPort(this.mappedPorts.get(originalPort));\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public String getHost() {\n        return proxyContainer.getHost();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public String getContainerId() {\n        return this.container.getId();\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/Container.java",
    "content": "package org.testcontainers.containers;\n\nimport com.github.dockerjava.api.model.Bind;\nimport lombok.AccessLevel;\nimport lombok.AllArgsConstructor;\nimport lombok.NonNull;\nimport lombok.Value;\nimport org.testcontainers.containers.output.OutputFrame;\nimport org.testcontainers.containers.startupcheck.StartupCheckStrategy;\nimport org.testcontainers.containers.traits.LinkableContainer;\nimport org.testcontainers.containers.wait.strategy.WaitStrategy;\nimport org.testcontainers.images.ImagePullPolicy;\nimport org.testcontainers.images.builder.Transferable;\nimport org.testcontainers.utility.LogUtils;\nimport org.testcontainers.utility.MountableFile;\n\nimport java.time.Duration;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.concurrent.Future;\nimport java.util.function.Consumer;\nimport java.util.function.Function;\n\npublic interface Container<SELF extends Container<SELF>> extends LinkableContainer, ContainerState {\n    /**\n     * @return a reference to this container instance, cast to the expected generic type.\n     */\n    @SuppressWarnings(\"unchecked\")\n    default SELF self() {\n        return (SELF) this;\n    }\n\n    /**\n     * Class to hold results from a \"docker exec\" command\n     */\n    @Value\n    @AllArgsConstructor(access = AccessLevel.MODULE)\n    class ExecResult {\n\n        int exitCode;\n\n        String stdout;\n\n        String stderr;\n    }\n\n    /**\n     * Set the command that should be run in the container. Consider using {@link #withCommand(String)}\n     * for building a container in a fluent style.\n     *\n     * @param command a command in single string format (will automatically be split on spaces)\n     */\n    void setCommand(@NonNull String command);\n\n    /**\n     * Set the command that should be run in the container. Consider using {@link #withCommand(String...)}\n     * for building a container in a fluent style.\n     *\n     * @param commandParts a command as an array of string parts\n     */\n    void setCommand(@NonNull String... commandParts);\n\n    /**\n     * Add an environment variable to be passed to the container. Consider using {@link #withEnv(String, String)}\n     * for building a container in a fluent style.\n     *\n     * @param key   environment variable key\n     * @param value environment variable value\n     */\n    void addEnv(String key, String value);\n\n    /**\n     * Adds a file system binding. Consider using {@link #withFileSystemBind(String, String, BindMode)}\n     * for building a container in a fluent style.\n     *\n     * @param hostPath      the file system path on the host\n     * @param containerPath the file system path inside the container\n     * @param mode          the bind mode\n     * @deprecated use {@link GenericContainer#withCopyToContainer(Transferable, String)}\n     */\n    @Deprecated\n    default void addFileSystemBind(final String hostPath, final String containerPath, final BindMode mode) {\n        addFileSystemBind(hostPath, containerPath, mode, SelinuxContext.SHARED);\n    }\n\n    /**\n     * Adds a file system binding. Consider using {@link #withFileSystemBind(String, String, BindMode)}\n     * for building a container in a fluent style.\n     *\n     * @param hostPath      the file system path on the host\n     * @param containerPath the file system path inside the container\n     * @param mode          the bind mode\n     * @param selinuxContext selinux context argument to use for this file\n     * @deprecated use {@link GenericContainer#withCopyToContainer(Transferable, String)}\n     */\n    @Deprecated\n    void addFileSystemBind(String hostPath, String containerPath, BindMode mode, SelinuxContext selinuxContext);\n\n    /**\n     * Add a link to another container.\n     *\n     * @param otherContainer the other container object to link to\n     * @param alias the alias (for the other container) that this container should be able to use\n     * @deprecated Links are deprecated (see <a href=\"https://github.com/testcontainers/testcontainers-java/issues/465\">#465</a>). Please use {@link Network} features instead.\n     */\n    @Deprecated\n    void addLink(LinkableContainer otherContainer, String alias);\n\n    /**\n     * Add an exposed port. Consider using {@link #withExposedPorts(Integer...)}\n     * for building a container in a fluent style.\n     *\n     * @param port a TCP port\n     */\n    void addExposedPort(Integer port);\n\n    /**\n     * Add exposed ports. Consider using {@link #withExposedPorts(Integer...)}\n     * for building a container in a fluent style.\n     *\n     * @param ports an array of TCP ports\n     */\n    void addExposedPorts(int... ports);\n\n    /**\n     * Specify the {@link WaitStrategy} to use to determine if the container is ready.\n     *\n     * @see org.testcontainers.containers.wait.strategy.Wait#defaultWaitStrategy()\n     * @param waitStrategy the WaitStrategy to use\n     * @return this\n     */\n    SELF waitingFor(@NonNull WaitStrategy waitStrategy);\n\n    /**\n     * Adds a file system binding.\n     *\n     * @param hostPath the file system path on the host\n     * @param containerPath the file system path inside the container\n     * @return this\n     * @deprecated use {@link GenericContainer#withCopyToContainer(Transferable, String)}\n     */\n    @Deprecated\n    default SELF withFileSystemBind(String hostPath, String containerPath) {\n        return withFileSystemBind(hostPath, containerPath, BindMode.READ_WRITE);\n    }\n\n    /**\n     * Adds a file system binding.\n     *\n     * @param hostPath the file system path on the host\n     * @param containerPath the file system path inside the container\n     * @param mode the bind mode\n     * @return this\n     * @deprecated use {@link GenericContainer#withCopyToContainer(Transferable, String)}\n     */\n    @Deprecated\n    SELF withFileSystemBind(String hostPath, String containerPath, BindMode mode);\n\n    /**\n     * Adds container volumes.\n     *\n     * @param container the container to add volumes from\n     * @param mode the bind mode\n     * @return this\n     */\n    SELF withVolumesFrom(Container container, BindMode mode);\n\n    /**\n     * Set the ports that this container listens on\n     *\n     * @param ports an array of TCP ports\n     * @return this\n     */\n    SELF withExposedPorts(Integer... ports);\n\n    /**\n     * Set the file to be copied before starting a created container\n     *\n     * @param mountableFile a Mountable file with path of source file / folder on host machine\n     * @param containerPath a destination path on container to which the files / folders to be copied\n     * @return this\n     *\n     * @deprecated Use {@link #withCopyToContainer(Transferable, String)} instead\n     */\n    @Deprecated\n    SELF withCopyFileToContainer(MountableFile mountableFile, String containerPath);\n\n    /**\n     * Set the content to be copied before starting a created container\n     *\n     * @param transferable a Transferable\n     * @param containerPath a destination path on container to which the files / folders to be copied\n     * @return this\n     */\n    SELF withCopyToContainer(Transferable transferable, String containerPath);\n\n    /**\n     * Add an environment variable to be passed to the container.\n     *\n     * @param key   environment variable key\n     * @param value environment variable value\n     * @return this\n     */\n    SELF withEnv(String key, String value);\n\n    /**\n     * Add an environment variable to be passed to the container.\n     *\n     * @param key   environment variable key\n     * @param mapper environment variable value mapper, accepts old value as an argument\n     * @return this\n     */\n    default SELF withEnv(String key, Function<Optional<String>, String> mapper) {\n        Optional<String> oldValue = Optional.ofNullable(getEnvMap().get(key));\n        return withEnv(key, mapper.apply(oldValue));\n    }\n\n    /**\n     * Add environment variables to be passed to the container.\n     *\n     * @param env map of environment variables\n     * @return this\n     */\n    SELF withEnv(Map<String, String> env);\n\n    /**\n     * Add a label to the container.\n     *\n     * @param key   label key\n     * @param value label value\n     * @return this\n     */\n    SELF withLabel(String key, String value);\n\n    /**\n     * Add labels to the container.\n     * @param labels map of labels\n     * @return this\n     */\n    SELF withLabels(Map<String, String> labels);\n\n    /**\n     * Set the command that should be run in the container\n     *\n     * @param cmd a command in single string format (will automatically be split on spaces)\n     * @return this\n     */\n    SELF withCommand(String cmd);\n\n    /**\n     * Set the command that should be run in the container\n     *\n     * @param commandParts a command as an array of string parts\n     * @return this\n     */\n    SELF withCommand(String... commandParts);\n\n    /**\n     * Add an extra host entry to be passed to the container\n     * @param hostname hostname to use for this hosts file entry\n     * @param ipAddress IP address to use for this hosts file entry\n     * @return this\n     */\n    SELF withExtraHost(String hostname, String ipAddress);\n\n    /**\n     * Set the network mode for this container, similar to the <code>--net &lt;name&gt;</code>\n     * option on the docker CLI.\n     *\n     * @param networkMode network mode, e.g. including 'host', 'bridge', 'none' or the name of an existing named network.\n     * @return this\n     */\n    SELF withNetworkMode(String networkMode);\n\n    /**\n     * Set the network for this container, similar to the <code>--network &lt;name&gt;</code>\n     * option on the docker CLI.\n     *\n     * @param network the instance of {@link Network}\n     * @return this\n     */\n    SELF withNetwork(Network network);\n\n    /**\n     * Set the network aliases for this container, similar to the <code>--network-alias &lt;my-service&gt;</code>\n     * option on the docker CLI.\n     *\n     * @param aliases the list of aliases\n     * @return this\n     */\n    SELF withNetworkAliases(String... aliases);\n\n    /**\n     * Set the image pull policy of the container\n     * @return\n     */\n    SELF withImagePullPolicy(ImagePullPolicy policy);\n\n    /**\n     * Map a resource (file or directory) on the classpath to a path inside the container.\n     * This will only work if you are running your tests outside a Docker container.\n     *\n     * @param resourcePath  path to the resource on the classpath (relative to the classpath root; should not start with a leading slash)\n     * @param containerPath path this should be mapped to inside the container\n     * @param mode          access mode for the file\n     * @return this\n     * @deprecated use {@link GenericContainer#withCopyToContainer(Transferable, String)}\n     */\n    @Deprecated\n    default SELF withClasspathResourceMapping(\n        final String resourcePath,\n        final String containerPath,\n        final BindMode mode\n    ) {\n        withClasspathResourceMapping(resourcePath, containerPath, mode, SelinuxContext.SHARED);\n        return self();\n    }\n\n    /**\n     * Map a resource (file or directory) on the classpath to a path inside the container.\n     * This will only work if you are running your tests outside a Docker container.\n     *\n     * @param resourcePath   path to the resource on the classpath (relative to the classpath root; should not start with a leading slash)\n     * @param containerPath  path this should be mapped to inside the container\n     * @param mode           access mode for the file\n     * @param selinuxContext selinux context argument to use for this file\n     * @return this\n     * @deprecated use {@link GenericContainer#withCopyToContainer(Transferable, String)}\n     */\n    @Deprecated\n    SELF withClasspathResourceMapping(\n        String resourcePath,\n        String containerPath,\n        BindMode mode,\n        SelinuxContext selinuxContext\n    );\n\n    /**\n     * Set the duration of waiting time until container treated as started.\n     * @see WaitStrategy#waitUntilReady(org.testcontainers.containers.wait.strategy.WaitStrategyTarget)\n     *\n     * @param startupTimeout timeout\n     * @return this\n     */\n    SELF withStartupTimeout(Duration startupTimeout);\n\n    /**\n     * Set the privilegedMode mode for the container\n     * @param mode boolean\n     * @return this\n     */\n    SELF withPrivilegedMode(boolean mode);\n\n    /**\n     * Only consider a container to have successfully started if it has been running for this duration. The default\n     * value is null; if that's the value, ignore this check.\n     *\n     * @param minimumRunningDuration duration this container should run for if started successfully\n     * @return this\n     */\n    SELF withMinimumRunningDuration(Duration minimumRunningDuration);\n\n    /**\n     * Set the startup check strategy used for checking whether the container has started.\n     *\n     * @param strategy startup check strategy\n     */\n    SELF withStartupCheckStrategy(StartupCheckStrategy strategy);\n\n    /**\n     * Set the working directory that the container should use on startup.\n     *\n     * @param workDir path to the working directory inside the container\n     */\n    SELF withWorkingDirectory(String workDir);\n\n    /**\n     * <b>Resolve</b> Docker image and set it.\n     *\n     * @param dockerImageName image name\n     */\n    void setDockerImageName(@NonNull String dockerImageName);\n\n    /**\n     * Get image name.\n     *\n     * @return image name\n     */\n    @NonNull\n    String getDockerImageName();\n\n    /**\n     * Get the IP address that containers (e.g. browsers) can use to reference a service running on the local machine,\n     * i.e. the machine on which this test is running.\n     * <p>\n     * For example, if a web server is running on port 8080 on this local machine, the containerized web driver needs\n     * to be pointed at \"http://\" + getTestHostIpAddress() + \":8080\" in order to access it. Trying to hit localhost\n     * from inside the container is not going to work, since the container has its own IP address.\n     *\n     * @return the IP address of the host machine\n     * @deprecated use {@link org.testcontainers.Testcontainers#exposeHostPorts(int...)}\n     */\n    @Deprecated\n    String getTestHostIpAddress();\n\n    /**\n     * Follow container output, sending each frame (usually, line) to a consumer. Stdout and stderr will be followed.\n     *\n     * @param consumer consumer that the frames should be sent to\n     */\n    default void followOutput(Consumer<OutputFrame> consumer) {\n        LogUtils.followOutput(getDockerClient(), getContainerId(), consumer);\n    }\n\n    /**\n     * Follow container output, sending each frame (usually, line) to a consumer. This method allows Stdout and/or stderr\n     * to be selected.\n     *\n     * @param consumer consumer that the frames should be sent to\n     * @param types    types that should be followed (one or both of STDOUT, STDERR)\n     */\n    default void followOutput(Consumer<OutputFrame> consumer, OutputFrame.OutputType... types) {\n        LogUtils.followOutput(getDockerClient(), getContainerId(), consumer, types);\n    }\n\n    /**\n     * Attach an output consumer at container startup, enabling stdout and stderr to be followed, waited on, etc.\n     * <p>\n     * More than one consumer may be registered.\n     *\n     * @param consumer consumer that output frames should be sent to\n     * @return this\n     */\n    SELF withLogConsumer(Consumer<OutputFrame> consumer);\n\n    List<String> getPortBindings();\n\n    List<String> getExtraHosts();\n\n    Future<String> getImage();\n\n    /**\n     *\n     * @deprecated use getEnvMap\n     */\n    @Deprecated\n    List<String> getEnv();\n\n    Map<String, String> getEnvMap();\n\n    String[] getCommandParts();\n\n    List<Bind> getBinds();\n\n    /**\n     * @deprecated Links are deprecated (see <a href=\"https://github.com/testcontainers/testcontainers-java/issues/465\">#465</a>). Please use {@link Network} features instead.\n     */\n    @Deprecated\n    Map<String, LinkableContainer> getLinkedContainers();\n\n    void setExposedPorts(List<Integer> exposedPorts);\n\n    void setPortBindings(List<String> portBindings);\n\n    void setExtraHosts(List<String> extraHosts);\n\n    void setImage(Future<String> image);\n\n    void setEnv(List<String> env);\n\n    void setCommandParts(String[] commandParts);\n\n    void setBinds(List<Bind> binds);\n\n    /**\n     * @deprecated Links are deprecated (see <a href=\"https://github.com/testcontainers/testcontainers-java/issues/465\">#465</a>). Please use {@link Network} features instead.\n     */\n    @Deprecated\n    void setLinkedContainers(Map<String, LinkableContainer> linkedContainers);\n\n    void setWaitStrategy(WaitStrategy waitStrategy);\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/ContainerDef.java",
    "content": "package org.testcontainers.containers;\n\nimport com.github.dockerjava.api.command.CreateContainerCmd;\nimport com.github.dockerjava.api.model.Bind;\nimport com.github.dockerjava.api.model.ExposedPort;\nimport com.github.dockerjava.api.model.HostConfig;\nimport com.github.dockerjava.api.model.InternetProtocol;\nimport com.github.dockerjava.api.model.PortBinding;\nimport com.github.dockerjava.api.model.Ports;\nimport lombok.Getter;\nimport lombok.extern.slf4j.Slf4j;\nimport org.testcontainers.DockerClientFactory;\nimport org.testcontainers.UnstableAPI;\nimport org.testcontainers.containers.wait.strategy.WaitStrategy;\nimport org.testcontainers.images.RemoteDockerImage;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.TestcontainersConfiguration;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.LinkedHashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\n@UnstableAPI\n@Slf4j\nclass ContainerDef {\n\n    @Getter\n    private RemoteDockerImage image;\n\n    Set<ExposedPort> exposedPorts = new LinkedHashSet<>();\n\n    Set<PortBinding> portBindings = new HashSet<>();\n\n    Map<String, String> labels = new HashMap<>();\n\n    Map<String, String> envVars = new HashMap<>();\n\n    private String[] entrypoint;\n\n    private String[] command = new String[0];\n\n    @Getter\n    private Network network;\n\n    Set<String> networkAliases = new LinkedHashSet<>();\n\n    @Getter\n    private String networkMode;\n\n    List<Bind> binds = new ArrayList<>();\n\n    @Getter\n    private boolean privilegedMode;\n\n    @Getter\n    private WaitStrategy waitStrategy = GenericContainer.DEFAULT_WAIT_STRATEGY;\n\n    public ContainerDef() {}\n\n    protected void applyTo(CreateContainerCmd createCommand) {\n        HostConfig hostConfig = createCommand.getHostConfig();\n        if (hostConfig == null) {\n            hostConfig = new HostConfig();\n            createCommand.withHostConfig(hostConfig);\n        }\n        // PortBindings must contain:\n        //  * all exposed ports with a randomized host port (equivalent to -p CONTAINER_PORT)\n        //  * all exposed ports with a fixed host port (equivalent to -p HOST_PORT:CONTAINER_PORT)\n        Map<ExposedPort, PortBinding> allPortBindings = new HashMap<>();\n        // First, collect all the randomized host ports from our 'exposedPorts' field\n        for (ExposedPort exposedPort : this.exposedPorts) {\n            allPortBindings.put(exposedPort, new PortBinding(Ports.Binding.empty(), exposedPort));\n        }\n        // Next, collect all the fixed host ports from our 'portBindings' field, overwriting any randomized ports so that\n        // we don't create two bindings for the same container port.\n        for (PortBinding portBinding : this.portBindings) {\n            allPortBindings.put(portBinding.getExposedPort(), portBinding);\n        }\n        hostConfig.withPortBindings(new ArrayList<>(allPortBindings.values()));\n\n        // Next, ExposedPorts must be set up to publish all of the above ports, both randomized and fixed.\n        createCommand.withExposedPorts(new ArrayList<>(allPortBindings.keySet()));\n\n        createCommand.withEnv(\n            this.envVars.entrySet()\n                .stream()\n                .filter(it -> it.getValue() != null)\n                .map(it -> it.getKey() + \"=\" + it.getValue())\n                .toArray(String[]::new)\n        );\n\n        if (this.entrypoint != null) {\n            createCommand.withEntrypoint(this.entrypoint);\n        }\n\n        if (this.command != null) {\n            createCommand.withCmd(this.command);\n        }\n\n        if (this.network != null) {\n            hostConfig.withNetworkMode(this.network.getId());\n            createCommand.withAliases(this.networkAliases.toArray(new String[0]));\n        } else {\n            if (this.networkMode != null) {\n                createCommand.getHostConfig().withNetworkMode(this.networkMode);\n            }\n        }\n\n        boolean shouldCheckFileMountingSupport =\n            this.binds.size() > 0 && !TestcontainersConfiguration.getInstance().isDisableChecks();\n        if (shouldCheckFileMountingSupport) {\n            if (!DockerClientFactory.instance().isFileMountingSupported()) {\n                log.warn(\n                    \"Unable to mount a file from test host into a running container. \" +\n                    \"This may be a misconfiguration or limitation of your Docker environment. \" +\n                    \"Some features might not work.\"\n                );\n            }\n        }\n\n        hostConfig.withBinds(this.binds.toArray(new Bind[0]));\n\n        if (this.privilegedMode) {\n            createCommand.getHostConfig().withPrivileged(this.privilegedMode);\n        }\n\n        Map<String, String> combinedLabels = new HashMap<>(this.labels);\n        if (createCommand.getLabels() != null) {\n            combinedLabels.putAll(createCommand.getLabels());\n        }\n\n        createCommand.withLabels(combinedLabels);\n    }\n\n    protected void setImage(RemoteDockerImage image) {\n        this.image = image;\n    }\n\n    protected void setImage(String image) {\n        setImage(DockerImageName.parse(image));\n    }\n\n    protected void setImage(DockerImageName image) {\n        setImage(new RemoteDockerImage(image));\n    }\n\n    public Set<ExposedPort> getExposedPorts() {\n        return new LinkedHashSet<>(this.exposedPorts);\n    }\n\n    protected void setExposedPorts(Set<ExposedPort> exposedPorts) {\n        this.exposedPorts.clear();\n        this.exposedPorts.addAll(exposedPorts);\n    }\n\n    protected void addExposedPorts(ExposedPort... exposedPorts) {\n        this.exposedPorts.addAll(Arrays.asList(exposedPorts));\n    }\n\n    protected void addExposedPort(ExposedPort exposedPort) {\n        this.exposedPorts.add(exposedPort);\n    }\n\n    protected void setExposedTcpPorts(Set<Integer> ports) {\n        this.exposedPorts.clear();\n        ports.forEach(port -> this.exposedPorts.add(ExposedPort.tcp(port)));\n    }\n\n    protected void addExposedTcpPorts(int... ports) {\n        for (int port : ports) {\n            this.exposedPorts.add(ExposedPort.tcp(port));\n        }\n    }\n\n    protected void addExposedTcpPort(int port) {\n        this.exposedPorts.add(ExposedPort.tcp(port));\n    }\n\n    protected void addExposedPort(int port, InternetProtocol protocol) {\n        this.exposedPorts.add(new ExposedPort(port, protocol));\n    }\n\n    public Set<PortBinding> getPortBindings() {\n        return new HashSet<>(this.portBindings);\n    }\n\n    protected void setPortBindings(Set<PortBinding> portBindings) {\n        this.portBindings.clear();\n        this.portBindings.addAll(portBindings);\n    }\n\n    protected void addPortBindings(PortBinding... portBindings) {\n        this.portBindings.addAll(Arrays.asList(portBindings));\n    }\n\n    protected void addPortBinding(PortBinding portBinding) {\n        this.portBindings.add(portBinding);\n    }\n\n    public Map<String, String> getLabels() {\n        return new HashMap<>(this.labels);\n    }\n\n    protected void setLabels(Map<String, String> labels) {\n        this.labels.clear();\n        this.labels.putAll(labels);\n    }\n\n    protected void addLabels(Map<String, String> labels) {\n        this.labels.putAll(labels);\n    }\n\n    protected void addLabel(String key, String value) {\n        this.labels.put(key, value);\n    }\n\n    public Map<String, String> getEnvVars() {\n        return new HashMap<>(this.envVars);\n    }\n\n    protected void setEnvVars(Map<String, String> envVars) {\n        this.envVars.clear();\n        this.envVars.putAll(envVars);\n    }\n\n    protected void addEnvVars(Map<String, String> envVars) {\n        this.envVars.putAll(envVars);\n    }\n\n    protected void addEnvVar(String key, String value) {\n        this.envVars.put(key, value);\n    }\n\n    public String[] getEntrypoint() {\n        return Arrays.copyOf(this.entrypoint, this.entrypoint.length);\n    }\n\n    protected void setEntrypoint(String... entrypoint) {\n        this.entrypoint = entrypoint;\n    }\n\n    public String[] getCommand() {\n        return Arrays.copyOf(this.command, this.command.length);\n    }\n\n    protected void setCommand(String... command) {\n        this.command = command;\n    }\n\n    protected void setNetwork(Network network) {\n        this.network = network;\n    }\n\n    public Set<String> getNetworkAliases() {\n        return new LinkedHashSet<>(this.networkAliases);\n    }\n\n    protected void setNetworkAliases(Set<String> aliases) {\n        this.networkAliases.clear();\n        this.networkAliases.addAll(aliases);\n    }\n\n    protected void addNetworkAliases(String... aliases) {\n        this.networkAliases.addAll(Arrays.asList(aliases));\n    }\n\n    protected void addNetworkAlias(String alias) {\n        this.networkAliases.add(alias);\n    }\n\n    protected void setNetworkMode(String networkMode) {\n        this.networkMode = networkMode;\n    }\n\n    protected void setPrivilegedMode(boolean privilegedMode) {\n        this.privilegedMode = privilegedMode;\n    }\n\n    public List<Bind> getBinds() {\n        return new ArrayList<>(this.binds);\n    }\n\n    protected void setBinds(List<Bind> binds) {\n        this.binds.clear();\n        this.binds.addAll(binds);\n    }\n\n    protected void addBinds(Bind... binds) {\n        this.binds.addAll(Arrays.asList(binds));\n    }\n\n    protected void addBind(Bind bind) {\n        this.binds.add(bind);\n    }\n\n    protected void setWaitStrategy(WaitStrategy waitStrategy) {\n        this.waitStrategy = waitStrategy;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/ContainerFetchException.java",
    "content": "package org.testcontainers.containers;\n\n/**\n * Created by rnorth on 15/10/2015.\n */\npublic class ContainerFetchException extends RuntimeException {\n\n    public ContainerFetchException(String s, Exception e) {\n        super(s, e);\n    }\n\n    public ContainerFetchException(String s) {\n        super(s);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/ContainerLaunchException.java",
    "content": "package org.testcontainers.containers;\n\n/**\n * AN exception that may be raised during launch of a container.\n */\npublic class ContainerLaunchException extends RuntimeException {\n\n    public ContainerLaunchException(String message) {\n        super(message);\n    }\n\n    public ContainerLaunchException(String message, Throwable cause) {\n        super(message, cause);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/ContainerState.java",
    "content": "package org.testcontainers.containers;\n\nimport com.github.dockerjava.api.DockerClient;\nimport com.github.dockerjava.api.command.HealthState;\nimport com.github.dockerjava.api.command.InspectContainerResponse;\nimport com.github.dockerjava.api.exception.DockerException;\nimport com.github.dockerjava.api.model.ExposedPort;\nimport com.github.dockerjava.api.model.PortBinding;\nimport com.github.dockerjava.api.model.Ports;\nimport com.google.common.base.Preconditions;\nimport lombok.SneakyThrows;\nimport org.apache.commons.compress.archivers.tar.TarArchiveInputStream;\nimport org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;\nimport org.apache.commons.compress.utils.IOUtils;\nimport org.apache.commons.lang3.math.NumberUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.testcontainers.DockerClientFactory;\nimport org.testcontainers.containers.output.OutputFrame;\nimport org.testcontainers.images.builder.Transferable;\nimport org.testcontainers.utility.LogUtils;\nimport org.testcontainers.utility.MountableFile;\nimport org.testcontainers.utility.ThrowingFunction;\n\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.PipedInputStream;\nimport java.io.PipedOutputStream;\nimport java.nio.charset.Charset;\nimport java.nio.charset.StandardCharsets;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.stream.Collectors;\n\npublic interface ContainerState {\n    String STATE_HEALTHY = \"healthy\";\n\n    /**\n     * Get the IP address that this container may be reached on (may not be the local machine).\n     *\n     * @return an IP address\n     * @deprecated use {@link #getHost()}\n     * @see #getHost()\n     */\n    @Deprecated\n    default String getContainerIpAddress() {\n        return getHost();\n    }\n\n    default DockerClient getDockerClient() {\n        return DockerClientFactory.lazyClient();\n    }\n\n    /**\n     * Get the host that this container may be reached on (may not be the local machine).\n     *\n     * @return a host\n     */\n    default String getHost() {\n        return DockerClientFactory.instance().dockerHostIpAddress();\n    }\n\n    /**\n     * @return is the container currently running?\n     */\n    default boolean isRunning() {\n        if (getContainerId() == null) {\n            return false;\n        }\n\n        try {\n            Boolean running = getCurrentContainerInfo().getState().getRunning();\n            return Boolean.TRUE.equals(running);\n        } catch (DockerException e) {\n            return false;\n        }\n    }\n\n    /**\n     * @return is the container created?\n     */\n    default boolean isCreated() {\n        if (getContainerId() == null) {\n            return false;\n        }\n\n        try {\n            String status = getCurrentContainerInfo().getState().getStatus();\n            return \"created\".equalsIgnoreCase(status) || isRunning();\n        } catch (DockerException e) {\n            return false;\n        }\n    }\n\n    /**\n     * @return has the container health state 'healthy'?\n     */\n    default boolean isHealthy() {\n        if (getContainerId() == null) {\n            return false;\n        }\n\n        try {\n            InspectContainerResponse inspectContainerResponse = getCurrentContainerInfo();\n            HealthState health = inspectContainerResponse.getState().getHealth();\n            if (health == null) {\n                throw new RuntimeException(\n                    \"This container's image does not have a healthcheck declared, so health cannot be determined. Either amend the image or use another approach to determine whether containers are healthy.\"\n                );\n            }\n\n            return STATE_HEALTHY.equals(health.getStatus());\n        } catch (DockerException e) {\n            return false;\n        }\n    }\n\n    /**\n     * Inspects the container and returns up-to-date inspection response.\n     *\n     * @return up-to-date container inspect response\n     * @see #getContainerInfo()\n     */\n    default InspectContainerResponse getCurrentContainerInfo() {\n        return getDockerClient().inspectContainerCmd(getContainerId()).exec();\n    }\n\n    /**\n     * Get the actual mapped port for a first port exposed by the container.\n     * Should be used in conjunction with {@link #getHost()}.\n     *\n     * @return the port that the exposed port is mapped to\n     * @throws IllegalStateException if there are no exposed ports\n     */\n    default Integer getFirstMappedPort() {\n        return getExposedPorts()\n            .stream()\n            .findFirst()\n            .map(this::getMappedPort)\n            .orElseThrow(() -> new IllegalStateException(\"Container doesn't expose any ports\"));\n    }\n\n    /**\n     * Get the actual mapped port for a given port exposed by the container.\n     * It should be used in conjunction with {@link #getHost()}.\n     * <p>\n     * Note: The returned port number might be outdated (for instance, after disconnecting from a network and reconnecting\n     * again). If you always need up-to-date value, override the {@link #getContainerInfo()} to return the\n     * {@link #getCurrentContainerInfo()}.\n     *\n     * @param originalPort the original TCP port that is exposed\n     * @return the port that the exposed port is mapped to, or null if it is not exposed\n     * @see #getContainerInfo()\n     * @see #getCurrentContainerInfo()\n     */\n    default Integer getMappedPort(int originalPort) {\n        Preconditions.checkState(\n            this.getContainerId() != null,\n            \"Mapped port can only be obtained after the container is started\"\n        );\n\n        Ports.Binding[] binding = new Ports.Binding[0];\n        final InspectContainerResponse containerInfo = this.getContainerInfo();\n        if (containerInfo != null) {\n            binding = containerInfo.getNetworkSettings().getPorts().getBindings().get(new ExposedPort(originalPort));\n        }\n\n        if (binding != null && binding.length > 0 && binding[0] != null) {\n            return Integer.valueOf(binding[0].getHostPortSpec());\n        } else {\n            throw new IllegalArgumentException(\"Requested port (\" + originalPort + \") is not mapped\");\n        }\n    }\n\n    /**\n     * @return the exposed ports\n     */\n    List<Integer> getExposedPorts();\n\n    /**\n     * @return the port bindings\n     */\n    default List<String> getPortBindings() {\n        List<String> portBindings = new ArrayList<>();\n        final Ports hostPortBindings = this.getContainerInfo().getHostConfig().getPortBindings();\n        for (Map.Entry<ExposedPort, Ports.Binding[]> binding : hostPortBindings.getBindings().entrySet()) {\n            for (Ports.Binding portBinding : binding.getValue()) {\n                portBindings.add(String.format(\"%s:%s\", portBinding.toString(), binding.getKey()));\n            }\n        }\n        return portBindings;\n    }\n\n    /**\n     * @return the bound port numbers\n     */\n    default List<Integer> getBoundPortNumbers() {\n        return getPortBindings()\n            .stream()\n            .map(PortBinding::parse)\n            .map(PortBinding::getBinding)\n            .map(Ports.Binding::getHostPortSpec)\n            .filter(Objects::nonNull)\n            .filter(NumberUtils::isNumber)\n            .map(Integer::valueOf)\n            .filter(port -> port > 0)\n            .collect(Collectors.toList());\n    }\n\n    /**\n     * @return all log output from the container from start until the current instant (both stdout and stderr)\n     */\n    default String getLogs() {\n        return LogUtils.getOutput(getDockerClient(), getContainerId());\n    }\n\n    /**\n     * @param types log types to return\n     * @return all log output from the container from start until the current instant\n     */\n    default String getLogs(OutputFrame.OutputType... types) {\n        return LogUtils.getOutput(getDockerClient(), getContainerId(), types);\n    }\n\n    /**\n     * @return the id of the container\n     */\n    default String getContainerId() {\n        return getContainerInfo().getId();\n    }\n\n    /**\n     * Returns the container inspect response. The response might be cached/outdated.\n     *\n     * @return the container info\n     * @see #getCurrentContainerInfo()\n     */\n    InspectContainerResponse getContainerInfo();\n\n    /**\n     * Run a command inside a running container, as though using \"docker exec\", and interpreting\n     * the output as UTF8.\n     * <p>\n     * @see #execInContainer(Charset, String...)\n     */\n    default Container.ExecResult execInContainer(String... command)\n        throws UnsupportedOperationException, IOException, InterruptedException {\n        return execInContainer(StandardCharsets.UTF_8, command);\n    }\n\n    /**\n     * Run a command inside a running container, as though using \"docker exec\".\n     * <p>\n     * @see ExecInContainerPattern#execInContainer(DockerClient, InspectContainerResponse, Charset, String...)\n     */\n    default Container.ExecResult execInContainer(Charset outputCharset, String... command)\n        throws UnsupportedOperationException, IOException, InterruptedException {\n        return ExecInContainerPattern.execInContainer(getDockerClient(), getContainerInfo(), outputCharset, command);\n    }\n\n    /**\n     * Run a command inside a running container as a given user, as using \"docker exec -u user\".\n     * <p>\n     * @see ExecInContainerPattern#execInContainerWithUser(DockerClient, InspectContainerResponse, String, String...)\n     * @deprecated use {@link #execInContainer(ExecConfig)}\n     */\n    @Deprecated\n    default Container.ExecResult execInContainerWithUser(String user, String... command)\n        throws UnsupportedOperationException, IOException, InterruptedException {\n        return ExecInContainerPattern.execInContainer(\n            getDockerClient(),\n            getContainerInfo(),\n            ExecConfig.builder().user(user).command(command).build()\n        );\n    }\n\n    /**\n     * Run a command inside a running container as a given user, as using \"docker exec -u user\".\n     * <p>\n     * @see ExecInContainerPattern#execInContainerWithUser(DockerClient, InspectContainerResponse, Charset, String, String...)\n     * @deprecated use {@link #execInContainer(Charset, ExecConfig)}\n     */\n    @Deprecated\n    default Container.ExecResult execInContainerWithUser(Charset outputCharset, String user, String... command)\n        throws UnsupportedOperationException, IOException, InterruptedException {\n        return ExecInContainerPattern.execInContainer(\n            getDockerClient(),\n            getContainerInfo(),\n            outputCharset,\n            ExecConfig.builder().user(user).command(command).build()\n        );\n    }\n\n    /**\n     * Run a command inside a running container, as though using \"docker exec\".\n     */\n    default Container.ExecResult execInContainer(ExecConfig execConfig)\n        throws UnsupportedOperationException, IOException, InterruptedException {\n        return ExecInContainerPattern.execInContainer(getDockerClient(), getContainerInfo(), execConfig);\n    }\n\n    /**\n     * Run a command inside a running container, as though using \"docker exec\".\n     */\n    default Container.ExecResult execInContainer(Charset outputCharset, ExecConfig execConfig)\n        throws UnsupportedOperationException, IOException, InterruptedException {\n        return ExecInContainerPattern.execInContainer(getDockerClient(), getContainerInfo(), outputCharset, execConfig);\n    }\n\n    /**\n     *\n     * Copies a file or directory to the container.\n     *\n     * @param mountableFile file or directory which is copied into the container\n     * @param containerPath destination path inside the container\n     */\n    default void copyFileToContainer(MountableFile mountableFile, String containerPath) {\n        File sourceFile = new File(mountableFile.getResolvedPath());\n\n        if (containerPath.endsWith(\"/\") && sourceFile.isFile()) {\n            final Logger logger = LoggerFactory.getLogger(GenericContainer.class);\n            logger.warn(\n                \"folder-like containerPath in copyFileToContainer is deprecated, please explicitly specify a file path\"\n            );\n            copyFileToContainer((Transferable) mountableFile, containerPath + sourceFile.getName());\n        } else {\n            copyFileToContainer((Transferable) mountableFile, containerPath);\n        }\n    }\n\n    /**\n     *\n     * Copies a file to the container.\n     *\n     * @param transferable file which is copied into the container\n     * @param containerPath destination path inside the container\n     */\n    @SneakyThrows({ IOException.class, InterruptedException.class })\n    default void copyFileToContainer(Transferable transferable, String containerPath) {\n        if (getContainerId() == null) {\n            throw new IllegalStateException(\"copyFileToContainer can only be used with created / running container\");\n        }\n\n        try (\n            PipedOutputStream pipedOutputStream = new PipedOutputStream();\n            PipedInputStream pipedInputStream = new PipedInputStream(pipedOutputStream);\n            TarArchiveOutputStream tarArchive = new TarArchiveOutputStream(pipedOutputStream)\n        ) {\n            Thread thread = new Thread(() -> {\n                try {\n                    tarArchive.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX);\n                    tarArchive.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_POSIX);\n\n                    transferable.transferTo(tarArchive, containerPath);\n                } finally {\n                    IOUtils.closeQuietly(tarArchive);\n                }\n            });\n\n            thread.start();\n\n            getDockerClient()\n                .copyArchiveToContainerCmd(getContainerId())\n                .withTarInputStream(pipedInputStream)\n                .withRemotePath(\"/\")\n                .exec();\n\n            thread.join();\n        }\n    }\n\n    /**\n     * Copies a file which resides inside the container to user defined directory\n     *\n     * @param containerPath path to file which is copied from container\n     * @param destinationPath destination path to which file is copied with file name\n     * @throws IOException if there's an issue communicating with Docker or receiving entry from TarArchiveInputStream\n     * @throws InterruptedException if the thread waiting for the response is interrupted\n     */\n    default void copyFileFromContainer(String containerPath, String destinationPath)\n        throws IOException, InterruptedException {\n        copyFileFromContainer(\n            containerPath,\n            inputStream -> {\n                try (FileOutputStream output = new FileOutputStream(destinationPath)) {\n                    IOUtils.copy(inputStream, output);\n                    return null;\n                }\n            }\n        );\n    }\n\n    /**\n     * Streams a file which resides inside the container\n     *\n     * @param containerPath path to file which is copied from container\n     * @param function function that takes InputStream of the copied file\n     */\n    @SneakyThrows\n    default <T> T copyFileFromContainer(String containerPath, ThrowingFunction<InputStream, T> function) {\n        if (getContainerId() == null) {\n            throw new IllegalStateException(\"copyFileFromContainer can only be used when the Container is created.\");\n        }\n\n        DockerClient dockerClient = getDockerClient();\n        try (\n            InputStream inputStream = dockerClient.copyArchiveFromContainerCmd(getContainerId(), containerPath).exec();\n            TarArchiveInputStream tarInputStream = new TarArchiveInputStream(inputStream)\n        ) {\n            tarInputStream.getNextTarEntry();\n            return function.apply(tarInputStream);\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/ContainerisedDockerCompose.java",
    "content": "package org.testcontainers.containers;\n\nimport com.google.common.base.Joiner;\nimport com.google.common.util.concurrent.Uninterruptibles;\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.commons.lang3.SystemUtils;\nimport org.testcontainers.DockerClientFactory;\nimport org.testcontainers.containers.output.Slf4jLogConsumer;\nimport org.testcontainers.containers.startupcheck.IndefiniteWaitOneShotStartupCheckStrategy;\nimport org.testcontainers.utility.AuditLogger;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.MountableFile;\nimport org.testcontainers.utility.PathUtils;\n\nimport java.io.File;\nimport java.util.List;\nimport java.util.concurrent.TimeUnit;\nimport java.util.stream.Collectors;\n\n/**\n * Use Docker Compose container.\n */\nclass ContainerisedDockerCompose extends GenericContainer<ContainerisedDockerCompose> implements DockerCompose {\n\n    public static final char UNIX_PATH_SEPARATOR = ':';\n\n    public ContainerisedDockerCompose(\n        DockerImageName dockerImageName,\n        List<File> composeFiles,\n        String identifier,\n        List<String> fileCopyInclusions\n    ) {\n        super(dockerImageName);\n        addEnv(ENV_PROJECT_NAME, identifier);\n\n        // Map the docker compose file into the container\n        final File dockerComposeBaseFile = composeFiles.get(0);\n        final String pwd = dockerComposeBaseFile.getAbsoluteFile().getParentFile().getAbsolutePath();\n        final String containerPwd = convertToUnixFilesystemPath(pwd);\n\n        final List<String> absoluteDockerComposeFiles = composeFiles\n            .stream()\n            .map(File::getAbsolutePath)\n            .map(MountableFile::forHostPath)\n            .map(MountableFile::getFilesystemPath)\n            .map(this::convertToUnixFilesystemPath)\n            .collect(Collectors.toList());\n        final String composeFileEnvVariableValue = Joiner.on(UNIX_PATH_SEPARATOR).join(absoluteDockerComposeFiles); // we always need the UNIX path separator\n        logger().debug(\"Set env COMPOSE_FILE={}\", composeFileEnvVariableValue);\n        addEnv(ENV_COMPOSE_FILE, composeFileEnvVariableValue);\n        if (fileCopyInclusions.isEmpty()) {\n            logger().info(\"Copying all files in {} into the container\", pwd);\n            withCopyFileToContainer(MountableFile.forHostPath(pwd), containerPwd);\n        } else {\n            // Always copy the compose file itself\n            logger().info(\"Copying docker compose file: {}\", dockerComposeBaseFile.getAbsolutePath());\n            withCopyFileToContainer(\n                MountableFile.forHostPath(dockerComposeBaseFile.getAbsolutePath()),\n                convertToUnixFilesystemPath(dockerComposeBaseFile.getAbsolutePath())\n            );\n            for (String pathToCopy : fileCopyInclusions) {\n                String hostPath = pwd + \"/\" + pathToCopy;\n                logger().info(\"Copying inclusion file: {}\", hostPath);\n                withCopyFileToContainer(MountableFile.forHostPath(hostPath), convertToUnixFilesystemPath(hostPath));\n            }\n        }\n\n        // Ensure that compose can access docker. Since the container is assumed to be running on the same machine\n        //  as the docker daemon, just mapping the docker control socket is OK.\n        // As there seems to be a problem with mapping to the /var/run directory in certain environments (e.g. CircleCI)\n        //  we map the socket file outside of /var/run, as just /docker.sock\n        addFileSystemBind(\n            DockerClientFactory.instance().getRemoteDockerUnixSocketPath(),\n            \"/docker.sock\",\n            BindMode.READ_WRITE\n        );\n        addEnv(\"DOCKER_HOST\", \"unix:///docker.sock\");\n        setStartupCheckStrategy(new IndefiniteWaitOneShotStartupCheckStrategy());\n        setWorkingDirectory(containerPwd);\n    }\n\n    @Override\n    public void invoke() {\n        super.start();\n\n        followOutput(new Slf4jLogConsumer(logger()));\n\n        // wait for the compose container to stop, which should only happen after it has spawned all the service containers\n        logger().info(\"Docker Compose container is running for command: {}\", Joiner.on(\" \").join(getCommandParts()));\n        while (isRunning()) {\n            logger().trace(\"Compose container is still running\");\n            Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS);\n        }\n\n        AuditLogger.doComposeLog(getCommandParts(), getEnv());\n\n        final Integer exitCode = getDockerClient()\n            .inspectContainerCmd(getContainerId())\n            .exec()\n            .getState()\n            .getExitCode();\n\n        if (exitCode == null || exitCode != 0) {\n            throw new ContainerLaunchException(\n                \"Containerised Docker Compose exited abnormally with code \" +\n                exitCode +\n                \" whilst running command: \" +\n                StringUtils.join(getCommandParts(), ' ')\n            );\n        }\n\n        logger().info(\"Docker Compose has finished running\");\n    }\n\n    private String convertToUnixFilesystemPath(String path) {\n        return SystemUtils.IS_OS_WINDOWS ? PathUtils.createMinGWPath(path).substring(1) : path;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/DockerCompose.java",
    "content": "package org.testcontainers.containers;\n\nimport java.util.Map;\n\ninterface DockerCompose {\n    String ENV_PROJECT_NAME = \"COMPOSE_PROJECT_NAME\";\n\n    String ENV_COMPOSE_FILE = \"COMPOSE_FILE\";\n\n    DockerCompose withCommand(String cmd);\n\n    DockerCompose withEnv(Map<String, String> env);\n\n    void invoke();\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/DockerComposeContainer.java",
    "content": "package org.testcontainers.containers;\n\nimport com.github.dockerjava.api.model.Container;\nimport com.google.common.annotations.VisibleForTesting;\nimport lombok.NonNull;\nimport lombok.extern.slf4j.Slf4j;\nimport org.apache.commons.lang3.SystemUtils;\nimport org.testcontainers.containers.output.OutputFrame;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.containers.wait.strategy.WaitStrategy;\nimport org.testcontainers.lifecycle.Startable;\nimport org.testcontainers.utility.Base58;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.File;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.function.Consumer;\n\n/**\n * Container which launches Docker Compose, for the purposes of launching a defined set of containers.\n */\n@Slf4j\npublic class DockerComposeContainer<SELF extends DockerComposeContainer<SELF>> implements Startable {\n\n    private final Map<String, Integer> scalingPreferences = new HashMap<>();\n\n    private boolean localCompose;\n\n    private boolean pull = true;\n\n    private boolean build = false;\n\n    private Set<String> options = new HashSet<>();\n\n    private boolean tailChildContainers;\n\n    private static final Object MUTEX = new Object();\n\n    private List<String> services = new ArrayList<>();\n\n    /**\n     * Properties that should be passed through to all Compose and ambassador containers (not\n     * necessarily to containers that are spawned by Compose itself)\n     */\n    private Map<String, String> env = new HashMap<>();\n\n    private RemoveImages removeImages;\n\n    private boolean removeVolumes = true;\n\n    public static final String COMPOSE_EXECUTABLE = SystemUtils.IS_OS_WINDOWS ? \"docker-compose.exe\" : \"docker-compose\";\n\n    private final ComposeDelegate composeDelegate;\n\n    private String project;\n\n    private List<String> filesInDirectory = new ArrayList<>();\n\n    /**\n     * Creates a new DockerComposeContainer using the specified Docker image and compose files.\n     *\n     * @param image        The Docker image to use for the container\n     * @param composeFiles One or more Docker Compose configuration files\n     */\n    public DockerComposeContainer(DockerImageName image, File... composeFiles) {\n        this(image, Arrays.asList(composeFiles));\n    }\n\n    /**\n     * Creates a new DockerComposeContainer using the specified Docker image and compose files.\n     *\n     * @param image        The Docker image to use for the container\n     * @param composeFiles A list of Docker Compose configuration files\n     */\n    public DockerComposeContainer(DockerImageName image, List<File> composeFiles) {\n        this(image, Base58.randomString(6).toLowerCase(), composeFiles);\n    }\n\n    /**\n     * Creates a new DockerComposeContainer with the specified Docker image, identifier, and compose files.\n     *\n     * @param image        The Docker image to use for the container\n     * @param identifier   A unique identifier for this compose environment\n     * @param composeFiles One or more Docker Compose configuration files\n     */\n    public DockerComposeContainer(DockerImageName image, String identifier, File... composeFiles) {\n        this(image, identifier, Arrays.asList(composeFiles));\n    }\n\n    /**\n     * Creates a new DockerComposeContainer with the specified Docker image, identifier, and a single compose file.\n     *\n     * @param image        The Docker image to use for the container\n     * @param identifier   A unique identifier for this compose environment\n     * @param composeFile  A Docker Compose configuration file\n     */\n    public DockerComposeContainer(DockerImageName image, String identifier, File composeFile) {\n        this(image, identifier, Collections.singletonList(composeFile));\n    }\n\n    /**\n     * Creates a new DockerComposeContainer with the specified Docker image, identifier, and compose files.\n     *\n     * @param image        The Docker image to use for the container\n     * @param identifier   A unique identifier for this compose environment\n     * @param composeFiles A list of Docker Compose configuration files\n     */\n    public DockerComposeContainer(DockerImageName image, String identifier, List<File> composeFiles) {\n        this.composeDelegate =\n            new ComposeDelegate(ComposeDelegate.ComposeVersion.V1, composeFiles, identifier, COMPOSE_EXECUTABLE, image);\n        this.project = this.composeDelegate.getProject();\n    }\n\n    /**\n     * Use the new constructor {@link #DockerComposeContainer(DockerImageName image, String identifier, File composeFile)}\n     */\n    public DockerComposeContainer(File composeFile, String identifier) {\n        this(identifier, composeFile);\n        this.localCompose = true;\n    }\n\n    /**\n     * Use the new constructor {@link #DockerComposeContainer(DockerImageName image, List composeFiles)}\n     */\n    public DockerComposeContainer(File... composeFiles) {\n        this(Arrays.asList(composeFiles));\n        this.localCompose = true;\n    }\n\n    /**\n     * Use the new constructor {@link #DockerComposeContainer(DockerImageName image, List composeFiles)}\n     */\n    @Deprecated\n    public DockerComposeContainer(List<File> composeFiles) {\n        this(Base58.randomString(6).toLowerCase(), composeFiles);\n        this.localCompose = true;\n    }\n\n    /**\n     * Use the new constructor {@link #DockerComposeContainer(DockerImageName image, String identifier, File... composeFiles)}\n     */\n    public DockerComposeContainer(String identifier, File... composeFiles) {\n        this(identifier, Arrays.asList(composeFiles));\n        this.localCompose = true;\n    }\n\n    /**\n     * Use the new constructor {@link #DockerComposeContainer(DockerImageName image, String identifier, List composeFiles)}\n     */\n    public DockerComposeContainer(String identifier, List<File> composeFiles) {\n        this.composeDelegate =\n            new ComposeDelegate(\n                ComposeDelegate.ComposeVersion.V1,\n                composeFiles,\n                identifier,\n                COMPOSE_EXECUTABLE,\n                DockerImageName.parse(\"docker/compose:1.29.2\")\n            );\n        this.project = this.composeDelegate.getProject();\n        this.localCompose = true;\n    }\n\n    @Override\n    public void start() {\n        synchronized (MUTEX) {\n            this.composeDelegate.registerContainersForShutdown();\n            if (pull) {\n                try {\n                    this.composeDelegate.pullImages();\n                } catch (ContainerLaunchException e) {\n                    log.warn(\"Exception while pulling images, using local images if available\", e);\n                }\n            }\n            this.composeDelegate.createServices(\n                    this.localCompose,\n                    this.build,\n                    this.options,\n                    this.services,\n                    this.scalingPreferences,\n                    this.env,\n                    this.filesInDirectory\n                );\n            this.composeDelegate.startAmbassadorContainer();\n            this.composeDelegate.waitUntilServiceStarted(this.tailChildContainers);\n        }\n    }\n\n    @VisibleForTesting\n    List<Container> listChildContainers() {\n        return this.composeDelegate.listChildContainers();\n    }\n\n    public SELF withServices(@NonNull String... services) {\n        this.services = Arrays.asList(services);\n        return self();\n    }\n\n    @Override\n    public void stop() {\n        synchronized (MUTEX) {\n            try {\n                this.composeDelegate.getAmbassadorContainer().stop();\n\n                // Kill the services using docker-compose\n                String cmd = ComposeCommand.getDownCommand(ComposeDelegate.ComposeVersion.V1, this.options);\n\n                if (removeVolumes) {\n                    cmd += \" -v\";\n                }\n                if (removeImages != null) {\n                    cmd += \" --rmi \" + removeImages.dockerRemoveImagesType();\n                }\n                this.composeDelegate.runWithCompose(this.localCompose, cmd, this.env, this.filesInDirectory);\n            } finally {\n                this.composeDelegate.clear();\n                this.project = this.composeDelegate.randomProjectId();\n            }\n        }\n    }\n\n    public SELF withExposedService(String serviceName, int servicePort) {\n        this.composeDelegate.withExposedService(serviceName, servicePort, Wait.defaultWaitStrategy());\n        return self();\n    }\n\n    public DockerComposeContainer withExposedService(String serviceName, int instance, int servicePort) {\n        return withExposedService(serviceName + \"_\" + instance, servicePort);\n    }\n\n    public DockerComposeContainer withExposedService(\n        String serviceName,\n        int instance,\n        int servicePort,\n        WaitStrategy waitStrategy\n    ) {\n        this.composeDelegate.withExposedService(serviceName + \"_\" + instance, servicePort, waitStrategy);\n        return self();\n    }\n\n    public SELF withExposedService(String serviceName, int servicePort, @NonNull WaitStrategy waitStrategy) {\n        this.composeDelegate.withExposedService(serviceName, servicePort, waitStrategy);\n        return self();\n    }\n\n    /**\n     * Specify the {@link WaitStrategy} to use to determine if the container is ready.\n     *\n     * @param serviceName  the name of the service to wait for\n     * @param waitStrategy the WaitStrategy to use\n     * @return this\n     * @see org.testcontainers.containers.wait.strategy.Wait#defaultWaitStrategy()\n     */\n    public SELF waitingFor(String serviceName, @NonNull WaitStrategy waitStrategy) {\n        String serviceInstanceName = this.composeDelegate.getServiceInstanceName(serviceName);\n        this.composeDelegate.addWaitStrategy(serviceInstanceName, waitStrategy);\n        return self();\n    }\n\n    /**\n     * Get the host (e.g. IP address or hostname) that an exposed service can be found at, from the host machine\n     * (i.e. should be the machine that's running this Java process).\n     * <p>\n     * The service must have been declared using DockerComposeContainer#withExposedService.\n     *\n     * @param serviceName the name of the service as set in the docker-compose.yml file.\n     * @param servicePort the port exposed by the service container.\n     * @return a host IP address or hostname that can be used for accessing the service container.\n     */\n    public String getServiceHost(String serviceName, Integer servicePort) {\n        return this.composeDelegate.getServiceHost();\n    }\n\n    /**\n     * Get the port that an exposed service can be found at, from the host machine\n     * (i.e. should be the machine that's running this Java process).\n     * <p>\n     * The service must have been declared using DockerComposeContainer#withExposedService.\n     *\n     * @param serviceName the name of the service as set in the docker-compose.yml file.\n     * @param servicePort the port exposed by the service container.\n     * @return a port that can be used for accessing the service container.\n     */\n    public Integer getServicePort(String serviceName, Integer servicePort) {\n        return this.composeDelegate.getServicePort(serviceName, servicePort);\n    }\n\n    public SELF withScaledService(String serviceBaseName, int numInstances) {\n        scalingPreferences.put(serviceBaseName, numInstances);\n        return self();\n    }\n\n    public SELF withEnv(String key, String value) {\n        env.put(key, value);\n        return self();\n    }\n\n    public SELF withEnv(Map<String, String> env) {\n        env.forEach(this.env::put);\n        return self();\n    }\n\n    /**\n     * Whether to pull images first.\n     *\n     * @return this instance, for chaining\n     */\n    public SELF withPull(boolean pull) {\n        this.pull = pull;\n        return self();\n    }\n\n    /**\n     * Whether to tail child container logs.\n     *\n     * @return this instance, for chaining\n     */\n    public SELF withTailChildContainers(boolean tailChildContainers) {\n        this.tailChildContainers = tailChildContainers;\n        return self();\n    }\n\n    /**\n     * Attach an output consumer at container startup, enabling stdout and stderr to be followed, waited on, etc.\n     * <p>\n     * More than one consumer may be registered.\n     *\n     * @param serviceName the name of the service as set in the docker-compose.yml file\n     * @param consumer    consumer that output frames should be sent to\n     * @return this instance, for chaining\n     */\n    public SELF withLogConsumer(String serviceName, Consumer<OutputFrame> consumer) {\n        this.composeDelegate.withLogConsumer(serviceName, consumer);\n        return self();\n    }\n\n    /**\n     * Whether to always build images before starting containers.\n     *\n     * @return this instance, for chaining\n     */\n    public SELF withBuild(boolean build) {\n        this.build = build;\n        return self();\n    }\n\n    /**\n     * Adds options to the docker-compose command, e.g. docker-compose --compatibility.\n     *\n     * @return this instance, for chaining\n     */\n    public SELF withOptions(String... options) {\n        this.options = new HashSet<>(Arrays.asList(options));\n        return self();\n    }\n\n    /**\n     * Remove images after containers shutdown.\n     *\n     * @return this instance, for chaining\n     */\n    public SELF withRemoveImages(RemoveImages removeImages) {\n        this.removeImages = removeImages;\n        return self();\n    }\n\n    /**\n     * Remove volumes after containers shut down.\n     *\n     * @param removeVolumes whether volumes are to be removed.\n     * @return this instance, for chaining.\n     */\n    public SELF withRemoveVolumes(boolean removeVolumes) {\n        this.removeVolumes = removeVolumes;\n        return self();\n    }\n\n    /**\n     * Set the maximum startup timeout all the waits set are bounded to.\n     *\n     * @return this instance. for chaining\n     */\n    public SELF withStartupTimeout(Duration startupTimeout) {\n        this.composeDelegate.setStartupTimeout(startupTimeout);\n        return self();\n    }\n\n    public SELF withCopyFilesInContainer(String... fileCopyInclusions) {\n        this.filesInDirectory = Arrays.asList(fileCopyInclusions);\n        return self();\n    }\n\n    public Optional<ContainerState> getContainerByServiceName(String serviceName) {\n        return this.composeDelegate.getContainerByServiceName(serviceName);\n    }\n\n    private void followLogs(String containerId, Consumer<OutputFrame> consumer) {\n        this.followLogs(containerId, consumer);\n    }\n\n    private SELF self() {\n        return (SELF) this;\n    }\n\n    public enum RemoveImages {\n        /**\n         * Remove all images used by any service.\n         */\n        ALL(\"all\"),\n\n        /**\n         * Remove only images that don't have a custom tag set by the `image` field.\n         */\n        LOCAL(\"local\");\n\n        private final String dockerRemoveImagesType;\n\n        RemoveImages(final String dockerRemoveImagesType) {\n            this.dockerRemoveImagesType = dockerRemoveImagesType;\n        }\n\n        public String dockerRemoveImagesType() {\n            return dockerRemoveImagesType;\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/DockerComposeFiles.java",
    "content": "package org.testcontainers.containers;\n\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.File;\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.stream.Collectors;\n\npublic class DockerComposeFiles {\n\n    private final List<ParsedDockerComposeFile> parsedComposeFiles;\n\n    public DockerComposeFiles(List<File> composeFiles) {\n        this.parsedComposeFiles = composeFiles.stream().map(ParsedDockerComposeFile::new).collect(Collectors.toList());\n    }\n\n    public Set<String> getDependencyImages() {\n        Map<String, Set<String>> mergedServiceNameToImageNames = mergeServiceDependencyImageNames();\n\n        return getImageNames(mergedServiceNameToImageNames);\n    }\n\n    private Map<String, Set<String>> mergeServiceDependencyImageNames() {\n        Map<String, Set<String>> mergedServiceNameToImageNames = new HashMap<>();\n        for (ParsedDockerComposeFile parsedComposeFile : parsedComposeFiles) {\n            mergedServiceNameToImageNames.putAll(parsedComposeFile.getServiceNameToImageNames());\n        }\n        return mergedServiceNameToImageNames;\n    }\n\n    private Set<String> getImageNames(Map<String, Set<String>> serviceToImageNames) {\n        return serviceToImageNames\n            .values()\n            .stream()\n            .flatMap(Collection::stream)\n            // Pass through DockerImageName to convert image names to canonical form (e.g. making implicit latest tag explicit)\n            .map(DockerImageName::parse)\n            .map(DockerImageName::asCanonicalNameString)\n            .collect(Collectors.toSet());\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/DockerMcpGatewayContainer.java",
    "content": "package org.testcontainers.containers;\n\nimport org.testcontainers.DockerClientFactory;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.images.builder.Transferable;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * Testcontainers implementation of the Docker MCP Gateway container.\n * <p>\n * Supported images: {@code docker/mcp-gateway}\n * <p>\n * Exposed ports: 8811\n */\npublic class DockerMcpGatewayContainer extends GenericContainer<DockerMcpGatewayContainer> {\n\n    private static final String DOCKER_MCP_GATEWAY_IMAGE = \"docker/mcp-gateway\";\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(DOCKER_MCP_GATEWAY_IMAGE);\n\n    private static final int DEFAULT_PORT = 8811;\n\n    private static final String SECRETS_PATH = \"/testcontainers/app/secrets\";\n\n    private final List<String> servers = new ArrayList<>();\n\n    private final List<String> tools = new ArrayList<>();\n\n    private final Map<String, String> secrets = new HashMap<>();\n\n    public DockerMcpGatewayContainer(String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    public DockerMcpGatewayContainer(DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n        withExposedPorts(DEFAULT_PORT);\n        withFileSystemBind(DockerClientFactory.instance().getRemoteDockerUnixSocketPath(), \"/var/run/docker.sock\");\n        waitingFor(Wait.forLogMessage(\".*Start sse server on port.*\", 1));\n    }\n\n    @Override\n    protected void configure() {\n        List<String> command = new ArrayList<>();\n        command.add(\"--transport=sse\");\n        for (String server : this.servers) {\n            if (!server.isEmpty()) {\n                command.add(\"--servers=\" + server);\n            }\n        }\n        for (String tool : this.tools) {\n            if (!tool.isEmpty()) {\n                command.add(\"--tools=\" + tool);\n            }\n        }\n        if (this.secrets != null && !this.secrets.isEmpty()) {\n            command.add(\"--secrets=\" + SECRETS_PATH);\n        }\n        withCommand(String.join(\" \", command));\n    }\n\n    @Override\n    protected void containerIsCreated(String containerId) {\n        if (this.secrets != null && !this.secrets.isEmpty()) {\n            StringBuilder secretsFile = new StringBuilder();\n            for (Map.Entry<String, String> entry : this.secrets.entrySet()) {\n                secretsFile.append(entry.getKey()).append(\"=\").append(entry.getValue()).append(\"\\n\");\n            }\n            copyFileToContainer(Transferable.of(secretsFile.toString()), SECRETS_PATH);\n        }\n    }\n\n    public DockerMcpGatewayContainer withServer(String server, List<String> tools) {\n        this.servers.add(server);\n        this.tools.addAll(tools);\n        return this;\n    }\n\n    public DockerMcpGatewayContainer withServer(String server, String... tools) {\n        this.servers.add(server);\n        this.tools.addAll(Arrays.asList(tools));\n        return this;\n    }\n\n    public DockerMcpGatewayContainer withSecrets(Map<String, String> secrets) {\n        this.secrets.putAll(secrets);\n        return this;\n    }\n\n    public DockerMcpGatewayContainer withSecret(String secretKey, String secretValue) {\n        this.secrets.put(secretKey, secretValue);\n        return this;\n    }\n\n    public String getEndpoint() {\n        return \"http://\" + getHost() + \":\" + getMappedPort(DEFAULT_PORT);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/DockerModelRunnerContainer.java",
    "content": "package org.testcontainers.containers;\n\nimport com.github.dockerjava.api.command.InspectContainerResponse;\nimport lombok.extern.slf4j.Slf4j;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.io.OutputStream;\nimport java.net.HttpURLConnection;\nimport java.net.URL;\nimport java.nio.charset.StandardCharsets;\n\n/**\n * Testcontainers proxy container for the Docker Model Runner service\n * provided by Docker Desktop.\n * <p>\n * Supported images: {@code alpine/socat}\n * <p>\n * Exposed ports: 80\n */\n@Slf4j\npublic class DockerModelRunnerContainer extends SocatContainer {\n\n    private static final String MODEL_RUNNER_ENDPOINT = \"model-runner.docker.internal\";\n\n    private static final int PORT = 80;\n\n    private String model;\n\n    public DockerModelRunnerContainer(String image) {\n        this(DockerImageName.parse(image));\n    }\n\n    public DockerModelRunnerContainer(DockerImageName image) {\n        super(image);\n        withTarget(PORT, MODEL_RUNNER_ENDPOINT);\n        waitingFor(Wait.forHttp(\"/\").forResponsePredicate(res -> res.contains(\"The service is running\")));\n    }\n\n    @Override\n    protected void containerIsStarted(InspectContainerResponse containerInfo) {\n        if (this.model != null) {\n            logger().info(\"Pulling model: {}. Please be patient.\", this.model);\n\n            String url = getBaseEndpoint() + \"/models/create\";\n            String payload = String.format(\"{\\\"from\\\": \\\"%s\\\"}\", this.model);\n\n            try {\n                HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();\n                connection.setRequestMethod(\"POST\");\n                connection.setRequestProperty(\"Content-Type\", \"application/json\");\n                connection.setDoOutput(true);\n\n                try (OutputStream os = connection.getOutputStream()) {\n                    os.write(payload.getBytes());\n                    os.flush();\n                }\n\n                try (\n                    BufferedReader br = new BufferedReader(\n                        new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8)\n                    )\n                ) {\n                    while (br.readLine() != null) {}\n                }\n                connection.disconnect();\n            } catch (IOException e) {\n                logger().error(\"Failed to pull model {}: {}\", this.model, e);\n            }\n            logger().info(\"Finished pulling model: {}\", this.model);\n        }\n    }\n\n    public DockerModelRunnerContainer withModel(String model) {\n        this.model = model;\n        return this;\n    }\n\n    /**\n     * Returns the base endpoint URL for the Docker Model Runner service.\n     *\n     * @return the base URL in the format {@code http://<host>:<port>}\n     */\n    public String getBaseEndpoint() {\n        return \"http://\" + getHost() + \":\" + getMappedPort(PORT);\n    }\n\n    /**\n     * Returns the OpenAI-compatible API endpoint URL for the Docker Model Runner service.\n     *\n     * @return the OpenAI-compatible endpoint URL in the format {@code http://<host>:<port>/engines}\n     */\n    public String getOpenAIEndpoint() {\n        return getBaseEndpoint() + \"/engines\";\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/ExecConfig.java",
    "content": "package org.testcontainers.containers;\n\nimport lombok.Builder;\nimport lombok.Getter;\n\nimport java.util.Map;\n\n/**\n * Exec configuration.\n */\n@Builder\n@Getter\npublic class ExecConfig {\n\n    /**\n     * The command to run.\n     */\n    private String[] command;\n\n    /**\n     * The user to run the exec process.\n     */\n    private String user;\n\n    /**\n     * Key-value pairs of environment variables.\n     */\n    private Map<String, String> envVars;\n\n    /**\n     * The working directory for the exec process.\n     */\n    private String workDir;\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/ExecInContainerPattern.java",
    "content": "package org.testcontainers.containers;\n\nimport com.github.dockerjava.api.DockerClient;\nimport com.github.dockerjava.api.command.ExecCreateCmd;\nimport com.github.dockerjava.api.command.ExecCreateCmdResponse;\nimport com.github.dockerjava.api.command.InspectContainerResponse;\nimport com.github.dockerjava.api.exception.DockerException;\nimport lombok.experimental.UtilityClass;\nimport lombok.extern.slf4j.Slf4j;\nimport org.testcontainers.DockerClientFactory;\nimport org.testcontainers.containers.output.FrameConsumerResultCallback;\nimport org.testcontainers.containers.output.OutputFrame;\nimport org.testcontainers.containers.output.ToStringConsumer;\nimport org.testcontainers.utility.TestEnvironment;\n\nimport java.io.IOException;\nimport java.nio.charset.Charset;\nimport java.nio.charset.StandardCharsets;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.stream.Collectors;\n\n/**\n * Provides utility methods for executing commands in containers\n */\n@UtilityClass\n@Slf4j\npublic class ExecInContainerPattern {\n\n    /**\n     *\n     * @deprecated use {@link #execInContainer(DockerClient, InspectContainerResponse, String...)}\n     */\n    @Deprecated\n    public Container.ExecResult execInContainer(InspectContainerResponse containerInfo, String... command)\n        throws UnsupportedOperationException, IOException, InterruptedException {\n        DockerClient dockerClient = DockerClientFactory.instance().client();\n        return execInContainer(dockerClient, containerInfo, command);\n    }\n\n    /**\n     *\n     * @deprecated use {@link #execInContainer(DockerClient, InspectContainerResponse, Charset, String...)}\n     */\n    @Deprecated\n    public Container.ExecResult execInContainer(\n        InspectContainerResponse containerInfo,\n        Charset outputCharset,\n        String... command\n    ) throws UnsupportedOperationException, IOException, InterruptedException {\n        DockerClient dockerClient = DockerClientFactory.instance().client();\n        return execInContainerWithUser(dockerClient, containerInfo, outputCharset, null, command);\n    }\n\n    /**\n     * Run a command inside a running container, as though using \"docker exec\", and interpreting\n     * the output as UTF8.\n     * <p></p>\n     * @param dockerClient the {@link DockerClient}\n     * @param containerInfo the container info\n     * @param command the command to execute\n     * @see #execInContainerWithUser(DockerClient, InspectContainerResponse, String, String...)\n     */\n    public Container.ExecResult execInContainer(\n        DockerClient dockerClient,\n        InspectContainerResponse containerInfo,\n        String... command\n    ) throws UnsupportedOperationException, IOException, InterruptedException {\n        return execInContainerWithUser(dockerClient, containerInfo, StandardCharsets.UTF_8, null, command);\n    }\n\n    /**\n     * Run a command inside a running container, as though using \"docker exec\", and interpreting\n     * the output as UTF8.\n     * <p></p>\n     * @param dockerClient the {@link DockerClient}\n     * @param containerInfo the container info\n     * @param outputCharset the character set used to interpret the output.\n     * @param command the command to execute\n     * @see #execInContainerWithUser(DockerClient, InspectContainerResponse, Charset, String, String...)\n     */\n    public Container.ExecResult execInContainer(\n        DockerClient dockerClient,\n        InspectContainerResponse containerInfo,\n        Charset outputCharset,\n        String... command\n    ) throws UnsupportedOperationException, IOException, InterruptedException {\n        return execInContainerWithUser(dockerClient, containerInfo, outputCharset, null, command);\n    }\n\n    /**\n     * Run a command inside a running container as a given user, as using \"docker exec -u user\" and\n     * interpreting the output as UTF8.\n     * <p>\n     * This functionality is not available on a docker daemon running the older \"lxc\" execution driver. At\n     * the time of writing, CircleCI was using this driver.\n     * @param dockerClient the {@link DockerClient}\n     * @param containerInfo the container info\n     * @param user the user to run the command with, optional\n     * @param command the command to execute\n     * @see #execInContainerWithUser(DockerClient, InspectContainerResponse, Charset, String,\n     *     String...)\n     * @deprecated use {@link #execInContainer(DockerClient, InspectContainerResponse, ExecConfig)}\n     */\n    @Deprecated\n    public Container.ExecResult execInContainerWithUser(\n        DockerClient dockerClient,\n        InspectContainerResponse containerInfo,\n        String user,\n        String... command\n    ) throws UnsupportedOperationException, IOException, InterruptedException {\n        return execInContainerWithUser(dockerClient, containerInfo, StandardCharsets.UTF_8, user, command);\n    }\n\n    /**\n     * Run a command inside a running container as a given user, as using \"docker exec -u user\".\n     * <p>\n     * This functionality is not available on a docker daemon running the older \"lxc\" execution\n     * driver. At the time of writing, CircleCI was using this driver.\n     * @param dockerClient the {@link DockerClient}\n     * @param containerInfo the container info\n     * @param outputCharset the character set used to interpret the output.\n     * @param user the user to run the command with, optional\n     * @param command the parts of the command to run\n     * @return the result of execution\n     * @throws IOException if there's an issue communicating with Docker\n     * @throws InterruptedException if the thread waiting for the response is interrupted\n     * @throws UnsupportedOperationException if the docker daemon you're connecting to doesn't support \"exec\".\n     * @deprecated use {@link #execInContainer(DockerClient, InspectContainerResponse, Charset, ExecConfig)}\n     */\n    @Deprecated\n    public Container.ExecResult execInContainerWithUser(\n        DockerClient dockerClient,\n        InspectContainerResponse containerInfo,\n        Charset outputCharset,\n        String user,\n        String... command\n    ) throws UnsupportedOperationException, IOException, InterruptedException {\n        return execInContainer(\n            dockerClient,\n            containerInfo,\n            outputCharset,\n            ExecConfig.builder().user(user).command(command).build()\n        );\n    }\n\n    /**\n     * Run a command inside a running container as a given user, as using \"docker exec -u user\".\n     * <p>\n     * This functionality is not available on a docker daemon running the older \"lxc\" execution\n     * driver. At the time of writing, CircleCI was using this driver.\n     * @param dockerClient the {@link DockerClient}\n     * @param containerInfo the container info\n     * @param execConfig the exec configuration\n     * @return the result of execution\n     * @throws IOException if there's an issue communicating with Docker\n     * @throws InterruptedException if the thread waiting for the response is interrupted\n     * @throws UnsupportedOperationException if the docker daemon you're connecting to doesn't support \"exec\".\n     */\n    public Container.ExecResult execInContainer(\n        DockerClient dockerClient,\n        InspectContainerResponse containerInfo,\n        ExecConfig execConfig\n    ) throws UnsupportedOperationException, IOException, InterruptedException {\n        return execInContainer(dockerClient, containerInfo, StandardCharsets.UTF_8, execConfig);\n    }\n\n    /**\n     * Run a command inside a running container as a given user, as using \"docker exec -u user\".\n     * <p>\n     * This functionality is not available on a docker daemon running the older \"lxc\" execution\n     * driver. At the time of writing, CircleCI was using this driver.\n     * @param dockerClient the {@link DockerClient}\n     * @param containerInfo the container info\n     * @param outputCharset the character set used to interpret the output.\n     * @param execConfig the exec configuration\n     * @return the result of execution\n     * @throws IOException if there's an issue communicating with Docker\n     * @throws InterruptedException if the thread waiting for the response is interrupted\n     * @throws UnsupportedOperationException if the docker daemon you're connecting to doesn't support \"exec\".\n     */\n    public Container.ExecResult execInContainer(\n        DockerClient dockerClient,\n        InspectContainerResponse containerInfo,\n        Charset outputCharset,\n        ExecConfig execConfig\n    ) throws UnsupportedOperationException, IOException, InterruptedException {\n        if (!TestEnvironment.dockerExecutionDriverSupportsExec()) {\n            // at time of writing, this is the expected result in CircleCI.\n            throw new UnsupportedOperationException(\n                \"Your docker daemon is running the \\\"lxc\\\" driver, which doesn't support \\\"docker exec\\\".\"\n            );\n        }\n\n        if (!isRunning(containerInfo)) {\n            throw new IllegalStateException(\"execInContainer can only be used while the Container is running\");\n        }\n\n        String containerId = containerInfo.getId();\n        String containerName = containerInfo.getName();\n\n        String[] command = execConfig.getCommand();\n        log.debug(\"{}: Running \\\"exec\\\" command: {}\", containerName, String.join(\" \", command));\n        final ExecCreateCmd execCreateCmd = dockerClient\n            .execCreateCmd(containerId)\n            .withAttachStdout(true)\n            .withAttachStderr(true)\n            .withCmd(command);\n\n        String user = execConfig.getUser();\n        if (user != null && !user.isEmpty()) {\n            log.debug(\"{}: Running \\\"exec\\\" command with user: {}\", containerName, user);\n            execCreateCmd.withUser(user);\n        }\n\n        String workDir = execConfig.getWorkDir();\n        if (workDir != null && !workDir.isEmpty()) {\n            log.debug(\"{}: Running \\\"exec\\\" command inside workingDir: {}\", containerName, workDir);\n            execCreateCmd.withWorkingDir(workDir);\n        }\n\n        Map<String, String> envVars = execConfig.getEnvVars();\n        if (envVars != null && !envVars.isEmpty()) {\n            List<String> envVarList = envVars\n                .entrySet()\n                .stream()\n                .map(e -> e.getKey() + \"=\" + e.getValue())\n                .collect(Collectors.toList());\n            execCreateCmd.withEnv(envVarList);\n        }\n\n        final ExecCreateCmdResponse execCreateCmdResponse = execCreateCmd.exec();\n\n        final ToStringConsumer stdoutConsumer = new ToStringConsumer();\n        final ToStringConsumer stderrConsumer = new ToStringConsumer();\n\n        try (FrameConsumerResultCallback callback = new FrameConsumerResultCallback()) {\n            callback.addConsumer(OutputFrame.OutputType.STDOUT, stdoutConsumer);\n            callback.addConsumer(OutputFrame.OutputType.STDERR, stderrConsumer);\n\n            dockerClient.execStartCmd(execCreateCmdResponse.getId()).exec(callback).awaitCompletion();\n        }\n        int exitCode = dockerClient.inspectExecCmd(execCreateCmdResponse.getId()).exec().getExitCodeLong().intValue();\n\n        final Container.ExecResult result = new Container.ExecResult(\n            exitCode,\n            stdoutConsumer.toString(outputCharset),\n            stderrConsumer.toString(outputCharset)\n        );\n\n        log.trace(\"{}: stdout: {}\", containerName, result.getStdout());\n        log.trace(\"{}: stderr: {}\", containerName, result.getStderr());\n        return result;\n    }\n\n    private boolean isRunning(InspectContainerResponse containerInfo) {\n        try {\n            return containerInfo != null && containerInfo.getState().getRunning();\n        } catch (DockerException e) {\n            return false;\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/FixedHostPortGenericContainer.java",
    "content": "package org.testcontainers.containers;\n\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * Variant of {@link GenericContainer} that allows a fixed port on the docker host to be mapped to a container port.\n *\n * <p><strong>Normally this should not be required, and Docker should be allowed to choose a free host port instead</strong>.\n * However, when a fixed host port is absolutely required for some reason, this class can be used to set it.</p>\n *\n * <p>Callers are responsible for ensuring that this fixed port is actually available; failure will occur if it is\n * not available - which could manifest as flaky or unstable tests.</p>\n */\npublic class FixedHostPortGenericContainer<SELF extends FixedHostPortGenericContainer<SELF>>\n    extends GenericContainer<SELF> {\n\n    /**\n     * @deprecated it is highly recommended that {@link FixedHostPortGenericContainer} not be used, as it risks port conflicts.\n     */\n    @Deprecated\n    public FixedHostPortGenericContainer(@NotNull String dockerImageName) {\n        super(dockerImageName);\n    }\n\n    /**\n     * Bind a fixed TCP port on the docker host to a container port\n     * @param hostPort          a port on the docker host, which must be available\n     * @param containerPort     a port in the container\n     * @return                  this container\n     */\n    public SELF withFixedExposedPort(int hostPort, int containerPort) {\n        return withFixedExposedPort(hostPort, containerPort, InternetProtocol.TCP);\n    }\n\n    /**\n     * Bind a fixed port on the docker host to a container port\n     * @param hostPort          a port on the docker host, which must be available\n     * @param containerPort     a port in the container\n     * @param protocol          an internet protocol (tcp or udp)\n     * @return                  this container\n     */\n    public SELF withFixedExposedPort(int hostPort, int containerPort, InternetProtocol protocol) {\n        super.addFixedExposedPort(hostPort, containerPort, protocol);\n\n        return self();\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/FutureContainer.java",
    "content": "package org.testcontainers.containers;\n\nimport lombok.Data;\nimport org.testcontainers.containers.traits.LinkableContainer;\n\n/**\n * A container that may not have been launched yet.\n */\n@Data\npublic class FutureContainer implements LinkableContainer {\n\n    private final String containerName;\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/GenericContainer.java",
    "content": "package org.testcontainers.containers;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.MapperFeature;\nimport com.fasterxml.jackson.databind.SerializationFeature;\nimport com.github.dockerjava.api.DockerClient;\nimport com.github.dockerjava.api.command.CreateContainerCmd;\nimport com.github.dockerjava.api.command.InspectContainerResponse;\nimport com.github.dockerjava.api.exception.NotFoundException;\nimport com.github.dockerjava.api.model.Bind;\nimport com.github.dockerjava.api.model.ContainerNetwork;\nimport com.github.dockerjava.api.model.ExposedPort;\nimport com.github.dockerjava.api.model.HostConfig;\nimport com.github.dockerjava.api.model.Link;\nimport com.github.dockerjava.api.model.PortBinding;\nimport com.github.dockerjava.api.model.Ports;\nimport com.github.dockerjava.api.model.Volume;\nimport com.github.dockerjava.api.model.VolumesFrom;\nimport com.github.dockerjava.core.DefaultDockerClientConfig;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Strings;\nimport com.google.common.collect.ImmutableMap;\nimport com.google.common.collect.Lists;\nimport com.google.common.hash.Hashing;\nimport lombok.AccessLevel;\nimport lombok.Data;\nimport lombok.Getter;\nimport lombok.NonNull;\nimport lombok.Setter;\nimport lombok.SneakyThrows;\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.commons.lang3.SystemUtils;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\nimport org.rnorth.ducttape.unreliables.Unreliables;\nimport org.slf4j.Logger;\nimport org.testcontainers.DockerClientFactory;\nimport org.testcontainers.UnstableAPI;\nimport org.testcontainers.containers.output.OutputFrame;\nimport org.testcontainers.containers.startupcheck.IsRunningStartupCheckStrategy;\nimport org.testcontainers.containers.startupcheck.MinimumDurationRunningStartupCheckStrategy;\nimport org.testcontainers.containers.startupcheck.StartupCheckStrategy;\nimport org.testcontainers.containers.traits.LinkableContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.containers.wait.strategy.WaitStrategy;\nimport org.testcontainers.containers.wait.strategy.WaitStrategyTarget;\nimport org.testcontainers.core.CreateContainerCmdModifier;\nimport org.testcontainers.images.ImagePullPolicy;\nimport org.testcontainers.images.RemoteDockerImage;\nimport org.testcontainers.images.builder.Transferable;\nimport org.testcontainers.lifecycle.Startable;\nimport org.testcontainers.lifecycle.Startables;\nimport org.testcontainers.utility.Base58;\nimport org.testcontainers.utility.CommandLine;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.DockerLoggerFactory;\nimport org.testcontainers.utility.DockerMachineClient;\nimport org.testcontainers.utility.DynamicPollInterval;\nimport org.testcontainers.utility.MountableFile;\nimport org.testcontainers.utility.PathUtils;\nimport org.testcontainers.utility.ResourceReaper;\nimport org.testcontainers.utility.TestcontainersConfiguration;\n\nimport java.io.File;\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\nimport java.lang.reflect.UndeclaredThrowableException;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Iterator;\nimport java.util.LinkedHashMap;\nimport java.util.LinkedHashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.ServiceLoader;\nimport java.util.Set;\nimport java.util.UUID;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.function.Consumer;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\nimport java.util.zip.Adler32;\nimport java.util.zip.Checksum;\n\nimport static org.awaitility.Awaitility.await;\n\n/**\n * Base class for that allows a container to be launched and controlled.\n */\n@Data\npublic class GenericContainer<SELF extends GenericContainer<SELF>>\n    implements Container<SELF>, AutoCloseable, WaitStrategyTarget, Startable {\n\n    public static final int CONTAINER_RUNNING_TIMEOUT_SEC = 30;\n\n    public static final String INTERNAL_HOST_HOSTNAME = \"host.testcontainers.internal\";\n\n    static final String HASH_LABEL = \"org.testcontainers.hash\";\n\n    static final String COPIED_FILES_HASH_LABEL = \"org.testcontainers.copied_files.hash\";\n\n    /*\n     * Default settings\n     */\n    @NonNull\n    private List<String> extraHosts = new ArrayList<>();\n\n    @NonNull\n    private RemoteDockerImage image;\n\n    @NonNull\n    private List<VolumesFrom> volumesFroms = new ArrayList<>();\n\n    /**\n     * @deprecated Links are deprecated (see <a href=\"https://github.com/testcontainers/testcontainers-java/issues/465\">#465</a>). Please use {@link Network} features instead.\n     */\n    @NonNull\n    @Deprecated\n    private Map<String, LinkableContainer> linkedContainers = new HashMap<>();\n\n    private StartupCheckStrategy startupCheckStrategy = new IsRunningStartupCheckStrategy();\n\n    private int startupAttempts = 1;\n\n    @Nullable\n    private String workingDirectory = null;\n\n    /**\n     * The shared memory size to use when starting the container.\n     * This value is in bytes.\n     */\n    @Nullable\n    private Long shmSize;\n\n    // Maintain order in which entries are added, as earlier target location may be a prefix of a later location.\n    @Deprecated\n    private Map<MountableFile, String> copyToFileContainerPathMap = new LinkedHashMap<>();\n\n    // Maintain order in which entries are added, as earlier target location may be a prefix of a later location.\n    @Setter(AccessLevel.NONE)\n    @Getter(AccessLevel.MODULE)\n    @VisibleForTesting\n    private Map<Transferable, String> copyToTransferableContainerPathMap = new LinkedHashMap<>();\n\n    protected final Set<Startable> dependencies = new HashSet<>();\n\n    /**\n     * Unique instance of DockerClient for use by this container object.\n     * We use {@link DockerClientFactory#lazyClient()} here to avoid eager client creation\n     */\n    @Setter(AccessLevel.NONE)\n    protected DockerClient dockerClient = DockerClientFactory.lazyClient();\n\n    /**\n     * Set during container startup\n     */\n    @Setter(AccessLevel.NONE)\n    @VisibleForTesting\n    String containerId;\n\n    @Setter(AccessLevel.NONE)\n    private InspectContainerResponse containerInfo;\n\n    static WaitStrategy DEFAULT_WAIT_STRATEGY = Wait.defaultWaitStrategy();\n\n    /**\n     * The approach to determine if the container is ready.\n     */\n    @NonNull\n    protected WaitStrategy waitStrategy = DEFAULT_WAIT_STRATEGY;\n\n    private List<Consumer<OutputFrame>> logConsumers = new ArrayList<>();\n\n    private static final Set<String> AVAILABLE_IMAGE_NAME_CACHE = new HashSet<>();\n\n    @Nullable\n    private Map<String, String> tmpFsMapping;\n\n    @Setter(AccessLevel.NONE)\n    private boolean shouldBeReused = false;\n\n    private boolean hostAccessible = false;\n\n    private final Set<CreateContainerCmdModifier> createContainerCmdModifiers = loadCreateContainerCmdCustomizers();\n\n    private ContainerDef containerDef;\n\n    ContainerDef createContainerDef() {\n        return new ContainerDef();\n    }\n\n    ContainerDef getContainerDef() {\n        return this.containerDef;\n    }\n\n    private Set<CreateContainerCmdModifier> loadCreateContainerCmdCustomizers() {\n        ServiceLoader<CreateContainerCmdModifier> containerCmdCustomizers = ServiceLoader.load(\n            CreateContainerCmdModifier.class\n        );\n        Set<CreateContainerCmdModifier> loadedCustomizers = new LinkedHashSet<>();\n        for (CreateContainerCmdModifier customizer : containerCmdCustomizers) {\n            loadedCustomizers.add(customizer);\n        }\n        return loadedCustomizers;\n    }\n\n    public GenericContainer(@NonNull final DockerImageName dockerImageName) {\n        this(new RemoteDockerImage(dockerImageName));\n    }\n\n    public GenericContainer(@NonNull final RemoteDockerImage image) {\n        this.image = image;\n        this.containerDef = createContainerDef();\n        this.containerDef.addNetworkAlias(\"tc-\" + Base58.randomString(8));\n        this.containerDef.setImage(image);\n    }\n\n    /**\n     * @deprecated use {@link #GenericContainer(DockerImageName)} instead\n     */\n    @Deprecated\n    public GenericContainer() {\n        this(TestcontainersConfiguration.getInstance().getTinyImage());\n    }\n\n    public GenericContainer(@NonNull final String dockerImageName) {\n        this(new RemoteDockerImage(DockerImageName.parse(dockerImageName)));\n    }\n\n    public GenericContainer(@NonNull final Future<String> image) {\n        this(new RemoteDockerImage(image));\n    }\n\n    GenericContainer(@NonNull final ContainerDef containerDef) {\n        this.image = containerDef.getImage();\n        this.containerDef = containerDef;\n    }\n\n    public void setImage(Future<String> image) {\n        this.image = new RemoteDockerImage(image);\n        this.containerDef.setImage(new RemoteDockerImage(image));\n    }\n\n    @Override\n    public List<Integer> getExposedPorts() {\n        List<Integer> exposedPorts = new ArrayList<>();\n        for (ExposedPort exposedPort : this.containerDef.getExposedPorts()) {\n            exposedPorts.add(exposedPort.getPort());\n        }\n        return exposedPorts;\n    }\n\n    @Override\n    public void setExposedPorts(List<Integer> exposedPorts) {\n        this.containerDef.exposedPorts.clear();\n        for (Integer exposedPort : exposedPorts) {\n            this.containerDef.addExposedTcpPort(exposedPort);\n        }\n    }\n\n    /**\n     * @see #dependsOn(Iterable)\n     */\n    public SELF dependsOn(Startable... startables) {\n        Collections.addAll(dependencies, startables);\n        return self();\n    }\n\n    /**\n     * @see #dependsOn(Iterable)\n     */\n    public SELF dependsOn(List<? extends Startable> startables) {\n        return this.dependsOn((Iterable<? extends Startable>) startables);\n    }\n\n    /**\n     * Delays this container's creation and start until provided {@link Startable}s start first.\n     * Note that the circular dependencies are not supported.\n     *\n     * @param startables a list of {@link Startable} to depend on\n     * @see Startables#deepStart(Iterable)\n     */\n    public SELF dependsOn(Iterable<? extends Startable> startables) {\n        startables.forEach(dependencies::add);\n        return self();\n    }\n\n    public String getContainerId() {\n        return containerId;\n    }\n\n    /**\n     * Starts the container using docker, pulling an image if necessary.\n     */\n    @Override\n    @SneakyThrows({ InterruptedException.class, ExecutionException.class })\n    public void start() {\n        if (containerId != null) {\n            return;\n        }\n        Startables.deepStart(dependencies).get();\n        // trigger LazyDockerClient's resolve so that we fail fast here and not in getDockerImageName()\n        dockerClient.authConfig();\n        doStart();\n    }\n\n    protected void doStart() {\n        try {\n            if (this.waitStrategy != DEFAULT_WAIT_STRATEGY) {\n                this.containerDef.setWaitStrategy(this.waitStrategy);\n            }\n\n            configure();\n\n            logger().debug(\"Starting container: {}\", getDockerImageName());\n\n            AtomicInteger attempt = new AtomicInteger(0);\n            Unreliables.retryUntilSuccess(\n                startupAttempts,\n                () -> {\n                    logger()\n                        .debug(\n                            \"Trying to start container: {} (attempt {}/{})\",\n                            getDockerImageName(),\n                            attempt.incrementAndGet(),\n                            startupAttempts\n                        );\n                    tryStart();\n                    return true;\n                }\n            );\n        } catch (Exception e) {\n            throw new ContainerLaunchException(\"Container startup failed for image \" + getDockerImageName(), e);\n        }\n    }\n\n    @UnstableAPI\n    @SneakyThrows\n    protected boolean canBeReused() {\n        for (Class<?> type = getClass(); type != GenericContainer.class; type = type.getSuperclass()) {\n            try {\n                Method method = type.getDeclaredMethod(\"containerIsCreated\", String.class);\n                if (method.getDeclaringClass() != GenericContainer.class) {\n                    logger().warn(\"{} can't be reused because it overrides {}\", getClass(), method.getName());\n                    return false;\n                }\n            } catch (NoSuchMethodException | NoClassDefFoundError e) {\n                // ignore\n            }\n        }\n\n        return true;\n    }\n\n    private void tryStart() {\n        try {\n            String dockerImageName = getDockerImageName();\n            logger().debug(\"Starting container: {}\", dockerImageName);\n\n            Instant startedAt = Instant.now();\n            logger().info(\"Creating container for image: {}\", dockerImageName);\n            CreateContainerCmd createCommand = dockerClient.createContainerCmd(dockerImageName);\n            applyConfiguration(createCommand);\n\n            createCommand.getLabels().putAll(DockerClientFactory.DEFAULT_LABELS);\n\n            boolean reused = false;\n            final boolean reusable;\n            if (shouldBeReused) {\n                if (!canBeReused()) {\n                    throw new IllegalStateException(\"This container does not support reuse\");\n                }\n\n                if (TestcontainersConfiguration.getInstance().environmentSupportsReuse()) {\n                    createCommand\n                        .getLabels()\n                        .put(COPIED_FILES_HASH_LABEL, Long.toHexString(hashCopiedFiles().getValue()));\n\n                    String hash = hash(createCommand);\n\n                    containerId = findContainerForReuse(hash).orElse(null);\n\n                    if (containerId != null) {\n                        logger().info(\"Reusing container with ID: {} and hash: {}\", containerId, hash);\n                        reused = true;\n                    } else {\n                        logger().debug(\"Can't find a reusable running container with hash: {}\", hash);\n\n                        createCommand.getLabels().put(HASH_LABEL, hash);\n                    }\n                    reusable = true;\n                } else {\n                    logger()\n                        .warn(\n                            \"\" +\n                            \"Reuse was requested but the environment does not support the reuse of containers\\n\" +\n                            \"To enable reuse of containers, you must set 'testcontainers.reuse.enable=true' in a file located at {}\",\n                            Paths.get(System.getProperty(\"user.home\"), \".testcontainers.properties\")\n                        );\n                    reusable = false;\n                }\n            } else {\n                reusable = false;\n            }\n\n            if (!reusable) {\n                //noinspection deprecation\n                createCommand = ResourceReaper.instance().register(this, createCommand);\n            }\n\n            if (!reused) {\n                containerId = createCommand.exec().getId();\n\n                // TODO use single \"copy\" invocation (and calculate an hash of the resulting tar archive)\n                copyToFileContainerPathMap.forEach(this::copyFileToContainer);\n\n                copyToTransferableContainerPathMap.forEach(this::copyFileToContainer);\n            }\n\n            connectToPortForwardingNetwork(createCommand.getNetworkMode());\n\n            if (!reused) {\n                containerIsCreated(containerId);\n\n                logger().info(\"Container {} is starting: {}\", dockerImageName, containerId);\n                dockerClient.startContainerCmd(containerId).exec();\n            } else {\n                logger().info(\"Reusing existing container ({}) and not creating a new one\", containerId);\n            }\n\n            // For all registered output consumers, start following as close to container startup as possible\n            this.logConsumers.forEach(this::followOutput);\n\n            // Wait until inspect container returns the mapped ports\n            containerInfo =\n                await()\n                    .atMost(5, TimeUnit.SECONDS)\n                    .pollInterval(DynamicPollInterval.ofMillis(50))\n                    .pollInSameThread()\n                    .until(\n                        () -> dockerClient.inspectContainerCmd(containerId).exec(),\n                        inspectContainerResponse -> {\n                            Set<ExposedPort> exposedAndMappedPorts = inspectContainerResponse\n                                .getNetworkSettings()\n                                .getPorts()\n                                .getBindings()\n                                .entrySet()\n                                .stream()\n                                .filter(it -> Objects.nonNull(it.getValue())) // filter out exposed but not yet mapped\n                                .map(Entry::getKey)\n                                .collect(Collectors.toSet());\n\n                            return exposedAndMappedPorts.containsAll(this.containerDef.getExposedPorts());\n                        }\n                    );\n\n            String emulationWarning = checkForEmulation();\n            if (emulationWarning != null) {\n                logger().warn(emulationWarning);\n            }\n\n            // Tell subclasses that we're starting\n            containerIsStarting(containerInfo, reused);\n\n            // Wait until the container has reached the desired running state\n            if (!this.startupCheckStrategy.waitUntilStartupSuccessful(this)) {\n                // Bail out, don't wait for the port to start listening.\n                // (Exception thrown here will be caught below and wrapped)\n                throw new IllegalStateException(\"Container did not start correctly.\");\n            }\n\n            // Wait until the process within the container has become ready for use (e.g. listening on network, log message emitted, etc).\n            try {\n                waitUntilContainerStarted();\n            } catch (Exception e) {\n                logger().debug(\"Wait strategy threw an exception\", e);\n                InspectContainerResponse inspectContainerResponse = null;\n                try {\n                    inspectContainerResponse = dockerClient.inspectContainerCmd(containerId).exec();\n                } catch (NotFoundException notFoundException) {\n                    logger().debug(\"Container {} not found\", containerId, notFoundException);\n                }\n\n                if (inspectContainerResponse == null) {\n                    throw new IllegalStateException(\"Wait strategy failed. Container is removed\", e);\n                }\n\n                InspectContainerResponse.ContainerState state = inspectContainerResponse.getState();\n                if (Boolean.TRUE.equals(state.getDead())) {\n                    throw new IllegalStateException(\"Wait strategy failed. Container is dead\", e);\n                }\n\n                if (Boolean.TRUE.equals(state.getOOMKilled())) {\n                    throw new IllegalStateException(\n                        \"Wait strategy failed. Container crashed with out-of-memory (OOMKilled)\",\n                        e\n                    );\n                }\n\n                String error = state.getError();\n                if (!StringUtils.isBlank(error)) {\n                    throw new IllegalStateException(\"Wait strategy failed. Container crashed: \" + error, e);\n                }\n\n                if (!Boolean.TRUE.equals(state.getRunning())) {\n                    throw new IllegalStateException(\n                        \"Wait strategy failed. Container exited with code \" + state.getExitCode(),\n                        e\n                    );\n                }\n\n                throw e;\n            }\n\n            logger().info(\"Container {} started in {}\", dockerImageName, Duration.between(startedAt, Instant.now()));\n            containerIsStarted(containerInfo, reused);\n        } catch (Exception e) {\n            if (e instanceof UndeclaredThrowableException && e.getCause() instanceof Exception) {\n                e = (Exception) e.getCause();\n            }\n            if (e instanceof InvocationTargetException && e.getCause() instanceof Exception) {\n                e = (Exception) e.getCause();\n            }\n            logger().error(\"Could not start container\", e);\n\n            if (containerId != null) {\n                // Log output if startup failed, either due to a container failure or exception (including timeout)\n                final String containerLogs = getLogs();\n\n                if (containerLogs.length() > 0) {\n                    logger().error(\"Log output from the failed container:\\n{}\", getLogs());\n                } else {\n                    logger().error(\"There are no stdout/stderr logs available for the failed container\");\n                }\n                stop();\n            }\n\n            throw new ContainerLaunchException(\"Could not create/start container\", e);\n        }\n    }\n\n    @VisibleForTesting\n    Checksum hashCopiedFiles() {\n        Checksum checksum = new Adler32();\n        Stream\n            .of(copyToFileContainerPathMap, copyToTransferableContainerPathMap)\n            .flatMap(it -> it.entrySet().stream())\n            .sorted(Entry.comparingByValue())\n            .forEach(entry -> {\n                byte[] pathBytes = entry.getValue().getBytes();\n                // Add path to the hash\n                checksum.update(pathBytes, 0, pathBytes.length);\n\n                entry.getKey().updateChecksum(checksum);\n            });\n        return checksum;\n    }\n\n    @UnstableAPI\n    @SneakyThrows(JsonProcessingException.class)\n    final String hash(CreateContainerCmd createCommand) {\n        DefaultDockerClientConfig dockerClientConfig = DefaultDockerClientConfig.createDefaultConfigBuilder().build();\n\n        byte[] commandJson = dockerClientConfig\n            .getObjectMapper()\n            .copy()\n            .enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY)\n            .enable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS)\n            .writeValueAsBytes(createCommand);\n\n        // TODO add Testcontainers' version to the hash\n        return Hashing.sha1().hashBytes(commandJson).toString();\n    }\n\n    @VisibleForTesting\n    Optional<String> findContainerForReuse(String hash) {\n        // TODO locking\n        return dockerClient\n            .listContainersCmd()\n            .withLabelFilter(ImmutableMap.of(HASH_LABEL, hash))\n            .withLimit(1)\n            .withStatusFilter(Arrays.asList(\"running\"))\n            .exec()\n            .stream()\n            .findAny()\n            .map(it -> it.getId());\n    }\n\n    /**\n     * Set any custom settings for the create command such as shared memory size.\n     */\n    private HostConfig buildHostConfig(HostConfig config) {\n        if (shmSize != null) {\n            config.withShmSize(shmSize);\n        }\n        if (tmpFsMapping != null) {\n            config.withTmpFs(tmpFsMapping);\n        }\n        return config;\n    }\n\n    private void connectToPortForwardingNetwork(String networkMode) {\n        PortForwardingContainer.INSTANCE\n            .getNetwork()\n            .map(ContainerNetwork::getNetworkID)\n            .ifPresent(networkId -> {\n                if (!Arrays.asList(networkId, \"none\", \"host\").contains(networkMode)) {\n                    com.github.dockerjava.api.model.Network network =\n                        this.dockerClient.inspectNetworkCmd().withNetworkId(networkId).exec();\n                    if (!network.getContainers().containsKey(this.containerId)) {\n                        this.dockerClient.connectToNetworkCmd()\n                            .withContainerId(this.containerId)\n                            .withNetworkId(networkId)\n                            .exec();\n                    }\n                }\n            });\n    }\n\n    /**\n     * Kill and remove the container.\n     */\n    @Override\n    public void stop() {\n        if (containerId == null) {\n            return;\n        }\n\n        try {\n            String imageName;\n\n            try {\n                imageName = getDockerImageName();\n            } catch (Exception e) {\n                imageName = \"<unknown>\";\n            }\n\n            containerIsStopping(containerInfo);\n            ResourceReaper.instance().stopAndRemoveContainer(containerId, imageName);\n            containerIsStopped(containerInfo);\n        } finally {\n            containerId = null;\n            containerInfo = null;\n        }\n    }\n\n    /**\n     * Provide a logger that references the docker image name.\n     *\n     * @return a logger that references the docker image name\n     */\n    protected Logger logger() {\n        return DockerLoggerFactory.getLogger(this.getDockerImageName());\n    }\n\n    /**\n     * Creates a directory on the local filesystem which will be mounted as a volume for the container.\n     *\n     * @param temporary is the volume directory temporary? If true, the directory will be deleted on JVM shutdown.\n     * @return path to the volume directory\n     */\n    @Deprecated\n    protected Path createVolumeDirectory(boolean temporary) {\n        Path directory = new File(\".tmp-volume-\" + UUID.randomUUID()).toPath();\n        PathUtils.mkdirp(directory);\n\n        if (temporary) {\n            Runtime\n                .getRuntime()\n                .addShutdownHook(\n                    new Thread(\n                        DockerClientFactory.TESTCONTAINERS_THREAD_GROUP,\n                        () -> {\n                            PathUtils.recursiveDeleteDir(directory);\n                        }\n                    )\n                );\n        }\n\n        return directory;\n    }\n\n    protected void configure() {}\n\n    @SuppressWarnings({ \"EmptyMethod\", \"UnusedParameters\" })\n    protected void containerIsCreated(String containerId) {}\n\n    @SuppressWarnings({ \"EmptyMethod\", \"UnusedParameters\" })\n    protected void containerIsStarting(InspectContainerResponse containerInfo) {}\n\n    @SuppressWarnings({ \"EmptyMethod\", \"UnusedParameters\" })\n    @UnstableAPI\n    protected void containerIsStarting(InspectContainerResponse containerInfo, boolean reused) {\n        containerIsStarting(containerInfo);\n    }\n\n    @SuppressWarnings({ \"EmptyMethod\", \"UnusedParameters\" })\n    protected void containerIsStarted(InspectContainerResponse containerInfo) {}\n\n    @SuppressWarnings({ \"EmptyMethod\", \"UnusedParameters\" })\n    @UnstableAPI\n    protected void containerIsStarted(InspectContainerResponse containerInfo, boolean reused) {\n        containerIsStarted(containerInfo);\n    }\n\n    /**\n     * A hook that is executed before the container is stopped with {@link #stop()}.\n     * Warning! This hook won't be executed if the container is terminated during\n     * the JVM's shutdown hook or by Ryuk.\n     */\n    @SuppressWarnings({ \"EmptyMethod\", \"UnusedParameters\" })\n    protected void containerIsStopping(InspectContainerResponse containerInfo) {}\n\n    /**\n     * A hook that is executed after the container is stopped with {@link #stop()}.\n     * Warning! This hook won't be executed if the container is terminated during\n     * the JVM's shutdown hook or by Ryuk.\n     */\n    @SuppressWarnings({ \"EmptyMethod\", \"UnusedParameters\" })\n    protected void containerIsStopped(InspectContainerResponse containerInfo) {}\n\n    /**\n     * @return the port on which to check if the container is ready\n     * @deprecated see {@link GenericContainer#getLivenessCheckPorts()} for replacement\n     */\n    @Deprecated\n    protected Integer getLivenessCheckPort() {\n        // legacy implementation for backwards compatibility\n        Iterator<ExposedPort> exposedPortsIterator = this.containerDef.getExposedPorts().iterator();\n        if (exposedPortsIterator.hasNext()) {\n            return getMappedPort(exposedPortsIterator.next().getPort());\n        } else if (!this.containerDef.getPortBindings().isEmpty()) {\n            return Integer.valueOf(\n                this.containerDef.getPortBindings().iterator().next().getBinding().getHostPortSpec()\n            );\n        } else {\n            return null;\n        }\n    }\n\n    /**\n     * @return the ports on which to check if the container is ready\n     * @deprecated use {@link #getLivenessCheckPortNumbers()} instead\n     */\n    @NotNull\n    @NonNull\n    @Deprecated\n    protected Set<Integer> getLivenessCheckPorts() {\n        final Set<Integer> result = WaitStrategyTarget.super.getLivenessCheckPortNumbers();\n\n        // for backwards compatibility\n        if (this.getLivenessCheckPort() != null) {\n            result.add(this.getLivenessCheckPort());\n        }\n\n        return result;\n    }\n\n    @Override\n    public Set<Integer> getLivenessCheckPortNumbers() {\n        return this.getLivenessCheckPorts();\n    }\n\n    private void applyConfiguration(CreateContainerCmd createCommand) {\n        this.containerDef.applyTo(createCommand);\n        buildHostConfig(createCommand.getHostConfig());\n\n        VolumesFrom[] volumesFromsArray = volumesFroms.stream().toArray(VolumesFrom[]::new);\n        createCommand.withVolumesFrom(volumesFromsArray);\n\n        Set<Link> allLinks = new HashSet<>();\n        Set<String> allLinkedContainerNetworks = new HashSet<>();\n        for (Entry<String, LinkableContainer> linkEntries : linkedContainers.entrySet()) {\n            String alias = linkEntries.getKey();\n            LinkableContainer linkableContainer = linkEntries.getValue();\n\n            Set<Link> links = findLinksFromThisContainer(alias, linkableContainer);\n            allLinks.addAll(links);\n\n            if (allLinks.size() == 0) {\n                throw new ContainerLaunchException(\n                    \"Aborting attempt to link to container \" +\n                    linkableContainer.getContainerName() +\n                    \" as it is not running\"\n                );\n            }\n\n            Set<String> linkedContainerNetworks = findAllNetworksForLinkedContainers(linkableContainer);\n            allLinkedContainerNetworks.addAll(linkedContainerNetworks);\n        }\n\n        createCommand.withLinks(allLinks.toArray(new Link[allLinks.size()]));\n\n        allLinkedContainerNetworks.remove(\"bridge\");\n        if (allLinkedContainerNetworks.size() > 1) {\n            logger()\n                .warn(\n                    \"Container needs to be on more than one custom network to link to other \" +\n                    \"containers - this is not currently supported. Required networks are: {}\",\n                    allLinkedContainerNetworks\n                );\n        }\n\n        Optional<String> networkForLinks = allLinkedContainerNetworks.stream().findFirst();\n        if (networkForLinks.isPresent()) {\n            logger().debug(\"Associating container with network: {}\", networkForLinks.get());\n            createCommand.withNetworkMode(networkForLinks.get());\n        }\n\n        if (hostAccessible) {\n            PortForwardingContainer.INSTANCE.start();\n        }\n        PortForwardingContainer.INSTANCE\n            .getNetwork()\n            .ifPresent(it -> {\n                withExtraHost(INTERNAL_HOST_HOSTNAME, it.getIpAddress());\n            });\n\n        String[] extraHostsArray = extraHosts.stream().distinct().toArray(String[]::new);\n        createCommand.withExtraHosts(extraHostsArray);\n\n        if (workingDirectory != null) {\n            createCommand.withWorkingDir(workingDirectory);\n        }\n\n        for (CreateContainerCmdModifier createContainerCmdModifier : this.createContainerCmdModifiers) {\n            createCommand = createContainerCmdModifier.modify(createCommand);\n        }\n    }\n\n    private Set<Link> findLinksFromThisContainer(String alias, LinkableContainer linkableContainer) {\n        return dockerClient\n            .listContainersCmd()\n            .withStatusFilter(Arrays.asList(\"running\"))\n            .exec()\n            .stream()\n            .flatMap(container -> Stream.of(container.getNames()))\n            .filter(name -> name.endsWith(linkableContainer.getContainerName()))\n            .map(name -> new Link(name, alias))\n            .collect(Collectors.toSet());\n    }\n\n    private Set<String> findAllNetworksForLinkedContainers(LinkableContainer linkableContainer) {\n        return dockerClient\n            .listContainersCmd()\n            .exec()\n            .stream()\n            .filter(container -> container.getNames()[0].endsWith(linkableContainer.getContainerName()))\n            .filter(container -> {\n                return container.getNetworkSettings() != null && container.getNetworkSettings().getNetworks() != null;\n            })\n            .flatMap(container -> container.getNetworkSettings().getNetworks().keySet().stream())\n            .distinct()\n            .collect(Collectors.toSet());\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public SELF waitingFor(@NonNull WaitStrategy waitStrategy) {\n        this.waitStrategy = waitStrategy;\n        return self();\n    }\n\n    /**\n     * The {@link WaitStrategy} to use to determine if the container is ready.\n     * Defaults to {@link Wait#defaultWaitStrategy()}.\n     *\n     * @return the {@link WaitStrategy} to use\n     */\n    protected WaitStrategy getWaitStrategy() {\n        return this.waitStrategy == DEFAULT_WAIT_STRATEGY ? this.containerDef.getWaitStrategy() : this.waitStrategy;\n    }\n\n    @Override\n    public void setWaitStrategy(WaitStrategy waitStrategy) {\n        this.waitStrategy = waitStrategy;\n    }\n\n    /**\n     * Wait until the container has started. The default implementation simply\n     * waits for a port to start listening; other implementations are available\n     * as implementations of {@link WaitStrategy}\n     *\n     * @see #waitingFor(WaitStrategy)\n     */\n    protected void waitUntilContainerStarted() {\n        WaitStrategy waitStrategy = getWaitStrategy();\n        if (waitStrategy != null) {\n            waitStrategy.waitUntilReady(this);\n        }\n    }\n\n    private String checkForEmulation() {\n        try {\n            DockerClient dockerClient = DockerClientFactory.instance().client();\n            String imageId = getContainerInfo().getImageId();\n            String imageArch = dockerClient.inspectImageCmd(imageId).exec().getArch();\n            String serverArch = dockerClient.versionCmd().exec().getArch();\n\n            if (!serverArch.equals(imageArch)) {\n                return (\n                    \"The architecture '\" +\n                    imageArch +\n                    \"' for image '\" +\n                    getDockerImageName() +\n                    \"' (ID \" +\n                    imageId +\n                    \") does not match the Docker server architecture '\" +\n                    serverArch +\n                    \"'. This will cause the container to execute much more slowly due to emulation and may lead to timeout failures.\"\n                );\n            }\n        } catch (Exception archCheckException) {\n            // ignore any exceptions since this is just used for a log message\n        }\n        return null;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public void setCommand(@NonNull String command) {\n        this.containerDef.setCommand(command.split(\" \"));\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public void setCommand(@NonNull String... commandParts) {\n        this.containerDef.setCommand(commandParts);\n    }\n\n    @Override\n    public Map<String, String> getEnvMap() {\n        return this.containerDef.envVars;\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public List<String> getEnv() {\n        return this.containerDef.getEnvVars()\n            .entrySet()\n            .stream()\n            .map(it -> it.getKey() + \"=\" + it.getValue())\n            .collect(Collectors.toList());\n    }\n\n    @Override\n    public void setEnv(List<String> env) {\n        this.containerDef.setEnvVars(\n                env.stream().map(it -> it.split(\"=\")).collect(Collectors.toMap(it -> it[0], it -> it[1]))\n            );\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public void addEnv(String key, String value) {\n        this.containerDef.addEnvVar(key, value);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public void addFileSystemBind(\n        final String hostPath,\n        final String containerPath,\n        final BindMode mode,\n        final SelinuxContext selinuxContext\n    ) {\n        if (SystemUtils.IS_OS_WINDOWS && hostPath.startsWith(\"/\")) {\n            // e.g. Docker socket mount\n            this.containerDef.addBinds(\n                    new Bind(hostPath, new Volume(containerPath), mode.accessMode, selinuxContext.selContext)\n                );\n        } else {\n            final MountableFile mountableFile = MountableFile.forHostPath(hostPath);\n            this.containerDef.addBinds(\n                    new Bind(\n                        mountableFile.getResolvedPath(),\n                        new Volume(containerPath),\n                        mode.accessMode,\n                        selinuxContext.selContext\n                    )\n                );\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public SELF withFileSystemBind(String hostPath, String containerPath, BindMode mode) {\n        addFileSystemBind(hostPath, containerPath, mode);\n        return self();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public SELF withVolumesFrom(Container container, BindMode mode) {\n        addVolumesFrom(container, mode);\n        return self();\n    }\n\n    private void addVolumesFrom(Container container, BindMode mode) {\n        volumesFroms.add(new VolumesFrom(container.getContainerName(), mode.accessMode));\n    }\n\n    /**\n     * @deprecated Links are deprecated (see <a href=\"https://github.com/testcontainers/testcontainers-java/issues/465\">#465</a>). Please use {@link Network} features instead.\n     */\n    @Deprecated\n    @Override\n    public void addLink(LinkableContainer otherContainer, String alias) {\n        this.linkedContainers.put(alias, otherContainer);\n    }\n\n    @Override\n    public void addExposedPort(Integer port) {\n        this.containerDef.addExposedTcpPort(port);\n    }\n\n    @Override\n    public void addExposedPorts(int... ports) {\n        this.containerDef.addExposedTcpPorts(ports);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public SELF withExposedPorts(Integer... ports) {\n        this.setExposedPorts(Lists.newArrayList(ports));\n        return self();\n    }\n\n    /**\n     * Add a TCP container port that should be bound to a fixed port on the docker host.\n     * <p>\n     * Note that this method is protected scope to discourage use, as clashes or instability are more likely when\n     * using fixed port mappings. If you need to use this method from a test, please use {@link FixedHostPortGenericContainer}\n     * instead of GenericContainer.\n     *\n     * @param hostPort\n     * @param containerPort\n     */\n    protected void addFixedExposedPort(int hostPort, int containerPort) {\n        addFixedExposedPort(hostPort, containerPort, InternetProtocol.TCP);\n    }\n\n    /**\n     * Add a container port that should be bound to a fixed port on the docker host.\n     * <p>\n     * Note that this method is protected scope to discourage use, as clashes or instability are more likely when\n     * using fixed port mappings. If you need to use this method from a test, please use {@link FixedHostPortGenericContainer}\n     * instead of GenericContainer.\n     *\n     * @param hostPort\n     * @param containerPort\n     * @param protocol\n     */\n    protected void addFixedExposedPort(int hostPort, int containerPort, InternetProtocol protocol) {\n        ExposedPort exposedPort = new ExposedPort(\n            containerPort,\n            com.github.dockerjava.api.model.InternetProtocol.parse(protocol.name())\n        );\n        PortBinding portBinding = new PortBinding(Ports.Binding.bindPort(hostPort), exposedPort);\n        this.containerDef.addPortBindings(portBinding);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public SELF withEnv(String key, String value) {\n        this.addEnv(key, value);\n        return self();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public SELF withEnv(Map<String, String> env) {\n        env.forEach(this.containerDef::addEnvVar);\n        return self();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public SELF withLabel(String key, String value) {\n        if (key.startsWith(\"org.testcontainers\")) {\n            throw new IllegalArgumentException(\"The org.testcontainers namespace is reserved for interal use\");\n        }\n        this.containerDef.addLabel(key, value);\n        return self();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public SELF withLabels(Map<String, String> labels) {\n        labels.forEach(this::withLabel);\n        return self();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public SELF withCommand(String cmd) {\n        this.setCommand(cmd);\n        return self();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public SELF withCommand(String... commandParts) {\n        this.setCommand(commandParts);\n        return self();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public SELF withExtraHost(String hostname, String ipAddress) {\n        this.extraHosts.add(String.format(\"%s:%s\", hostname, ipAddress));\n        return self();\n    }\n\n    @Override\n    public SELF withNetworkMode(String networkMode) {\n        this.containerDef.setNetworkMode(networkMode);\n        return self();\n    }\n\n    @Override\n    public SELF withNetwork(Network network) {\n        this.containerDef.setNetwork(network);\n        return self();\n    }\n\n    @Override\n    public SELF withNetworkAliases(String... aliases) {\n        this.containerDef.addNetworkAliases(aliases);\n        return self();\n    }\n\n    @Override\n    public SELF withImagePullPolicy(ImagePullPolicy imagePullPolicy) {\n        this.image = this.image.withImagePullPolicy(imagePullPolicy);\n        return self();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public SELF withClasspathResourceMapping(\n        final String resourcePath,\n        final String containerPath,\n        final BindMode mode\n    ) {\n        return withClasspathResourceMapping(resourcePath, containerPath, mode, SelinuxContext.SHARED);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public SELF withClasspathResourceMapping(\n        final String resourcePath,\n        final String containerPath,\n        final BindMode mode,\n        final SelinuxContext selinuxContext\n    ) {\n        final MountableFile mountableFile = MountableFile.forClasspathResource(resourcePath);\n\n        if (mode == BindMode.READ_WRITE) {\n            addFileSystemBind(mountableFile.getResolvedPath(), containerPath, mode, selinuxContext);\n        } else {\n            withCopyFileToContainer(mountableFile, containerPath);\n        }\n\n        return self();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public SELF withStartupTimeout(Duration startupTimeout) {\n        getWaitStrategy().withStartupTimeout(startupTimeout);\n        return self();\n    }\n\n    @Override\n    public SELF withPrivilegedMode(boolean mode) {\n        this.containerDef.setPrivilegedMode(mode);\n        return self();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public SELF withMinimumRunningDuration(Duration minimumRunningDuration) {\n        this.startupCheckStrategy = new MinimumDurationRunningStartupCheckStrategy(minimumRunningDuration);\n        return self();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public SELF withStartupCheckStrategy(StartupCheckStrategy strategy) {\n        this.startupCheckStrategy = strategy;\n        return self();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public SELF withWorkingDirectory(String workDir) {\n        this.setWorkingDirectory(workDir);\n        return self();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public SELF withCopyFileToContainer(MountableFile mountableFile, String containerPath) {\n        if (copyToFileContainerPathMap.containsKey(mountableFile)) {\n            throw new IllegalStateException(\"Path already configured for copy: \" + mountableFile);\n        }\n        copyToFileContainerPathMap.put(mountableFile, containerPath);\n        return self();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public SELF withCopyToContainer(Transferable transferable, String containerPath) {\n        copyToTransferableContainerPathMap.put(transferable, containerPath);\n        return self();\n    }\n\n    /**\n     * Get the IP address that this container may be reached on (may not be the local machine).\n     *\n     * @return an IP address\n     * @deprecated please use getContainerIpAddress() instead\n     */\n    @Deprecated\n    public String getIpAddress() {\n        return getHost();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public void setDockerImageName(@NonNull String dockerImageName) {\n        this.image = new RemoteDockerImage(dockerImageName);\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    @NonNull\n    public String getDockerImageName() {\n        try {\n            return image.get();\n        } catch (Exception e) {\n            throw new ContainerFetchException(\"Can't get Docker image: \" + image, e);\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    @Deprecated\n    public String getTestHostIpAddress() {\n        if (DockerMachineClient.instance().isInstalled()) {\n            try {\n                Optional<String> defaultMachine = DockerMachineClient.instance().getDefaultMachine();\n                if (!defaultMachine.isPresent()) {\n                    throw new IllegalStateException(\"Could not find a default docker-machine instance\");\n                }\n\n                String sshConnectionString = CommandLine\n                    .runShellCommand(\"docker-machine\", \"ssh\", defaultMachine.get(), \"echo $SSH_CONNECTION\")\n                    .trim();\n                if (Strings.isNullOrEmpty(sshConnectionString)) {\n                    throw new IllegalStateException(\n                        \"Could not obtain SSH_CONNECTION environment variable for docker machine \" +\n                        defaultMachine.get()\n                    );\n                }\n\n                String[] sshConnectionParts = sshConnectionString.split(\"\\\\s\");\n                if (sshConnectionParts.length != 4) {\n                    throw new IllegalStateException(\n                        \"Unexpected pattern for SSH_CONNECTION for docker machine - expected 'IP PORT IP PORT' pattern but found '\" +\n                        sshConnectionString +\n                        \"'\"\n                    );\n                }\n\n                return sshConnectionParts[0];\n            } catch (Exception e) {\n                throw new RuntimeException(e);\n            }\n        } else {\n            throw new UnsupportedOperationException(\n                \"getTestHostIpAddress() is only implemented for docker-machine right now\"\n            );\n        }\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public SELF withLogConsumer(Consumer<OutputFrame> consumer) {\n        this.logConsumers.add(consumer);\n\n        return self();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    @SneakyThrows\n    public void copyFileFromContainer(String containerPath, String destinationPath) {\n        Container.super.copyFileFromContainer(containerPath, destinationPath);\n    }\n\n    /**\n     * Allow container startup to be attempted more than once if an error occurs. To be used if containers are\n     * 'flaky' but this flakiness is not something that should affect test outcomes.\n     *\n     * @param attempts number of attempts\n     */\n    public SELF withStartupAttempts(int attempts) {\n        this.startupAttempts = attempts;\n        return self();\n    }\n\n    /**\n     * Allow low level modifications of {@link CreateContainerCmd} after it was pre-configured in {@link #tryStart()}.\n     * Invocation happens eagerly on a moment when container is created.\n     * Warning: this does expose the underlying docker-java API so might change outside of our control.\n     *\n     * @param modifier {@link Consumer} of {@link CreateContainerCmd}.\n     * @return this\n     */\n    public SELF withCreateContainerCmdModifier(Consumer<CreateContainerCmd> modifier) {\n        this.createContainerCmdModifiers.add(cmd -> {\n                modifier.accept(cmd);\n                return cmd;\n            });\n        return self();\n    }\n\n    /**\n     * Size of /dev/shm\n     *\n     * @param bytes The number of bytes to assign the shared memory. If null, it will apply the Docker default which is 64 MB.\n     * @return this\n     */\n    public SELF withSharedMemorySize(Long bytes) {\n        this.shmSize = bytes;\n        return self();\n    }\n\n    /**\n     * First class support for configuring tmpfs\n     *\n     * @param mapping path and params of tmpfs/mount flag for container\n     * @return this\n     */\n    public SELF withTmpFs(Map<String, String> mapping) {\n        this.tmpFsMapping = mapping;\n        return self();\n    }\n\n    @UnstableAPI\n    public SELF withReuse(boolean reusable) {\n        this.shouldBeReused = reusable;\n        return self();\n    }\n\n    /**\n     * Forces access to the tests host machine.\n     * Use this method if you need to call {@link org.testcontainers.Testcontainers#exposeHostPorts(int...)}\n     * after you start this container.\n     *\n     * @return this\n     */\n    public SELF withAccessToHost(boolean value) {\n        this.hostAccessible = value;\n        return self();\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        return this == o;\n    }\n\n    @Override\n    public int hashCode() {\n        return System.identityHashCode(this);\n    }\n\n    @Override\n    public String getContainerName() {\n        return getContainerInfo().getName();\n    }\n\n    public Network getNetwork() {\n        return this.containerDef.getNetwork();\n    }\n\n    @Override\n    public List<Bind> getBinds() {\n        return this.containerDef.binds;\n    }\n\n    @Override\n    public void setBinds(List<Bind> binds) {\n        this.containerDef.setBinds(binds);\n    }\n\n    @Override\n    public String[] getCommandParts() {\n        return this.containerDef.getCommand();\n    }\n\n    @Override\n    public void setCommandParts(String[] commandParts) {\n        this.containerDef.setCommand(commandParts);\n    }\n\n    public List<String> getNetworkAliases() {\n        return new ArrayList<>(this.containerDef.getNetworkAliases());\n    }\n\n    public void setNetworkAliases(List<String> aliases) {\n        this.containerDef.setNetworkAliases(new HashSet<>(aliases));\n    }\n\n    @Override\n    public List<String> getPortBindings() {\n        return this.containerDef.portBindings.stream()\n            .map(it -> String.format(\"%s:%s\", it.getBinding(), it.getExposedPort()))\n            .collect(Collectors.toList());\n    }\n\n    @Override\n    public void setPortBindings(List<String> portBindings) {\n        this.containerDef.setPortBindings(portBindings.stream().map(PortBinding::parse).collect(Collectors.toSet()));\n    }\n\n    public void setPrivilegedMode(boolean mode) {\n        this.containerDef.setPrivilegedMode(mode);\n    }\n\n    public boolean isPrivilegedMode() {\n        return this.containerDef.isPrivilegedMode();\n    }\n\n    public Map<String, String> getLabels() {\n        return this.containerDef.labels;\n    }\n\n    public void setLabels(Map<String, String> labels) {\n        this.containerDef.setLabels(labels);\n    }\n\n    public String getNetworkMode() {\n        return this.containerDef.getNetworkMode();\n    }\n\n    public void setNetworkMode(String networkMode) {\n        this.containerDef.setNetworkMode(networkMode);\n    }\n\n    public void setNetwork(Network network) {\n        this.containerDef.setNetwork(network);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/InternetProtocol.java",
    "content": "package org.testcontainers.containers;\n\n/**\n * The IP protocols supported by Docker.\n */\npublic enum InternetProtocol {\n    TCP,\n    UDP;\n\n    public String toDockerNotation() {\n        return name().toLowerCase();\n    }\n\n    public static InternetProtocol fromDockerNotation(String protocol) {\n        return valueOf(protocol.toUpperCase());\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/LocalDockerCompose.java",
    "content": "package org.testcontainers.containers;\n\nimport com.github.dockerjava.core.LocalDirectorySSLConfig;\nimport com.github.dockerjava.transport.SSLConfig;\nimport com.google.common.base.Splitter;\nimport com.google.common.collect.Maps;\nimport org.slf4j.Logger;\nimport org.testcontainers.DockerClientFactory;\nimport org.testcontainers.dockerclient.TransportConfig;\nimport org.testcontainers.utility.CommandLine;\nimport org.testcontainers.utility.DockerLoggerFactory;\nimport org.zeroturnaround.exec.InvalidExitValueException;\nimport org.zeroturnaround.exec.ProcessExecutor;\nimport org.zeroturnaround.exec.stream.slf4j.Slf4jStream;\n\nimport java.io.File;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\n/**\n * Use local Docker Compose binary, if present.\n */\nclass LocalDockerCompose implements DockerCompose {\n\n    private final List<File> composeFiles;\n\n    private final String identifier;\n\n    private String cmd = \"\";\n\n    private Map<String, String> env = new HashMap<>();\n\n    private final String composeExecutable;\n\n    public LocalDockerCompose(String composeExecutable, List<File> composeFiles, String identifier) {\n        this.composeExecutable = composeExecutable;\n        this.composeFiles = composeFiles;\n        this.identifier = identifier;\n    }\n\n    @Override\n    public DockerCompose withCommand(String cmd) {\n        this.cmd = cmd;\n        return this;\n    }\n\n    @Override\n    public DockerCompose withEnv(Map<String, String> env) {\n        this.env = env;\n        return this;\n    }\n\n    @Override\n    public void invoke() {\n        // bail out early\n        if (!CommandLine.executableExists(this.composeExecutable)) {\n            throw new ContainerLaunchException(\n                \"Local Docker Compose not found. Is \" + this.composeExecutable + \" on the PATH?\"\n            );\n        }\n\n        final Map<String, String> environment = Maps.newHashMap(env);\n        environment.put(ENV_PROJECT_NAME, identifier);\n\n        TransportConfig transportConfig = DockerClientFactory.instance().getTransportConfig();\n        SSLConfig sslConfig = transportConfig.getSslConfig();\n        if (sslConfig != null) {\n            if (sslConfig instanceof LocalDirectorySSLConfig) {\n                environment.put(\"DOCKER_CERT_PATH\", ((LocalDirectorySSLConfig) sslConfig).getDockerCertPath());\n                environment.put(\"DOCKER_TLS_VERIFY\", \"true\");\n            } else {\n                logger()\n                    .warn(\n                        \"Couldn't set DOCKER_CERT_PATH. `sslConfig` is present but it's not LocalDirectorySSLConfig.\"\n                    );\n            }\n        }\n        String dockerHost = transportConfig.getDockerHost().toString();\n        environment.put(\"DOCKER_HOST\", dockerHost);\n\n        final Stream<String> absoluteDockerComposeFilePaths = composeFiles\n            .stream()\n            .map(File::getAbsolutePath)\n            .map(Objects::toString);\n\n        final String composeFileEnvVariableValue = absoluteDockerComposeFilePaths.collect(\n            Collectors.joining(File.pathSeparator + \"\")\n        );\n        logger().debug(\"Set env COMPOSE_FILE={}\", composeFileEnvVariableValue);\n\n        final File pwd = composeFiles.get(0).getAbsoluteFile().getParentFile().getAbsoluteFile();\n        environment.put(ENV_COMPOSE_FILE, composeFileEnvVariableValue);\n\n        logger().info(\"Local Docker Compose is running command: {}\", cmd);\n\n        final List<String> command = Splitter\n            .onPattern(\" \")\n            .omitEmptyStrings()\n            .splitToList(this.composeExecutable + \" \" + cmd);\n\n        try {\n            new ProcessExecutor()\n                .command(command)\n                .redirectOutput(Slf4jStream.of(logger()).asInfo())\n                .redirectError(Slf4jStream.of(logger()).asInfo()) // docker-compose will log pull information to stderr\n                .environment(environment)\n                .directory(pwd)\n                .exitValueNormal()\n                .executeNoTimeout();\n\n            logger().info(\"Docker Compose has finished running\");\n        } catch (InvalidExitValueException e) {\n            throw new ContainerLaunchException(\n                \"Local Docker Compose exited abnormally with code \" +\n                e.getExitValue() +\n                \" whilst running command: \" +\n                cmd\n            );\n        } catch (Exception e) {\n            throw new ContainerLaunchException(\"Error running local Docker Compose command: \" + cmd, e);\n        }\n    }\n\n    /**\n     * @return a logger\n     */\n    private Logger logger() {\n        return DockerLoggerFactory.getLogger(this.composeExecutable);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/Network.java",
    "content": "package org.testcontainers.containers;\n\nimport com.github.dockerjava.api.command.CreateNetworkCmd;\nimport lombok.Builder;\nimport lombok.Getter;\nimport lombok.Singular;\nimport org.testcontainers.DockerClientFactory;\nimport org.testcontainers.utility.ResourceReaper;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.UUID;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.function.Consumer;\n\npublic interface Network extends AutoCloseable {\n    Network SHARED = new NetworkImpl(false, null, Collections.emptySet(), null) {\n        @Override\n        public void close() {\n            // Do not allow users to close SHARED network, only ResourceReaper is allowed to close (destroy) it\n        }\n    };\n\n    String getId();\n\n    @Override\n    void close();\n\n    static Network newNetwork() {\n        return builder().build();\n    }\n\n    static NetworkImpl.NetworkImplBuilder builder() {\n        return NetworkImpl.builder();\n    }\n\n    @Builder\n    @Getter\n    class NetworkImpl implements Network {\n\n        private final String name = UUID.randomUUID().toString();\n\n        private Boolean enableIpv6;\n\n        private String driver;\n\n        @Singular\n        private Set<Consumer<CreateNetworkCmd>> createNetworkCmdModifiers;\n\n        @Deprecated\n        private String id;\n\n        private final AtomicBoolean initialized = new AtomicBoolean();\n\n        @Override\n        public synchronized String getId() {\n            if (initialized.compareAndSet(false, true)) {\n                boolean success = false;\n                try {\n                    id = create();\n                    success = true;\n                } finally {\n                    if (!success) {\n                        initialized.set(false);\n                    }\n                }\n            }\n            return id;\n        }\n\n        private String create() {\n            CreateNetworkCmd createNetworkCmd = DockerClientFactory.instance().client().createNetworkCmd();\n\n            createNetworkCmd.withName(name);\n            createNetworkCmd.withCheckDuplicate(true);\n\n            if (enableIpv6 != null) {\n                createNetworkCmd.withEnableIpv6(enableIpv6);\n            }\n\n            if (driver != null) {\n                createNetworkCmd.withDriver(driver);\n            }\n\n            for (Consumer<CreateNetworkCmd> consumer : createNetworkCmdModifiers) {\n                consumer.accept(createNetworkCmd);\n            }\n\n            Map<String, String> labels = createNetworkCmd.getLabels();\n            labels = new HashMap<>(labels != null ? labels : Collections.emptyMap());\n            labels.putAll(DockerClientFactory.DEFAULT_LABELS);\n            //noinspection deprecation\n            labels.putAll(ResourceReaper.instance().getLabels());\n            createNetworkCmd.withLabels(labels);\n\n            return createNetworkCmd.exec().getId();\n        }\n\n        @Override\n        public synchronized void close() {\n            if (initialized.getAndSet(false)) {\n                ResourceReaper.instance().removeNetworkById(id);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/ParsedDockerComposeFile.java",
    "content": "package org.testcontainers.containers;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.collect.Sets;\nimport lombok.EqualsAndHashCode;\nimport lombok.Getter;\nimport lombok.extern.slf4j.Slf4j;\nimport org.apache.commons.io.FileUtils;\nimport org.testcontainers.images.ParsedDockerfile;\nimport org.yaml.snakeyaml.DumperOptions;\nimport org.yaml.snakeyaml.LoaderOptions;\nimport org.yaml.snakeyaml.Yaml;\nimport org.yaml.snakeyaml.constructor.SafeConstructor;\nimport org.yaml.snakeyaml.nodes.Node;\nimport org.yaml.snakeyaml.nodes.Tag;\nimport org.yaml.snakeyaml.representer.Representer;\nimport org.yaml.snakeyaml.resolver.Resolver;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Set;\n\n/**\n * Representation of a docker-compose file, with partial parsing for validation and extraction of a minimal set of\n * data.\n */\n@Slf4j\n@EqualsAndHashCode\nclass ParsedDockerComposeFile {\n\n    private final Map<String, Object> composeFileContent;\n\n    private final String composeFileName;\n\n    private final File composeFile;\n\n    @Getter\n    private final Map<String, Set<String>> serviceNameToImageNames = new HashMap<>();\n\n    ParsedDockerComposeFile(File composeFile) {\n        // The default is 50 and a big docker-compose.yml file can easily go above that number. 1,000 should give us some room\n        LoaderOptions options = new LoaderOptions();\n        options.setMaxAliasesForCollections(1_000);\n        DumperOptions dumperOptions = new DumperOptions();\n\n        SafeConstructor constructor = new SafeConstructor(options) {\n            @Override\n            protected Object constructObject(Node node) {\n                if (node.getTag().equals(new Tag(\"!reset\"))) {\n                    return null;\n                }\n                return super.constructObject(node);\n            }\n        };\n        Yaml yaml = new Yaml(constructor, new Representer(dumperOptions), dumperOptions, options, new Resolver());\n        try (FileInputStream fileInputStream = FileUtils.openInputStream(composeFile)) {\n            composeFileContent = yaml.load(fileInputStream);\n        } catch (Exception e) {\n            throw new IllegalArgumentException(\"Unable to parse YAML file from \" + composeFile.getAbsolutePath(), e);\n        }\n        this.composeFileName = composeFile.getAbsolutePath();\n        this.composeFile = composeFile;\n        parseAndValidate();\n    }\n\n    @VisibleForTesting\n    ParsedDockerComposeFile(Map<String, Object> testContent) {\n        this.composeFileContent = testContent;\n        this.composeFileName = \"\";\n        this.composeFile = new File(\".\");\n\n        parseAndValidate();\n    }\n\n    private void parseAndValidate() {\n        final Map<String, ?> servicesMap;\n        if (composeFileContent.containsKey(\"version\") && \"2.0\".equals(composeFileContent.get(\"version\"))) {\n            log.warn(\n                \"Testcontainers may not be able to clean up networks spawned using Docker Compose v2.0 files. \" +\n                \"Please see https://github.com/testcontainers/moby-ryuk/issues/2, and specify 'version: \\\"2.1\\\"' or \" +\n                \"higher in {}\",\n                composeFileName\n            );\n        }\n\n        if (composeFileContent.containsKey(\"services\")) {\n            final Object servicesElement = composeFileContent.get(\"services\");\n            if (servicesElement == null) {\n                log.debug(\n                    \"Compose file {} has an unknown format: 'version' is set but 'services' is not defined\",\n                    composeFileName\n                );\n                return;\n            }\n            if (!(servicesElement instanceof Map)) {\n                log.debug(\"Compose file {} has an unknown format: 'services' is not Map\", composeFileName);\n                return;\n            }\n\n            @SuppressWarnings(\"unchecked\")\n            Map<String, ?> temp = (Map<String, ?>) servicesElement;\n            servicesMap = temp;\n        } else {\n            servicesMap = composeFileContent;\n        }\n\n        for (Map.Entry<String, ?> entry : servicesMap.entrySet()) {\n            String serviceName = entry.getKey();\n            Object serviceDefinition = entry.getValue();\n            if (!(serviceDefinition instanceof Map)) {\n                log.debug(\n                    \"Compose file {} has an unknown format: service '{}' is not Map\",\n                    composeFileName,\n                    serviceName\n                );\n                break;\n            }\n\n            @SuppressWarnings(\"unchecked\")\n            final Map<String, ?> serviceDefinitionMap = (Map<String, ?>) serviceDefinition;\n\n            validateNoContainerNameSpecified(serviceName, serviceDefinitionMap);\n            findServiceImageName(serviceName, serviceDefinitionMap);\n            findImageNamesInDockerfile(serviceName, serviceDefinitionMap);\n        }\n    }\n\n    private void validateNoContainerNameSpecified(String serviceName, Map<String, ?> serviceDefinitionMap) {\n        if (serviceDefinitionMap.containsKey(\"container_name\")) {\n            throw new IllegalStateException(\n                String.format(\n                    \"Compose file %s has 'container_name' property set for service '%s' but this property is not supported by Testcontainers, consider removing it\",\n                    composeFileName,\n                    serviceName\n                )\n            );\n        }\n    }\n\n    private void findServiceImageName(String serviceName, Map<String, ?> serviceDefinitionMap) {\n        Object result = serviceDefinitionMap.get(\"image\");\n        if (result instanceof String) {\n            final String imageName = (String) result;\n            log.debug(\"Resolved dependency image for Docker Compose in {}: {}\", composeFileName, imageName);\n            serviceNameToImageNames.put(serviceName, Sets.newHashSet(imageName));\n        }\n    }\n\n    private void findImageNamesInDockerfile(String serviceName, Map<String, ?> serviceDefinitionMap) {\n        final Object buildNode = serviceDefinitionMap.get(\"build\");\n        Path dockerfilePath = null;\n\n        if (buildNode instanceof Map) {\n            final Map<?, ?> buildElement = (Map<?, ?>) buildNode;\n            final Object dockerfileRelativePath = buildElement.get(\"dockerfile\");\n            final Object contextRelativePath = buildElement.get(\"context\");\n            if (dockerfileRelativePath instanceof String && contextRelativePath instanceof String) {\n                dockerfilePath =\n                    composeFile\n                        .getAbsoluteFile()\n                        .getParentFile()\n                        .toPath()\n                        .resolve((String) contextRelativePath)\n                        .resolve((String) dockerfileRelativePath)\n                        .normalize();\n            }\n        } else if (buildNode instanceof String) {\n            dockerfilePath =\n                composeFile\n                    .getAbsoluteFile()\n                    .getParentFile()\n                    .toPath()\n                    .resolve((String) buildNode)\n                    .resolve(\"./Dockerfile\")\n                    .normalize();\n        }\n\n        if (dockerfilePath != null && Files.exists(dockerfilePath)) {\n            Set<String> resolvedImageNames = new ParsedDockerfile(dockerfilePath).getDependencyImageNames();\n            if (!resolvedImageNames.isEmpty()) {\n                log.debug(\n                    \"Resolved Dockerfile dependency images for Docker Compose in {} -> {}: {}\",\n                    composeFileName,\n                    dockerfilePath,\n                    resolvedImageNames\n                );\n                this.serviceNameToImageNames.put(serviceName, resolvedImageNames);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/PortForwardingContainer.java",
    "content": "package org.testcontainers.containers;\n\nimport com.github.dockerjava.api.model.ContainerNetwork;\nimport com.trilead.ssh2.Connection;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.SneakyThrows;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.time.Duration;\nimport java.util.AbstractMap;\nimport java.util.Collections;\nimport java.util.Map.Entry;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.UUID;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.atomic.AtomicReference;\n\npublic enum PortForwardingContainer {\n    INSTANCE;\n\n    private static String PASSWORD = UUID.randomUUID().toString();\n\n    private static ContainerDef DEFINITION = new ContainerDef() {\n        {\n            setImage(DockerImageName.parse(\"testcontainers/sshd:1.3.0\"));\n            addExposedTcpPort(22);\n            addEnvVar(\"PASSWORD\", PASSWORD);\n        }\n    };\n\n    private GenericContainer<?> container;\n\n    private final Set<Entry<Integer, Integer>> exposedPorts = Collections.newSetFromMap(new ConcurrentHashMap<>());\n\n    @Getter(value = AccessLevel.PRIVATE, lazy = true)\n    private final Connection sshConnection = createSSHSession();\n\n    @SneakyThrows\n    private Connection createSSHSession() {\n        container = new GenericContainer<>(DEFINITION);\n        container.start();\n\n        Connection connection = new Connection(container.getHost(), container.getMappedPort(22));\n\n        connection.setTCPNoDelay(true);\n        connection.connect(\n            (hostname, port, serverHostKeyAlgorithm, serverHostKey) -> true,\n            (int) Duration.ofSeconds(30).toMillis(),\n            (int) Duration.ofSeconds(30).toMillis()\n        );\n\n        if (!connection.authenticateWithPassword(\"root\", PASSWORD)) {\n            throw new IllegalStateException(\"Authentication failed.\");\n        }\n\n        return connection;\n    }\n\n    @SneakyThrows\n    public void exposeHostPort(int port) {\n        exposeHostPort(port, port);\n    }\n\n    @SneakyThrows\n    public void exposeHostPort(int hostPort, int containerPort) {\n        if (exposedPorts.add(new AbstractMap.SimpleEntry<>(hostPort, containerPort))) {\n            getSshConnection().requestRemotePortForwarding(\"\", containerPort, \"localhost\", hostPort);\n        }\n    }\n\n    void start() {\n        getSshConnection();\n    }\n\n    Optional<ContainerNetwork> getNetwork() {\n        return Optional\n            .ofNullable(container)\n            .map(GenericContainer::getContainerInfo)\n            .flatMap(it -> it.getNetworkSettings().getNetworks().values().stream().findFirst());\n    }\n\n    void reset() {\n        if (container != null) {\n            container.stop();\n        }\n        container = null;\n\n        ((AtomicReference<?>) (Object) sshConnection).set(null);\n\n        exposedPorts.clear();\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/SelinuxContext.java",
    "content": "package org.testcontainers.containers;\n\nimport com.github.dockerjava.api.model.SELContext;\nimport lombok.AllArgsConstructor;\n\n/**\n * Possible contexts for use with SELinux\n */\n@AllArgsConstructor\npublic enum SelinuxContext {\n    SHARED(SELContext.shared),\n    SINGLE(SELContext.single),\n    NONE(SELContext.none);\n\n    public final SELContext selContext;\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/SocatContainer.java",
    "content": "package org.testcontainers.containers;\n\nimport org.testcontainers.utility.Base58;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.stream.Collectors;\n\n/**\n * A socat container is used as a TCP proxy, enabling any TCP port of another container to be exposed\n * publicly, even if that container does not make the port public itself.\n */\npublic class SocatContainer extends GenericContainer<SocatContainer> {\n\n    private final Map<Integer, String> targets = new HashMap<>();\n\n    public SocatContainer() {\n        this(DockerImageName.parse(\"alpine/socat:1.7.4.3-r0\"));\n    }\n\n    public SocatContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        withCreateContainerCmdModifier(it -> it.withEntrypoint(\"/bin/sh\"));\n        withCreateContainerCmdModifier(it -> it.withName(\"testcontainers-socat-\" + Base58.randomString(8)));\n    }\n\n    public SocatContainer withTarget(int exposedPort, String host) {\n        return withTarget(exposedPort, host, exposedPort);\n    }\n\n    public SocatContainer withTarget(int exposedPort, String host, int internalPort) {\n        addExposedPort(exposedPort);\n        targets.put(exposedPort, String.format(\"%s:%s\", host, internalPort));\n        return self();\n    }\n\n    @Override\n    protected void configure() {\n        withCommand(\n            \"-c\",\n            targets\n                .entrySet()\n                .stream()\n                .map(entry -> \"socat TCP-LISTEN:\" + entry.getKey() + \",fork,reuseaddr TCP:\" + entry.getValue())\n                .collect(Collectors.joining(\" & \"))\n        );\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/VncRecordingContainer.java",
    "content": "package org.testcontainers.containers;\n\nimport lombok.Getter;\nimport lombok.NonNull;\nimport lombok.RequiredArgsConstructor;\nimport lombok.SneakyThrows;\nimport lombok.ToString;\nimport org.apache.commons.compress.archivers.tar.TarArchiveInputStream;\nimport org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.file.Files;\nimport java.nio.file.StandardCopyOption;\nimport java.time.Duration;\nimport java.time.temporal.ChronoUnit;\nimport java.util.Base64;\n\n/**\n * 'Sidekick container' with the sole purpose of recording the VNC screen output from another container.\n *\n */\n@Getter\n@ToString\npublic class VncRecordingContainer extends GenericContainer<VncRecordingContainer> {\n\n    private static final String ORIGINAL_RECORDING_FILE_NAME = \"/screen.flv\";\n\n    public static final String DEFAULT_VNC_PASSWORD = \"secret\";\n\n    public static final int DEFAULT_VNC_PORT = 5900;\n\n    static final VncRecordingFormat DEFAULT_RECORDING_FORMAT = VncRecordingFormat.FLV;\n\n    private final String targetNetworkAlias;\n\n    private String vncPassword = DEFAULT_VNC_PASSWORD;\n\n    private VncRecordingFormat videoFormat = DEFAULT_RECORDING_FORMAT;\n\n    private int vncPort = 5900;\n\n    private int frameRate = 30;\n\n    public VncRecordingContainer(@NonNull GenericContainer<?> targetContainer) {\n        this(\n            targetContainer.getNetwork(),\n            targetContainer\n                .getNetworkAliases()\n                .stream()\n                .findFirst()\n                .orElseThrow(() -> new IllegalStateException(\"Target container must have a network alias\"))\n        );\n    }\n\n    /**\n     * Create a sidekick container and attach it to another container. The VNC output of that container will be recorded.\n     */\n    public VncRecordingContainer(@NonNull Network network, @NonNull String targetNetworkAlias)\n        throws IllegalStateException {\n        super(DockerImageName.parse(\"testcontainers/vnc-recorder:1.3.0\"));\n        this.targetNetworkAlias = targetNetworkAlias;\n        withNetwork(network);\n        waitingFor(\n            new LogMessageWaitStrategy()\n                .withRegEx(\".*Connected.*\")\n                .withStartupTimeout(Duration.of(15, ChronoUnit.SECONDS))\n        );\n    }\n\n    public VncRecordingContainer withVncPassword(@NonNull String vncPassword) {\n        this.vncPassword = vncPassword;\n        return this;\n    }\n\n    public VncRecordingContainer withVncPort(int vncPort) {\n        this.vncPort = vncPort;\n        return this;\n    }\n\n    public VncRecordingContainer withVideoFormat(VncRecordingFormat videoFormat) {\n        if (videoFormat != null) {\n            this.videoFormat = videoFormat;\n        }\n        return this;\n    }\n\n    public VncRecordingContainer withFrameRate(int frameRate) {\n        this.frameRate = frameRate;\n        return this;\n    }\n\n    @Override\n    protected void configure() {\n        withCreateContainerCmdModifier(it -> it.withEntrypoint(\"/bin/sh\"));\n        String encodedPassword = Base64.getEncoder().encodeToString(vncPassword.getBytes());\n        setCommand(\n            \"-c\",\n            \"echo '\" +\n            encodedPassword +\n            \"' | base64 -d > /vnc_password && \" +\n            \"flvrec.py -o \" +\n            ORIGINAL_RECORDING_FILE_NAME +\n            \" -d -r \" +\n            frameRate +\n            \" -P /vnc_password \" +\n            targetNetworkAlias +\n            \" \" +\n            vncPort\n        );\n    }\n\n    @SneakyThrows\n    public InputStream streamRecording() {\n        String newRecordingFileName = videoFormat.reencodeRecording(this, ORIGINAL_RECORDING_FILE_NAME);\n\n        TarArchiveInputStream archiveInputStream = new TarArchiveInputStream(\n            dockerClient.copyArchiveFromContainerCmd(getContainerId(), newRecordingFileName).exec()\n        );\n        archiveInputStream.getNextEntry();\n        return archiveInputStream;\n    }\n\n    @SneakyThrows\n    public void saveRecordingToFile(@NonNull File file) {\n        try (InputStream inputStream = streamRecording()) {\n            Files.copy(inputStream, file.toPath(), StandardCopyOption.REPLACE_EXISTING);\n        }\n    }\n\n    @RequiredArgsConstructor\n    public enum VncRecordingFormat {\n        FLV(\"flv\") {\n            @Override\n            String reencodeRecording(@NonNull VncRecordingContainer container, @NonNull String source)\n                throws IOException, InterruptedException {\n                String newFileOutput = \"/newScreen.flv\";\n                container.execInContainer(\"ffmpeg\", \"-i\", source, \"-vcodec\", \"libx264\", newFileOutput);\n                return newFileOutput;\n            }\n        },\n        MP4(\"mp4\") {\n            @Override\n            String reencodeRecording(@NonNull VncRecordingContainer container, @NonNull String source)\n                throws IOException, InterruptedException {\n                String newFileOutput = \"/newScreen.mp4\";\n                container.execInContainer(\n                    \"ffmpeg\",\n                    \"-i\",\n                    source,\n                    \"-vcodec\",\n                    \"libx264\",\n                    \"-movflags\",\n                    \"faststart\",\n                    \"-pix_fmt\",\n                    \"yuv420p\",\n                    newFileOutput\n                );\n                return newFileOutput;\n            }\n        };\n\n        abstract String reencodeRecording(VncRecordingContainer container, String source)\n            throws IOException, InterruptedException;\n\n        @Getter\n        private final String filenameExtension;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/output/BaseConsumer.java",
    "content": "package org.testcontainers.containers.output;\n\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport java.util.function.Consumer;\n\npublic abstract class BaseConsumer<SELF extends BaseConsumer<SELF>> implements Consumer<OutputFrame> {\n\n    @Getter\n    @Setter\n    private boolean removeColorCodes = true;\n\n    public SELF withRemoveAnsiCodes(boolean removeAnsiCodes) {\n        this.removeColorCodes = removeAnsiCodes;\n        return (SELF) this;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/output/FrameConsumerResultCallback.java",
    "content": "package org.testcontainers.containers.output;\n\nimport com.github.dockerjava.api.async.ResultCallbackTemplate;\nimport com.github.dockerjava.api.model.Frame;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.function.Consumer;\nimport java.util.regex.Pattern;\n\n/**\n * This class can be used as a generic callback for docker-java commands that produce Frames.\n */\npublic class FrameConsumerResultCallback extends ResultCallbackTemplate<FrameConsumerResultCallback, Frame> {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(FrameConsumerResultCallback.class);\n\n    private final Map<OutputFrame.OutputType, LineConsumer> consumers = new HashMap<>();\n\n    private final CountDownLatch completionLatch = new CountDownLatch(1);\n\n    /**\n     * Set this callback to use the specified consumer for the given output type.\n     * The same consumer can be configured for more than one output type.\n     * @param outputType the output type to configure\n     * @param consumer the consumer to use for that output type\n     */\n    public void addConsumer(OutputFrame.OutputType outputType, Consumer<OutputFrame> consumer) {\n        consumers.put(outputType, new LineConsumer(outputType, consumer));\n    }\n\n    @Override\n    public void onNext(Frame frame) {\n        if (frame != null) {\n            final OutputFrame.OutputType type = OutputFrame.OutputType.forStreamType(frame.getStreamType());\n            if (type != null) {\n                final LineConsumer consumer = consumers.get(type);\n                if (consumer == null) {\n                    LOGGER.error(\"got frame with type {}, for which no handler is configured\", frame.getStreamType());\n                } else if (frame.getPayload() != null) {\n                    consumer.processFrame(frame.getPayload());\n                }\n            }\n        }\n    }\n\n    @Override\n    public void onError(Throwable throwable) {\n        // Sink any errors\n        try {\n            close();\n        } catch (IOException ignored) {}\n    }\n\n    @Override\n    public void close() throws IOException {\n        if (completionLatch.getCount() == 0) {\n            return;\n        }\n\n        consumers.values().forEach(LineConsumer::processBuffer);\n        consumers.values().forEach(LineConsumer::end);\n        super.close();\n\n        completionLatch.countDown();\n    }\n\n    /**\n     * @return a {@link CountDownLatch} that may be used to wait until {@link #close()} has been called.\n     */\n    public CountDownLatch getCompletionLatch() {\n        return completionLatch;\n    }\n\n    private static class LineConsumer {\n\n        private static final Pattern ANSI_COLOR_PATTERN = Pattern.compile(\"\\u001B\\\\[[0-9;]+m\");\n\n        private final OutputFrame.OutputType type;\n\n        private final Consumer<OutputFrame> consumer;\n\n        private final ByteArrayOutputStream buffer = new ByteArrayOutputStream();\n\n        private boolean lastCR = false;\n\n        LineConsumer(final OutputFrame.OutputType type, final Consumer<OutputFrame> consumer) {\n            this.type = type;\n            this.consumer = consumer;\n        }\n\n        void processFrame(final byte[] b) {\n            int start = 0;\n            int i = 0;\n            while (i < b.length) {\n                switch (b[i]) {\n                    case '\\n':\n                        buffer.write(b, start, i + 1 - start);\n                        start = i + 1;\n                        consume();\n                        lastCR = false;\n                        break;\n                    case '\\r':\n                        if (lastCR) {\n                            consume();\n                        }\n                        buffer.write(b, start, i + 1 - start);\n                        start = i + 1;\n                        lastCR = true;\n                        break;\n                    default:\n                        if (lastCR) {\n                            consume();\n                        }\n                        lastCR = false;\n                }\n                i++;\n            }\n            buffer.write(b, start, b.length - start);\n        }\n\n        void processBuffer() {\n            if (buffer.size() > 0) {\n                consume();\n            }\n        }\n\n        void end() {\n            consumer.accept(OutputFrame.END);\n        }\n\n        private void consume() {\n            final String string = new String(buffer.toByteArray(), StandardCharsets.UTF_8);\n            final byte[] bytes = processAnsiColorCodes(string).getBytes(StandardCharsets.UTF_8);\n            consumer.accept(new OutputFrame(type, bytes));\n            buffer.reset();\n        }\n\n        private String processAnsiColorCodes(final String utf8String) {\n            if (!(consumer instanceof BaseConsumer) || ((BaseConsumer<?>) consumer).isRemoveColorCodes()) {\n                return ANSI_COLOR_PATTERN.matcher(utf8String).replaceAll(\"\");\n            }\n            return utf8String;\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/output/OutputFrame.java",
    "content": "package org.testcontainers.containers.output;\n\nimport com.github.dockerjava.api.model.Frame;\nimport com.github.dockerjava.api.model.StreamType;\n\nimport java.nio.charset.StandardCharsets;\n\n/**\n * Holds exactly one complete line of container output. Lines are split on newline characters (LF, CR LF).\n */\npublic class OutputFrame {\n\n    public static final OutputFrame END = new OutputFrame(OutputType.END, null);\n\n    private final OutputType type;\n\n    private final byte[] bytes;\n\n    public OutputFrame(final OutputType type, final byte[] bytes) {\n        this.type = type;\n        this.bytes = bytes;\n    }\n\n    public OutputType getType() {\n        return type;\n    }\n\n    public byte[] getBytes() {\n        return bytes;\n    }\n\n    public String getUtf8String() {\n        return (bytes == null) ? \"\" : new String(bytes, StandardCharsets.UTF_8);\n    }\n\n    public String getUtf8StringWithoutLineEnding() {\n        if (bytes == null) {\n            return \"\";\n        }\n        return new String(bytes, 0, bytes.length - determineLineEndingLength(bytes), StandardCharsets.UTF_8);\n    }\n\n    private static int determineLineEndingLength(final byte[] bytes) {\n        if (bytes.length > 0) {\n            switch (bytes[bytes.length - 1]) {\n                case '\\r':\n                    return 1;\n                case '\\n':\n                    return ((bytes.length > 1) && (bytes[bytes.length - 2] == '\\r')) ? 2 : 1;\n            }\n        }\n        return 0;\n    }\n\n    public enum OutputType {\n        STDOUT,\n        STDERR,\n        END;\n\n        public static OutputType forStreamType(StreamType streamType) {\n            switch (streamType) {\n                case RAW:\n                case STDOUT:\n                    return STDOUT;\n                case STDERR:\n                    return STDERR;\n                default:\n                    return null;\n            }\n        }\n    }\n\n    public static OutputFrame forFrame(Frame frame) {\n        final OutputType outputType = OutputType.forStreamType(frame.getStreamType());\n        if (outputType == null) {\n            return null;\n        }\n        return new OutputFrame(outputType, frame.getPayload());\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/output/Slf4jLogConsumer.java",
    "content": "package org.testcontainers.containers.output;\n\nimport org.slf4j.Logger;\nimport org.slf4j.MDC;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * A consumer for container output that logs output to an SLF4J logger.\n */\npublic class Slf4jLogConsumer extends BaseConsumer<Slf4jLogConsumer> {\n\n    private final Logger logger;\n\n    private final Map<String, String> mdc = new HashMap<>();\n\n    private boolean separateOutputStreams;\n\n    private String prefix = \"\";\n\n    public Slf4jLogConsumer(Logger logger) {\n        this(logger, false);\n    }\n\n    public Slf4jLogConsumer(Logger logger, boolean separateOutputStreams) {\n        this.logger = logger;\n        this.separateOutputStreams = separateOutputStreams;\n    }\n\n    public Slf4jLogConsumer withPrefix(String prefix) {\n        this.prefix = \"[\" + prefix + \"] \";\n        return this;\n    }\n\n    public Slf4jLogConsumer withMdc(String key, String value) {\n        mdc.put(key, value);\n        return this;\n    }\n\n    public Slf4jLogConsumer withMdc(Map<String, String> mdc) {\n        this.mdc.putAll(mdc);\n        return this;\n    }\n\n    public Slf4jLogConsumer withSeparateOutputStreams() {\n        this.separateOutputStreams = true;\n        return this;\n    }\n\n    @Override\n    public void accept(OutputFrame outputFrame) {\n        final OutputFrame.OutputType outputType = outputFrame.getType();\n        final String utf8String = outputFrame.getUtf8StringWithoutLineEnding();\n\n        final Map<String, String> originalMdc = MDC.getCopyOfContextMap();\n        MDC.setContextMap(mdc);\n        try {\n            switch (outputType) {\n                case END:\n                    break;\n                case STDOUT:\n                    if (separateOutputStreams) {\n                        logger.info(\"{}{}\", prefix.isEmpty() ? \"\" : (prefix + \": \"), utf8String);\n                    } else {\n                        logger.info(\"{}{}: {}\", prefix, outputType, utf8String);\n                    }\n                    break;\n                case STDERR:\n                    if (separateOutputStreams) {\n                        logger.error(\"{}{}\", prefix.isEmpty() ? \"\" : (prefix + \": \"), utf8String);\n                    } else {\n                        logger.info(\"{}{}: {}\", prefix, outputType, utf8String);\n                    }\n                    break;\n                default:\n                    throw new IllegalArgumentException(\"Unexpected outputType \" + outputType);\n            }\n        } finally {\n            if (originalMdc == null) {\n                MDC.clear();\n            } else {\n                MDC.setContextMap(originalMdc);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/output/ToStringConsumer.java",
    "content": "package org.testcontainers.containers.output;\n\nimport com.google.common.base.Charsets;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.nio.charset.Charset;\n\n/**\n * Created by rnorth on 26/03/2016.\n */\npublic class ToStringConsumer extends BaseConsumer<ToStringConsumer> {\n\n    private final ByteArrayOutputStream stringBuffer = new ByteArrayOutputStream();\n\n    @Override\n    public void accept(OutputFrame outputFrame) {\n        try {\n            final byte[] bytes = outputFrame.getBytes();\n            if (bytes != null) {\n                stringBuffer.write(bytes);\n            }\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    public String toUtf8String() {\n        byte[] bytes = stringBuffer.toByteArray();\n        return new String(bytes, Charsets.UTF_8);\n    }\n\n    public String toString(Charset charset) {\n        byte[] bytes = stringBuffer.toByteArray();\n        return new String(bytes, charset);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/output/WaitingConsumer.java",
    "content": "package org.testcontainers.containers.output;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.util.concurrent.LinkedBlockingDeque;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\nimport java.util.function.Predicate;\n\n/**\n * A consumer for container output that buffers lines in a {@link java.util.concurrent.BlockingDeque} and enables tests\n * to wait for a matching condition.\n */\npublic class WaitingConsumer extends BaseConsumer<WaitingConsumer> {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(WaitingConsumer.class);\n\n    private LinkedBlockingDeque<OutputFrame> frames = new LinkedBlockingDeque<>();\n\n    @Override\n    public void accept(OutputFrame frame) {\n        frames.add(frame);\n    }\n\n    /**\n     * Get access to the underlying frame buffer. Modifying the buffer contents is likely to cause problems if the\n     * waitUntil() methods are also being used, as they feed on the same data.\n     *\n     * @return the collection of frames\n     */\n    public LinkedBlockingDeque<OutputFrame> getFrames() {\n        return frames;\n    }\n\n    /**\n     * Wait until any frame (usually, line) of output matches the provided predicate.\n     * <p>\n     * Note that lines will often have a trailing newline character, and this is not stripped off before the\n     * predicate is tested.\n     *\n     * @param predicate a predicate to test against each frame\n     */\n    public void waitUntil(Predicate<OutputFrame> predicate) throws TimeoutException {\n        // ~2.9 thousands centuries ought to be enough for anyone\n        waitUntil(predicate, Long.MAX_VALUE, 1);\n    }\n\n    /**\n     * Wait until any frame (usually, line) of output matches the provided predicate.\n     * <p>\n     * Note that lines will often have a trailing newline character, and this is not stripped off before the\n     * predicate is tested.\n     *\n     * @param predicate a predicate to test against each frame\n     * @param limit     maximum time to wait\n     * @param limitUnit maximum time to wait (units)\n     */\n    public void waitUntil(Predicate<OutputFrame> predicate, int limit, TimeUnit limitUnit) throws TimeoutException {\n        waitUntil(predicate, limit, limitUnit, 1);\n    }\n\n    /**\n     * Wait until any frame (usually, line) of output matches the provided predicate.\n     * <p>\n     * Note that lines will often have a trailing newline character, and this is not stripped off before the\n     * predicate is tested.\n     *\n     * @param predicate a predicate to test against each frame\n     * @param limit     maximum time to wait\n     * @param limitUnit maximum time to wait (units)\n     * @param times     number of times the predicate has to match\n     */\n    public void waitUntil(Predicate<OutputFrame> predicate, long limit, TimeUnit limitUnit, int times)\n        throws TimeoutException {\n        long timeoutLimitInNanos = limitUnit.toNanos(limit);\n\n        waitUntil(predicate, timeoutLimitInNanos, times);\n    }\n\n    private void waitUntil(Predicate<OutputFrame> predicate, long timeoutLimitInNanos, int times)\n        throws TimeoutException {\n        int numberOfMatches = 0;\n\n        final long startTime = System.nanoTime();\n\n        while (System.nanoTime() - startTime < timeoutLimitInNanos) {\n            try {\n                final OutputFrame frame = frames.pollLast(100, TimeUnit.MILLISECONDS);\n\n                if (frame != null) {\n                    LOGGER.debug(\"{}: {}\", frame.getType(), frame.getUtf8StringWithoutLineEnding());\n\n                    if (predicate.test(frame)) {\n                        numberOfMatches++;\n\n                        if (numberOfMatches == times) {\n                            return;\n                        }\n                    }\n                }\n\n                if (frames.isEmpty()) {\n                    // sleep for a moment to avoid excessive CPU spinning\n                    Thread.sleep(10L);\n                }\n            } catch (InterruptedException e) {\n                throw new RuntimeException(e);\n            }\n        }\n\n        // did not return before expiry was reached\n        throw new TimeoutException();\n    }\n\n    /**\n     * Wait until Docker closes the stream of output.\n     */\n    public void waitUntilEnd() {\n        try {\n            waitUntilEnd(Long.MAX_VALUE);\n        } catch (TimeoutException e) {\n            // timeout condition can never occur in a realistic timeframe\n            throw new IllegalStateException(e);\n        }\n    }\n\n    /**\n     * Wait until Docker closes the stream of output.\n     *\n     * @param limit     maximum time to wait\n     * @param limitUnit maximum time to wait (units)\n     */\n    public void waitUntilEnd(long limit, TimeUnit limitUnit) throws TimeoutException {\n        long expiry = limitUnit.toNanos(limit) + System.nanoTime();\n\n        waitUntilEnd(expiry);\n    }\n\n    private void waitUntilEnd(Long expiry) throws TimeoutException {\n        while (System.nanoTime() < expiry) {\n            try {\n                OutputFrame frame = frames.pollLast(100, TimeUnit.MILLISECONDS);\n\n                if (frame == OutputFrame.END) {\n                    return;\n                }\n\n                if (frames.isEmpty()) {\n                    // sleep for a moment to avoid excessive CPU spinning\n                    Thread.sleep(10L);\n                }\n            } catch (InterruptedException e) {\n                throw new RuntimeException(e);\n            }\n        }\n        throw new TimeoutException(\"Expiry time reached before end of output\");\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/startupcheck/IndefiniteWaitOneShotStartupCheckStrategy.java",
    "content": "package org.testcontainers.containers.startupcheck;\n\nimport com.github.dockerjava.api.DockerClient;\nimport com.google.common.util.concurrent.Uninterruptibles;\n\nimport java.util.concurrent.TimeUnit;\n\n/**\n * Variant of {@link OneShotStartupCheckStrategy} that does not impose a timeout.\n * Intended for situation such as when a long running task forms part of container startup.\n * <p>\n * It has to be assumed that the container will stop of its own accord, either with a success or failure exit code.\n */\npublic class IndefiniteWaitOneShotStartupCheckStrategy extends OneShotStartupCheckStrategy {\n\n    @Override\n    public boolean waitUntilStartupSuccessful(DockerClient dockerClient, String containerId) {\n        while (checkStartupState(dockerClient, containerId) == StartupStatus.NOT_YET_KNOWN) {\n            Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS);\n        }\n\n        return checkStartupState(dockerClient, containerId) == StartupStatus.SUCCESSFUL;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/startupcheck/IsRunningStartupCheckStrategy.java",
    "content": "package org.testcontainers.containers.startupcheck;\n\nimport com.github.dockerjava.api.DockerClient;\nimport com.github.dockerjava.api.command.InspectContainerResponse;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.utility.DockerStatus;\n\n/**\n * Simplest possible implementation of {@link StartupCheckStrategy} - just check that the container\n * has reached the running state and has not exited.\n */\npublic class IsRunningStartupCheckStrategy extends StartupCheckStrategy {\n\n    @SuppressWarnings(\"deprecation\")\n    @Override\n    public boolean waitUntilStartupSuccessful(GenericContainer<?> container) {\n        // Optimization: container already has the initial \"after start\" state, check it first\n        if (checkState(container.getContainerInfo().getState()) == StartupStatus.SUCCESSFUL) {\n            return true;\n        }\n        return super.waitUntilStartupSuccessful(container);\n    }\n\n    @Override\n    public StartupStatus checkStartupState(DockerClient dockerClient, String containerId) {\n        InspectContainerResponse.ContainerState state = getCurrentState(dockerClient, containerId);\n        return checkState(state);\n    }\n\n    private StartupStatus checkState(InspectContainerResponse.ContainerState state) {\n        if (Boolean.TRUE.equals(state.getRunning())) {\n            return StartupStatus.SUCCESSFUL;\n        } else if (DockerStatus.isContainerStopped(state)) {\n            if (DockerStatus.isContainerExitCodeSuccess(state)) {\n                return StartupStatus.SUCCESSFUL;\n            } else {\n                return StartupStatus.FAILED;\n            }\n        } else {\n            return StartupStatus.NOT_YET_KNOWN;\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/startupcheck/MinimumDurationRunningStartupCheckStrategy.java",
    "content": "package org.testcontainers.containers.startupcheck;\n\nimport com.github.dockerjava.api.DockerClient;\nimport com.github.dockerjava.api.command.InspectContainerResponse;\nimport org.jetbrains.annotations.NotNull;\nimport org.testcontainers.utility.DockerStatus;\n\nimport java.time.Duration;\nimport java.time.Instant;\n\n/**\n * Implementation of {@link StartupCheckStrategy} that checks the container is running and has been running for\n * a defined minimum period of time.\n */\npublic class MinimumDurationRunningStartupCheckStrategy extends StartupCheckStrategy {\n\n    @NotNull\n    private final Duration minimumRunningDuration;\n\n    public MinimumDurationRunningStartupCheckStrategy(@NotNull Duration minimumRunningDuration) {\n        this.minimumRunningDuration = minimumRunningDuration;\n    }\n\n    @Override\n    public StartupStatus checkStartupState(DockerClient dockerClient, String containerId) {\n        // record \"now\" before fetching status; otherwise the time to fetch the status\n        // will contribute to how long the container has been running.\n        Instant now = Instant.now();\n        InspectContainerResponse.ContainerState state = getCurrentState(dockerClient, containerId);\n\n        if (DockerStatus.isContainerRunning(state, minimumRunningDuration, now)) {\n            return StartupStatus.SUCCESSFUL;\n        } else if (DockerStatus.isContainerStopped(state)) {\n            return StartupStatus.FAILED;\n        }\n        return StartupStatus.NOT_YET_KNOWN;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/startupcheck/OneShotStartupCheckStrategy.java",
    "content": "package org.testcontainers.containers.startupcheck;\n\nimport com.github.dockerjava.api.DockerClient;\nimport com.github.dockerjava.api.command.InspectContainerResponse;\nimport org.testcontainers.utility.DockerStatus;\n\n/**\n * Implementation of {@link StartupCheckStrategy} intended for use with containers that only run briefly and\n * exit of their own accord. As such, success is deemed to be when the container has stopped with exit code 0.\n */\npublic class OneShotStartupCheckStrategy extends StartupCheckStrategy {\n\n    @Override\n    public StartupStatus checkStartupState(DockerClient dockerClient, String containerId) {\n        InspectContainerResponse.ContainerState state = getCurrentState(dockerClient, containerId);\n\n        if (!DockerStatus.isContainerStopped(state)) {\n            return StartupStatus.NOT_YET_KNOWN;\n        }\n\n        if (DockerStatus.isContainerStopped(state) && DockerStatus.isContainerExitCodeSuccess(state)) {\n            return StartupStatus.SUCCESSFUL;\n        } else {\n            return StartupStatus.FAILED;\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/startupcheck/StartupCheckStrategy.java",
    "content": "package org.testcontainers.containers.startupcheck;\n\nimport com.github.dockerjava.api.DockerClient;\nimport com.github.dockerjava.api.command.InspectContainerResponse;\nimport org.rnorth.ducttape.ratelimits.RateLimiter;\nimport org.rnorth.ducttape.ratelimits.RateLimiterBuilder;\nimport org.rnorth.ducttape.unreliables.Unreliables;\nimport org.testcontainers.containers.GenericContainer;\n\nimport java.time.Duration;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * Approach to determine whether a container has 'started up' correctly.\n */\npublic abstract class StartupCheckStrategy {\n\n    private static final RateLimiter DOCKER_CLIENT_RATE_LIMITER = RateLimiterBuilder\n        .newBuilder()\n        .withRate(1, TimeUnit.SECONDS)\n        .withConstantThroughput()\n        .build();\n\n    private Duration timeout = Duration.ofSeconds(GenericContainer.CONTAINER_RUNNING_TIMEOUT_SEC);\n\n    @SuppressWarnings(\"unchecked\")\n    public <SELF extends StartupCheckStrategy> SELF withTimeout(Duration timeout) {\n        this.timeout = timeout;\n        return (SELF) this;\n    }\n\n    /**\n     *\n     * @deprecated internal API\n     */\n    @Deprecated\n    public boolean waitUntilStartupSuccessful(GenericContainer<?> container) {\n        return waitUntilStartupSuccessful(container.getDockerClient(), container.getContainerId());\n    }\n\n    public boolean waitUntilStartupSuccessful(DockerClient dockerClient, String containerId) {\n        final Boolean[] startedOK = { null };\n        Unreliables.retryUntilTrue(\n            (int) timeout.toMillis(),\n            TimeUnit.MILLISECONDS,\n            () -> {\n                //noinspection CodeBlock2Expr\n                return DOCKER_CLIENT_RATE_LIMITER.getWhenReady(() -> {\n                    StartupStatus state = checkStartupState(dockerClient, containerId);\n                    switch (state) {\n                        case SUCCESSFUL:\n                            startedOK[0] = true;\n                            return true;\n                        case FAILED:\n                            startedOK[0] = false;\n                            return true;\n                        default:\n                            return false;\n                    }\n                });\n            }\n        );\n        return startedOK[0];\n    }\n\n    public abstract StartupStatus checkStartupState(DockerClient dockerClient, String containerId);\n\n    protected InspectContainerResponse.ContainerState getCurrentState(DockerClient dockerClient, String containerId) {\n        return dockerClient.inspectContainerCmd(containerId).exec().getState();\n    }\n\n    public enum StartupStatus {\n        NOT_YET_KNOWN,\n        SUCCESSFUL,\n        FAILED,\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/traits/LinkableContainer.java",
    "content": "package org.testcontainers.containers.traits;\n\n/**\n * A container which can be linked to by other containers.\n *\n * @deprecated Links are deprecated (see <a href=\"https://github.com/testcontainers/testcontainers-java/issues/465\">#465</a>). Please use {@link org.testcontainers.containers.Network} features instead.\n */\n@Deprecated\npublic interface LinkableContainer {\n    String getContainerName();\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/wait/internal/ExternalPortListeningCheck.java",
    "content": "package org.testcontainers.containers.wait.internal;\n\nimport lombok.RequiredArgsConstructor;\nimport org.testcontainers.containers.ContainerState;\n\nimport java.io.IOException;\nimport java.net.InetSocketAddress;\nimport java.net.Socket;\nimport java.util.Set;\nimport java.util.concurrent.Callable;\n\n/**\n * Mechanism for testing that a socket is listening when run from the test host.\n */\n@RequiredArgsConstructor\npublic class ExternalPortListeningCheck implements Callable<Boolean> {\n\n    private final ContainerState containerState;\n\n    private final Set<Integer> externalLivenessCheckPorts;\n\n    @Override\n    public Boolean call() {\n        String address = containerState.getHost();\n\n        externalLivenessCheckPorts\n            .parallelStream()\n            .forEach(externalPort -> {\n                try (Socket socket = new Socket()) {\n                    InetSocketAddress inetSocketAddress = new InetSocketAddress(address, externalPort);\n                    socket.connect(inetSocketAddress, 1000);\n                } catch (IOException e) {\n                    throw new IllegalStateException(\"Socket not listening yet: \" + externalPort);\n                }\n            });\n        return true;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/wait/internal/InternalCommandPortListeningCheck.java",
    "content": "package org.testcontainers.containers.wait.internal;\n\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.testcontainers.containers.Container.ExecResult;\nimport org.testcontainers.containers.ExecInContainerPattern;\nimport org.testcontainers.containers.wait.strategy.WaitStrategyTarget;\n\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.util.Set;\n\n/**\n * Mechanism for testing that a socket is listening when run from the container being checked.\n */\n@RequiredArgsConstructor\n@Slf4j\npublic class InternalCommandPortListeningCheck implements java.util.concurrent.Callable<Boolean> {\n\n    private final WaitStrategyTarget waitStrategyTarget;\n\n    private final Set<Integer> internalPorts;\n\n    @Override\n    public Boolean call() {\n        StringBuilder command = new StringBuilder(\"while true; do ( true \");\n\n        for (int internalPort : internalPorts) {\n            command.append(\" && \");\n            command.append(\" (\");\n            command.append(String.format(\"grep -i ':0*%x' /proc/net/tcp*\", internalPort));\n            command.append(\" || \");\n            command.append(String.format(\"nc -vz -w 1 localhost %d\", internalPort));\n            command.append(\" || \");\n            command.append(String.format(\"/bin/bash -c '</dev/tcp/localhost/%d'\", internalPort));\n            command.append(\")\");\n        }\n        command.append(\" ) && exit 0 || sleep 0.1; done\");\n\n        Instant before = Instant.now();\n        try {\n            ExecResult result = ExecInContainerPattern.execInContainer(\n                waitStrategyTarget.getDockerClient(),\n                waitStrategyTarget.getContainerInfo(),\n                \"/bin/sh\",\n                \"-c\",\n                command.toString()\n            );\n            log.trace(\n                \"Check for {} took {}. Result code '{}', stdout message: '{}'\",\n                internalPorts,\n                Duration.between(before, Instant.now()),\n                result.getExitCode(),\n                result.getStdout()\n            );\n            int exitCode = result.getExitCode();\n            if (exitCode != 0 && exitCode != 1) {\n                log.warn(\"An exception while executing the internal check: {}\", result);\n            }\n            return exitCode == 0;\n        } catch (Exception e) {\n            throw new IllegalStateException(e);\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/wait/strategy/AbstractWaitStrategy.java",
    "content": "package org.testcontainers.containers.wait.strategy;\n\nimport lombok.NonNull;\nimport org.rnorth.ducttape.ratelimits.RateLimiter;\nimport org.rnorth.ducttape.ratelimits.RateLimiterBuilder;\n\nimport java.time.Duration;\nimport java.util.Set;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.ThreadFactory;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicLong;\n\npublic abstract class AbstractWaitStrategy implements WaitStrategy {\n\n    static final ExecutorService EXECUTOR = Executors.newCachedThreadPool(\n        new ThreadFactory() {\n            private final AtomicLong COUNTER = new AtomicLong(0);\n\n            @Override\n            public Thread newThread(Runnable r) {\n                Thread thread = new Thread(r, \"testcontainers-wait-\" + COUNTER.getAndIncrement());\n                thread.setDaemon(true);\n                return thread;\n            }\n        }\n    );\n\n    private static final RateLimiter DOCKER_CLIENT_RATE_LIMITER = RateLimiterBuilder\n        .newBuilder()\n        .withRate(1, TimeUnit.SECONDS)\n        .withConstantThroughput()\n        .build();\n\n    protected WaitStrategyTarget waitStrategyTarget;\n\n    @NonNull\n    protected Duration startupTimeout = Duration.ofSeconds(60);\n\n    @NonNull\n    private RateLimiter rateLimiter = DOCKER_CLIENT_RATE_LIMITER;\n\n    /**\n     * Wait until the target has started.\n     *\n     * @param waitStrategyTarget the target of the WaitStrategy\n     */\n    @Override\n    public void waitUntilReady(WaitStrategyTarget waitStrategyTarget) {\n        this.waitStrategyTarget = waitStrategyTarget;\n        waitUntilReady();\n    }\n\n    /**\n     * Wait until {@link #waitStrategyTarget} has started.\n     */\n    protected abstract void waitUntilReady();\n\n    /**\n     * Set the duration of waiting time until container treated as started.\n     *\n     * @param startupTimeout timeout\n     * @return this\n     * @see WaitStrategy#waitUntilReady(WaitStrategyTarget)\n     */\n    public WaitStrategy withStartupTimeout(Duration startupTimeout) {\n        this.startupTimeout = startupTimeout;\n        return this;\n    }\n\n    /**\n     * @return the ports on which to check if the container is ready\n     */\n    protected Set<Integer> getLivenessCheckPorts() {\n        return waitStrategyTarget.getLivenessCheckPortNumbers();\n    }\n\n    /**\n     * @return the rate limiter to use\n     */\n    protected RateLimiter getRateLimiter() {\n        return rateLimiter;\n    }\n\n    /**\n     * Set the rate limiter being used\n     *\n     * @param rateLimiter rateLimiter\n     * @return this\n     */\n    public WaitStrategy withRateLimiter(RateLimiter rateLimiter) {\n        this.rateLimiter = rateLimiter;\n        return this;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/wait/strategy/DockerHealthcheckWaitStrategy.java",
    "content": "package org.testcontainers.containers.wait.strategy;\n\nimport org.rnorth.ducttape.TimeoutException;\nimport org.rnorth.ducttape.unreliables.Unreliables;\nimport org.testcontainers.containers.ContainerLaunchException;\n\nimport java.util.concurrent.TimeUnit;\n\n/**\n * Wait strategy leveraging Docker's built-in healthcheck mechanism.\n *\n * @see <a href=\"https://docs.docker.com/engine/reference/builder/#healthcheck\">https://docs.docker.com/engine/reference/builder/#healthcheck</a>\n */\npublic class DockerHealthcheckWaitStrategy extends AbstractWaitStrategy {\n\n    @Override\n    protected void waitUntilReady() {\n        try {\n            Unreliables.retryUntilTrue(\n                (int) startupTimeout.getSeconds(),\n                TimeUnit.SECONDS,\n                waitStrategyTarget::isHealthy\n            );\n        } catch (TimeoutException e) {\n            throw new ContainerLaunchException(\"Timed out waiting for container to become healthy\");\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/wait/strategy/HostPortWaitStrategy.java",
    "content": "package org.testcontainers.containers.wait.strategy;\n\nimport lombok.SneakyThrows;\nimport lombok.extern.slf4j.Slf4j;\nimport org.awaitility.Awaitility;\nimport org.testcontainers.containers.ContainerLaunchException;\nimport org.testcontainers.containers.wait.internal.ExternalPortListeningCheck;\nimport org.testcontainers.containers.wait.internal.InternalCommandPortListeningCheck;\n\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.CancellationException;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\nimport java.util.stream.Collectors;\n\n/**\n * Waits until a socket connection can be established on a port exposed or mapped by the container.\n */\n@Slf4j\npublic class HostPortWaitStrategy extends AbstractWaitStrategy {\n\n    private int[] ports;\n\n    @Override\n    @SneakyThrows(InterruptedException.class)\n    protected void waitUntilReady() {\n        final Set<Integer> externalLivenessCheckPorts;\n        if (this.ports == null || this.ports.length == 0) {\n            externalLivenessCheckPorts = getLivenessCheckPorts();\n            if (externalLivenessCheckPorts.isEmpty()) {\n                if (log.isDebugEnabled()) {\n                    log.debug(\n                        \"Liveness check ports of {} is empty. Not waiting.\",\n                        waitStrategyTarget.getContainerInfo().getName()\n                    );\n                }\n                return;\n            }\n        } else {\n            externalLivenessCheckPorts =\n                Arrays\n                    .stream(this.ports)\n                    .mapToObj(port -> waitStrategyTarget.getMappedPort(port))\n                    .collect(Collectors.toSet());\n        }\n\n        List<Integer> exposedPorts = waitStrategyTarget.getExposedPorts();\n\n        final Set<Integer> internalPorts = getInternalPorts(externalLivenessCheckPorts, exposedPorts);\n\n        Callable<Boolean> internalCheck = new InternalCommandPortListeningCheck(waitStrategyTarget, internalPorts);\n\n        Callable<Boolean> externalCheck = new ExternalPortListeningCheck(\n            waitStrategyTarget,\n            externalLivenessCheckPorts\n        );\n\n        try {\n            List<Future<Boolean>> futures = EXECUTOR.invokeAll(\n                Arrays.asList(\n                    // Blocking\n                    () -> {\n                        Instant now = Instant.now();\n                        Boolean result = internalCheck.call();\n                        log.debug(\n                            \"Internal port check {} for {} in {}\",\n                            Boolean.TRUE.equals(result) ? \"passed\" : \"failed\",\n                            internalPorts,\n                            Duration.between(now, Instant.now())\n                        );\n                        return result;\n                    },\n                    // Polling\n                    () -> {\n                        Instant now = Instant.now();\n                        Awaitility\n                            .await()\n                            .pollInSameThread()\n                            .pollInterval(Duration.ofMillis(100))\n                            .pollDelay(Duration.ZERO)\n                            .failFast(\"container is no longer running\", () -> !waitStrategyTarget.isRunning())\n                            .ignoreExceptions()\n                            .forever()\n                            .until(externalCheck);\n\n                        log.debug(\n                            \"External port check passed for {} mapped as {} in {}\",\n                            internalPorts,\n                            externalLivenessCheckPorts,\n                            Duration.between(now, Instant.now())\n                        );\n                        return true;\n                    }\n                ),\n                startupTimeout.getSeconds(),\n                TimeUnit.SECONDS\n            );\n\n            for (Future<Boolean> future : futures) {\n                future.get(0, TimeUnit.SECONDS);\n            }\n        } catch (CancellationException | ExecutionException | TimeoutException e) {\n            throw new ContainerLaunchException(\n                \"Timed out waiting for container port to open (\" +\n                waitStrategyTarget.getHost() +\n                \" ports: \" +\n                externalLivenessCheckPorts +\n                \" should be listening)\"\n            );\n        }\n    }\n\n    private Set<Integer> getInternalPorts(Set<Integer> externalLivenessCheckPorts, List<Integer> exposedPorts) {\n        return exposedPorts\n            .stream()\n            .filter(it -> externalLivenessCheckPorts.contains(waitStrategyTarget.getMappedPort(it)))\n            .collect(Collectors.toSet());\n    }\n\n    public HostPortWaitStrategy forPorts(int... ports) {\n        this.ports = ports;\n        return this;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/wait/strategy/HttpWaitStrategy.java",
    "content": "package org.testcontainers.containers.wait.strategy;\n\nimport com.google.common.base.Strings;\nimport com.google.common.io.BaseEncoding;\nimport lombok.extern.slf4j.Slf4j;\nimport org.rnorth.ducttape.TimeoutException;\nimport org.testcontainers.containers.ContainerLaunchException;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.net.HttpURLConnection;\nimport java.net.MalformedURLException;\nimport java.net.Socket;\nimport java.net.URI;\nimport java.net.URL;\nimport java.security.KeyManagementException;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.SecureRandom;\nimport java.security.cert.X509Certificate;\nimport java.time.Duration;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.Predicate;\n\nimport javax.net.ssl.HttpsURLConnection;\nimport javax.net.ssl.KeyManager;\nimport javax.net.ssl.SSLContext;\nimport javax.net.ssl.SSLEngine;\nimport javax.net.ssl.TrustManager;\nimport javax.net.ssl.X509ExtendedTrustManager;\n\nimport static org.rnorth.ducttape.unreliables.Unreliables.retryUntilSuccess;\n\n@Slf4j\npublic class HttpWaitStrategy extends AbstractWaitStrategy {\n\n    /**\n     * Authorization HTTP header.\n     */\n    private static final String HEADER_AUTHORIZATION = \"Authorization\";\n\n    /**\n     * Basic Authorization scheme prefix.\n     */\n    private static final String AUTH_BASIC = \"Basic \";\n\n    private String path = \"/\";\n\n    private String method = \"GET\";\n\n    private Set<Integer> statusCodes = new HashSet<>();\n\n    private boolean tlsEnabled;\n\n    private String username;\n\n    private String password;\n\n    private final Map<String, String> headers = new HashMap<>();\n\n    private Predicate<String> responsePredicate;\n\n    private Predicate<Integer> statusCodePredicate = null;\n\n    private Optional<Integer> livenessPort = Optional.empty();\n\n    private Duration readTimeout = Duration.ofSeconds(1);\n\n    private boolean allowInsecure;\n\n    /**\n     * Waits for the given status code.\n     *\n     * @param statusCode the expected status code\n     * @return this\n     */\n    public HttpWaitStrategy forStatusCode(int statusCode) {\n        statusCodes.add(statusCode);\n        return this;\n    }\n\n    /**\n     * Waits for the status code to pass the given predicate\n     * @param statusCodePredicate The predicate to test the response against\n     * @return this\n     */\n    public HttpWaitStrategy forStatusCodeMatching(Predicate<Integer> statusCodePredicate) {\n        this.statusCodePredicate = statusCodePredicate;\n        return this;\n    }\n\n    /**\n     * Waits for the given path.\n     *\n     * @param path the path to check\n     * @return this\n     */\n    public HttpWaitStrategy forPath(String path) {\n        this.path = path;\n        return this;\n    }\n\n    /**\n     * Wait for the given port.\n     *\n     * @param port the given port\n     * @return this\n     */\n    public HttpWaitStrategy forPort(int port) {\n        this.livenessPort = Optional.of(port);\n        return this;\n    }\n\n    /**\n     * Indicates that the status check should use HTTPS.\n     *\n     * @return this\n     */\n    public HttpWaitStrategy usingTls() {\n        this.tlsEnabled = true;\n        return this;\n    }\n\n    /**\n     * Indicates the HTTP method to use (<code>GET</code> by default).\n     *\n     * @param method the HTTP method.\n     * @return this\n     */\n    public HttpWaitStrategy withMethod(String method) {\n        this.method = method;\n        return this;\n    }\n\n    /**\n     * Indicates that HTTPS connection could use untrusted (self signed) certificate chains.\n     *\n     * @return this\n     */\n    public HttpWaitStrategy allowInsecure() {\n        this.allowInsecure = true;\n        return this;\n    }\n\n    /**\n     * Authenticate with HTTP Basic Authorization credentials.\n     *\n     * @param username the username\n     * @param password the password\n     * @return this\n     */\n    public HttpWaitStrategy withBasicCredentials(String username, String password) {\n        this.username = username;\n        this.password = password;\n        return this;\n    }\n\n    /**\n     * Add a custom HTTP Header to the call.\n     * @param name The HTTP Header name\n     * @param value The HTTP Header value\n     * @return this\n     */\n    public HttpWaitStrategy withHeader(String name, String value) {\n        this.headers.put(name, value);\n        return this;\n    }\n\n    /**\n     * Add multiple custom HTTP Headers to the call.\n     * @param headers Headers map of name/value\n     * @return this\n     */\n    public HttpWaitStrategy withHeaders(Map<String, String> headers) {\n        this.headers.putAll(headers);\n        return this;\n    }\n\n    /**\n     * Set the HTTP connections read timeout.\n     *\n     * @param timeout the timeout (minimum 1 millisecond)\n     * @return this\n     */\n    public HttpWaitStrategy withReadTimeout(Duration timeout) {\n        if (timeout.toMillis() < 1) {\n            throw new IllegalArgumentException(\"you cannot specify a value smaller than 1 ms\");\n        }\n        this.readTimeout = timeout;\n        return this;\n    }\n\n    /**\n     * Waits for the response to pass the given predicate\n     * @param responsePredicate The predicate to test the response against\n     * @return this\n     */\n    public HttpWaitStrategy forResponsePredicate(Predicate<String> responsePredicate) {\n        this.responsePredicate = responsePredicate;\n        return this;\n    }\n\n    @Override\n    protected void waitUntilReady() {\n        final String containerName = waitStrategyTarget.getContainerInfo().getName();\n\n        final Integer livenessCheckPort = livenessPort\n            .map(waitStrategyTarget::getMappedPort)\n            .orElseGet(() -> {\n                final Set<Integer> livenessCheckPorts = getLivenessCheckPorts();\n                if (livenessCheckPorts == null || livenessCheckPorts.isEmpty()) {\n                    log.warn(\"{}: No exposed ports or mapped ports - cannot wait for status\", containerName);\n                    return -1;\n                }\n                return livenessCheckPorts.iterator().next();\n            });\n\n        if (null == livenessCheckPort || -1 == livenessCheckPort) {\n            return;\n        }\n        final URI rawUri = buildLivenessUri(livenessCheckPort);\n        final String uri = rawUri.toString();\n\n        try {\n            // Un-map the port for logging\n            int originalPort = waitStrategyTarget\n                .getExposedPorts()\n                .stream()\n                .filter(exposedPort -> rawUri.getPort() == waitStrategyTarget.getMappedPort(exposedPort))\n                .findFirst()\n                .orElseThrow(() -> new IllegalStateException(\"Target port \" + rawUri.getPort() + \" is not exposed\"));\n            log.info(\n                \"{}: Waiting for {} seconds for URL: {} (where port {} maps to container port {})\",\n                containerName,\n                startupTimeout.getSeconds(),\n                uri,\n                rawUri.getPort(),\n                originalPort\n            );\n        } catch (RuntimeException e) {\n            // do not allow a failure in logging to prevent progress, but log for diagnosis\n            log.warn(\"Unexpected error occurred - will proceed to try to wait anyway\", e);\n        }\n\n        // try to connect to the URL\n        try {\n            retryUntilSuccess(\n                (int) startupTimeout.getSeconds(),\n                TimeUnit.SECONDS,\n                () -> {\n                    getRateLimiter()\n                        .doWhenReady(() -> {\n                            try {\n                                final HttpURLConnection connection = openConnection(uri);\n                                connection.setReadTimeout(Math.toIntExact(readTimeout.toMillis()));\n\n                                // authenticate\n                                if (!Strings.isNullOrEmpty(username)) {\n                                    connection.setRequestProperty(\n                                        HEADER_AUTHORIZATION,\n                                        buildAuthString(username, password)\n                                    );\n                                    connection.setUseCaches(false);\n                                }\n\n                                // Add user configured headers\n                                this.headers.forEach(connection::setRequestProperty);\n                                connection.setRequestMethod(method);\n                                connection.connect();\n\n                                log.trace(\"Get response code {}\", connection.getResponseCode());\n\n                                // Choose the statusCodePredicate strategy depending on what we defined.\n                                Predicate<Integer> predicate;\n                                if (statusCodes.isEmpty() && statusCodePredicate == null) {\n                                    // We have no status code and no predicate so we expect a 200 OK response code\n                                    predicate = responseCode -> HttpURLConnection.HTTP_OK == responseCode;\n                                } else if (!statusCodes.isEmpty() && statusCodePredicate == null) {\n                                    // We use the default status predicate checker when we only have status codes\n                                    predicate = responseCode -> statusCodes.contains(responseCode);\n                                } else if (statusCodes.isEmpty()) {\n                                    // We only have a predicate\n                                    predicate = statusCodePredicate;\n                                } else {\n                                    // We have both predicate and status code\n                                    predicate =\n                                        statusCodePredicate.or(responseCode -> statusCodes.contains(responseCode));\n                                }\n                                if (!predicate.test(connection.getResponseCode())) {\n                                    throw new RuntimeException(\n                                        String.format(\"HTTP response code was: %s\", connection.getResponseCode())\n                                    );\n                                }\n\n                                if (responsePredicate != null) {\n                                    String responseBody = getResponseBody(connection);\n\n                                    log.trace(\"Get response {}\", responseBody);\n\n                                    if (!responsePredicate.test(responseBody)) {\n                                        throw new RuntimeException(\n                                            String.format(\"Response: %s did not match predicate\", responseBody)\n                                        );\n                                    }\n                                }\n                            } catch (IOException e) {\n                                throw new RuntimeException(e);\n                            }\n                        });\n                    return true;\n                }\n            );\n        } catch (TimeoutException e) {\n            throw new ContainerLaunchException(\n                String.format(\n                    \"Timed out waiting for URL to be accessible (%s should return HTTP %s)\",\n                    uri,\n                    statusCodes.isEmpty() ? HttpURLConnection.HTTP_OK : statusCodes\n                ),\n                e\n            );\n        }\n    }\n\n    private HttpURLConnection openConnection(final String uri) throws IOException, MalformedURLException {\n        if (tlsEnabled) {\n            final HttpsURLConnection connection = (HttpsURLConnection) new URL(uri).openConnection();\n            if (allowInsecure) {\n                // Create a trust manager that does not validate certificate chains\n                // and trust all certificates\n                final TrustManager[] trustAllCerts = new TrustManager[] {\n                    new X509ExtendedTrustManager() {\n                        @Override\n                        public X509Certificate[] getAcceptedIssuers() {\n                            return new X509Certificate[0];\n                        }\n\n                        @Override\n                        public void checkClientTrusted(final X509Certificate[] certs, final String authType) {}\n\n                        @Override\n                        public void checkServerTrusted(final X509Certificate[] certs, final String authType) {}\n\n                        @Override\n                        public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket) {}\n\n                        @Override\n                        public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket) {}\n\n                        @Override\n                        public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine) {}\n\n                        @Override\n                        public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine) {}\n                    },\n                };\n\n                try {\n                    // Create custom SSL context and set the \"trust all certificates\" trust manager\n                    final SSLContext sc = SSLContext.getInstance(\"SSL\");\n                    sc.init(new KeyManager[0], trustAllCerts, new SecureRandom());\n                    connection.setSSLSocketFactory(sc.getSocketFactory());\n                } catch (final NoSuchAlgorithmException | KeyManagementException ex) {\n                    throw new IOException(\"Unable to create custom SSL factory instance\", ex);\n                }\n            }\n\n            return connection;\n        } else {\n            return (HttpURLConnection) new URL(uri).openConnection();\n        }\n    }\n\n    /**\n     * Build the URI on which to check if the container is ready.\n     *\n     * @param livenessCheckPort the liveness port\n     * @return the liveness URI\n     */\n    private URI buildLivenessUri(int livenessCheckPort) {\n        final String scheme = (tlsEnabled ? \"https\" : \"http\") + \"://\";\n        final String host = waitStrategyTarget.getHost();\n\n        final String portSuffix;\n        if ((tlsEnabled && 443 == livenessCheckPort) || (!tlsEnabled && 80 == livenessCheckPort)) {\n            portSuffix = \"\";\n        } else {\n            portSuffix = \":\" + livenessCheckPort;\n        }\n\n        return URI.create(scheme + host + portSuffix + path);\n    }\n\n    /**\n     * @param username the username\n     * @param password the password\n     * @return a basic authentication string for the given credentials\n     */\n    private String buildAuthString(String username, String password) {\n        return AUTH_BASIC + BaseEncoding.base64().encode((username + \":\" + password).getBytes());\n    }\n\n    private String getResponseBody(HttpURLConnection connection) throws IOException {\n        BufferedReader reader;\n        if (200 <= connection.getResponseCode() && connection.getResponseCode() <= 299) {\n            reader = new BufferedReader(new InputStreamReader((connection.getInputStream())));\n        } else {\n            reader = new BufferedReader(new InputStreamReader((connection.getErrorStream())));\n        }\n\n        StringBuilder builder = new StringBuilder();\n        String line;\n        while ((line = reader.readLine()) != null) {\n            builder.append(line);\n        }\n        return builder.toString();\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/wait/strategy/LogMessageWaitStrategy.java",
    "content": "package org.testcontainers.containers.wait.strategy;\n\nimport com.github.dockerjava.api.command.LogContainerCmd;\nimport lombok.SneakyThrows;\nimport org.testcontainers.containers.ContainerLaunchException;\nimport org.testcontainers.containers.output.FrameConsumerResultCallback;\nimport org.testcontainers.containers.output.OutputFrame;\nimport org.testcontainers.containers.output.WaitingConsumer;\n\nimport java.io.IOException;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\nimport java.util.function.Predicate;\n\npublic class LogMessageWaitStrategy extends AbstractWaitStrategy {\n\n    private String regEx;\n\n    private int times = 1;\n\n    @Override\n    @SneakyThrows(IOException.class)\n    protected void waitUntilReady() {\n        WaitingConsumer waitingConsumer = new WaitingConsumer();\n\n        LogContainerCmd cmd = waitStrategyTarget\n            .getDockerClient()\n            .logContainerCmd(waitStrategyTarget.getContainerId())\n            .withFollowStream(true)\n            .withSince(0)\n            .withStdOut(true)\n            .withStdErr(true);\n\n        try (FrameConsumerResultCallback callback = new FrameConsumerResultCallback()) {\n            callback.addConsumer(OutputFrame.OutputType.STDOUT, waitingConsumer);\n            callback.addConsumer(OutputFrame.OutputType.STDERR, waitingConsumer);\n\n            cmd.exec(callback);\n\n            Predicate<OutputFrame> waitPredicate = outputFrame -> {\n                // (?s) enables line terminator matching (equivalent to Pattern.DOTALL)\n                return outputFrame.getUtf8String().matches(\"(?s)\" + regEx);\n            };\n            try {\n                waitingConsumer.waitUntil(waitPredicate, startupTimeout.getSeconds(), TimeUnit.SECONDS, times);\n            } catch (TimeoutException e) {\n                throw new ContainerLaunchException(\"Timed out waiting for log output matching '\" + regEx + \"'\");\n            }\n        }\n    }\n\n    public LogMessageWaitStrategy withRegEx(String regEx) {\n        this.regEx = regEx;\n        return this;\n    }\n\n    public LogMessageWaitStrategy withTimes(int times) {\n        this.times = times;\n        return this;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/wait/strategy/ShellStrategy.java",
    "content": "package org.testcontainers.containers.wait.strategy;\n\nimport org.rnorth.ducttape.TimeoutException;\nimport org.rnorth.ducttape.unreliables.Unreliables;\nimport org.testcontainers.containers.ContainerLaunchException;\n\nimport java.util.concurrent.TimeUnit;\n\npublic class ShellStrategy extends AbstractWaitStrategy {\n\n    private String command;\n\n    public ShellStrategy withCommand(String command) {\n        this.command = command;\n        return this;\n    }\n\n    @Override\n    protected void waitUntilReady() {\n        try {\n            Unreliables.retryUntilTrue(\n                (int) startupTimeout.getSeconds(),\n                TimeUnit.SECONDS,\n                () -> waitStrategyTarget.execInContainer(\"/bin/sh\", \"-c\", this.command).getExitCode() == 0\n            );\n        } catch (TimeoutException e) {\n            throw new ContainerLaunchException(\n                \"Timed out waiting for container to execute `\" + this.command + \"` successfully.\"\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/wait/strategy/Wait.java",
    "content": "package org.testcontainers.containers.wait.strategy;\n\n/**\n * Convenience class with logic for building common {@link WaitStrategy} instances.\n *\n */\npublic class Wait {\n\n    /**\n     * Convenience method to return the default WaitStrategy.\n     *\n     * @return a WaitStrategy\n     */\n    public static WaitStrategy defaultWaitStrategy() {\n        return forListeningPort();\n    }\n\n    /**\n     * Convenience method to return a WaitStrategy for an exposed or mapped port.\n     *\n     * @return the WaitStrategy\n     * @see HostPortWaitStrategy\n     */\n    public static HostPortWaitStrategy forListeningPort() {\n        return new HostPortWaitStrategy();\n    }\n\n    /**\n     * Convenience method to return a WaitStrategy for exposed or mapped ports.\n     *\n     * @param ports the port to check\n     * @return the WaitStrategy\n     */\n    public static HostPortWaitStrategy forListeningPorts(int... ports) {\n        return new HostPortWaitStrategy().forPorts(ports);\n    }\n\n    /**\n     * Convenience method to return a WaitStrategy for an HTTP endpoint.\n     *\n     * @param path the path to check\n     * @return the WaitStrategy\n     * @see HttpWaitStrategy\n     */\n    public static HttpWaitStrategy forHttp(String path) {\n        return new HttpWaitStrategy().forPath(path);\n    }\n\n    /**\n     * Convenience method to return a WaitStrategy for an HTTPS endpoint.\n     *\n     * @param path the path to check\n     * @return the WaitStrategy\n     * @see HttpWaitStrategy\n     */\n    public static HttpWaitStrategy forHttps(String path) {\n        return forHttp(path).usingTls();\n    }\n\n    /**\n     * Convenience method to return a WaitStrategy for log messages.\n     *\n     * @param regex the regex pattern to check for\n     * @param times the number of times the pattern is expected\n     * @return LogMessageWaitStrategy\n     */\n    public static LogMessageWaitStrategy forLogMessage(String regex, int times) {\n        return new LogMessageWaitStrategy().withRegEx(regex).withTimes(times);\n    }\n\n    /**\n     * Convenience method to return a WaitStrategy leveraging Docker's built-in healthcheck.\n     *\n     * @return DockerHealthcheckWaitStrategy\n     */\n    public static DockerHealthcheckWaitStrategy forHealthcheck() {\n        return new DockerHealthcheckWaitStrategy();\n    }\n\n    /**\n     * Convenience method to return a WaitStrategy for a shell command.\n     *\n     * @param command the command to run\n     * @return ShellStrategy\n     */\n    public static ShellStrategy forSuccessfulCommand(String command) {\n        return new ShellStrategy().withCommand(command);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/wait/strategy/WaitAllStrategy.java",
    "content": "package org.testcontainers.containers.wait.strategy;\n\nimport org.rnorth.ducttape.timeouts.Timeouts;\n\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.TimeUnit;\n\npublic class WaitAllStrategy implements WaitStrategy {\n\n    public enum Mode {\n        /**\n         * This is the default mode: The timeout of the {@link WaitAllStrategy strategy} is applied to each individual\n         * strategy, so that the container waits maximum for\n         * {@link org.testcontainers.containers.wait.strategy.WaitAllStrategy#timeout}.\n         */\n        WITH_OUTER_TIMEOUT,\n\n        /**\n         * Using this mode triggers the following behaviour: The outer timeout is disabled and the outer enclosing\n         * strategy waits for all inner strategies according to their timeout. Once set, it disables\n         * {@link org.testcontainers.containers.wait.strategy.WaitAllStrategy#withStartupTimeout(java.time.Duration)} method,\n         * as it would overwrite inner timeouts.\n         */\n        WITH_INDIVIDUAL_TIMEOUTS_ONLY,\n\n        /**\n         * This is the original mode of this strategy: The inner strategies wait with their preconfigured timeout\n         * individually and the wait all strategy kills them, if the outer limit is reached.\n         */\n        WITH_MAXIMUM_OUTER_TIMEOUT,\n    }\n\n    private final Mode mode;\n\n    private final List<WaitStrategy> strategies = new ArrayList<>();\n\n    private Duration timeout = Duration.ofSeconds(30);\n\n    public WaitAllStrategy() {\n        this(Mode.WITH_OUTER_TIMEOUT);\n    }\n\n    public WaitAllStrategy(Mode mode) {\n        this.mode = mode;\n    }\n\n    @Override\n    public void waitUntilReady(WaitStrategyTarget waitStrategyTarget) {\n        if (mode == Mode.WITH_INDIVIDUAL_TIMEOUTS_ONLY) {\n            waitUntilNestedStrategiesAreReady(waitStrategyTarget);\n        } else {\n            Timeouts.doWithTimeout(\n                (int) timeout.toMillis(),\n                TimeUnit.MILLISECONDS,\n                () -> {\n                    waitUntilNestedStrategiesAreReady(waitStrategyTarget);\n                }\n            );\n        }\n    }\n\n    private void waitUntilNestedStrategiesAreReady(WaitStrategyTarget waitStrategyTarget) {\n        for (WaitStrategy strategy : strategies) {\n            strategy.waitUntilReady(waitStrategyTarget);\n        }\n    }\n\n    public WaitAllStrategy withStrategy(WaitStrategy strategy) {\n        if (mode == Mode.WITH_OUTER_TIMEOUT) {\n            applyStartupTimeout(strategy);\n        }\n\n        this.strategies.add(strategy);\n        return this;\n    }\n\n    @Override\n    public WaitAllStrategy withStartupTimeout(Duration startupTimeout) {\n        if (mode == Mode.WITH_INDIVIDUAL_TIMEOUTS_ONLY) {\n            throw new IllegalStateException(\n                String.format(\n                    \"Changing startup timeout is not supported with mode %s\",\n                    Mode.WITH_INDIVIDUAL_TIMEOUTS_ONLY\n                )\n            );\n        }\n\n        this.timeout = startupTimeout;\n        strategies.forEach(this::applyStartupTimeout);\n        return this;\n    }\n\n    private void applyStartupTimeout(WaitStrategy childStrategy) {\n        childStrategy.withStartupTimeout(this.timeout);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/wait/strategy/WaitStrategy.java",
    "content": "package org.testcontainers.containers.wait.strategy;\n\nimport java.time.Duration;\n\npublic interface WaitStrategy {\n    void waitUntilReady(WaitStrategyTarget waitStrategyTarget);\n\n    WaitStrategy withStartupTimeout(Duration startupTimeout);\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/containers/wait/strategy/WaitStrategyTarget.java",
    "content": "package org.testcontainers.containers.wait.strategy;\n\nimport org.testcontainers.containers.ContainerState;\n\nimport java.util.Set;\nimport java.util.stream.Collectors;\n\npublic interface WaitStrategyTarget extends ContainerState {\n    /**\n     * @return the ports on which to check if the container is ready\n     */\n    default Set<Integer> getLivenessCheckPortNumbers() {\n        final Set<Integer> result = getExposedPorts()\n            .stream()\n            .map(this::getMappedPort)\n            .distinct()\n            .collect(Collectors.toSet());\n        result.addAll(getBoundPortNumbers());\n        return result;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/core/CreateContainerCmdModifier.java",
    "content": "package org.testcontainers.core;\n\nimport com.github.dockerjava.api.command.CreateContainerCmd;\n\n/**\n * Callback interface that can be used to customize a {@link CreateContainerCmd}.\n */\npublic interface CreateContainerCmdModifier {\n    /**\n     * Callback to modify a {@link CreateContainerCmd} instance.\n     */\n    CreateContainerCmd modify(CreateContainerCmd createContainerCmd);\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/dockerclient/AuditLoggingDockerClient.java",
    "content": "package org.testcontainers.dockerclient;\n\nimport com.github.dockerjava.api.DockerClient;\nimport com.github.dockerjava.api.command.CreateContainerCmd;\nimport com.github.dockerjava.api.command.CreateNetworkCmd;\nimport com.github.dockerjava.api.command.KillContainerCmd;\nimport com.github.dockerjava.api.command.RemoveContainerCmd;\nimport com.github.dockerjava.api.command.RemoveNetworkCmd;\nimport com.github.dockerjava.api.command.StartContainerCmd;\nimport com.github.dockerjava.api.command.StopContainerCmd;\nimport com.github.dockerjava.api.command.SyncDockerCmd;\nimport lombok.experimental.Delegate;\nimport lombok.extern.slf4j.Slf4j;\nimport org.jetbrains.annotations.NotNull;\nimport org.testcontainers.utility.AuditLogger;\n\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Proxy;\nimport java.util.function.BiConsumer;\n\n/**\n * Wrapper for {@link DockerClient} to facilitate 'audit logging' of potentially destruction actions using\n * {@link org.testcontainers.utility.AuditLogger}.\n *\n */\n@Slf4j\n@SuppressWarnings(\"unchecked\")\nclass AuditLoggingDockerClient implements DockerClient {\n\n    @Delegate(excludes = InterceptedMethods.class)\n    private final DockerClient wrappedClient;\n\n    public AuditLoggingDockerClient(DockerClient wrappedClient) {\n        this.wrappedClient = wrappedClient;\n    }\n\n    @Override\n    public CreateContainerCmd createContainerCmd(@NotNull String image) {\n        return wrappedCommand(\n            CreateContainerCmd.class,\n            wrappedClient.createContainerCmd(image),\n            (cmd, res) -> AuditLogger.doLog(\"CREATE\", image, res.getId(), cmd),\n            (cmd, e) -> AuditLogger.doLog(\"CREATE\", image, null, cmd, e)\n        );\n    }\n\n    @Override\n    public StartContainerCmd startContainerCmd(@NotNull String containerId) {\n        return wrappedCommand(\n            StartContainerCmd.class,\n            wrappedClient.startContainerCmd(containerId),\n            (cmd, res) -> AuditLogger.doLog(\"START\", null, containerId, cmd),\n            (cmd, e) -> AuditLogger.doLog(\"START\", null, containerId, cmd, e)\n        );\n    }\n\n    @Override\n    public RemoveContainerCmd removeContainerCmd(@NotNull String containerId) {\n        return wrappedCommand(\n            RemoveContainerCmd.class,\n            wrappedClient.removeContainerCmd(containerId),\n            (cmd, res) -> AuditLogger.doLog(\"REMOVE\", null, containerId, cmd),\n            (cmd, e) -> AuditLogger.doLog(\"REMOVE\", null, containerId, cmd, e)\n        );\n    }\n\n    @Override\n    public StopContainerCmd stopContainerCmd(@NotNull String containerId) {\n        return wrappedCommand(\n            StopContainerCmd.class,\n            wrappedClient.stopContainerCmd(containerId),\n            (cmd, res) -> AuditLogger.doLog(\"STOP\", null, containerId, cmd),\n            (cmd, e) -> AuditLogger.doLog(\"STOP\", null, containerId, cmd, e)\n        );\n    }\n\n    @Override\n    public KillContainerCmd killContainerCmd(@NotNull String containerId) {\n        return wrappedCommand(\n            KillContainerCmd.class,\n            wrappedClient.killContainerCmd(containerId),\n            (cmd, res) -> AuditLogger.doLog(\"KILL\", null, containerId, cmd),\n            (cmd, e) -> AuditLogger.doLog(\"KILL\", null, containerId, cmd, e)\n        );\n    }\n\n    @Override\n    public CreateNetworkCmd createNetworkCmd() {\n        return wrappedCommand(\n            CreateNetworkCmd.class,\n            wrappedClient.createNetworkCmd(),\n            (cmd, res) -> AuditLogger.doLog(\"CREATE_NETWORK\", null, null, cmd),\n            (cmd, e) -> AuditLogger.doLog(\"CREATE_NETWORK\", null, null, cmd, e)\n        );\n    }\n\n    @Override\n    public RemoveNetworkCmd removeNetworkCmd(@NotNull String networkId) {\n        return wrappedCommand(\n            RemoveNetworkCmd.class,\n            wrappedClient.removeNetworkCmd(networkId),\n            (cmd, res) -> AuditLogger.doLog(\"REMOVE_NETWORK\", null, null, cmd),\n            (cmd, e) -> AuditLogger.doLog(\"REMOVE_NETWORK\", null, null, cmd, e)\n        );\n    }\n\n    private <T extends SyncDockerCmd<R>, R> T wrappedCommand(\n        Class<T> clazz,\n        T cmd,\n        BiConsumer<T, R> successConsumer,\n        BiConsumer<T, Exception> failureConsumer\n    ) {\n        return (T) Proxy.newProxyInstance(\n            clazz.getClassLoader(),\n            new Class<?>[] { clazz },\n            (proxy, method, args) -> {\n                if (method.getName().equals(\"exec\")) {\n                    try {\n                        R r = (R) method.invoke(cmd, args);\n                        successConsumer.accept(cmd, r);\n                        return r;\n                    } catch (Exception e) {\n                        if (e instanceof InvocationTargetException && e.getCause() instanceof Exception) {\n                            e = (Exception) e.getCause();\n                        }\n                        failureConsumer.accept(cmd, e);\n                        throw e;\n                    }\n                } else {\n                    return method.invoke(cmd, args);\n                }\n            }\n        );\n    }\n\n    @SuppressWarnings(\"unused\")\n    private interface InterceptedMethods {\n        CreateContainerCmd createContainerCmd(String image);\n\n        StartContainerCmd startContainerCmd(String containerId);\n\n        RemoveContainerCmd removeContainerCmd(String containerId);\n\n        StopContainerCmd stopContainerCmd(String containerId);\n\n        KillContainerCmd killContainerCmd(String containerId);\n\n        CreateNetworkCmd createNetworkCmd();\n\n        RemoveNetworkCmd removeNetworkCmd(String networkId);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/dockerclient/AuthDelegatingDockerClientConfig.java",
    "content": "package org.testcontainers.dockerclient;\n\nimport com.github.dockerjava.api.model.AuthConfig;\nimport com.github.dockerjava.core.DockerClientConfig;\nimport com.github.dockerjava.core.DockerClientConfigDelegate;\nimport lombok.extern.slf4j.Slf4j;\nimport org.testcontainers.utility.AuthConfigUtil;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.RegistryAuthLocator;\n\n/**\n * Facade implementation for {@link DockerClientConfig} which overrides how authentication\n * configuration is obtained. A delegate {@link DockerClientConfig} will be called first\n * to try and obtain auth credentials, but after that {@link RegistryAuthLocator} will be\n * used to try and improve the auth resolution (e.g. using credential helpers).\n *\n * TODO move to docker-java\n */\n@Slf4j\nclass AuthDelegatingDockerClientConfig extends DockerClientConfigDelegate {\n\n    public AuthDelegatingDockerClientConfig(DockerClientConfig delegate) {\n        super(delegate);\n    }\n\n    @Override\n    public AuthConfig effectiveAuthConfig(String imageName) {\n        // allow docker-java auth config to be used as a fallback\n        AuthConfig fallbackAuthConfig;\n        try {\n            fallbackAuthConfig = super.effectiveAuthConfig(imageName);\n        } catch (Exception e) {\n            log.debug(\n                \"Delegate call to effectiveAuthConfig failed with cause: '{}'. \" +\n                \"Resolution of auth config will continue using RegistryAuthLocator.\",\n                e.getMessage()\n            );\n            fallbackAuthConfig = new AuthConfig();\n        }\n\n        // try and obtain more accurate auth config using our resolution\n        final DockerImageName parsed = DockerImageName.parse(imageName);\n        final AuthConfig effectiveAuthConfig = RegistryAuthLocator\n            .instance()\n            .lookupAuthConfig(parsed, fallbackAuthConfig);\n\n        log.debug(\"Effective auth config [{}]\", AuthConfigUtil.toSafeString(effectiveAuthConfig));\n        return effectiveAuthConfig;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/dockerclient/DockerClientConfigUtils.java",
    "content": "package org.testcontainers.dockerclient;\n\nimport lombok.Getter;\nimport lombok.extern.slf4j.Slf4j;\nimport org.apache.commons.lang3.StringUtils;\nimport org.testcontainers.DockerClientFactory;\n\nimport java.io.File;\nimport java.net.URI;\nimport java.util.Optional;\nimport java.util.concurrent.TimeUnit;\n\n@Slf4j\npublic class DockerClientConfigUtils {\n\n    // See https://github.com/docker/docker/blob/a9fa38b1edf30b23cae3eade0be48b3d4b1de14b/daemon/initlayer/setup_unix.go#L25\n    public static final boolean IN_A_CONTAINER = new File(\"/.dockerenv\").exists();\n\n    @Getter(lazy = true)\n    private static final Optional<String> defaultGateway = Optional\n        .ofNullable(\n            DockerClientFactory\n                .instance()\n                .runInsideDocker(\n                    cmd -> cmd.withCmd(\"sh\", \"-c\", \"ip route|awk '/default/ { print $3 }'\"),\n                    (client, id) -> {\n                        try {\n                            LogToStringContainerCallback loggingCallback = new LogToStringContainerCallback();\n                            client\n                                .logContainerCmd(id)\n                                .withStdOut(true)\n                                .withFollowStream(true)\n                                .exec(loggingCallback)\n                                .awaitStarted();\n                            loggingCallback.awaitCompletion(3, TimeUnit.SECONDS);\n                            return loggingCallback.toString();\n                        } catch (Exception e) {\n                            log.warn(\"Can't parse the default gateway IP\", e);\n                            return null;\n                        }\n                    }\n                )\n        )\n        .map(StringUtils::trimToEmpty)\n        .filter(StringUtils::isNotBlank);\n\n    /**\n     * @deprecated use {@link DockerClientProviderStrategy#getDockerHostIpAddress()}\n     */\n    @Deprecated\n    public static String getDockerHostIpAddress(URI dockerHost) {\n        switch (dockerHost.getScheme()) {\n            case \"http\":\n            case \"https\":\n            case \"tcp\":\n                return dockerHost.getHost();\n            case \"unix\":\n            case \"npipe\":\n                if (IN_A_CONTAINER) {\n                    return getDefaultGateway().orElse(\"localhost\");\n                }\n                return \"localhost\";\n            default:\n                return null;\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/dockerclient/DockerClientProviderStrategy.java",
    "content": "package org.testcontainers.dockerclient;\n\nimport com.github.dockerjava.api.DockerClient;\nimport com.github.dockerjava.api.model.Info;\nimport com.github.dockerjava.api.model.Network;\nimport com.github.dockerjava.core.DefaultDockerClientConfig;\nimport com.github.dockerjava.core.DockerClientImpl;\nimport com.github.dockerjava.core.RemoteApiVersion;\nimport com.github.dockerjava.transport.DockerHttpClient;\nimport com.github.dockerjava.transport.NamedPipeSocket;\nimport com.github.dockerjava.transport.SSLConfig;\nimport com.github.dockerjava.transport.UnixSocket;\nimport com.github.dockerjava.zerodep.ZerodepDockerHttpClient;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Throwables;\nimport lombok.Getter;\nimport lombok.extern.slf4j.Slf4j;\nimport org.apache.commons.io.IOUtils;\nimport org.apache.commons.lang3.StringUtils;\nimport org.awaitility.Awaitility;\nimport org.jetbrains.annotations.Nullable;\nimport org.rnorth.ducttape.TimeoutException;\nimport org.rnorth.ducttape.ratelimits.RateLimiter;\nimport org.rnorth.ducttape.ratelimits.RateLimiterBuilder;\nimport org.rnorth.ducttape.unreliables.Unreliables;\nimport org.testcontainers.DockerClientFactory;\nimport org.testcontainers.UnstableAPI;\nimport org.testcontainers.utility.TestcontainersConfiguration;\n\nimport java.io.File;\nimport java.net.InetSocketAddress;\nimport java.net.Socket;\nimport java.net.SocketAddress;\nimport java.net.URI;\nimport java.security.KeyManagementException;\nimport java.security.KeyStoreException;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.UnrecoverableKeyException;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.Comparator;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.function.Predicate;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\nimport javax.net.SocketFactory;\n\n/**\n * Mechanism to find a viable Docker client configuration according to the host system environment.\n * <p>\n * The order is:\n * <ul>\n *     <li>{@code TestcontainersHostPropertyClientProviderStrategy}</li>\n *     <li>{@code EnvironmentAndSystemPropertyClientProviderStrategy}</li>\n *     <li>Persistable {@code DockerClientProviderStrategy} in <code>~/.testcontainers.properties</code></li>\n *     <li>Other strategies order by priority</li>\n * </ul>\n */\n@Slf4j\npublic abstract class DockerClientProviderStrategy {\n\n    @Getter(lazy = true)\n    private final DockerClient dockerClient = getClientForConfig(getTransportConfig());\n\n    private String dockerHostIpAddress;\n\n    @Getter\n    private Info info;\n\n    private final RateLimiter PING_RATE_LIMITER = RateLimiterBuilder\n        .newBuilder()\n        .withRate(10, TimeUnit.SECONDS)\n        .withConstantThroughput()\n        .build();\n\n    private static final AtomicBoolean FAIL_FAST_ALWAYS = new AtomicBoolean(false);\n\n    /**\n     * @return a short textual description of the strategy\n     */\n    public abstract String getDescription();\n\n    protected boolean isApplicable() {\n        return true;\n    }\n\n    protected boolean isPersistable() {\n        return true;\n    }\n\n    public boolean allowUserOverrides() {\n        return true;\n    }\n\n    /**\n   /* @return the path under which the Docker unix socket is reachable relative to the Docker daemon\n    */\n    public String getRemoteDockerUnixSocketPath() {\n        return null;\n    }\n\n    /**\n     * @return highest to lowest priority value\n     */\n    protected int getPriority() {\n        return 0;\n    }\n\n    /**\n     * @throws InvalidConfigurationException if this strategy fails\n     */\n    public abstract TransportConfig getTransportConfig() throws InvalidConfigurationException;\n\n    /**\n     * @return a usable, tested, Docker client configuration for the host system environment\n     *\n     * @deprecated use {@link #getDockerClient()}\n     */\n    @Deprecated\n    public DockerClient getClient() {\n        DockerClient dockerClient = getDockerClient();\n        try {\n            Unreliables.retryUntilSuccess(\n                30,\n                TimeUnit.SECONDS,\n                () -> {\n                    return PING_RATE_LIMITER.getWhenReady(() -> {\n                        log.debug(\"Pinging docker daemon...\");\n                        dockerClient.pingCmd().exec();\n                        log.debug(\"Pinged\");\n                        return true;\n                    });\n                }\n            );\n        } catch (TimeoutException e) {\n            IOUtils.closeQuietly(dockerClient);\n            throw e;\n        }\n        return dockerClient;\n    }\n\n    /**\n     * TODO we should consider moving this to docker-java at some point\n     */\n    @UnstableAPI\n    protected boolean test() {\n        TransportConfig transportConfig = getTransportConfig();\n        URI dockerHost = transportConfig.getDockerHost();\n\n        Callable<Socket> socketProvider;\n        SocketAddress socketAddress;\n        switch (dockerHost.getScheme()) {\n            case \"tcp\":\n            case \"http\":\n            case \"https\":\n                SocketFactory socketFactory = SocketFactory.getDefault();\n                SSLConfig sslConfig = transportConfig.getSslConfig();\n                if (sslConfig != null) {\n                    try {\n                        socketFactory = sslConfig.getSSLContext().getSocketFactory();\n                    } catch (\n                        KeyManagementException\n                        | UnrecoverableKeyException\n                        | NoSuchAlgorithmException\n                        | KeyStoreException e\n                    ) {\n                        log.warn(\"Exception while creating SSLSocketFactory\", e);\n                        return false;\n                    }\n                }\n                socketProvider = socketFactory::createSocket;\n                socketAddress = new InetSocketAddress(dockerHost.getHost(), dockerHost.getPort());\n                break;\n            case \"unix\":\n            case \"npipe\":\n                if (!new File(dockerHost.getPath()).exists()) {\n                    log.debug(\"DOCKER_HOST socket file '{}' does not exist\", dockerHost.getPath());\n                    return false;\n                }\n                socketProvider =\n                    () -> {\n                        switch (dockerHost.getScheme()) {\n                            case \"unix\":\n                                return UnixSocket.get(dockerHost.getPath());\n                            case \"npipe\":\n                                return new NamedPipeSocket(dockerHost.getPath());\n                            default:\n                                throw new IllegalStateException(\"Unexpected scheme \" + dockerHost.getScheme());\n                        }\n                    };\n                socketAddress = new InetSocketAddress(\"localhost\", 2375);\n                break;\n            default:\n                log.warn(\"Unknown DOCKER_HOST scheme {}, skipping the strategy test...\", dockerHost.getScheme());\n                return true;\n        }\n\n        try (Socket socket = socketProvider.call()) {\n            Awaitility\n                .await()\n                .atMost(TestcontainersConfiguration.getInstance().getClientPingTimeout(), TimeUnit.SECONDS) // timeout after configured duration\n                .pollInterval(Duration.ofMillis(200)) // check state every 200ms\n                .pollDelay(Duration.ofSeconds(0)) // start checking immediately\n                .untilAsserted(() -> socket.connect(socketAddress));\n            return true;\n        } catch (Exception e) {\n            log.warn(\"DOCKER_HOST {} is not listening\", dockerHost, e);\n            return false;\n        }\n    }\n\n    /**\n     * Determine the right DockerClientConfig to use for building clients by trial-and-error.\n     *\n     * @return a working DockerClientConfig, as determined by successful execution of a ping command\n     */\n    public static DockerClientProviderStrategy getFirstValidStrategy(List<DockerClientProviderStrategy> strategies) {\n        if (FAIL_FAST_ALWAYS.get()) {\n            throw new IllegalStateException(\n                \"Previous attempts to find a Docker environment failed. Will not retry. Please see logs and check configuration\"\n            );\n        }\n\n        List<String> configurationFailures = new ArrayList<>();\n        List<DockerClientProviderStrategy> allStrategies = new ArrayList<>();\n\n        // Manually enforce priority independent of priority property of strategy\n        allStrategies.add(new TestcontainersHostPropertyClientProviderStrategy());\n        allStrategies.add(new EnvironmentAndSystemPropertyClientProviderStrategy());\n\n        // Next strategy to try out is the one configured using the Testcontainers configuration mechanism\n        loadConfiguredStrategy().ifPresent(allStrategies::add);\n\n        // Finally, add all other strategies ordered by their internal priority\n        strategies\n            .stream()\n            .sorted(Comparator.comparing(DockerClientProviderStrategy::getPriority).reversed())\n            .collect(Collectors.toCollection(() -> allStrategies));\n\n        Predicate<DockerClientProviderStrategy> distinctStrategyClassPredicate = new Predicate<DockerClientProviderStrategy>() {\n            final Set<Class<? extends DockerClientProviderStrategy>> classes = new HashSet<>();\n\n            @Override\n            public boolean test(DockerClientProviderStrategy dockerClientProviderStrategy) {\n                return classes.add(dockerClientProviderStrategy.getClass());\n            }\n        };\n\n        return allStrategies\n            .stream()\n            .filter(distinctStrategyClassPredicate)\n            .filter(DockerClientProviderStrategy::isApplicable)\n            .filter(strategy -> tryOutStrategy(configurationFailures, strategy))\n            .findFirst()\n            .orElseThrow(() -> {\n                log.error(\n                    \"Could not find a valid Docker environment. Please check configuration. Attempted configurations were:\\n\" +\n                    configurationFailures.stream().map(it -> \"\\t\" + it).collect(Collectors.joining(\"\\n\")) +\n                    \"As no valid configuration was found, execution cannot continue.\\n\" +\n                    \"See https://java.testcontainers.org/on_failure.html for more details.\"\n                );\n\n                FAIL_FAST_ALWAYS.set(true);\n                return new IllegalStateException(\n                    \"Could not find a valid Docker environment. Please see logs and check configuration\"\n                );\n            });\n    }\n\n    private static boolean tryOutStrategy(List<String> configurationFailures, DockerClientProviderStrategy strategy) {\n        try {\n            log.debug(\"Trying out strategy: {}\", strategy.getClass().getSimpleName());\n\n            if (!strategy.test()) {\n                log.debug(\"strategy {} did not pass the test\", strategy.getClass().getSimpleName());\n                return false;\n            }\n\n            strategy.info = strategy.getDockerClient().infoCmd().exec();\n            log.info(\"Found Docker environment with {}\", strategy.getDescription());\n            log.debug(\n                \"Transport type: '{}', Docker host: '{}'\",\n                TestcontainersConfiguration.getInstance().getTransportType(),\n                strategy.getTransportConfig().getDockerHost()\n            );\n\n            log.debug(\"Checking Docker OS type for {}\", strategy.getDescription());\n            String osType = strategy.getInfo().getOsType();\n            if (StringUtils.isBlank(osType)) {\n                log.warn(\"Could not determine Docker OS type\");\n            } else if (!osType.equals(\"linux\")) {\n                log.warn(\"{} is currently not supported\", osType);\n                throw new InvalidConfigurationException(osType + \" containers are currently not supported\");\n            }\n\n            if (strategy.isPersistable()) {\n                TestcontainersConfiguration\n                    .getInstance()\n                    .updateUserConfig(\"docker.client.strategy\", strategy.getClass().getName());\n            }\n\n            return true;\n        } catch (Exception | ExceptionInInitializerError | NoClassDefFoundError e) {\n            @Nullable\n            String throwableMessage = e.getMessage();\n            @SuppressWarnings(\"ThrowableResultOfMethodCallIgnored\")\n            Throwable rootCause = Throwables.getRootCause(e);\n            @Nullable\n            String rootCauseMessage = rootCause.getMessage();\n\n            String failureDescription;\n            if (throwableMessage != null && throwableMessage.equals(rootCauseMessage)) {\n                failureDescription =\n                    String.format(\n                        \"%s: failed with exception %s (%s)\",\n                        strategy.getClass().getSimpleName(),\n                        e.getClass().getSimpleName(),\n                        throwableMessage\n                    );\n            } else {\n                failureDescription =\n                    String.format(\n                        \"%s: failed with exception %s (%s). Root cause %s (%s)\",\n                        strategy.getClass().getSimpleName(),\n                        e.getClass().getSimpleName(),\n                        throwableMessage,\n                        rootCause.getClass().getSimpleName(),\n                        rootCauseMessage\n                    );\n            }\n            configurationFailures.add(failureDescription);\n\n            log.debug(failureDescription);\n            return false;\n        }\n    }\n\n    private static Optional<? extends DockerClientProviderStrategy> loadConfiguredStrategy() {\n        String configuredDockerClientStrategyClassName = TestcontainersConfiguration\n            .getInstance()\n            .getDockerClientStrategyClassName();\n\n        return Stream\n            .of(configuredDockerClientStrategyClassName)\n            .filter(Objects::nonNull)\n            .flatMap(it -> {\n                try {\n                    Class<? extends DockerClientProviderStrategy> strategyClass = (Class) Thread\n                        .currentThread()\n                        .getContextClassLoader()\n                        .loadClass(it);\n                    return Stream.of(strategyClass.newInstance());\n                } catch (ClassNotFoundException e) {\n                    log.warn(\n                        \"Can't instantiate a strategy from {} (ClassNotFoundException). \" +\n                        \"This probably means that cached configuration refers to a client provider \" +\n                        \"class that is not available in this version of Testcontainers. Other \" +\n                        \"strategies will be tried instead.\",\n                        it\n                    );\n                    return Stream.empty();\n                } catch (InstantiationException | IllegalAccessException e) {\n                    log.warn(\"Can't instantiate a strategy from {}\", it, e);\n                    return Stream.empty();\n                }\n            })\n            // Ignore persisted strategy if it's not persistable anymore\n            .filter(DockerClientProviderStrategy::isPersistable)\n            .peek(strategy -> {\n                log.info(\n                    \"Loaded {} from ~/.testcontainers.properties, will try it first\",\n                    strategy.getClass().getName()\n                );\n            })\n            .findFirst();\n    }\n\n    public static DockerClient getClientForConfig(TransportConfig transportConfig) {\n        final DockerHttpClient dockerHttpClient;\n\n        String transportType = TestcontainersConfiguration.getInstance().getTransportType();\n        switch (transportType) {\n            case \"httpclient5\":\n                dockerHttpClient =\n                    new ZerodepDockerHttpClient.Builder()\n                        .dockerHost(transportConfig.getDockerHost())\n                        .sslConfig(transportConfig.getSslConfig())\n                        .build();\n                break;\n            default:\n                throw new IllegalArgumentException(\"Unknown transport type '\" + transportType + \"'\");\n        }\n\n        DefaultDockerClientConfig.Builder configBuilder = DefaultDockerClientConfig\n            .createDefaultConfigBuilder()\n            .withDockerHost(transportConfig.getDockerHost().toString());\n\n        Map<String, String> headers = new HashMap<>();\n        headers.put(\"x-tc-sid\", DockerClientFactory.SESSION_ID);\n        headers.put(\"User-Agent\", String.format(\"tc-java/%s\", DockerClientFactory.TESTCONTAINERS_VERSION));\n\n        try {\n            if (configBuilder.build().getApiVersion() == RemoteApiVersion.UNKNOWN_VERSION) {\n                configBuilder.withApiVersion(RemoteApiVersion.VERSION_1_44);\n            }\n            DockerClient client = DockerClientImpl.getInstance(\n                new AuthDelegatingDockerClientConfig(configBuilder.build()),\n                new HeadersAddingDockerHttpClient(dockerHttpClient, headers)\n            );\n            log.debug(\"Pinging Docker API version 1.44.\");\n            client.pingCmd().exec();\n            return client;\n        } catch (Exception ex) {\n            log.debug(\"Fallback to Docker API version 1.32.\");\n            return DockerClientImpl.getInstance(\n                new AuthDelegatingDockerClientConfig(\n                    configBuilder.withApiVersion(RemoteApiVersion.VERSION_1_32).build()\n                ),\n                new HeadersAddingDockerHttpClient(dockerHttpClient, headers)\n            );\n        }\n    }\n\n    public synchronized String getDockerHostIpAddress() {\n        if (dockerHostIpAddress == null) {\n            dockerHostIpAddress =\n                resolveDockerHostIpAddress(\n                    getDockerClient(),\n                    getTransportConfig().getDockerHost(),\n                    allowUserOverrides()\n                );\n        }\n        return dockerHostIpAddress;\n    }\n\n    @VisibleForTesting\n    static String resolveDockerHostIpAddress(DockerClient client, URI dockerHost, boolean allowUserOverrides) {\n        if (allowUserOverrides) {\n            String hostOverride = System.getenv(\"TESTCONTAINERS_HOST_OVERRIDE\");\n            if (!StringUtils.isBlank(hostOverride)) {\n                return hostOverride;\n            }\n        }\n\n        switch (dockerHost.getScheme()) {\n            case \"http\":\n            case \"https\":\n            case \"tcp\":\n                return dockerHost.getHost();\n            case \"unix\":\n            case \"npipe\":\n                if (DockerClientConfigUtils.IN_A_CONTAINER) {\n                    return client\n                        .inspectNetworkCmd()\n                        .withNetworkId(\"bridge\")\n                        .exec()\n                        .getIpam()\n                        .getConfig()\n                        .stream()\n                        .filter(it -> it.getGateway() != null)\n                        .findAny()\n                        .map(Network.Ipam.Config::getGateway)\n                        .orElseGet(() -> {\n                            return DockerClientConfigUtils.getDefaultGateway().orElse(\"localhost\");\n                        });\n                }\n                return \"localhost\";\n            default:\n                return null;\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/dockerclient/DockerDesktopClientProviderStrategy.java",
    "content": "package org.testcontainers.dockerclient;\n\nimport lombok.Getter;\nimport lombok.extern.slf4j.Slf4j;\nimport org.apache.commons.lang3.SystemUtils;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.net.URI;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Optional;\n\n/**\n * Look at the following paths:\n * <ul>\n *     <li>Linux: ~/.docker/desktop/docker.sock</li>\n *     <li>MacOS: ~/.docker/run/docker.sock</li>\n * </ul>\n *\n * @deprecated this class is used by the SPI and should not be used directly\n */\n@Slf4j\n@Deprecated\npublic class DockerDesktopClientProviderStrategy extends DockerClientProviderStrategy {\n\n    public static final int PRIORITY = UnixSocketClientProviderStrategy.PRIORITY - 1;\n\n    @Getter(lazy = true)\n    @Nullable\n    private final Path socketPath = resolveSocketPath();\n\n    private Path resolveSocketPath() {\n        Path linuxPath = Paths.get(System.getProperty(\"user.home\")).resolve(\".docker\").resolve(\"desktop\");\n        return tryFolder(linuxPath)\n            .orElseGet(() -> {\n                Path macosPath = Paths.get(System.getProperty(\"user.home\")).resolve(\".docker\").resolve(\"run\");\n                return tryFolder(macosPath).orElse(null);\n            });\n    }\n\n    @Override\n    public String getDescription() {\n        return \"Docker accessed via Unix socket (\" + getSocketPath() + \")\";\n    }\n\n    @Override\n    public TransportConfig getTransportConfig() throws InvalidConfigurationException {\n        return TransportConfig.builder().dockerHost(URI.create(\"unix://\" + getSocketPath().toString())).build();\n    }\n\n    @Override\n    protected int getPriority() {\n        return PRIORITY;\n    }\n\n    @Override\n    protected boolean isPersistable() {\n        return false;\n    }\n\n    @Override\n    public String getRemoteDockerUnixSocketPath() {\n        return \"/var/run/docker.sock\";\n    }\n\n    @Override\n    protected boolean isApplicable() {\n        return (SystemUtils.IS_OS_LINUX || SystemUtils.IS_OS_MAC) && this.socketPath != null;\n    }\n\n    private Optional<Path> tryFolder(Path path) {\n        if (!Files.exists(path)) {\n            log.debug(\"'{}' does not exist.\", path);\n            return Optional.empty();\n        }\n        Path socketPath = path.resolve(\"docker.sock\");\n        if (!Files.exists(socketPath)) {\n            log.debug(\"'{}' does not exist.\", socketPath);\n            return Optional.empty();\n        }\n        return Optional.of(socketPath);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/dockerclient/DockerMachineClientProviderStrategy.java",
    "content": "package org.testcontainers.dockerclient;\n\nimport com.github.dockerjava.core.LocalDirectorySSLConfig;\nimport com.google.common.base.Preconditions;\nimport lombok.Getter;\nimport lombok.extern.slf4j.Slf4j;\nimport org.testcontainers.utility.CommandLine;\nimport org.testcontainers.utility.DockerMachineClient;\n\nimport java.net.URI;\nimport java.nio.file.Paths;\nimport java.util.Arrays;\nimport java.util.Optional;\n\n/**\n * Use Docker machine (if available on the PATH) to locate a Docker environment.\n *\n * @deprecated this class is used by the SPI and should not be used directly\n */\n@Slf4j\n@Deprecated\npublic final class DockerMachineClientProviderStrategy extends DockerClientProviderStrategy {\n\n    @Getter(lazy = true)\n    private final TransportConfig transportConfig = resolveTransportConfig();\n\n    private TransportConfig resolveTransportConfig() throws InvalidConfigurationException {\n        boolean installed = DockerMachineClient.instance().isInstalled();\n        Preconditions.checkArgument(\n            installed,\n            \"docker-machine executable was not found on PATH (\" + Arrays.toString(CommandLine.getSystemPath()) + \")\"\n        );\n\n        Optional<String> machineNameOptional = DockerMachineClient.instance().getDefaultMachine();\n        Preconditions.checkArgument(\n            machineNameOptional.isPresent(),\n            \"docker-machine is installed but no default machine could be found\"\n        );\n        String machineName = machineNameOptional.get();\n\n        log.info(\"Found docker-machine, and will use machine named {}\", machineName);\n\n        DockerMachineClient.instance().ensureMachineRunning(machineName);\n\n        String dockerDaemonUrl = DockerMachineClient.instance().getDockerDaemonUrl(machineName);\n\n        log.info(\"Docker daemon URL for docker machine {} is {}\", machineName, dockerDaemonUrl);\n\n        return TransportConfig\n            .builder()\n            .dockerHost(URI.create(dockerDaemonUrl))\n            .sslConfig(\n                new LocalDirectorySSLConfig(\n                    Paths.get(System.getProperty(\"user.home\") + \"/.docker/machine/certs/\").toString()\n                )\n            )\n            .build();\n    }\n\n    @Override\n    protected boolean isApplicable() {\n        boolean installed = DockerMachineClient.instance().isInstalled();\n        if (!installed) {\n            log.info(\n                \"docker-machine executable was not found on PATH ({})\",\n                Arrays.toString(CommandLine.getSystemPath())\n            );\n            return false;\n        }\n\n        Optional<String> machineNameOptional = DockerMachineClient.instance().getDefaultMachine();\n        if (!machineNameOptional.isPresent()) {\n            log.info(\"docker-machine is installed but no default machine could be found\");\n        }\n\n        return true;\n    }\n\n    @Override\n    protected boolean isPersistable() {\n        return false;\n    }\n\n    @Override\n    protected int getPriority() {\n        return EnvironmentAndSystemPropertyClientProviderStrategy.PRIORITY - 100;\n    }\n\n    @Override\n    public String getDescription() {\n        return \"docker-machine\";\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/dockerclient/EnvironmentAndSystemPropertyClientProviderStrategy.java",
    "content": "package org.testcontainers.dockerclient;\n\nimport com.github.dockerjava.core.DefaultDockerClientConfig;\nimport com.github.dockerjava.core.DockerClientConfig;\nimport lombok.Getter;\nimport org.testcontainers.utility.TestcontainersConfiguration;\n\nimport java.util.Optional;\n\n/**\n * Use environment variables and system properties (as supported by the underlying DockerClient DefaultConfigBuilder)\n * to try and locate a docker environment.\n * <p>\n * Resolution order is:\n * <ol>\n *     <li>DOCKER_HOST env var</li>\n *     <li>docker.host in ~/.testcontainers.properties</li>\n * </ol>\n *\n * @deprecated this class is used by the SPI and should not be used directly\n */\n@Deprecated\npublic final class EnvironmentAndSystemPropertyClientProviderStrategy extends DockerClientProviderStrategy {\n\n    public static final int PRIORITY = 100;\n\n    private final DockerClientConfig dockerClientConfig;\n\n    @Getter\n    private final boolean applicable;\n\n    public EnvironmentAndSystemPropertyClientProviderStrategy() {\n        // use docker-java defaults if present, overridden if our own configuration is set\n        this(DefaultDockerClientConfig.createDefaultConfigBuilder());\n    }\n\n    EnvironmentAndSystemPropertyClientProviderStrategy(DefaultDockerClientConfig.Builder configBuilder) {\n        String dockerConfigSource = TestcontainersConfiguration\n            .getInstance()\n            .getEnvVarOrProperty(\"dockerconfig.source\", \"auto\");\n\n        switch (dockerConfigSource) {\n            case \"auto\":\n                Optional<String> dockerHost = getSetting(\"docker.host\");\n                dockerHost.ifPresent(configBuilder::withDockerHost);\n                applicable = dockerHost.isPresent();\n                getSetting(\"docker.tls.verify\").ifPresent(configBuilder::withDockerTlsVerify);\n                getSetting(\"docker.cert.path\").ifPresent(configBuilder::withDockerCertPath);\n                break;\n            case \"autoIgnoringUserProperties\":\n                applicable = configBuilder.isDockerHostSetExplicitly();\n                break;\n            default:\n                throw new InvalidConfigurationException(\"Invalid value for dockerconfig.source: \" + dockerConfigSource);\n        }\n\n        dockerClientConfig = configBuilder.build();\n    }\n\n    private Optional<String> getSetting(final String name) {\n        return Optional.ofNullable(TestcontainersConfiguration.getInstance().getEnvVarOrUserProperty(name, null));\n    }\n\n    @Override\n    public TransportConfig getTransportConfig() {\n        return TransportConfig\n            .builder()\n            .dockerHost(dockerClientConfig.getDockerHost())\n            .sslConfig(dockerClientConfig.getSSLConfig())\n            .build();\n    }\n\n    @Override\n    protected int getPriority() {\n        return PRIORITY;\n    }\n\n    @Override\n    public String getDescription() {\n        return (\n            \"Environment variables, system properties and defaults. Resolved dockerHost=\" +\n            dockerClientConfig.getDockerHost()\n        );\n    }\n\n    @Override\n    protected boolean isPersistable() {\n        return false;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/dockerclient/HeadersAddingDockerHttpClient.java",
    "content": "package org.testcontainers.dockerclient;\n\nimport com.github.dockerjava.transport.DockerHttpClient;\nimport lombok.RequiredArgsConstructor;\nimport lombok.ToString;\nimport lombok.experimental.Delegate;\n\nimport java.io.Closeable;\nimport java.util.Map;\n\n@RequiredArgsConstructor\n@ToString\nclass HeadersAddingDockerHttpClient implements DockerHttpClient {\n\n    @Delegate(types = Closeable.class)\n    final DockerHttpClient delegate;\n\n    final Map<String, String> headers;\n\n    @Override\n    public Response execute(Request request) {\n        request = Request.builder().from(request).putAllHeaders(headers).build();\n        return delegate.execute(request);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/dockerclient/InvalidConfigurationException.java",
    "content": "package org.testcontainers.dockerclient;\n\n/**\n * Exception to indicate that a {@link DockerClientProviderStrategy} fails.\n */\npublic class InvalidConfigurationException extends RuntimeException {\n\n    public InvalidConfigurationException(String s) {\n        super(s);\n    }\n\n    public InvalidConfigurationException(String message, Throwable cause) {\n        super(message, cause);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/dockerclient/LogToStringContainerCallback.java",
    "content": "package org.testcontainers.dockerclient;\n\nimport com.github.dockerjava.api.async.ResultCallback;\nimport com.github.dockerjava.api.model.Frame;\n\n/**\n *\n * @deprecated use {@link ResultCallback.Adapter}\n */\n@Deprecated\npublic class LogToStringContainerCallback extends ResultCallback.Adapter<Frame> {\n\n    private final StringBuffer log = new StringBuffer();\n\n    @Override\n    public void onNext(Frame frame) {\n        log.append(new String(frame.getPayload()));\n    }\n\n    @Override\n    public String toString() {\n        try {\n            awaitCompletion();\n        } catch (InterruptedException e) {\n            throw new RuntimeException(e);\n        }\n        return log.toString();\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/dockerclient/NpipeSocketClientProviderStrategy.java",
    "content": "package org.testcontainers.dockerclient;\n\nimport org.apache.commons.lang3.SystemUtils;\n\nimport java.net.URI;\n\n/**\n *\n * @deprecated this class is used by the SPI and should not be used directly\n */\n@Deprecated\npublic final class NpipeSocketClientProviderStrategy extends DockerClientProviderStrategy {\n\n    protected static final String DOCKER_SOCK_PATH = \"//./pipe/docker_engine\";\n\n    private static final String SOCKET_LOCATION = \"npipe://\" + DOCKER_SOCK_PATH;\n\n    public static final int PRIORITY = EnvironmentAndSystemPropertyClientProviderStrategy.PRIORITY - 20;\n\n    @Override\n    public TransportConfig getTransportConfig() {\n        return TransportConfig.builder().dockerHost(URI.create(SOCKET_LOCATION)).build();\n    }\n\n    @Override\n    protected boolean isApplicable() {\n        return SystemUtils.IS_OS_WINDOWS;\n    }\n\n    @Override\n    public String getDescription() {\n        return \"local Npipe socket (\" + SOCKET_LOCATION + \")\";\n    }\n\n    @Override\n    protected int getPriority() {\n        return PRIORITY;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/dockerclient/RootlessDockerClientProviderStrategy.java",
    "content": "package org.testcontainers.dockerclient;\n\nimport com.sun.jna.Library;\nimport com.sun.jna.Native;\nimport lombok.Getter;\nimport lombok.extern.slf4j.Slf4j;\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.commons.lang3.SystemUtils;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.net.URI;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Optional;\n\n/**\n *\n * @deprecated this class is used by the SPI and should not be used directly\n */\n@Deprecated\n@Slf4j\npublic final class RootlessDockerClientProviderStrategy extends DockerClientProviderStrategy {\n\n    public static final int PRIORITY = UnixSocketClientProviderStrategy.PRIORITY + 1;\n\n    @Getter(lazy = true)\n    @Nullable\n    private final Path socketPath = resolveSocketPath();\n\n    private Path resolveSocketPath() {\n        return tryEnv()\n            .orElseGet(() -> {\n                Path homePath = Paths.get(System.getProperty(\"user.home\")).resolve(\".docker\").resolve(\"run\");\n                return tryFolder(homePath)\n                    .orElseGet(() -> {\n                        Path implicitPath = Paths.get(\"/run/user/\" + LibC.INSTANCE.getuid());\n                        return tryFolder(implicitPath).orElse(null);\n                    });\n            });\n    }\n\n    private Optional<Path> tryEnv() {\n        String xdgRuntimeDir = System.getenv(\"XDG_RUNTIME_DIR\");\n        if (StringUtils.isBlank(xdgRuntimeDir)) {\n            log.debug(\"$XDG_RUNTIME_DIR is not set.\");\n            return Optional.empty();\n        }\n        Path path = Paths.get(xdgRuntimeDir);\n        if (!Files.exists(path)) {\n            log.debug(\"$XDG_RUNTIME_DIR is set to '{}' but the folder does not exist.\", path);\n            return Optional.empty();\n        }\n        Path socketPath = path.resolve(\"docker.sock\");\n        if (!Files.exists(socketPath)) {\n            log.debug(\"$XDG_RUNTIME_DIR is set but '{}' does not exist.\", socketPath);\n            return Optional.empty();\n        }\n        return Optional.of(socketPath);\n    }\n\n    private Optional<Path> tryFolder(Path path) {\n        if (!Files.exists(path)) {\n            log.debug(\"'{}' does not exist.\", path);\n            return Optional.empty();\n        }\n        Path socketPath = path.resolve(\"docker.sock\");\n        if (!Files.exists(socketPath)) {\n            log.debug(\"'{}' does not exist.\", socketPath);\n            return Optional.empty();\n        }\n        return Optional.of(socketPath);\n    }\n\n    @Override\n    public TransportConfig getTransportConfig() throws InvalidConfigurationException {\n        return TransportConfig.builder().dockerHost(URI.create(\"unix://\" + getSocketPath().toString())).build();\n    }\n\n    @Override\n    protected boolean isApplicable() {\n        return SystemUtils.IS_OS_LINUX && getSocketPath() != null && Files.exists(getSocketPath());\n    }\n\n    @Override\n    public String getDescription() {\n        return \"Rootless Docker accessed via Unix socket (\" + getSocketPath() + \")\";\n    }\n\n    @Override\n    protected int getPriority() {\n        return PRIORITY;\n    }\n\n    private interface LibC extends Library {\n        LibC INSTANCE = Native.loadLibrary(\"c\", LibC.class);\n\n        int getuid();\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/dockerclient/TestcontainersHostPropertyClientProviderStrategy.java",
    "content": "package org.testcontainers.dockerclient;\n\nimport com.github.dockerjava.core.DefaultDockerClientConfig;\nimport com.github.dockerjava.core.DockerClientConfig;\nimport org.testcontainers.utility.TestcontainersConfiguration;\n\nimport java.util.Optional;\n\n/**\n * Use <code>tc.host</code> in <code>~/.testcontainers.properties</code>\n * to try and locate a docker environment.\n *\n * @deprecated this class is used by the SPI and should not be used directly\n */\n@Deprecated\npublic final class TestcontainersHostPropertyClientProviderStrategy extends DockerClientProviderStrategy {\n\n    public static final int PRIORITY = EnvironmentAndSystemPropertyClientProviderStrategy.PRIORITY - 10;\n\n    private DockerClientConfig dockerClientConfig;\n\n    public TestcontainersHostPropertyClientProviderStrategy() {\n        this(DefaultDockerClientConfig.createDefaultConfigBuilder());\n    }\n\n    TestcontainersHostPropertyClientProviderStrategy(DefaultDockerClientConfig.Builder configBuilder) {\n        Optional<String> tcHost = Optional.ofNullable(\n            TestcontainersConfiguration.getInstance().getUserProperty(\"tc.host\", null)\n        );\n\n        if (tcHost.isPresent()) {\n            configBuilder.withDockerHost(tcHost.get());\n            this.dockerClientConfig = configBuilder.build();\n        }\n    }\n\n    @Override\n    public String getDescription() {\n        return \"Testcontainers Host with tc.host=\" + this.dockerClientConfig.getDockerHost();\n    }\n\n    @Override\n    public TransportConfig getTransportConfig() throws InvalidConfigurationException {\n        return TransportConfig\n            .builder()\n            .dockerHost(dockerClientConfig.getDockerHost())\n            .sslConfig(dockerClientConfig.getSSLConfig())\n            .build();\n    }\n\n    @Override\n    protected boolean isApplicable() {\n        return this.dockerClientConfig != null;\n    }\n\n    @Override\n    protected int getPriority() {\n        return PRIORITY;\n    }\n\n    @Override\n    protected boolean isPersistable() {\n        return false;\n    }\n\n    @Override\n    public boolean allowUserOverrides() {\n        return false;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/dockerclient/TransportConfig.java",
    "content": "package org.testcontainers.dockerclient;\n\nimport com.github.dockerjava.transport.SSLConfig;\nimport lombok.Builder;\nimport lombok.Value;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.net.URI;\n\n@Builder\n@Value\npublic class TransportConfig {\n\n    URI dockerHost;\n\n    @Nullable\n    SSLConfig sslConfig;\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/dockerclient/UnixSocketClientProviderStrategy.java",
    "content": "package org.testcontainers.dockerclient;\n\nimport org.apache.commons.lang3.SystemUtils;\n\nimport java.io.IOException;\nimport java.net.URI;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\n\n/**\n *\n * @deprecated this class is used by the SPI and should not be used directly\n */\n@Deprecated\npublic final class UnixSocketClientProviderStrategy extends DockerClientProviderStrategy {\n\n    protected static final String DOCKER_SOCK_PATH = \"/var/run/docker.sock\";\n\n    private static final String SOCKET_LOCATION = \"unix://\" + DOCKER_SOCK_PATH;\n\n    private static final int SOCKET_FILE_MODE_MASK = 0xc000;\n\n    public static final int PRIORITY = EnvironmentAndSystemPropertyClientProviderStrategy.PRIORITY - 20;\n\n    @Override\n    public TransportConfig getTransportConfig() throws InvalidConfigurationException {\n        Path dockerSocketFile = Paths.get(DOCKER_SOCK_PATH);\n        Integer mode;\n        try {\n            mode = (Integer) Files.getAttribute(dockerSocketFile, \"unix:mode\");\n        } catch (IOException e) {\n            throw new InvalidConfigurationException(\"Could not find unix domain socket\", e);\n        }\n\n        if ((mode & 0xc000) != SOCKET_FILE_MODE_MASK) {\n            throw new InvalidConfigurationException(\n                \"Found docker unix domain socket but file mode was not as expected (expected: srwxr-xr-x). This problem is possibly due to occurrence of this issue in the past: https://github.com/docker/docker/issues/13121\"\n            );\n        }\n\n        return TransportConfig.builder().dockerHost(URI.create(SOCKET_LOCATION)).build();\n    }\n\n    @Override\n    protected boolean isApplicable() {\n        return SystemUtils.IS_OS_LINUX || SystemUtils.IS_OS_MAC;\n    }\n\n    @Override\n    public String getDescription() {\n        return \"local Unix socket (\" + SOCKET_LOCATION + \")\";\n    }\n\n    @Override\n    protected int getPriority() {\n        return PRIORITY;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/images/AbstractImagePullPolicy.java",
    "content": "package org.testcontainers.images;\n\nimport lombok.extern.slf4j.Slf4j;\nimport org.slf4j.Logger;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.DockerLoggerFactory;\n\n@Slf4j\npublic abstract class AbstractImagePullPolicy implements ImagePullPolicy {\n\n    private static final LocalImagesCache LOCAL_IMAGES_CACHE = LocalImagesCache.INSTANCE;\n\n    @Override\n    public boolean shouldPull(DockerImageName imageName) {\n        Logger logger = DockerLoggerFactory.getLogger(imageName.asCanonicalNameString());\n\n        // Does our cache already know the image?\n        ImageData cachedImageData = LOCAL_IMAGES_CACHE.get(imageName);\n        if (cachedImageData != null) {\n            logger.trace(\"{} is already in image name cache\", imageName);\n        } else {\n            logger.debug(\"{} is not in image name cache, updating...\", imageName);\n            // Was not in cache, inspecting...\n            cachedImageData = LOCAL_IMAGES_CACHE.refreshCache(imageName).orElse(null);\n\n            if (cachedImageData == null) {\n                log.debug(\"Not available locally, should pull image: {}\", imageName);\n                return true;\n            }\n        }\n\n        if (shouldPullCached(imageName, cachedImageData)) {\n            log.debug(\"Should pull locally available image: {}\", imageName);\n            return true;\n        } else {\n            log.debug(\"Using locally available and not pulling image: {}\", imageName);\n            return false;\n        }\n    }\n\n    /**\n     * Implement this method to decide whether a locally available image should be pulled\n     * (e.g. to always pull images, or to pull them after some duration of time)\n     *\n     * @return {@code true} to update the locally available image, {@code false} to use local instead\n     */\n    protected abstract boolean shouldPullCached(DockerImageName imageName, ImageData localImageData);\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/images/AgeBasedPullPolicy.java",
    "content": "package org.testcontainers.images;\n\nimport lombok.Value;\nimport lombok.extern.slf4j.Slf4j;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.time.Duration;\nimport java.time.Instant;\n\n/**\n * An ImagePullPolicy which pulls the image from a remote repository only if its created date is older than maxAge\n */\n@Slf4j\n@Value\nclass AgeBasedPullPolicy extends AbstractImagePullPolicy {\n\n    Duration maxAge;\n\n    @Override\n    protected boolean shouldPullCached(DockerImageName imageName, ImageData localImageData) {\n        Duration imageAge = Duration.between(localImageData.getCreatedAt(), Instant.now());\n        boolean result = imageAge.compareTo(maxAge) > 0;\n        if (result) {\n            log.trace(\"Should pull image: {}\", imageName);\n        }\n        return result;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/images/AlwaysPullPolicy.java",
    "content": "package org.testcontainers.images;\n\nimport lombok.ToString;\nimport lombok.extern.slf4j.Slf4j;\nimport org.testcontainers.utility.DockerImageName;\n\n/***\n * An ImagePullPolicy which pulls the image even if it exists locally.\n * Useful for obtaining the latest version of an image with a static tag, i.e. 'latest'\n */\n@Slf4j\n@ToString\nclass AlwaysPullPolicy implements ImagePullPolicy {\n\n    @Override\n    public boolean shouldPull(DockerImageName imageName) {\n        log.trace(\"Unconditionally pulling an image: {}\", imageName);\n        return true;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/images/DefaultPullPolicy.java",
    "content": "package org.testcontainers.images;\n\nimport lombok.ToString;\nimport lombok.extern.slf4j.Slf4j;\nimport org.testcontainers.utility.DockerImageName;\n\n/**\n * The default imagePullPolicy, which pulls the image from a remote repository only if it does not exist locally\n */\n@Slf4j\n@ToString\nclass DefaultPullPolicy extends AbstractImagePullPolicy {\n\n    @Override\n    protected boolean shouldPullCached(DockerImageName imageName, ImageData localImageData) {\n        return false;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/images/ImageData.java",
    "content": "package org.testcontainers.images;\n\nimport com.github.dockerjava.api.command.InspectImageResponse;\nimport com.github.dockerjava.api.model.Image;\nimport lombok.Builder;\nimport lombok.NonNull;\nimport lombok.Value;\n\nimport java.time.Instant;\nimport java.time.ZonedDateTime;\n\n@Value\n@Builder\npublic class ImageData {\n\n    @NonNull\n    Instant createdAt;\n\n    static ImageData from(InspectImageResponse inspectImageResponse) {\n        final String created = inspectImageResponse.getCreated();\n        final Instant createdInstant = ((created == null) || created.isEmpty())\n            ? Instant.EPOCH\n            : ZonedDateTime.parse(created).toInstant();\n        return ImageData.builder().createdAt(createdInstant).build();\n    }\n\n    static ImageData from(Image image) {\n        final Long created = image.getCreated();\n        final Instant createdInstant = (created == null) ? Instant.EPOCH : Instant.ofEpochSecond(created);\n        return ImageData.builder().createdAt(createdInstant).build();\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/images/ImagePullPolicy.java",
    "content": "package org.testcontainers.images;\n\nimport org.testcontainers.utility.DockerImageName;\n\npublic interface ImagePullPolicy {\n    boolean shouldPull(DockerImageName imageName);\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/images/LocalImagesCache.java",
    "content": "package org.testcontainers.images;\n\nimport com.github.dockerjava.api.DockerClient;\nimport com.github.dockerjava.api.command.InspectImageResponse;\nimport com.github.dockerjava.api.exception.NotFoundException;\nimport com.github.dockerjava.api.model.Image;\nimport com.google.common.annotations.VisibleForTesting;\nimport lombok.extern.slf4j.Slf4j;\nimport org.testcontainers.DockerClientFactory;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\n@Slf4j\nenum LocalImagesCache {\n    INSTANCE;\n\n    @VisibleForTesting\n    final AtomicBoolean initialized = new AtomicBoolean(false);\n\n    @VisibleForTesting\n    final Map<DockerImageName, ImageData> cache = new ConcurrentHashMap<>();\n\n    public ImageData get(DockerImageName imageName) {\n        maybeInitCache(DockerClientFactory.instance().client());\n        return cache.get(imageName);\n    }\n\n    public Optional<ImageData> refreshCache(DockerImageName imageName) {\n        DockerClient dockerClient = DockerClientFactory.instance().client();\n        if (!maybeInitCache(dockerClient)) {\n            // Cache may be stale, trying inspectImageCmd...\n\n            InspectImageResponse response = null;\n            try {\n                response = dockerClient.inspectImageCmd(imageName.asCanonicalNameString()).exec();\n            } catch (NotFoundException e) {\n                log.trace(\"Image {} not found\", imageName, e);\n            }\n            if (response != null) {\n                ImageData imageData = ImageData.from(response);\n                cache.put(imageName, imageData);\n                return Optional.of(imageData);\n            } else {\n                cache.remove(imageName);\n                return Optional.empty();\n            }\n        }\n\n        return Optional.ofNullable(cache.get(imageName));\n    }\n\n    private synchronized boolean maybeInitCache(DockerClient dockerClient) {\n        if (!initialized.compareAndSet(false, true)) {\n            return false;\n        }\n\n        if (Boolean.parseBoolean(System.getProperty(\"useFilter\"))) {\n            return false;\n        }\n\n        populateFromList(dockerClient.listImagesCmd().exec());\n\n        return true;\n    }\n\n    private void populateFromList(List<Image> images) {\n        for (Image image : images) {\n            String[] repoTags = image.getRepoTags();\n            if (repoTags == null) {\n                log.debug(\"repoTags is null, skipping image: {}\", image);\n                continue;\n            }\n\n            cache.putAll(\n                Stream\n                    .of(repoTags)\n                    // Protection against some edge case where local image repository tags end up with duplicates\n                    // making toMap crash at merge time.\n                    .distinct()\n                    .collect(Collectors.toMap(DockerImageName::new, it -> ImageData.from(image)))\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/images/LoggedPullImageResultCallback.java",
    "content": "package org.testcontainers.images;\n\nimport com.github.dockerjava.api.command.PullImageResultCallback;\nimport com.github.dockerjava.api.model.PullResponseItem;\nimport org.apache.commons.io.FileUtils;\nimport org.slf4j.Logger;\n\nimport java.io.Closeable;\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Set;\n\n/**\n * {@link PullImageResultCallback} with improved logging of pull progress.\n */\nclass LoggedPullImageResultCallback extends PullImageResultCallback {\n\n    private final Logger logger;\n\n    private final Set<String> allLayers = new HashSet<>();\n\n    private final Set<String> downloadedLayers = new HashSet<>();\n\n    private final Set<String> pulledLayers = new HashSet<>();\n\n    private final Map<String, Long> totalSizes = new HashMap<>();\n\n    private final Map<String, Long> currentSizes = new HashMap<>();\n\n    private boolean completed;\n\n    private Instant start;\n\n    LoggedPullImageResultCallback(final Logger logger) {\n        this.logger = logger;\n    }\n\n    @Override\n    public void onStart(final Closeable stream) {\n        super.onStart(stream);\n        start = Instant.now();\n\n        logger.info(\"Starting to pull image\");\n    }\n\n    @Override\n    public void onNext(final PullResponseItem item) {\n        super.onNext(item);\n\n        final String statusLowercase = item.getStatus() != null ? item.getStatus().toLowerCase() : \"\";\n        final String id = item.getId();\n\n        if (item.getProgressDetail() != null) {\n            allLayers.add(id);\n        }\n\n        if (statusLowercase.equalsIgnoreCase(\"download complete\")) {\n            downloadedLayers.add(id);\n        }\n\n        if (statusLowercase.equalsIgnoreCase(\"pull complete\")) {\n            pulledLayers.add(id);\n        }\n\n        if (item.getProgressDetail() != null) {\n            Long total = item.getProgressDetail().getTotal();\n            Long current = item.getProgressDetail().getCurrent();\n\n            if (total != null && total > totalSizes.getOrDefault(id, 0L)) {\n                totalSizes.put(id, total);\n            }\n            if (current != null && current > currentSizes.getOrDefault(id, 0L)) {\n                currentSizes.put(id, current);\n            }\n        }\n\n        if (statusLowercase.startsWith(\"pulling from\") || statusLowercase.contains(\"complete\")) {\n            long totalSize = totalLayerSize();\n            long currentSize = downloadedLayerSize();\n\n            int pendingCount = allLayers.size() - downloadedLayers.size();\n            String friendlyTotalSize;\n            if (pendingCount > 0) {\n                friendlyTotalSize = \"? MB\";\n            } else {\n                friendlyTotalSize = FileUtils.byteCountToDisplaySize(totalSize);\n            }\n\n            logger.info(\n                \"Pulling image layers: {} pending, {} downloaded, {} extracted, ({}/{})\",\n                String.format(\"%2d\", pendingCount),\n                String.format(\"%2d\", downloadedLayers.size()),\n                String.format(\"%2d\", pulledLayers.size()),\n                FileUtils.byteCountToDisplaySize(currentSize),\n                friendlyTotalSize\n            );\n        }\n\n        if (statusLowercase.contains(\"complete\")) {\n            completed = true;\n        }\n    }\n\n    @Override\n    public void onComplete() {\n        super.onComplete();\n\n        final long downloadedLayerSize = downloadedLayerSize();\n        final long duration = Duration.between(start, Instant.now()).getSeconds();\n\n        if (completed) {\n            logger.info(\n                \"Pull complete. {} layers, pulled in {}s (downloaded {} at {}/s)\",\n                allLayers.size(),\n                duration,\n                FileUtils.byteCountToDisplaySize(downloadedLayerSize),\n                FileUtils.byteCountToDisplaySize(downloadedLayerSize / duration)\n            );\n        }\n    }\n\n    private long downloadedLayerSize() {\n        return currentSizes.values().stream().filter(Objects::nonNull).mapToLong(it -> it).sum();\n    }\n\n    private long totalLayerSize() {\n        return totalSizes.values().stream().filter(Objects::nonNull).mapToLong(it -> it).sum();\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/images/ParsedDockerfile.java",
    "content": "package org.testcontainers.images;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport lombok.Getter;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport java.util.stream.Collectors;\n\n/**\n * Representation of a Dockerfile, with partial parsing for extraction of a minimal set of data.\n */\n@Slf4j\npublic class ParsedDockerfile {\n\n    private static final Pattern FROM_LINE_PATTERN = Pattern.compile(\n        \"FROM (?<arg>--[^\\\\s]+\\\\s)*(?<image>[^\\\\s]+).*\",\n        Pattern.CASE_INSENSITIVE\n    );\n\n    private final Path dockerFilePath;\n\n    @Getter\n    private final Set<String> dependencyImageNames;\n\n    public ParsedDockerfile(Path dockerFilePath) {\n        this.dockerFilePath = dockerFilePath;\n        this.dependencyImageNames = parse(read());\n    }\n\n    @VisibleForTesting\n    ParsedDockerfile(List<String> lines) {\n        this.dockerFilePath = Paths.get(\"dummy.Dockerfile\");\n        this.dependencyImageNames = parse(lines);\n    }\n\n    private List<String> read() {\n        if (!Files.exists(dockerFilePath)) {\n            log.warn(\"Tried to parse Dockerfile at path {} but none was found\", dockerFilePath);\n            return Collections.emptyList();\n        }\n\n        try {\n            return Files.readAllLines(dockerFilePath);\n        } catch (IOException e) {\n            log.warn(\"Unable to read Dockerfile at path {}\", dockerFilePath, e);\n            return Collections.emptyList();\n        }\n    }\n\n    private Set<String> parse(List<String> lines) {\n        Set<String> imageNames = lines\n            .stream()\n            .map(FROM_LINE_PATTERN::matcher)\n            .filter(Matcher::matches)\n            .map(matcher -> matcher.group(\"image\"))\n            .collect(Collectors.toSet());\n\n        if (!imageNames.isEmpty()) {\n            log.debug(\"Found dependency images in Dockerfile {}: {}\", dockerFilePath, imageNames);\n        }\n        return imageNames;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/images/PullPolicy.java",
    "content": "package org.testcontainers.images;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport lombok.experimental.UtilityClass;\nimport lombok.extern.slf4j.Slf4j;\nimport org.testcontainers.utility.TestcontainersConfiguration;\n\nimport java.time.Duration;\n\n/**\n * Convenience class with logic for building common {@link ImagePullPolicy} instances.\n *\n */\n@Slf4j\n@UtilityClass\npublic class PullPolicy {\n\n    @VisibleForTesting\n    static ImagePullPolicy instance;\n\n    @VisibleForTesting\n    static ImagePullPolicy defaultImplementation = new DefaultPullPolicy();\n\n    /**\n     * Convenience method for returning the {@link DefaultPullPolicy} default image pull policy\n     * @return {@link ImagePullPolicy}\n     */\n    public static synchronized ImagePullPolicy defaultPolicy() {\n        if (instance != null) {\n            return instance;\n        }\n\n        String imagePullPolicyClassName = TestcontainersConfiguration.getInstance().getImagePullPolicy();\n        if (imagePullPolicyClassName != null) {\n            log.debug(\"Attempting to instantiate an ImagePullPolicy with class: {}\", imagePullPolicyClassName);\n            ImagePullPolicy configuredInstance;\n            try {\n                configuredInstance =\n                    (ImagePullPolicy) Thread\n                        .currentThread()\n                        .getContextClassLoader()\n                        .loadClass(imagePullPolicyClassName)\n                        .getDeclaredConstructor()\n                        .newInstance();\n            } catch (Exception e) {\n                throw new IllegalArgumentException(\n                    \"Configured ImagePullPolicy could not be loaded: \" + imagePullPolicyClassName,\n                    e\n                );\n            }\n\n            log.info(\"Found configured Image Pull Policy: {}\", configuredInstance.getClass());\n\n            instance = configuredInstance;\n        } else {\n            instance = defaultImplementation;\n        }\n\n        log.info(\"Image pull policy will be performed by: {}\", instance);\n\n        return instance;\n    }\n\n    /**\n     * Convenience method for returning the {@link AlwaysPullPolicy} alwaysPull image pull policy\n     * @return {@link ImagePullPolicy}\n     */\n    public static ImagePullPolicy alwaysPull() {\n        return new AlwaysPullPolicy();\n    }\n\n    /**\n     * Convenience method for returning an {@link AgeBasedPullPolicy} Age based image pull policy,\n     * @return {@link ImagePullPolicy}\n     */\n    public static ImagePullPolicy ageBased(Duration maxAge) {\n        return new AgeBasedPullPolicy(maxAge);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/images/RemoteDockerImage.java",
    "content": "package org.testcontainers.images;\n\nimport com.github.dockerjava.api.DockerClient;\nimport com.github.dockerjava.api.command.PullImageCmd;\nimport com.github.dockerjava.api.exception.DockerClientException;\nimport com.github.dockerjava.api.exception.InternalServerErrorException;\nimport com.github.dockerjava.api.exception.NotFoundException;\nimport com.google.common.util.concurrent.Futures;\nimport lombok.AccessLevel;\nimport lombok.AllArgsConstructor;\nimport lombok.NonNull;\nimport lombok.SneakyThrows;\nimport lombok.ToString;\nimport lombok.With;\nimport org.awaitility.Awaitility;\nimport org.awaitility.pollinterval.IterativePollInterval;\nimport org.awaitility.pollinterval.PollInterval;\nimport org.slf4j.Logger;\nimport org.testcontainers.DockerClientFactory;\nimport org.testcontainers.containers.ContainerFetchException;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.DockerLoggerFactory;\nimport org.testcontainers.utility.ImageNameSubstitutor;\nimport org.testcontainers.utility.LazyFuture;\nimport org.testcontainers.utility.TestcontainersConfiguration;\n\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.atomic.AtomicReference;\n\n@ToString\n@AllArgsConstructor(access = AccessLevel.PACKAGE)\npublic class RemoteDockerImage extends LazyFuture<String> {\n\n    private static final Duration PULL_RETRY_TIME_LIMIT = Duration.ofSeconds(\n        TestcontainersConfiguration.getInstance().getImagePullTimeout()\n    );\n\n    @ToString.Exclude\n    private Future<DockerImageName> imageNameFuture;\n\n    @With\n    ImagePullPolicy imagePullPolicy = PullPolicy.defaultPolicy();\n\n    @With\n    private ImageNameSubstitutor imageNameSubstitutor = ImageNameSubstitutor.instance();\n\n    @ToString.Exclude\n    private DockerClient dockerClient = DockerClientFactory.lazyClient();\n\n    public RemoteDockerImage(DockerImageName dockerImageName) {\n        this.imageNameFuture = CompletableFuture.completedFuture(dockerImageName);\n    }\n\n    @Deprecated\n    public RemoteDockerImage(String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    @Deprecated\n    public RemoteDockerImage(@NonNull String repository, @NonNull String tag) {\n        this(DockerImageName.parse(repository).withTag(tag));\n    }\n\n    public RemoteDockerImage(@NonNull Future<String> imageFuture) {\n        this.imageNameFuture = Futures.lazyTransform(imageFuture, DockerImageName::new);\n    }\n\n    @Override\n    @SneakyThrows({ InterruptedException.class, ExecutionException.class })\n    protected final String resolve() {\n        final DockerImageName imageName = getImageName();\n        final Logger logger = DockerLoggerFactory.getLogger(imageName.toString());\n        try {\n            if (!imagePullPolicy.shouldPull(imageName)) {\n                return imageName.asCanonicalNameString();\n            }\n\n            // The image is not available locally - pull it\n            logger.info(\n                \"Pulling docker image: {}. Please be patient; this may take some time but only needs to be done once.\",\n                imageName\n            );\n\n            final Instant startedAt = Instant.now();\n            final Instant lastRetryAllowed = Instant.now().plus(PULL_RETRY_TIME_LIMIT);\n            final AtomicReference<Exception> lastFailure = new AtomicReference<>();\n            final PullImageCmd pullImageCmd = dockerClient\n                .pullImageCmd(imageName.getUnversionedPart())\n                .withTag(imageName.getVersionPart());\n            final AtomicReference<String> dockerImageName = new AtomicReference<>();\n\n            // The following poll interval in ms: 50, 100, 200, 400, 800....\n            // Results in ~70 requests in over 2 minutes\n            final PollInterval interval = IterativePollInterval\n                .iterative(duration -> duration.multipliedBy(2))\n                .startDuration(Duration.ofMillis(50));\n\n            Awaitility\n                .await()\n                .pollInSameThread()\n                .pollDelay(Duration.ZERO) // start checking immediately\n                .atMost(PULL_RETRY_TIME_LIMIT)\n                .pollInterval(interval)\n                .until(\n                    tryImagePullCommand(pullImageCmd, logger, dockerImageName, imageName, lastFailure, lastRetryAllowed)\n                );\n\n            if (dockerImageName.get() == null) {\n                final Exception lastException = lastFailure.get();\n                logger.error(\n                    \"Failed to pull image: {}. Please check output of `docker pull {}`\",\n                    imageName,\n                    imageName,\n                    lastException\n                );\n                throw new ContainerFetchException(\"Failed to pull image: \" + imageName, lastException);\n            }\n\n            logger.info(\"Image {} pull took {}\", dockerImageName.get(), Duration.between(startedAt, Instant.now()));\n            LocalImagesCache.INSTANCE.refreshCache(imageName);\n            return dockerImageName.get();\n        } catch (DockerClientException e) {\n            throw new ContainerFetchException(\"Failed to get Docker client for \" + imageName, e);\n        }\n    }\n\n    private Callable<Boolean> tryImagePullCommand(\n        PullImageCmd pullImageCmd,\n        Logger logger,\n        AtomicReference<String> dockerImageName,\n        DockerImageName imageName,\n        AtomicReference<Exception> lastFailure,\n        Instant lastRetryAllowed\n    ) {\n        return () -> {\n            try {\n                pullImage(pullImageCmd, logger);\n                dockerImageName.set(imageName.asCanonicalNameString());\n                return true;\n            } catch (InterruptedException | InternalServerErrorException e) {\n                // these classes of exception often relate to timeout/connection errors so should be retried\n                lastFailure.set(e);\n                logger.warn(\n                    \"Retrying pull for image: {} ({}s remaining)\",\n                    imageName,\n                    Duration.between(Instant.now(), lastRetryAllowed).getSeconds()\n                );\n                return false;\n            }\n        };\n    }\n\n    private TimeLimitedLoggedPullImageResultCallback pullImage(PullImageCmd pullImageCmd, Logger logger)\n        throws InterruptedException {\n        try {\n            return pullImageCmd.exec(new TimeLimitedLoggedPullImageResultCallback(logger)).awaitCompletion();\n        } catch (DockerClientException | NotFoundException e) {\n            // Try to fallback to x86\n            return pullImageCmd\n                .withPlatform(\"linux/amd64\")\n                .exec(new TimeLimitedLoggedPullImageResultCallback(logger))\n                .awaitCompletion();\n        }\n    }\n\n    private DockerImageName getImageName() throws InterruptedException, ExecutionException {\n        final DockerImageName specifiedImageName = imageNameFuture.get();\n\n        // Allow the image name to be substituted\n        return imageNameSubstitutor.apply(specifiedImageName);\n    }\n\n    @ToString.Include(name = \"imageName\", rank = 1)\n    private String imageNameToString() {\n        if (!imageNameFuture.isDone()) {\n            return \"<resolving>\";\n        }\n\n        try {\n            return getImageName().asCanonicalNameString();\n        } catch (InterruptedException | ExecutionException e) {\n            return e.getMessage();\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/images/TimeLimitedLoggedPullImageResultCallback.java",
    "content": "package org.testcontainers.images;\n\nimport com.github.dockerjava.api.command.PullImageResultCallback;\nimport com.github.dockerjava.api.model.PullResponseItem;\nimport org.slf4j.Logger;\nimport org.testcontainers.DockerClientFactory;\nimport org.testcontainers.utility.TestcontainersConfiguration;\n\nimport java.io.Closeable;\nimport java.io.IOException;\nimport java.time.Duration;\nimport java.util.HashSet;\nimport java.util.Set;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.ScheduledExecutorService;\nimport java.util.concurrent.ScheduledFuture;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * {@link PullImageResultCallback} with improved logging of pull progress and a 'watchdog' which will abort the pull\n * if progress is not being made, to prevent a hanging test\n */\npublic class TimeLimitedLoggedPullImageResultCallback extends LoggedPullImageResultCallback {\n\n    private static final AtomicInteger THREAD_ID = new AtomicInteger(0);\n\n    private static final ScheduledExecutorService PROGRESS_WATCHDOG_EXECUTOR = Executors.newScheduledThreadPool(\n        0,\n        runnable -> {\n            Thread t = new Thread(DockerClientFactory.TESTCONTAINERS_THREAD_GROUP, runnable);\n            t.setDaemon(true);\n            t.setName(\"testcontainers-pull-watchdog-\" + THREAD_ID.incrementAndGet());\n            return t;\n        }\n    );\n\n    private static final Duration PULL_PAUSE_TOLERANCE = Duration.ofSeconds(\n        TestcontainersConfiguration.getInstance().getImagePullPauseTimeout()\n    );\n\n    private final Logger logger;\n\n    // A future which, if it ever fires, will kill the pull\n    private ScheduledFuture<?> nextCheckForProgress;\n\n    // All threads that are 'awaiting' this pull\n    private final Set<Thread> waitingThreads = new HashSet<>();\n\n    public TimeLimitedLoggedPullImageResultCallback(Logger logger) {\n        super(logger);\n        this.logger = logger;\n    }\n\n    @Override\n    public TimeLimitedLoggedPullImageResultCallback awaitCompletion() throws InterruptedException {\n        waitingThreads.add(Thread.currentThread());\n        super.awaitCompletion();\n        return this;\n    }\n\n    @Override\n    public boolean awaitCompletion(long timeout, TimeUnit timeUnit) throws InterruptedException {\n        waitingThreads.add(Thread.currentThread());\n        return super.awaitCompletion(timeout, timeUnit);\n    }\n\n    @Override\n    public void onNext(PullResponseItem item) {\n        if (item.getProgressDetail() != null) {\n            resetProgressWatchdog(false);\n        }\n        super.onNext(item);\n    }\n\n    @Override\n    public void onStart(Closeable stream) {\n        resetProgressWatchdog(false);\n        super.onStart(stream);\n    }\n\n    @Override\n    public void onError(Throwable throwable) {\n        resetProgressWatchdog(true);\n        super.onError(throwable);\n    }\n\n    @Override\n    public void onComplete() {\n        resetProgressWatchdog(true);\n        super.onComplete();\n    }\n\n    /*\n     * This method schedules a future task which will interrupt the waiting waiting threads if ever fired.\n     * Every time this method is called (from onStart or onNext), the task is cancelled and recreated 30s in the future,\n     * ensuring that it will only fire if the method stops being called regularly (e.g. if the pull has hung).\n     */\n    private void resetProgressWatchdog(boolean isFinished) {\n        if (nextCheckForProgress != null && !nextCheckForProgress.isCancelled()) {\n            nextCheckForProgress.cancel(false);\n        }\n        if (!isFinished) {\n            nextCheckForProgress =\n                PROGRESS_WATCHDOG_EXECUTOR.schedule(\n                    this::abortPull,\n                    PULL_PAUSE_TOLERANCE.getSeconds(),\n                    TimeUnit.SECONDS\n                );\n        }\n    }\n\n    private void abortPull() {\n        logger.error(\n            \"Docker image pull has not made progress in {}s - aborting pull\",\n            PULL_PAUSE_TOLERANCE.getSeconds()\n        );\n        // Interrupt any threads that are waiting, before closing streams, because the stream can take\n        //  an indeterminate amount of time to close\n        waitingThreads.forEach(Thread::interrupt);\n        try {\n            close();\n        } catch (IOException ignored) {\n            // no action\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/images/builder/ImageFromDockerfile.java",
    "content": "package org.testcontainers.images.builder;\n\nimport com.github.dockerjava.api.DockerClient;\nimport com.github.dockerjava.api.command.BuildImageCmd;\nimport com.github.dockerjava.api.command.BuildImageResultCallback;\nimport com.github.dockerjava.api.model.BuildResponseItem;\nimport lombok.Cleanup;\nimport lombok.Getter;\nimport lombok.extern.slf4j.Slf4j;\nimport org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;\nimport org.apache.commons.io.FileUtils;\nimport org.apache.commons.lang3.StringUtils;\nimport org.slf4j.Logger;\nimport org.testcontainers.DockerClientFactory;\nimport org.testcontainers.images.ParsedDockerfile;\nimport org.testcontainers.images.RemoteDockerImage;\nimport org.testcontainers.images.builder.traits.BuildContextBuilderTrait;\nimport org.testcontainers.images.builder.traits.ClasspathTrait;\nimport org.testcontainers.images.builder.traits.DockerfileTrait;\nimport org.testcontainers.images.builder.traits.FilesTrait;\nimport org.testcontainers.images.builder.traits.StringsTrait;\nimport org.testcontainers.utility.Base58;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.DockerLoggerFactory;\nimport org.testcontainers.utility.ImageNameSubstitutor;\nimport org.testcontainers.utility.LazyFuture;\nimport org.testcontainers.utility.ResourceReaper;\n\nimport java.io.IOException;\nimport java.io.PipedInputStream;\nimport java.io.PipedOutputStream;\nimport java.nio.file.Path;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.LinkedHashSet;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.function.Consumer;\nimport java.util.regex.Matcher;\nimport java.util.zip.GZIPOutputStream;\n\n@Slf4j\n@Getter\npublic class ImageFromDockerfile\n    extends LazyFuture<String>\n    implements\n        BuildContextBuilderTrait<ImageFromDockerfile>,\n        ClasspathTrait<ImageFromDockerfile>,\n        FilesTrait<ImageFromDockerfile>,\n        StringsTrait<ImageFromDockerfile>,\n        DockerfileTrait<ImageFromDockerfile> {\n\n    private final String dockerImageName;\n\n    private boolean deleteOnExit = true;\n\n    private final Map<String, Transferable> transferables = new HashMap<>();\n\n    private final Map<String, String> buildArgs = new HashMap<>();\n\n    private Optional<String> dockerFilePath = Optional.empty();\n\n    private Optional<Path> dockerfile = Optional.empty();\n\n    private Optional<String> target = Optional.empty();\n\n    private final Set<Consumer<BuildImageCmd>> buildImageCmdModifiers = new LinkedHashSet<>();\n\n    private Set<String> dependencyImageNames = Collections.emptySet();\n\n    public ImageFromDockerfile() {\n        this(\"localhost/testcontainers/\" + Base58.randomString(16).toLowerCase());\n    }\n\n    public ImageFromDockerfile(String dockerImageName) {\n        this(dockerImageName, true);\n    }\n\n    public ImageFromDockerfile(String dockerImageName, boolean deleteOnExit) {\n        this.dockerImageName = dockerImageName;\n        this.deleteOnExit = deleteOnExit;\n    }\n\n    @Override\n    public ImageFromDockerfile withFileFromTransferable(String path, Transferable transferable) {\n        Transferable oldValue = transferables.put(path, transferable);\n\n        if (oldValue != null) {\n            log.warn(\"overriding previous mapping for '{}'\", path);\n        }\n\n        return this;\n    }\n\n    @Override\n    protected final String resolve() {\n        Logger logger = DockerLoggerFactory.getLogger(dockerImageName);\n\n        //noinspection resource\n        DockerClient dockerClient = DockerClientFactory.instance().client();\n\n        try {\n            BuildImageResultCallback resultCallback = new BuildImageResultCallback() {\n                @Override\n                public void onNext(BuildResponseItem item) {\n                    super.onNext(item);\n\n                    if (item.isErrorIndicated()) {\n                        logger.error(item.getErrorDetail().getMessage());\n                    } else {\n                        logger.debug(StringUtils.removeEnd(item.getStream(), \"\\n\"));\n                    }\n                }\n            };\n\n            // We have to use pipes to avoid high memory consumption since users might want to build huge images\n            @Cleanup\n            PipedInputStream in = new PipedInputStream();\n            @Cleanup\n            PipedOutputStream out = new PipedOutputStream(in);\n\n            BuildImageCmd buildImageCmd = dockerClient.buildImageCmd(in);\n            configure(buildImageCmd);\n            Map<String, String> labels = new HashMap<>();\n            if (buildImageCmd.getLabels() != null) {\n                labels.putAll(buildImageCmd.getLabels());\n            }\n\n            labels.putAll(DockerClientFactory.DEFAULT_LABELS);\n            if (deleteOnExit) {\n                //noinspection deprecation\n                labels.putAll(ResourceReaper.instance().getLabels());\n            }\n            buildImageCmd.withLabels(labels);\n\n            prePullDependencyImages(dependencyImageNames);\n\n            BuildImageResultCallback exec = buildImageCmd.exec(resultCallback);\n\n            long bytesToDockerDaemon = 0;\n\n            // To build an image, we have to send the context to Docker in TAR archive format\n            try (TarArchiveOutputStream tarArchive = new TarArchiveOutputStream(new GZIPOutputStream(out))) {\n                tarArchive.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX);\n                tarArchive.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_POSIX);\n\n                for (Map.Entry<String, Transferable> entry : transferables.entrySet()) {\n                    Transferable transferable = entry.getValue();\n                    final String destination = entry.getKey();\n                    transferable.transferTo(tarArchive, destination);\n                    bytesToDockerDaemon += transferable.getSize();\n                }\n                tarArchive.finish();\n            }\n\n            log.info(\"Transferred {} to Docker daemon\", FileUtils.byteCountToDisplaySize(bytesToDockerDaemon));\n            if (bytesToDockerDaemon > FileUtils.ONE_MB * 50) {\n                log.warn( // warn if >50MB sent to docker daemon\n                    \"A large amount of data was sent to the Docker daemon ({}). Consider using a .dockerignore file for better performance.\",\n                    FileUtils.byteCountToDisplaySize(bytesToDockerDaemon)\n                );\n            }\n\n            exec.awaitImageId();\n\n            return dockerImageName;\n        } catch (IOException e) {\n            throw new RuntimeException(\"Can't close DockerClient\", e);\n        }\n    }\n\n    protected void configure(BuildImageCmd buildImageCmd) {\n        buildImageCmd.withTags(Collections.singleton(getDockerImageName()));\n        this.dockerFilePath.ifPresent(buildImageCmd::withDockerfilePath);\n        this.dockerfile.ifPresent(p -> {\n                buildImageCmd.withDockerfile(p.toFile());\n                dependencyImageNames = new ParsedDockerfile(p).getDependencyImageNames();\n\n                if (dependencyImageNames.size() > 0) {\n                    // if we'll be pre-pulling images, disable the built-in pull as it is not necessary and will fail for\n                    // authenticated registries\n                    buildImageCmd.withPull(false);\n                }\n            });\n\n        this.buildArgs.forEach(buildImageCmd::withBuildArg);\n        this.target.ifPresent(buildImageCmd::withTarget);\n        this.buildImageCmdModifiers.forEach(hook -> hook.accept(buildImageCmd));\n    }\n\n    private void prePullDependencyImages(Set<String> imagesToPull) {\n        imagesToPull.forEach(imageName -> {\n            String resolvedImageName = applyBuildArgsToImageName(imageName);\n            try {\n                log.info(\n                    \"Pre-emptively checking local images for '{}', referenced via a Dockerfile. If not available, it will be pulled.\",\n                    resolvedImageName\n                );\n                new RemoteDockerImage(DockerImageName.parse(resolvedImageName))\n                    .withImageNameSubstitutor(ImageNameSubstitutor.noop())\n                    .get();\n            } catch (Exception e) {\n                log.warn(\n                    \"Unable to pre-fetch an image ({}) depended upon by Dockerfile - image build will continue but may fail. Exception message was: {}\",\n                    resolvedImageName,\n                    e.getMessage()\n                );\n            }\n        });\n    }\n\n    /**\n     * See {@code filterForEnvironmentVars()} in {@link com.github.dockerjava.core.dockerfile.DockerfileStatement}.\n     */\n    private String applyBuildArgsToImageName(String imageName) {\n        for (Map.Entry<String, String> entry : buildArgs.entrySet()) {\n            String value = Matcher.quoteReplacement(entry.getValue());\n            // handle: $VARIABLE case\n            imageName = imageName.replace(\"$\" + entry.getKey(), value);\n            // handle ${VARIABLE} case\n            imageName = imageName.replace(\"${\" + entry.getKey() + \"}\", value);\n        }\n        return imageName;\n    }\n\n    public ImageFromDockerfile withBuildArg(final String key, final String value) {\n        this.buildArgs.put(key, value);\n        return this;\n    }\n\n    public ImageFromDockerfile withBuildArgs(final Map<String, String> args) {\n        this.buildArgs.putAll(args);\n        return this;\n    }\n\n    /**\n     * Sets the target build stage to use.\n     *\n     * @param target the target build stage\n     */\n    public ImageFromDockerfile withTarget(String target) {\n        this.target = Optional.of(target);\n        return this;\n    }\n\n    /**\n     * Sets the Dockerfile to be used for this image.\n     *\n     * @param relativePathFromBuildContextDirectory relative path to the Dockerfile, relative to the image build context directory\n     * @deprecated It is recommended to use {@link #withDockerfile} instead because it honors .dockerignore files and\n     * will therefore be more efficient. Additionally, using {@link #withDockerfile} supports Dockerfiles that depend\n     * upon images from authenticated private registries.\n     */\n    @Deprecated\n    public ImageFromDockerfile withDockerfilePath(String relativePathFromBuildContextDirectory) {\n        this.dockerFilePath = Optional.of(relativePathFromBuildContextDirectory);\n        return this;\n    }\n\n    /**\n     * Sets the Dockerfile to be used for this image. Honors .dockerignore files for efficiency.\n     * Additionally, supports Dockerfiles that depend upon images from authenticated private registries.\n     *\n     * @param dockerfile path to Dockerfile on the test host.\n     */\n    public ImageFromDockerfile withDockerfile(Path dockerfile) {\n        this.dockerfile = Optional.of(dockerfile);\n        return this;\n    }\n\n    /**\n     * Allow low level modifications of {@link BuildImageCmd}.\n     * Warning: this does expose the underlying docker-java API so might change outside of our control.\n     *\n     * @param modifier {@link Consumer} of {@link BuildImageCmd}.\n     * @return this\n     */\n    public ImageFromDockerfile withBuildImageCmdModifier(Consumer<BuildImageCmd> modifier) {\n        this.buildImageCmdModifiers.add(modifier);\n        return this;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/images/builder/Transferable.java",
    "content": "package org.testcontainers.images.builder;\n\nimport org.apache.commons.compress.archivers.tar.TarArchiveEntry;\nimport org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;\nimport org.apache.commons.io.IOUtils;\n\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.util.zip.Checksum;\n\npublic interface Transferable {\n    int DEFAULT_FILE_MODE = 0100644;\n\n    int DEFAULT_DIR_MODE = 040755;\n\n    static Transferable of(String string) {\n        return of(string.getBytes(StandardCharsets.UTF_8));\n    }\n\n    static Transferable of(String string, int fileMode) {\n        return of(string.getBytes(StandardCharsets.UTF_8), fileMode);\n    }\n\n    static Transferable of(byte[] bytes) {\n        return of(bytes, DEFAULT_FILE_MODE);\n    }\n\n    static Transferable of(byte[] bytes, int fileMode) {\n        return new Transferable() {\n            @Override\n            public long getSize() {\n                return bytes.length;\n            }\n\n            @Override\n            public byte[] getBytes() {\n                return bytes;\n            }\n\n            @Override\n            public void updateChecksum(Checksum checksum) {\n                checksum.update(bytes, 0, bytes.length);\n            }\n\n            @Override\n            public int getFileMode() {\n                return fileMode;\n            }\n        };\n    }\n\n    /**\n     * Get file mode. Default is 0100644.\n     *\n     * @return file mode\n     * @see Transferable#DEFAULT_FILE_MODE\n     */\n    default int getFileMode() {\n        return DEFAULT_FILE_MODE;\n    }\n\n    /**\n     * Size of an object.\n     *\n     * @return size in bytes\n     */\n    long getSize();\n\n    /**\n     * transfer content of this Transferable to the output stream. <b>Must not</b> close the stream.\n     *\n     * @param tarArchiveOutputStream stream to output\n     * @param destination\n     */\n    default void transferTo(TarArchiveOutputStream tarArchiveOutputStream, final String destination) {\n        TarArchiveEntry tarEntry = new TarArchiveEntry(destination);\n        tarEntry.setSize(getSize());\n        tarEntry.setMode(getFileMode());\n\n        try {\n            tarArchiveOutputStream.putArchiveEntry(tarEntry);\n            IOUtils.write(getBytes(), tarArchiveOutputStream);\n            tarArchiveOutputStream.closeArchiveEntry();\n        } catch (IOException e) {\n            throw new RuntimeException(\"Can't transfer \" + getDescription(), e);\n        }\n    }\n\n    default byte[] getBytes() {\n        return new byte[0];\n    }\n\n    default String getDescription() {\n        return \"\";\n    }\n\n    default void updateChecksum(Checksum checksum) {\n        throw new UnsupportedOperationException(\"Provide implementation in subclass\");\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/images/builder/dockerfile/DockerfileBuilder.java",
    "content": "package org.testcontainers.images.builder.dockerfile;\n\nimport lombok.Data;\nimport lombok.extern.slf4j.Slf4j;\nimport org.testcontainers.images.builder.dockerfile.statement.Statement;\nimport org.testcontainers.images.builder.dockerfile.traits.AddStatementTrait;\nimport org.testcontainers.images.builder.dockerfile.traits.CmdStatementTrait;\nimport org.testcontainers.images.builder.dockerfile.traits.CopyStatementTrait;\nimport org.testcontainers.images.builder.dockerfile.traits.DockerfileBuilderTrait;\nimport org.testcontainers.images.builder.dockerfile.traits.EntryPointStatementTrait;\nimport org.testcontainers.images.builder.dockerfile.traits.EnvStatementTrait;\nimport org.testcontainers.images.builder.dockerfile.traits.ExposeStatementTrait;\nimport org.testcontainers.images.builder.dockerfile.traits.FromStatementTrait;\nimport org.testcontainers.images.builder.dockerfile.traits.LabelStatementTrait;\nimport org.testcontainers.images.builder.dockerfile.traits.RunStatementTrait;\nimport org.testcontainers.images.builder.dockerfile.traits.UserStatementTrait;\nimport org.testcontainers.images.builder.dockerfile.traits.VolumeStatementTrait;\nimport org.testcontainers.images.builder.dockerfile.traits.WorkdirStatementTrait;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n@Data\n@Slf4j\npublic class DockerfileBuilder\n    implements\n        DockerfileBuilderTrait<DockerfileBuilder>,\n        FromStatementTrait<DockerfileBuilder>,\n        AddStatementTrait<DockerfileBuilder>,\n        CopyStatementTrait<DockerfileBuilder>,\n        RunStatementTrait<DockerfileBuilder>,\n        CmdStatementTrait<DockerfileBuilder>,\n        WorkdirStatementTrait<DockerfileBuilder>,\n        EnvStatementTrait<DockerfileBuilder>,\n        LabelStatementTrait<DockerfileBuilder>,\n        ExposeStatementTrait<DockerfileBuilder>,\n        EntryPointStatementTrait<DockerfileBuilder>,\n        VolumeStatementTrait<DockerfileBuilder>,\n        UserStatementTrait<DockerfileBuilder> {\n\n    private final List<Statement> statements = new ArrayList<>();\n\n    public String build() {\n        StringBuilder builder = new StringBuilder();\n\n        for (Statement statement : statements) {\n            builder.append(statement.getType());\n            builder.append(\" \");\n            statement.appendArguments(builder);\n            builder.append(\"\\n\");\n        }\n\n        String result = builder.toString();\n\n        log.debug(\"Returning Dockerfile:\\n{}\", result);\n\n        return result;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/images/builder/dockerfile/statement/KeyValuesStatement.java",
    "content": "package org.testcontainers.images.builder.dockerfile.statement;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\nimport java.util.Iterator;\nimport java.util.Map;\nimport java.util.Set;\n\npublic class KeyValuesStatement extends Statement {\n\n    private static final ObjectMapper objectMapper = new ObjectMapper();\n\n    protected final Map<String, String> entries;\n\n    public KeyValuesStatement(String type, Map<String, String> entries) {\n        super(type);\n        this.entries = entries;\n    }\n\n    @Override\n    public void appendArguments(StringBuilder dockerfileStringBuilder) {\n        Set<Map.Entry<String, String>> entries = this.entries.entrySet();\n\n        Iterator<Map.Entry<String, String>> iterator = entries.iterator();\n\n        while (iterator.hasNext()) {\n            Map.Entry<String, String> entry = iterator.next();\n\n            try {\n                dockerfileStringBuilder.append(objectMapper.writeValueAsString(entry.getKey()));\n                dockerfileStringBuilder.append(\"=\");\n                dockerfileStringBuilder.append(objectMapper.writeValueAsString(entry.getValue()));\n            } catch (JsonProcessingException e) {\n                throw new RuntimeException(\"Can't serialize entry: \" + entry, e);\n            }\n\n            if (iterator.hasNext()) {\n                dockerfileStringBuilder.append(\" \\\\\\n\\t\");\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/images/builder/dockerfile/statement/MultiArgsStatement.java",
    "content": "package org.testcontainers.images.builder.dockerfile.statement;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\nimport java.util.Arrays;\n\npublic class MultiArgsStatement extends Statement {\n\n    private static final ObjectMapper objectMapper = new ObjectMapper();\n\n    protected final String[] args;\n\n    public MultiArgsStatement(String type, String... args) {\n        super(type);\n        this.args = args;\n    }\n\n    @Override\n    public void appendArguments(StringBuilder dockerfileStringBuilder) {\n        try {\n            dockerfileStringBuilder.append(objectMapper.writeValueAsString(args));\n        } catch (JsonProcessingException e) {\n            throw new RuntimeException(\"Can't serialize arguments: \" + Arrays.toString(args), e);\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/images/builder/dockerfile/statement/RawStatement.java",
    "content": "package org.testcontainers.images.builder.dockerfile.statement;\n\npublic class RawStatement extends Statement {\n\n    final String rawValue;\n\n    public RawStatement(String type, String rawValue) {\n        super(type);\n        this.rawValue = rawValue;\n    }\n\n    @Override\n    public void appendArguments(StringBuilder dockerfileStringBuilder) {\n        dockerfileStringBuilder.append(rawValue);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/images/builder/dockerfile/statement/SingleArgumentStatement.java",
    "content": "package org.testcontainers.images.builder.dockerfile.statement;\n\npublic class SingleArgumentStatement extends Statement {\n\n    protected final String argument;\n\n    public SingleArgumentStatement(String type, String argument) {\n        super(type);\n        this.argument = argument;\n    }\n\n    @Override\n    public void appendArguments(StringBuilder dockerfileStringBuilder) {\n        dockerfileStringBuilder.append(argument.replace(\"\\n\", \"\\\\\\n\"));\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/images/builder/dockerfile/statement/Statement.java",
    "content": "package org.testcontainers.images.builder.dockerfile.statement;\n\nimport lombok.Data;\n\n@Data\npublic abstract class Statement {\n\n    final String type;\n\n    public abstract void appendArguments(StringBuilder dockerfileStringBuilder);\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/images/builder/dockerfile/traits/AddStatementTrait.java",
    "content": "package org.testcontainers.images.builder.dockerfile.traits;\n\nimport org.testcontainers.images.builder.dockerfile.statement.MultiArgsStatement;\n\npublic interface AddStatementTrait<SELF extends AddStatementTrait<SELF> & DockerfileBuilderTrait<SELF>> {\n    default SELF add(String source, String destination) {\n        return ((SELF) this).withStatement(new MultiArgsStatement(\"ADD\", source, destination));\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/images/builder/dockerfile/traits/CmdStatementTrait.java",
    "content": "package org.testcontainers.images.builder.dockerfile.traits;\n\nimport org.testcontainers.images.builder.dockerfile.statement.MultiArgsStatement;\nimport org.testcontainers.images.builder.dockerfile.statement.SingleArgumentStatement;\n\npublic interface CmdStatementTrait<SELF extends CmdStatementTrait<SELF> & DockerfileBuilderTrait<SELF>> {\n    default SELF cmd(String command) {\n        return ((SELF) this).withStatement(new SingleArgumentStatement(\"CMD\", command));\n    }\n\n    default SELF cmd(String... commandParts) {\n        return ((SELF) this).withStatement(new MultiArgsStatement(\"CMD\", commandParts));\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/images/builder/dockerfile/traits/CopyStatementTrait.java",
    "content": "package org.testcontainers.images.builder.dockerfile.traits;\n\nimport org.testcontainers.images.builder.dockerfile.statement.MultiArgsStatement;\n\npublic interface CopyStatementTrait<SELF extends CopyStatementTrait<SELF> & DockerfileBuilderTrait<SELF>> {\n    default SELF copy(String source, String destination) {\n        return ((SELF) this).withStatement(new MultiArgsStatement(\"COPY\", source, destination));\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/images/builder/dockerfile/traits/DockerfileBuilderTrait.java",
    "content": "package org.testcontainers.images.builder.dockerfile.traits;\n\nimport org.testcontainers.images.builder.dockerfile.statement.Statement;\n\nimport java.util.List;\n\npublic interface DockerfileBuilderTrait<SELF extends DockerfileBuilderTrait<SELF>> {\n    List<Statement> getStatements();\n\n    default SELF withStatement(Statement statement) {\n        getStatements().add(statement);\n\n        return (SELF) this;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/images/builder/dockerfile/traits/EntryPointStatementTrait.java",
    "content": "package org.testcontainers.images.builder.dockerfile.traits;\n\nimport org.testcontainers.images.builder.dockerfile.statement.MultiArgsStatement;\nimport org.testcontainers.images.builder.dockerfile.statement.SingleArgumentStatement;\n\npublic interface EntryPointStatementTrait<SELF extends EntryPointStatementTrait<SELF> & DockerfileBuilderTrait<SELF>> {\n    default SELF entryPoint(String command) {\n        return ((SELF) this).withStatement(new SingleArgumentStatement(\"ENTRYPOINT\", command));\n    }\n\n    default SELF entryPoint(String... commandParts) {\n        return ((SELF) this).withStatement(new MultiArgsStatement(\"ENTRYPOINT\", commandParts));\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/images/builder/dockerfile/traits/EnvStatementTrait.java",
    "content": "package org.testcontainers.images.builder.dockerfile.traits;\n\nimport org.testcontainers.images.builder.dockerfile.statement.KeyValuesStatement;\n\nimport java.util.Collections;\nimport java.util.Map;\n\npublic interface EnvStatementTrait<SELF extends EnvStatementTrait<SELF> & DockerfileBuilderTrait<SELF>> {\n    default SELF env(String key, String value) {\n        return env(Collections.singletonMap(key, value));\n    }\n\n    default SELF env(Map<String, String> entries) {\n        return ((SELF) this).withStatement(new KeyValuesStatement(\"ENV\", entries));\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/images/builder/dockerfile/traits/ExposeStatementTrait.java",
    "content": "package org.testcontainers.images.builder.dockerfile.traits;\n\nimport org.testcontainers.images.builder.dockerfile.statement.SingleArgumentStatement;\n\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\npublic interface ExposeStatementTrait<SELF extends ExposeStatementTrait<SELF> & DockerfileBuilderTrait<SELF>> {\n    default SELF expose(Integer... ports) {\n        return ((SELF) this).withStatement(\n                new SingleArgumentStatement(\n                    \"EXPOSE\",\n                    Stream.of(ports).map(Object::toString).collect(Collectors.joining(\" \"))\n                )\n            );\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/images/builder/dockerfile/traits/FromStatementTrait.java",
    "content": "package org.testcontainers.images.builder.dockerfile.traits;\n\nimport org.testcontainers.images.builder.dockerfile.statement.SingleArgumentStatement;\nimport org.testcontainers.utility.DockerImageName;\n\npublic interface FromStatementTrait<SELF extends FromStatementTrait<SELF> & DockerfileBuilderTrait<SELF>> {\n    default SELF from(String dockerImageName) {\n        DockerImageName.parse(dockerImageName).assertValid();\n\n        return ((SELF) this).withStatement(new SingleArgumentStatement(\"FROM\", dockerImageName));\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/images/builder/dockerfile/traits/LabelStatementTrait.java",
    "content": "package org.testcontainers.images.builder.dockerfile.traits;\n\nimport org.testcontainers.images.builder.dockerfile.statement.KeyValuesStatement;\n\nimport java.util.Collections;\nimport java.util.Map;\n\npublic interface LabelStatementTrait<SELF extends LabelStatementTrait<SELF> & DockerfileBuilderTrait<SELF>> {\n    default SELF label(String key, String value) {\n        return label(Collections.singletonMap(key, value));\n    }\n\n    default SELF label(Map<String, String> entries) {\n        return ((SELF) this).withStatement(new KeyValuesStatement(\"LABEL\", entries));\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/images/builder/dockerfile/traits/RunStatementTrait.java",
    "content": "package org.testcontainers.images.builder.dockerfile.traits;\n\nimport org.testcontainers.images.builder.dockerfile.statement.MultiArgsStatement;\nimport org.testcontainers.images.builder.dockerfile.statement.SingleArgumentStatement;\n\npublic interface RunStatementTrait<SELF extends RunStatementTrait<SELF> & DockerfileBuilderTrait<SELF>> {\n    default SELF run(String... commandParts) {\n        return ((SELF) this).withStatement(new MultiArgsStatement(\"RUN\", commandParts));\n    }\n\n    default SELF run(String command) {\n        return ((SELF) this).withStatement(new SingleArgumentStatement(\"RUN\", command));\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/images/builder/dockerfile/traits/UserStatementTrait.java",
    "content": "package org.testcontainers.images.builder.dockerfile.traits;\n\nimport org.testcontainers.images.builder.dockerfile.statement.SingleArgumentStatement;\n\npublic interface UserStatementTrait<SELF extends UserStatementTrait<SELF> & DockerfileBuilderTrait<SELF>> {\n    default SELF user(String user) {\n        return ((SELF) this).withStatement(new SingleArgumentStatement(\"USER\", user));\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/images/builder/dockerfile/traits/VolumeStatementTrait.java",
    "content": "package org.testcontainers.images.builder.dockerfile.traits;\n\nimport org.testcontainers.images.builder.dockerfile.statement.MultiArgsStatement;\n\npublic interface VolumeStatementTrait<SELF extends VolumeStatementTrait<SELF> & DockerfileBuilderTrait<SELF>> {\n    default SELF volume(String... volumes) {\n        return ((SELF) this).withStatement(new MultiArgsStatement(\"VOLUME\", volumes));\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/images/builder/dockerfile/traits/WorkdirStatementTrait.java",
    "content": "package org.testcontainers.images.builder.dockerfile.traits;\n\nimport org.testcontainers.images.builder.dockerfile.statement.SingleArgumentStatement;\n\npublic interface WorkdirStatementTrait<SELF extends WorkdirStatementTrait<SELF> & DockerfileBuilderTrait<SELF>> {\n    default SELF workDir(String workdir) {\n        return ((SELF) this).withStatement(new SingleArgumentStatement(\"WORKDIR\", workdir));\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/images/builder/traits/BuildContextBuilderTrait.java",
    "content": "package org.testcontainers.images.builder.traits;\n\nimport org.testcontainers.images.builder.Transferable;\n\n/**\n * base BuildContextBuilder's trait\n *\n */\npublic interface BuildContextBuilderTrait<SELF extends BuildContextBuilderTrait<SELF>> {\n    SELF withFileFromTransferable(String path, Transferable transferable);\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/images/builder/traits/ClasspathTrait.java",
    "content": "package org.testcontainers.images.builder.traits;\n\nimport org.testcontainers.utility.MountableFile;\n\nimport java.nio.file.Paths;\n\n/**\n * BuildContextBuilder's trait for classpath-based resources.\n *\n */\npublic interface ClasspathTrait<SELF extends ClasspathTrait<SELF> & BuildContextBuilderTrait<SELF> & FilesTrait<SELF>> {\n    default SELF withFileFromClasspath(String path, String resourcePath) {\n        final MountableFile mountableFile = MountableFile.forClasspathResource(resourcePath);\n\n        return ((SELF) this).withFileFromPath(path, Paths.get(mountableFile.getResolvedPath()));\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/images/builder/traits/DockerfileTrait.java",
    "content": "package org.testcontainers.images.builder.traits;\n\nimport lombok.Getter;\nimport org.testcontainers.images.builder.Transferable;\nimport org.testcontainers.images.builder.dockerfile.DockerfileBuilder;\n\nimport java.util.function.Consumer;\n\n/**\n * BuildContextBuilder's trait for Dockerfile-based resources.\n *\n */\npublic interface DockerfileTrait<\n    SELF extends DockerfileTrait<SELF> & BuildContextBuilderTrait<SELF> & StringsTrait<SELF>\n> {\n    default SELF withDockerfileFromBuilder(Consumer<DockerfileBuilder> builderConsumer) {\n        DockerfileBuilder builder = new DockerfileBuilder();\n\n        builderConsumer.accept(builder);\n\n        // return Transferable because we want to build Dockerfile's content lazily\n        return ((SELF) this).withFileFromTransferable(\n                \"Dockerfile\",\n                new Transferable() {\n                    @Getter(lazy = true)\n                    private final byte[] bytes = builder.build().getBytes();\n\n                    @Override\n                    public long getSize() {\n                        return getBytes().length;\n                    }\n\n                    @Override\n                    public String getDescription() {\n                        return \"Dockerfile: \" + builder;\n                    }\n                }\n            );\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/images/builder/traits/FilesTrait.java",
    "content": "package org.testcontainers.images.builder.traits;\n\nimport org.testcontainers.utility.MountableFile;\n\nimport java.io.File;\nimport java.nio.file.Path;\n\n/**\n * BuildContextBuilder's trait for NIO-based (Files and Paths) manipulations.\n *\n */\npublic interface FilesTrait<SELF extends FilesTrait<SELF> & BuildContextBuilderTrait<SELF>> {\n    /**\n     * Adds file to tarball copied into container.\n     * @param path in tarball\n     * @param file in host filesystem\n     * @return self\n     */\n    default SELF withFileFromFile(String path, File file) {\n        return withFileFromPath(path, file.toPath(), null);\n    }\n\n    /**\n     * Adds file to tarball copied into container.\n     * @param path in tarball\n     * @param filePath in host filesystem\n     * @return self\n     */\n    default SELF withFileFromPath(String path, Path filePath) {\n        return withFileFromPath(path, filePath, null);\n    }\n\n    /**\n     * Adds file with given mode to tarball copied into container.\n     * @param path in tarball\n     * @param file in host filesystem\n     * @param mode octal value of posix file mode (000..777)\n     * @return self\n     */\n    default SELF withFileFromFile(String path, File file, Integer mode) {\n        return withFileFromPath(path, file.toPath(), mode);\n    }\n\n    /**\n     * Adds file with given mode to tarball copied into container.\n     * @param path in tarball\n     * @param filePath in host filesystem\n     * @param mode octal value of posix file mode (000..777)\n     * @return self\n     */\n    default SELF withFileFromPath(String path, Path filePath, Integer mode) {\n        final MountableFile mountableFile = MountableFile.forHostPath(filePath, mode);\n        return ((SELF) this).withFileFromTransferable(path, mountableFile);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/images/builder/traits/StringsTrait.java",
    "content": "package org.testcontainers.images.builder.traits;\n\nimport lombok.Getter;\nimport org.apache.commons.lang3.StringUtils;\nimport org.testcontainers.images.builder.Transferable;\n\n/**\n * BuildContextBuilder's trait for String-based manipulations.\n *\n */\npublic interface StringsTrait<SELF extends StringsTrait<SELF> & BuildContextBuilderTrait<SELF>> {\n    default SELF withFileFromString(String path, String content) {\n        return ((SELF) this).withFileFromTransferable(\n                path,\n                new Transferable() {\n                    @Getter\n                    byte[] bytes = content.getBytes();\n\n                    @Override\n                    public long getSize() {\n                        return bytes.length;\n                    }\n\n                    @Override\n                    public String getDescription() {\n                        return \"String: \" + StringUtils.abbreviate(content, 100);\n                    }\n                }\n            );\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/jib/JibDockerClient.java",
    "content": "package org.testcontainers.jib;\n\nimport com.github.dockerjava.api.command.InspectImageResponse;\nimport com.github.dockerjava.api.command.LoadImageCallback;\nimport com.google.cloud.tools.jib.api.DockerClient;\nimport com.google.cloud.tools.jib.api.ImageDetails;\nimport com.google.cloud.tools.jib.api.ImageReference;\nimport com.google.cloud.tools.jib.http.NotifyingOutputStream;\nimport com.google.cloud.tools.jib.image.ImageTarball;\nimport com.google.common.io.ByteStreams;\nimport lombok.Cleanup;\nimport org.testcontainers.DockerClientFactory;\nimport org.testcontainers.UnstableAPI;\nimport org.testcontainers.images.RemoteDockerImage;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.BufferedInputStream;\nimport java.io.BufferedOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.io.PipedInputStream;\nimport java.io.PipedOutputStream;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.Map;\nimport java.util.function.Consumer;\n\n@UnstableAPI\nclass JibDockerClient implements DockerClient {\n\n    private static JibDockerClient instance;\n\n    private final com.github.dockerjava.api.DockerClient dockerClient = DockerClientFactory.lazyClient();\n\n    public static JibDockerClient instance() {\n        if (instance == null) {\n            instance = new JibDockerClient();\n        }\n\n        return instance;\n    }\n\n    @Override\n    public boolean supported(Map<String, String> map) {\n        return false;\n    }\n\n    @Override\n    public String load(ImageTarball imageTarball, Consumer<Long> writtenByteCountListener) throws IOException {\n        @Cleanup\n        PipedInputStream in = new PipedInputStream();\n        @Cleanup\n        PipedOutputStream out = new PipedOutputStream(in);\n        LoadImageCallback loadImage = this.dockerClient.loadImageAsyncCmd(in).exec(new LoadImageCallback());\n\n        try (NotifyingOutputStream stdin = new NotifyingOutputStream(out, writtenByteCountListener)) {\n            imageTarball.writeTo(stdin);\n        }\n\n        return loadImage.awaitMessage();\n    }\n\n    @Override\n    public void save(ImageReference imageReference, Path outputPath, Consumer<Long> writtenByteCountListener)\n        throws IOException {\n        try (\n            InputStream inputStream = this.dockerClient.saveImageCmd(imageReference.toString()).exec();\n            InputStream stdout = new BufferedInputStream(inputStream);\n            OutputStream fileStream = new BufferedOutputStream(Files.newOutputStream(outputPath));\n            NotifyingOutputStream notifyingFileStream = new NotifyingOutputStream(fileStream, writtenByteCountListener)\n        ) {\n            ByteStreams.copy(stdout, notifyingFileStream);\n        }\n    }\n\n    @Override\n    public ImageDetails inspect(ImageReference imageReference) {\n        new RemoteDockerImage(DockerImageName.parse(imageReference.toString())).get();\n\n        InspectImageResponse response = this.dockerClient.inspectImageCmd(imageReference.toString()).exec();\n        return new JibImageDetails(response.getSize(), response.getId(), response.getRootFS().getLayers());\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/jib/JibImage.java",
    "content": "package org.testcontainers.jib;\n\nimport com.google.cloud.tools.jib.api.Containerizer;\nimport com.google.cloud.tools.jib.api.DockerClient;\nimport com.google.cloud.tools.jib.api.DockerDaemonImage;\nimport com.google.cloud.tools.jib.api.Jib;\nimport com.google.cloud.tools.jib.api.JibContainer;\nimport com.google.cloud.tools.jib.api.JibContainerBuilder;\nimport lombok.SneakyThrows;\nimport org.testcontainers.DockerClientFactory;\nimport org.testcontainers.utility.Base58;\nimport org.testcontainers.utility.LazyFuture;\nimport org.testcontainers.utility.ResourceReaper;\n\nimport java.util.Map;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\npublic class JibImage extends LazyFuture<String> {\n\n    private final DockerClient dockerClient = JibDockerClient.instance();\n\n    private static final Map<String, String> DEFAULT_LABELS = Stream\n        .of(\n            DockerClientFactory.DEFAULT_LABELS.entrySet().stream(),\n            ResourceReaper.instance().getLabels().entrySet().stream()\n        )\n        .flatMap(Function.identity())\n        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));\n\n    private final String baseImage;\n\n    private final Function<JibContainerBuilder, JibContainerBuilder> jibContainerBuilderFn;\n\n    public JibImage(String baseImage, Function<JibContainerBuilder, JibContainerBuilder> jibContainerBuilderFn) {\n        this.baseImage = baseImage;\n        this.jibContainerBuilderFn = jibContainerBuilderFn;\n    }\n\n    @SneakyThrows\n    @Override\n    protected String resolve() {\n        JibContainerBuilder containerBuilder = Jib.from(this.dockerClient, DockerDaemonImage.named(this.baseImage));\n        Function<JibContainerBuilder, JibContainerBuilder> applyLabelsFn = jibContainerBuilder -> {\n            for (Map.Entry<String, String> entry : DEFAULT_LABELS.entrySet()) {\n                jibContainerBuilder.addLabel(entry.getKey(), entry.getValue());\n            }\n            return jibContainerBuilder;\n        };\n        JibContainer jibContainer =\n            this.jibContainerBuilderFn.andThen(applyLabelsFn)\n                .apply(containerBuilder)\n                .containerize(\n                    Containerizer.to(this.dockerClient, DockerDaemonImage.named(Base58.randomString(8).toLowerCase()))\n                );\n        return jibContainer.getTargetImage().toString();\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/jib/JibImageDetails.java",
    "content": "package org.testcontainers.jib;\n\nimport com.google.cloud.tools.jib.api.DescriptorDigest;\nimport com.google.cloud.tools.jib.api.ImageDetails;\n\nimport java.security.DigestException;\nimport java.util.ArrayList;\nimport java.util.List;\n\nclass JibImageDetails implements ImageDetails {\n\n    private long size;\n\n    private String imageId;\n\n    private List<String> layers;\n\n    public JibImageDetails(long size, String imageId, List<String> layers) {\n        this.size = size;\n        this.imageId = imageId;\n        this.layers = layers;\n    }\n\n    @Override\n    public long getSize() {\n        return this.size;\n    }\n\n    @Override\n    public DescriptorDigest getImageId() throws DigestException {\n        return DescriptorDigest.fromDigest(this.imageId);\n    }\n\n    @Override\n    public List<DescriptorDigest> getDiffIds() throws DigestException {\n        List<DescriptorDigest> processedDiffIds = new ArrayList<>(this.layers.size());\n        for (String diffId : this.layers) {\n            processedDiffIds.add(DescriptorDigest.fromDigest(diffId.trim()));\n        }\n        return processedDiffIds;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/lifecycle/Startable.java",
    "content": "package org.testcontainers.lifecycle;\n\nimport java.util.Collections;\nimport java.util.Set;\n\npublic interface Startable extends AutoCloseable {\n    default Set<Startable> getDependencies() {\n        return Collections.emptySet();\n    }\n\n    void start();\n\n    void stop();\n\n    @Override\n    default void close() {\n        stop();\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/lifecycle/Startables.java",
    "content": "package org.testcontainers.lifecycle;\n\nimport lombok.experimental.UtilityClass;\n\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.Executor;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.ThreadFactory;\nimport java.util.concurrent.atomic.AtomicLong;\nimport java.util.stream.Stream;\nimport java.util.stream.StreamSupport;\n\n@UtilityClass\npublic class Startables {\n\n    private static final Executor EXECUTOR = Executors.newCachedThreadPool(\n        new ThreadFactory() {\n            private final AtomicLong COUNTER = new AtomicLong(0);\n\n            @Override\n            public Thread newThread(Runnable r) {\n                Thread thread = new Thread(r, \"testcontainers-lifecycle-\" + COUNTER.getAndIncrement());\n                thread.setDaemon(true);\n                return thread;\n            }\n        }\n    );\n\n    /**\n     * @see #deepStart(Stream)\n     */\n    public CompletableFuture<Void> deepStart(Collection<? extends Startable> startables) {\n        return deepStart((Iterable<? extends Startable>) startables);\n    }\n\n    /**\n     * @see #deepStart(Stream)\n     */\n    public CompletableFuture<Void> deepStart(Iterable<? extends Startable> startables) {\n        return deepStart(StreamSupport.stream(startables.spliterator(), false));\n    }\n\n    /**\n     * @see #deepStart(Stream)\n     */\n    public CompletableFuture<Void> deepStart(Startable... startables) {\n        return deepStart(Arrays.stream(startables));\n    }\n\n    /**\n     * Start every {@link Startable} recursively and asynchronously and join on the result.\n     *\n     * Performance note:\n     * The method uses and returns {@link CompletableFuture}s to resolve as many {@link Startable}s at once as possible.\n     * This way, for the following graph:\n     *   / b \\\n     * a      e\n     *     c /\n     *     d /\n     * \"a\", \"c\" and \"d\" will resolve in parallel, then \"b\".\n     *\n     * If we would call blocking {@link Startable#start()}, \"e\" would wait for \"b\", \"b\" for \"a\", and only then \"c\", and then \"d\".\n     * But, since \"c\" and \"d\" are independent from \"a\", there is no point in waiting for \"a\" to be resolved first.\n     *\n     * @param startables a {@link Stream} of {@link Startable}s to start and scan for transitive dependencies.\n     * @return a {@link CompletableFuture} that resolves once all {@link Startable}s have started.\n     */\n    public CompletableFuture<Void> deepStart(Stream<? extends Startable> startables) {\n        return deepStart(new HashMap<>(), startables);\n    }\n\n    /**\n     *\n     * @param started an intermediate storage for already started {@link Startable}s to prevent multiple starts.\n     * @param startables a {@link Stream} of {@link Startable}s to start and scan for transitive dependencies.\n     */\n    private CompletableFuture<Void> deepStart(\n        Map<Startable, CompletableFuture<Void>> started,\n        Stream<? extends Startable> startables\n    ) {\n        CompletableFuture[] futures = startables\n            .sequential()\n            .map(it -> {\n                // avoid a recursive update in `computeIfAbsent`\n                Map<Startable, CompletableFuture<Void>> subStarted = new HashMap<>(started);\n                CompletableFuture<Void> future = started.computeIfAbsent(\n                    it,\n                    startable -> {\n                        return deepStart(subStarted, startable.getDependencies().stream())\n                            .thenRunAsync(startable::start, EXECUTOR);\n                    }\n                );\n                started.putAll(subStarted);\n                return future;\n            })\n            .toArray(CompletableFuture[]::new);\n\n        return allOfFailfast(futures);\n    }\n\n    private static <T> CompletableFuture<Void> allOfFailfast(CompletableFuture<?>[] futures) {\n        CompletableFuture<Void> result = CompletableFuture.allOf(futures);\n        for (CompletableFuture<?> future : futures) {\n            future.whenComplete((t, ex) -> {\n                if (ex != null) {\n                    result.completeExceptionally(ex);\n                }\n            });\n        }\n\n        return result;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/lifecycle/TestDescription.java",
    "content": "package org.testcontainers.lifecycle;\n\npublic interface TestDescription {\n    String getTestId();\n\n    String getFilesystemFriendlyName();\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/lifecycle/TestLifecycleAware.java",
    "content": "package org.testcontainers.lifecycle;\n\nimport java.util.Optional;\n\npublic interface TestLifecycleAware {\n    default void beforeTest(TestDescription description) {}\n\n    default void afterTest(TestDescription description, Optional<Throwable> throwable) {}\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/utility/AuditLogger.java",
    "content": "package org.testcontainers.utility;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.github.dockerjava.api.command.DockerCmd;\nimport com.google.common.base.Strings;\nimport lombok.experimental.UtilityClass;\nimport lombok.extern.slf4j.Slf4j;\nimport org.apache.commons.lang3.StringUtils;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\nimport org.slf4j.MDC;\n\nimport java.util.List;\n\n/**\n * Logger for tracking potentially destructive actions, intended for usage in a shared Docker environment where\n * traceability is needed. This class uses SLF4J, logging at TRACE level and capturing common fields as MDC fields.\n * <p>\n * Users should configure their test logging to apply appropriate filters/storage so that these logs are\n * captured appropriately.\n */\n@Slf4j\n@UtilityClass\npublic class AuditLogger {\n\n    private static final ObjectMapper objectMapper = new ObjectMapper();\n\n    public static final String MDC_PREFIX = AuditLogger.class.getCanonicalName();\n\n    public static void doLog(\n        @NotNull String action,\n        @Nullable String image,\n        @Nullable String containerId,\n        @NotNull DockerCmd<?> cmd\n    ) {\n        doLog(action, image, containerId, cmd, null);\n    }\n\n    public static void doLog(\n        @NotNull String action,\n        @Nullable String image,\n        @Nullable String containerId,\n        @NotNull DockerCmd<?> cmd,\n        @Nullable Exception e\n    ) {\n        if (!log.isTraceEnabled()) {\n            return;\n        }\n\n        MDC.put(MDC_PREFIX + \".Action\", Strings.nullToEmpty(action));\n        MDC.put(MDC_PREFIX + \".Image\", Strings.nullToEmpty(image));\n        MDC.put(MDC_PREFIX + \".ContainerId\", Strings.nullToEmpty(containerId));\n\n        try {\n            MDC.put(MDC_PREFIX + \".Command\", objectMapper.writeValueAsString(cmd));\n        } catch (JsonProcessingException ignored) {}\n\n        if (e != null) {\n            MDC.put(MDC_PREFIX + \".Exception\", e.getLocalizedMessage());\n            log.trace(\"{} action with image: {}, containerId: {}\", action, image, containerId, e);\n        } else {\n            log.trace(\"{} action with image: {}, containerId: {}\", action, image, containerId);\n        }\n\n        MDC.remove(MDC_PREFIX + \".Action\");\n        MDC.remove(MDC_PREFIX + \".Image\");\n        MDC.remove(MDC_PREFIX + \".ContainerId\");\n        MDC.remove(MDC_PREFIX + \".Command\");\n        MDC.remove(MDC_PREFIX + \".Exception\");\n    }\n\n    public static void doComposeLog(@NotNull String[] commandParts, @Nullable List<String> env) {\n        if (!log.isTraceEnabled()) {\n            return;\n        }\n\n        MDC.put(MDC_PREFIX + \".Action\", \"COMPOSE\");\n\n        if (env != null) {\n            MDC.put(MDC_PREFIX + \".Compose.Env\", env.toString());\n        }\n\n        final String command = StringUtils.join(commandParts, ' ');\n        MDC.put(MDC_PREFIX + \".Compose.Command\", command);\n\n        log.trace(\"COMPOSE action with command: {}, env: {}\", command, env);\n\n        MDC.remove(MDC_PREFIX + \".Action\");\n        MDC.remove(MDC_PREFIX + \".Compose.Command\");\n        MDC.remove(MDC_PREFIX + \".Compose.Env\");\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/utility/AuthConfigUtil.java",
    "content": "package org.testcontainers.utility;\n\nimport com.github.dockerjava.api.model.AuthConfig;\nimport com.google.common.base.MoreObjects;\nimport com.google.common.base.Strings;\nimport lombok.experimental.UtilityClass;\nimport org.jetbrains.annotations.NotNull;\n\n/**\n * TODO: Javadocs\n */\n@UtilityClass\npublic class AuthConfigUtil {\n\n    public static String toSafeString(AuthConfig authConfig) {\n        if (authConfig == null) {\n            return \"null\";\n        }\n\n        return MoreObjects\n            .toStringHelper(authConfig)\n            .add(\"username\", authConfig.getUsername())\n            .add(\"password\", obfuscated(authConfig.getPassword()))\n            .add(\"auth\", obfuscated(authConfig.getAuth()))\n            .add(\"email\", authConfig.getEmail())\n            .add(\"registryAddress\", authConfig.getRegistryAddress())\n            .add(\"registryToken\", obfuscated(authConfig.getRegistrytoken()))\n            .toString();\n    }\n\n    @NotNull\n    private static String obfuscated(String value) {\n        return Strings.isNullOrEmpty(value) ? \"blank\" : \"hidden non-blank value\";\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/utility/Base58.java",
    "content": "package org.testcontainers.utility;\n\nimport java.security.SecureRandom;\n\n/**\n * Utility class for creation of random strings of 58 easy-to-distinguish characters.\n */\npublic class Base58 {\n\n    private static final char[] ALPHABET = \"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\".toCharArray();\n\n    private static final SecureRandom RANDOM = new SecureRandom();\n\n    public static String randomString(int length) {\n        char[] result = new char[length];\n\n        for (int i = 0; i < length; i++) {\n            char pick = ALPHABET[RANDOM.nextInt(ALPHABET.length)];\n            result[i] = pick;\n        }\n\n        return new String(result);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/utility/ClasspathScanner.java",
    "content": "package org.testcontainers.utility;\n\nimport lombok.extern.slf4j.Slf4j;\nimport org.jetbrains.annotations.Nullable;\nimport org.jetbrains.annotations.VisibleForTesting;\n\nimport java.net.URL;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.Objects;\nimport java.util.stream.Stream;\n\n/**\n * Utility for identifying resource files on classloaders.\n */\n@Slf4j\nclass ClasspathScanner {\n\n    @VisibleForTesting\n    static Stream<URL> scanFor(final String name, ClassLoader... classLoaders) {\n        return Stream\n            .of(classLoaders)\n            .flatMap(classLoader -> getAllPropertyFilesOnClassloader(classLoader, name))\n            .filter(Objects::nonNull)\n            .sorted(\n                Comparator\n                    .comparing(ClasspathScanner::filesFileSchemeFirst) // resolve 'local' files first\n                    .thenComparing(URL::toString) // sort alphabetically for the sake of determinism\n            )\n            .distinct();\n    }\n\n    private static Integer filesFileSchemeFirst(final URL t) {\n        return t.getProtocol().equals(\"file\") ? 0 : 1;\n    }\n\n    /**\n     * @param name the resource name to search for\n     * @return distinct, ordered stream of resources found by searching this class' classloader and the current thread's\n     * context classloader. Results are currently alphabetically sorted.\n     */\n    static Stream<URL> scanFor(final String name) {\n        return scanFor(name, ClasspathScanner.class.getClassLoader(), Thread.currentThread().getContextClassLoader());\n    }\n\n    @Nullable\n    private static Stream<URL> getAllPropertyFilesOnClassloader(final ClassLoader it, final String s) {\n        try {\n            return Collections.list(it.getResources(s)).stream();\n        } catch (Exception e) {\n            log.error(\"Unable to read configuration from classloader {} - this is probably a bug\", it, e);\n            return Stream.empty();\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/utility/CommandLine.java",
    "content": "package org.testcontainers.utility;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.zeroturnaround.exec.InvalidExitValueException;\nimport org.zeroturnaround.exec.ProcessExecutor;\nimport org.zeroturnaround.exec.ProcessResult;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.concurrent.TimeoutException;\nimport java.util.regex.Pattern;\n\n/**\n * Process execution utility methods.\n */\npublic class CommandLine {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(CommandLine.class);\n\n    /**\n     * Run a shell command synchronously.\n     *\n     * @param command command to run and arguments\n     * @return the stdout output of the command\n     */\n    public static String runShellCommand(String... command) {\n        String joinedCommand = String.join(\" \", command);\n        LOGGER.debug(\"Executing shell command: `{}`\", joinedCommand);\n\n        try {\n            ProcessResult result = new ProcessExecutor().command(command).readOutput(true).exitValueNormal().execute();\n\n            return result.outputUTF8().trim();\n        } catch (IOException | InterruptedException | TimeoutException | InvalidExitValueException e) {\n            throw new ShellCommandException(\"Exception when executing \" + joinedCommand, e);\n        }\n    }\n\n    /**\n     * Check whether an executable exists, either at a specific path (if a full path is given) or\n     * on the PATH.\n     *\n     * @param executable the name of an executable on the PATH or a complete path to an executable that may/may not exist\n     * @return  whether the executable exists and is executable\n     */\n    public static boolean executableExists(String executable) {\n        // First check if we've been given the full path already\n        File directFile = new File(executable);\n        if (directFile.exists() && directFile.canExecute()) {\n            return true;\n        }\n\n        for (String pathString : getSystemPath()) {\n            Path path = Paths.get(pathString);\n            if (Files.exists(path.resolve(executable)) && Files.isExecutable(path.resolve(executable))) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    @NotNull\n    public static String[] getSystemPath() {\n        return System.getenv(\"PATH\").split(Pattern.quote(File.pathSeparator));\n    }\n\n    private static class ShellCommandException extends RuntimeException {\n\n        public ShellCommandException(String message, Exception e) {\n            super(message, e);\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/utility/ComparableVersion.java",
    "content": "package org.testcontainers.utility;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic final class ComparableVersion implements Comparable<ComparableVersion> {\n\n    private final int[] parts;\n\n    public static final ComparableVersion OS_VERSION = new ComparableVersion(System.getProperty(\"os.version\"));\n\n    public ComparableVersion(String version) {\n        this.parts = parseVersion(version);\n    }\n\n    @Override\n    public int compareTo(@NotNull ComparableVersion other) {\n        for (int i = 0; i < Math.min(this.parts.length, other.parts.length); i++) {\n            int thisPart = this.parts[i];\n            int otherPart = other.parts[i];\n            if (thisPart > otherPart) {\n                return 1;\n            } else if (thisPart < otherPart) {\n                return -1;\n            }\n        }\n\n        return 0;\n    }\n\n    public boolean isSemanticVersion() {\n        return parts.length > 0;\n    }\n\n    public boolean isLessThan(String other) {\n        return this.compareTo(new ComparableVersion(other)) < 0;\n    }\n\n    public boolean isGreaterThanOrEqualTo(String other) {\n        return this.compareTo(new ComparableVersion(other)) >= 0;\n    }\n\n    @VisibleForTesting\n    static int[] parseVersion(final String version) {\n        final List<Integer> parts = new ArrayList<>(5);\n\n        int acc = 0;\n        for (final char c : version.toCharArray()) {\n            if (c == '.') {\n                parts.add(acc);\n                acc = 0;\n            }\n\n            if (Character.isDigit(c)) {\n                acc = 10 * acc + Character.digit(c, 10);\n            }\n        }\n\n        if (acc != 0) {\n            parts.add(acc);\n        }\n\n        final int[] ret = new int[parts.size()];\n        for (int i = 0; i < ret.length; i++) {\n            ret[i] = parts.get(i);\n        }\n\n        return ret;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/utility/ConfigurationFileImageNameSubstitutor.java",
    "content": "package org.testcontainers.utility;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport lombok.extern.slf4j.Slf4j;\n\n/**\n * {@link ImageNameSubstitutor} which takes replacement image names from configuration.\n * See {@link TestcontainersConfiguration} for the subset of image names which can be substituted using this mechanism.\n */\n@Slf4j\nfinal class ConfigurationFileImageNameSubstitutor extends ImageNameSubstitutor {\n\n    private final TestcontainersConfiguration configuration;\n\n    public ConfigurationFileImageNameSubstitutor() {\n        this(TestcontainersConfiguration.getInstance());\n    }\n\n    @VisibleForTesting\n    ConfigurationFileImageNameSubstitutor(TestcontainersConfiguration configuration) {\n        this.configuration = configuration;\n    }\n\n    @Override\n    public DockerImageName apply(final DockerImageName original) {\n        final DockerImageName result = configuration\n            .getConfiguredSubstituteImage(original)\n            .asCompatibleSubstituteFor(original);\n\n        if (!result.equals(original)) {\n            log.warn(\n                \"Image name {} was substituted by configuration to {}. This approach is deprecated and will be removed in the future\",\n                original,\n                result\n            );\n        }\n\n        return result;\n    }\n\n    @Override\n    protected String getDescription() {\n        return getClass().getSimpleName();\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/utility/DefaultImageNameSubstitutor.java",
    "content": "package org.testcontainers.utility;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport lombok.extern.slf4j.Slf4j;\n\n/**\n * Testcontainers' default implementation of {@link ImageNameSubstitutor}.\n * Delegates to {@link ConfigurationFileImageNameSubstitutor} followed by {@link PrefixingImageNameSubstitutor}.\n */\n@Slf4j\nfinal class DefaultImageNameSubstitutor extends ImageNameSubstitutor {\n\n    private final ConfigurationFileImageNameSubstitutor configurationFileImageNameSubstitutor;\n\n    private final PrefixingImageNameSubstitutor prefixingImageNameSubstitutor;\n\n    public DefaultImageNameSubstitutor() {\n        configurationFileImageNameSubstitutor = new ConfigurationFileImageNameSubstitutor();\n        prefixingImageNameSubstitutor = new PrefixingImageNameSubstitutor();\n    }\n\n    @VisibleForTesting\n    DefaultImageNameSubstitutor(\n        final ConfigurationFileImageNameSubstitutor configurationFileImageNameSubstitutor,\n        final PrefixingImageNameSubstitutor prefixingImageNameSubstitutor\n    ) {\n        this.configurationFileImageNameSubstitutor = configurationFileImageNameSubstitutor;\n        this.prefixingImageNameSubstitutor = prefixingImageNameSubstitutor;\n    }\n\n    @Override\n    public DockerImageName apply(final DockerImageName original) {\n        return configurationFileImageNameSubstitutor.andThen(prefixingImageNameSubstitutor).apply(original);\n    }\n\n    @Override\n    protected String getDescription() {\n        return (\n            \"DefaultImageNameSubstitutor (composite of '\" +\n            configurationFileImageNameSubstitutor.getDescription() +\n            \"' and '\" +\n            prefixingImageNameSubstitutor.getDescription() +\n            \"')\"\n        );\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/utility/DockerImageName.java",
    "content": "package org.testcontainers.utility;\n\nimport com.google.common.net.HostAndPort;\nimport lombok.AccessLevel;\nimport lombok.AllArgsConstructor;\nimport lombok.EqualsAndHashCode;\nimport lombok.Getter;\nimport lombok.With;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\nimport org.testcontainers.utility.Versioning.Sha256Versioning;\nimport org.testcontainers.utility.Versioning.TagVersioning;\n\nimport java.util.regex.Pattern;\n\n@EqualsAndHashCode(exclude = { \"rawName\", \"compatibleSubstituteFor\" })\n@AllArgsConstructor(access = AccessLevel.PRIVATE)\npublic final class DockerImageName {\n\n    /* Regex patterns used for validation */\n    private static final String ALPHA_NUMERIC = \"[a-z0-9]+\";\n\n    private static final String SEPARATOR = \"([.]|_{1,2}|-+)\";\n\n    private static final String REPO_NAME_PART = ALPHA_NUMERIC + \"(\" + SEPARATOR + ALPHA_NUMERIC + \")*\";\n\n    private static final Pattern REPO_NAME = Pattern.compile(REPO_NAME_PART + \"(/\" + REPO_NAME_PART + \")*\");\n\n    private static final String LIBRARY_PREFIX = \"library/\";\n\n    private final String rawName;\n\n    @With\n    @Getter\n    private final String registry;\n\n    @With\n    @Getter\n    private final String repository;\n\n    @NotNull\n    @With(AccessLevel.PRIVATE)\n    private final Versioning versioning;\n\n    @Nullable\n    @With(AccessLevel.PRIVATE)\n    private final DockerImageName compatibleSubstituteFor;\n\n    /**\n     * Parses a docker image name from a provided string.\n     *\n     * @param fullImageName in standard Docker format, e.g. <code>name:tag</code>,\n     *                      <code>some.registry/path/name:tag</code>,\n     *                      <code>some.registry/path/name@sha256:abcdef...</code>, etc.\n     */\n    public static DockerImageName parse(String fullImageName) {\n        return new DockerImageName(fullImageName);\n    }\n\n    /**\n     * Parses a docker image name from a provided string.\n     *\n     * @param fullImageName in standard Docker format, e.g. <code>name:tag</code>,\n     *                      <code>some.registry/path/name:tag</code>,\n     *                      <code>some.registry/path/name@sha256:abcdef...</code>, etc.\n     * @deprecated use {@link DockerImageName#parse(String)} instead\n     */\n    @Deprecated\n    public DockerImageName(String fullImageName) {\n        this.rawName = fullImageName;\n        final int slashIndex = fullImageName.indexOf('/');\n\n        String remoteName;\n        if (\n            slashIndex == -1 ||\n            (\n                !fullImageName.substring(0, slashIndex).contains(\".\") &&\n                !fullImageName.substring(0, slashIndex).contains(\":\") &&\n                !fullImageName.substring(0, slashIndex).equals(\"localhost\")\n            )\n        ) {\n            registry = \"\";\n            remoteName = fullImageName;\n        } else {\n            registry = fullImageName.substring(0, slashIndex);\n            remoteName = fullImageName.substring(slashIndex + 1);\n        }\n\n        if (remoteName.contains(\"@sha256:\")) {\n            repository = remoteName.split(\"@sha256:\")[0];\n            versioning = new Sha256Versioning(remoteName.split(\"@sha256:\")[1]);\n        } else if (remoteName.contains(\":\")) {\n            repository = remoteName.split(\":\")[0];\n            versioning = new TagVersioning(remoteName.split(\":\")[1]);\n        } else {\n            repository = remoteName;\n            versioning = Versioning.ANY;\n        }\n\n        compatibleSubstituteFor = null;\n    }\n\n    /**\n     * Parses a docker image name from a provided string, and uses a separate provided version.\n     *\n     * @param nameWithoutTag in standard Docker format, e.g. <code>name</code>,\n     *                       <code>some.registry/path/name</code>,\n     *                       <code>some.registry/path/name</code>, etc.\n     * @param version        a docker image version identifier, either as a tag or sha256 checksum, e.g.\n     *                       <code>tag</code>,\n     *                       <code>sha256:abcdef...</code>.\n     * @deprecated use {@link DockerImageName#parse(String)}.{@link DockerImageName#withTag(String)} instead\n     */\n    @Deprecated\n    public DockerImageName(String nameWithoutTag, @NotNull String version) {\n        this.rawName = nameWithoutTag;\n        final int slashIndex = nameWithoutTag.indexOf('/');\n\n        String remoteName;\n        if (\n            slashIndex == -1 ||\n            (\n                !nameWithoutTag.substring(0, slashIndex).contains(\".\") &&\n                !nameWithoutTag.substring(0, slashIndex).contains(\":\") &&\n                !nameWithoutTag.substring(0, slashIndex).equals(\"localhost\")\n            )\n        ) {\n            registry = \"\";\n            remoteName = nameWithoutTag;\n        } else {\n            registry = nameWithoutTag.substring(0, slashIndex);\n            remoteName = nameWithoutTag.substring(slashIndex + 1);\n        }\n\n        if (version.startsWith(\"sha256:\")) {\n            repository = remoteName;\n            versioning = new Sha256Versioning(version.replace(\"sha256:\", \"\"));\n        } else {\n            repository = remoteName;\n            versioning = new TagVersioning(version);\n        }\n\n        compatibleSubstituteFor = null;\n    }\n\n    /**\n     * @return the unversioned (non 'tag') part of this name\n     */\n    public String getUnversionedPart() {\n        if (!\"\".equals(registry)) {\n            return registry + \"/\" + repository;\n        } else {\n            return repository;\n        }\n    }\n\n    /**\n     * @return the versioned part of this name (tag or sha256)\n     */\n    public String getVersionPart() {\n        return versioning.toString();\n    }\n\n    /**\n     * @return canonical name for the image\n     */\n    public String asCanonicalNameString() {\n        return getUnversionedPart() + versioning.getSeparator() + getVersionPart();\n    }\n\n    @Override\n    public String toString() {\n        return asCanonicalNameString();\n    }\n\n    /**\n     * Is the image name valid?\n     *\n     * @throws IllegalArgumentException if not valid\n     */\n    public void assertValid() {\n        //noinspection UnstableApiUsage\n        HostAndPort.fromString(registry); // return value ignored - this throws if registry is not a valid host:port string\n        if (!REPO_NAME.matcher(repository).matches()) {\n            throw new IllegalArgumentException(repository + \" is not a valid Docker image name (in \" + rawName + \")\");\n        }\n        if (!versioning.isValid()) {\n            throw new IllegalArgumentException(\n                versioning + \" is not a valid image versioning identifier (in \" + rawName + \")\"\n            );\n        }\n    }\n\n    /**\n     * @param newTag version tag for the copy to use\n     * @return an immutable copy of this {@link DockerImageName} with the new version tag\n     */\n    public DockerImageName withTag(final String newTag) {\n        return withVersioning(new TagVersioning(newTag));\n    }\n\n    /**\n     * Declare that this {@link DockerImageName} is a compatible substitute for another image - i.e. that this image\n     * behaves as the other does, and is compatible with Testcontainers' assumptions about the other image.\n     *\n     * @param otherImageName the image name of the other image\n     * @return an immutable copy of this {@link DockerImageName} with the compatibility declaration attached.\n     */\n    public DockerImageName asCompatibleSubstituteFor(String otherImageName) {\n        return withCompatibleSubstituteFor(DockerImageName.parse(otherImageName));\n    }\n\n    /**\n     * Declare that this {@link DockerImageName} is a compatible substitute for another image - i.e. that this image\n     * behaves as the other does, and is compatible with Testcontainers' assumptions about the other image.\n     *\n     * @param otherImageName the image name of the other image\n     * @return an immutable copy of this {@link DockerImageName} with the compatibility declaration attached.\n     */\n    public DockerImageName asCompatibleSubstituteFor(DockerImageName otherImageName) {\n        return withCompatibleSubstituteFor(otherImageName);\n    }\n\n    /**\n     * Test whether this {@link DockerImageName} has declared compatibility with another image (set using\n     * {@link DockerImageName#asCompatibleSubstituteFor(String)} or\n     * {@link DockerImageName#asCompatibleSubstituteFor(DockerImageName)}.\n     * <p>\n     * If a version tag part is present in the <code>other</code> image name, the tags must exactly match, unless it\n     * is 'latest'. If a version part is not present in the <code>other</code> image name, the tag contents are ignored.\n     *\n     * @param other the other image that we are trying to test compatibility with\n     * @return whether this image has declared compatibility.\n     */\n    public boolean isCompatibleWith(DockerImageName other) {\n        // Make sure we always compare against a version of the image name containing the LIBRARY_PREFIX\n        String finalImageName;\n        if (this.repository.startsWith(LIBRARY_PREFIX)) {\n            finalImageName = this.repository;\n        } else {\n            finalImageName = LIBRARY_PREFIX + this.repository;\n        }\n        DockerImageName imageWithLibraryPrefix = DockerImageName.parse(finalImageName);\n        if (other.equals(this) || imageWithLibraryPrefix.equals(this)) {\n            return true;\n        }\n\n        if (this.compatibleSubstituteFor == null) {\n            return false;\n        }\n\n        return this.compatibleSubstituteFor.isCompatibleWith(other);\n    }\n\n    /**\n     * Behaves as {@link DockerImageName#isCompatibleWith(DockerImageName)} but throws an exception\n     * rather than returning false if a mismatch is detected.\n     *\n     * @param anyOthers the other image(s) that we are trying to check compatibility with. If more\n     *                  than one is provided, this method will check compatibility with at least one\n     *                  of them.\n     * @throws IllegalStateException if {@link DockerImageName#isCompatibleWith(DockerImageName)}\n     *                               returns false\n     */\n    public void assertCompatibleWith(DockerImageName... anyOthers) {\n        if (anyOthers.length == 0) {\n            throw new IllegalArgumentException(\"anyOthers parameter must be non-empty\");\n        }\n\n        for (DockerImageName anyOther : anyOthers) {\n            if (this.isCompatibleWith(anyOther)) {\n                return;\n            }\n        }\n\n        final DockerImageName exampleOther = anyOthers[0];\n\n        throw new IllegalStateException(\n            String.format(\n                \"Failed to verify that image '%s' is a compatible substitute for '%s'. This generally means that \" +\n                \"you are trying to use an image that Testcontainers has not been designed to use. If this is \" +\n                \"deliberate, and if you are confident that the image is compatible, you should declare \" +\n                \"compatibility in code using the `asCompatibleSubstituteFor` method. For example:\\n\" +\n                \"   DockerImageName myImage = DockerImageName.parse(\\\"%s\\\").asCompatibleSubstituteFor(\\\"%s\\\");\\n\" +\n                \"and then use `myImage` instead.\",\n                this.rawName,\n                exampleOther.rawName,\n                this.rawName,\n                exampleOther.rawName\n            )\n        );\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/utility/DockerLoggerFactory.java",
    "content": "package org.testcontainers.utility;\n\nimport lombok.AccessLevel;\nimport lombok.NoArgsConstructor;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\n@NoArgsConstructor(access = AccessLevel.PRIVATE)\npublic final class DockerLoggerFactory {\n\n    public static Logger getLogger(String dockerImageName) {\n        final String abbreviatedName;\n        if (dockerImageName.contains(\"@sha256\")) {\n            abbreviatedName = dockerImageName.substring(0, dockerImageName.indexOf(\"@sha256\") + 14) + \"...\";\n        } else {\n            abbreviatedName = dockerImageName;\n        }\n\n        return LoggerFactory.getLogger(\"tc.\" + abbreviatedName);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/utility/DockerMachineClient.java",
    "content": "package org.testcontainers.utility;\n\nimport lombok.NonNull;\nimport org.apache.commons.lang3.SystemUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Optional;\n\n/**\n * Created by rnorth on 27/10/2015.\n */\npublic class DockerMachineClient {\n\n    private static DockerMachineClient instance;\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(DockerMachineClient.class);\n\n    private static final String executableName;\n\n    static {\n        if (SystemUtils.IS_OS_WINDOWS) {\n            executableName = \"docker-machine.exe\";\n        } else {\n            executableName = \"docker-machine\";\n        }\n    }\n\n    /**\n     * Private constructor\n     */\n    private DockerMachineClient() {}\n\n    /**\n     * Obtain an instance of the DockerMachineClient wrapper.\n     *\n     * @return the singleton instance of DockerMachineClient\n     */\n    public static synchronized DockerMachineClient instance() {\n        if (instance == null) {\n            instance = new DockerMachineClient();\n        }\n\n        return instance;\n    }\n\n    public boolean isInstalled() {\n        return CommandLine.executableExists(executableName);\n    }\n\n    public Optional<String> getDefaultMachine() {\n        String ls = CommandLine.runShellCommand(executableName, \"ls\", \"-q\");\n        List<String> machineNames = Arrays.asList(ls.split(\"\\n\"));\n\n        String envMachineName = System.getenv(\"DOCKER_MACHINE_NAME\");\n\n        if (machineNames.contains(envMachineName)) {\n            LOGGER.debug(\"Using docker-machine set in DOCKER_MACHINE_NAME: {}\", envMachineName);\n            return Optional.of(envMachineName);\n        } else if (machineNames.contains(\"default\")) {\n            LOGGER.debug(\"DOCKER_MACHINE_NAME is not set; Using 'default' docker-machine\", envMachineName);\n            return Optional.of(\"default\");\n        } else if (machineNames.size() > 0) {\n            LOGGER.debug(\n                \"DOCKER_MACHINE_NAME is not set and no machine named 'default' found; Using first machine found with `docker-machine ls`: {}\",\n                machineNames.get(0)\n            );\n            return Optional.of(machineNames.get(0));\n        } else {\n            return Optional.empty();\n        }\n    }\n\n    public void ensureMachineRunning(@NonNull String machineName) {\n        if (!isMachineRunning(machineName)) {\n            LOGGER.info(\"Docker-machine '{}' is not running. Will start it now\", machineName);\n            CommandLine.runShellCommand(\"docker-machine\", \"start\", machineName);\n        }\n    }\n\n    /**\n     * @deprecated Use getDockerDaemonUrl(@NonNull String machineName) for connection to docker-machine\n     */\n    @Deprecated\n    public String getDockerDaemonIpAddress(@NonNull String machineName) {\n        return CommandLine.runShellCommand(executableName, \"ip\", machineName);\n    }\n\n    public String getDockerDaemonUrl(@NonNull String machineName) {\n        return CommandLine.runShellCommand(executableName, \"url\", machineName);\n    }\n\n    public boolean isMachineRunning(String machineName) {\n        String status = CommandLine.runShellCommand(\"docker-machine\", \"status\", machineName);\n        return status.trim().equalsIgnoreCase(\"running\");\n    }\n\n    public boolean isDefaultMachineRunning() {\n        return isMachineRunning(getDefaultMachine().orElse(\"default\"));\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/utility/DockerStatus.java",
    "content": "package org.testcontainers.utility;\n\nimport com.github.dockerjava.api.command.InspectContainerResponse;\n\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.time.format.DateTimeFormatter;\n\n/**\n * Utility functions for dealing with docker status based on the information available to us, and trying to be\n * defensive.\n * <p>\n * <p>In docker-java version 2.2.0, which we're using, only these\n * fields are available in the container state returned from Docker Inspect: \"isRunning\", \"isPaused\", \"startedAt\", and\n * \"finishedAt\". There are states that can occur (including \"created\", \"OOMkilled\" and \"dead\") that aren't directly\n * shown through this result.\n * <p>\n * <p>Docker also doesn't seem to use null values for timestamps; see DOCKER_TIMESTAMP_ZERO, below.\n */\npublic class DockerStatus {\n\n    /**\n     * When the docker client has an \"empty\" timestamp, it returns this special value, rather than\n     * null or an empty string.\n     */\n    static final String DOCKER_TIMESTAMP_ZERO = \"0001-01-01T00:00:00Z\";\n\n    /**\n     * Based on this status, is this container running, and has it been doing so for the specified amount of time?\n     *\n     * @param state                  the state provided by InspectContainer\n     * @param minimumRunningDuration minimum duration to consider this as \"solidly\" running, or null\n     * @param now                    the time to consider as the current time\n     * @return true if we can conclude that the container is running, false otherwise\n     */\n    public static boolean isContainerRunning(\n        InspectContainerResponse.ContainerState state,\n        Duration minimumRunningDuration,\n        Instant now\n    ) {\n        if (state.getRunning()) {\n            if (minimumRunningDuration == null) {\n                return true;\n            }\n            Instant startedAt = DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(state.getStartedAt(), Instant::from);\n\n            if (startedAt.isBefore(now.minus(minimumRunningDuration))) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    /**\n     * Based on this status, has the container halted?\n     *\n     * @param state the state provided by InspectContainer\n     * @return true if we can conclude that the container has started but is now stopped, false otherwise.\n     */\n    public static boolean isContainerStopped(InspectContainerResponse.ContainerState state) {\n        // get some preconditions out of the way\n        if (state.getRunning() || state.getPaused()) {\n            return false;\n        }\n\n        // if the finished timestamp is non-empty, that means the container started and finished.\n        boolean hasStarted = isDockerTimestampNonEmpty(state.getStartedAt());\n        boolean hasFinished = isDockerTimestampNonEmpty(state.getFinishedAt());\n        return hasStarted && hasFinished;\n    }\n\n    public static boolean isDockerTimestampNonEmpty(String dockerTimestamp) {\n        // This is a defensive approach. Current versions of Docker use the DOCKER_TIMESTAMP_ZERO value, but\n        // that could change.\n        return (\n            dockerTimestamp != null &&\n            !dockerTimestamp.isEmpty() &&\n            !dockerTimestamp.equals(DOCKER_TIMESTAMP_ZERO) &&\n            DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(dockerTimestamp, Instant::from).getEpochSecond() >= 0L\n        );\n    }\n\n    public static boolean isContainerExitCodeSuccess(InspectContainerResponse.ContainerState state) {\n        int exitCode = state.getExitCode();\n        // 0 is the only exit code we can consider as success\n        return exitCode == 0;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/utility/DynamicPollInterval.java",
    "content": "package org.testcontainers.utility;\n\nimport org.awaitility.pollinterval.PollInterval;\n\nimport java.time.Duration;\nimport java.time.Instant;\n\n/**\n * Awaitility {@link org.awaitility.pollinterval.PollInterval} that takes execution time into consideration,\n * to allow a constant poll-interval, as opposed to Awaitility's default poll-delay behaviour.\n *\n * @deprecated For internal usage only.\n */\n@Deprecated\npublic class DynamicPollInterval implements PollInterval {\n\n    final Duration interval;\n\n    Instant lastTimestamp;\n\n    private DynamicPollInterval(Duration interval) {\n        this.interval = interval;\n        lastTimestamp = Instant.now();\n    }\n\n    public static DynamicPollInterval of(Duration duration) {\n        return new DynamicPollInterval(duration);\n    }\n\n    public static DynamicPollInterval ofMillis(long millis) {\n        return DynamicPollInterval.of(Duration.ofMillis(millis));\n    }\n\n    @Override\n    public Duration next(int pollCount, Duration previousDuration) {\n        Instant now = Instant.now();\n        Duration executionDuration = Duration.between(lastTimestamp, now);\n\n        Duration result = interval.minusMillis(Math.min(interval.toMillis(), executionDuration.toMillis()));\n        lastTimestamp = now.plus(result);\n        return result;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/utility/ImageNameSubstitutor.java",
    "content": "package org.testcontainers.utility;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport lombok.extern.slf4j.Slf4j;\nimport org.testcontainers.UnstableAPI;\n\nimport java.util.ServiceLoader;\nimport java.util.function.Function;\nimport java.util.stream.StreamSupport;\n\n/**\n * An image name substitutor converts a Docker image name, as may be specified in code, to an alternative name.\n * This is intended to provide a way to override image names, for example to enforce pulling of images from a private\n * registry.\n * <p>\n * This is marked as @{@link UnstableAPI} as this API is new. While we do not think major changes will be required, we\n * will react to feedback if necessary.\n */\n@Slf4j\n@UnstableAPI\npublic abstract class ImageNameSubstitutor implements Function<DockerImageName, DockerImageName> {\n\n    @VisibleForTesting\n    static ImageNameSubstitutor instance;\n\n    @VisibleForTesting\n    static ImageNameSubstitutor defaultImplementation = new DefaultImageNameSubstitutor();\n\n    public static synchronized ImageNameSubstitutor instance() {\n        return instance(Thread.currentThread().getContextClassLoader());\n    }\n\n    @VisibleForTesting\n    static synchronized ImageNameSubstitutor instance(ClassLoader classLoader) {\n        if (instance == null) {\n            ImageNameSubstitutor configuredInstance = getImageNameSubstitutor(classLoader);\n\n            if (configuredInstance != null) {\n                log.debug(\n                    \"Attempting to instantiate an ImageNameSubstitutor with class: {}\",\n                    configuredInstance.getClass().getCanonicalName()\n                );\n\n                log.info(\"Found configured ImageNameSubstitutor: {}\", configuredInstance.getDescription());\n\n                instance =\n                    new ChainedImageNameSubstitutor(\n                        wrapWithLogging(defaultImplementation),\n                        wrapWithLogging(configuredInstance)\n                    );\n            } else {\n                instance = wrapWithLogging(defaultImplementation);\n            }\n\n            log.info(\"Image name substitution will be performed by: {}\", instance.getDescription());\n        }\n\n        return instance;\n    }\n\n    private static ImageNameSubstitutor getImageNameSubstitutor(ClassLoader classLoader) {\n        final String configuredClassName = TestcontainersConfiguration.getInstance().getImageSubstitutorClassName();\n\n        if (configuredClassName != null) {\n            try {\n                return (ImageNameSubstitutor) classLoader.loadClass(configuredClassName).getConstructor().newInstance();\n            } catch (Exception e) {\n                throw new IllegalArgumentException(\n                    \"Configured Image Substitutor could not be loaded: \" + configuredClassName,\n                    e\n                );\n            }\n        }\n\n        return StreamSupport\n            .stream(ServiceLoader.load(ImageNameSubstitutor.class, classLoader).spliterator(), false)\n            .findFirst()\n            .orElse(null);\n    }\n\n    public static ImageNameSubstitutor noop() {\n        return new NoopImageNameSubstitutor();\n    }\n\n    private static ImageNameSubstitutor wrapWithLogging(final ImageNameSubstitutor wrappedInstance) {\n        return new LogWrappedImageNameSubstitutor(wrappedInstance);\n    }\n\n    /**\n     * Substitute a {@link DockerImageName} for another, for example to replace a generic Docker Hub image name with a\n     * private registry copy of the image.\n     *\n     * @param original original name to be replaced\n     * @return a replacement name, or the original, as appropriate\n     */\n    public abstract DockerImageName apply(DockerImageName original);\n\n    /**\n     * @return a human-readable description of the substitutor\n     */\n    protected abstract String getDescription();\n\n    /**\n     * Wrapper substitutor which logs which substitutions have been performed.\n     */\n    static class LogWrappedImageNameSubstitutor extends ImageNameSubstitutor {\n\n        @VisibleForTesting\n        final ImageNameSubstitutor wrappedInstance;\n\n        public LogWrappedImageNameSubstitutor(final ImageNameSubstitutor wrappedInstance) {\n            this.wrappedInstance = wrappedInstance;\n        }\n\n        @Override\n        public DockerImageName apply(final DockerImageName original) {\n            final DockerImageName replacementImage = wrappedInstance.apply(original);\n\n            if (!replacementImage.equals(original)) {\n                log.info(\n                    \"Using {} as a substitute image for {} (using image substitutor: {})\",\n                    replacementImage.asCanonicalNameString(),\n                    original.asCanonicalNameString(),\n                    wrappedInstance.getDescription()\n                );\n                return replacementImage;\n            } else {\n                log.debug(\n                    \"Did not find a substitute image for {} (using image substitutor: {})\",\n                    original.asCanonicalNameString(),\n                    wrappedInstance.getDescription()\n                );\n                return original;\n            }\n        }\n\n        @Override\n        protected String getDescription() {\n            return wrappedInstance.getDescription();\n        }\n    }\n\n    /**\n     * Wrapper substitutor that passes the original image name through a default substitutor and then the configured one\n     */\n    static class ChainedImageNameSubstitutor extends ImageNameSubstitutor {\n\n        private final ImageNameSubstitutor defaultInstance;\n\n        private final ImageNameSubstitutor configuredInstance;\n\n        public ChainedImageNameSubstitutor(\n            ImageNameSubstitutor defaultInstance,\n            ImageNameSubstitutor configuredInstance\n        ) {\n            this.defaultInstance = defaultInstance;\n            this.configuredInstance = configuredInstance;\n        }\n\n        @Override\n        public DockerImageName apply(DockerImageName original) {\n            return defaultInstance.andThen(configuredInstance).apply(original);\n        }\n\n        @Override\n        protected String getDescription() {\n            return String.format(\n                \"Chained substitutor of '%s' and then '%s'\",\n                defaultInstance.getDescription(),\n                configuredInstance.getDescription()\n            );\n        }\n\n        @Override\n        public String toString() {\n            return getDescription();\n        }\n    }\n\n    private static class NoopImageNameSubstitutor extends ImageNameSubstitutor {\n\n        @Override\n        public DockerImageName apply(DockerImageName original) {\n            return original;\n        }\n\n        @Override\n        protected String getDescription() {\n            return \"No-op substitutor\";\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/utility/JVMHookResourceReaper.java",
    "content": "package org.testcontainers.utility;\n\nimport com.github.dockerjava.api.model.Container;\nimport com.github.dockerjava.api.model.PruneType;\n\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * A {@link ResourceReaper} implementation that uses {@link Runtime#addShutdownHook(Thread)}\n * to cleanup containers.\n */\nclass JVMHookResourceReaper extends ResourceReaper {\n\n    @Override\n    public void init() {\n        setHook();\n    }\n\n    @Override\n    public synchronized void performCleanup() {\n        super.performCleanup();\n        synchronized (DEATH_NOTE) {\n            DEATH_NOTE.forEach(filters -> prune(PruneType.CONTAINERS, filters));\n            DEATH_NOTE.forEach(filters -> prune(PruneType.NETWORKS, filters));\n            DEATH_NOTE.forEach(filters -> prune(PruneType.VOLUMES, filters));\n            DEATH_NOTE.forEach(filters -> prune(PruneType.IMAGES, filters));\n        }\n    }\n\n    private void prune(PruneType pruneType, List<Map.Entry<String, String>> filters) {\n        String[] labels = filters\n            .stream()\n            .filter(it -> \"label\".equals(it.getKey()))\n            .map(Map.Entry::getValue)\n            .toArray(String[]::new);\n        switch (pruneType) {\n            // Docker only prunes stopped containers, so we have to do it manually\n            case CONTAINERS:\n                List<Container> containers = dockerClient\n                    .listContainersCmd()\n                    .withFilter(\"label\", Arrays.asList(labels))\n                    .withShowAll(true)\n                    .exec();\n\n                containers\n                    .parallelStream()\n                    .forEach(container -> {\n                        dockerClient\n                            .removeContainerCmd(container.getId())\n                            .withForce(true)\n                            .withRemoveVolumes(true)\n                            .exec();\n                    });\n                break;\n            default:\n                dockerClient.pruneCmd(pruneType).withLabelFilter(labels).exec();\n                break;\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/utility/LazyFuture.java",
    "content": "package org.testcontainers.utility;\n\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport org.rnorth.ducttape.timeouts.Timeouts;\n\nimport java.util.concurrent.Future;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\nimport java.util.concurrent.atomic.AtomicReference;\n\n/**\n * Future implementation with lazy result evaluation <b>in the same Thread</b> as caller.\n *\n * @param <T>\n */\npublic abstract class LazyFuture<T> implements Future<T> {\n\n    @Getter(value = AccessLevel.MODULE, lazy = true)\n    private final T resolvedValue = resolve();\n\n    protected abstract T resolve();\n\n    @Override\n    public boolean cancel(boolean mayInterruptIfRunning) {\n        return false;\n    }\n\n    @Override\n    public boolean isCancelled() {\n        return false;\n    }\n\n    @Override\n    public boolean isDone() {\n        return ((AtomicReference<?>) resolvedValue).get() != null;\n    }\n\n    @Override\n    public T get() {\n        return getResolvedValue();\n    }\n\n    @Override\n    public T get(long timeout, TimeUnit unit) throws TimeoutException {\n        try {\n            return Timeouts.getWithTimeout((int) timeout, unit, this::get);\n        } catch (org.rnorth.ducttape.TimeoutException e) {\n            throw new TimeoutException(e.getMessage());\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/utility/LicenseAcceptance.java",
    "content": "package org.testcontainers.utility;\n\nimport com.google.common.base.Charsets;\nimport com.google.common.io.Resources;\nimport lombok.experimental.UtilityClass;\n\nimport java.net.URL;\nimport java.util.List;\n\n/**\n * Utility class to ensure that licenses have been accepted by the developer.\n */\n@UtilityClass\npublic class LicenseAcceptance {\n\n    private static final String ACCEPTANCE_FILE_NAME = \"container-license-acceptance.txt\";\n\n    public static void assertLicenseAccepted(final String imageName) {\n        try {\n            final URL url = Resources.getResource(ACCEPTANCE_FILE_NAME);\n            final List<String> acceptedLicences = Resources.readLines(url, Charsets.UTF_8);\n\n            if (acceptedLicences.stream().map(String::trim).anyMatch(imageName::equals)) {\n                return;\n            }\n        } catch (Exception ignored) {\n            // suppressed\n        }\n\n        throw new IllegalStateException(\n            \"The image \" +\n            imageName +\n            \" requires you to accept a license agreement. \" +\n            \"Please place a file at the root of the classpath named \" +\n            ACCEPTANCE_FILE_NAME +\n            \", e.g. at \" +\n            \"src/test/resources/\" +\n            ACCEPTANCE_FILE_NAME +\n            \". This file should contain the line:\\n  \" +\n            imageName\n        );\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/utility/LogUtils.java",
    "content": "package org.testcontainers.utility;\n\nimport com.github.dockerjava.api.DockerClient;\nimport com.github.dockerjava.api.command.LogContainerCmd;\nimport lombok.SneakyThrows;\nimport lombok.experimental.UtilityClass;\nimport org.testcontainers.containers.output.FrameConsumerResultCallback;\nimport org.testcontainers.containers.output.OutputFrame;\nimport org.testcontainers.containers.output.ToStringConsumer;\nimport org.testcontainers.containers.output.WaitingConsumer;\n\nimport java.io.Closeable;\nimport java.io.IOException;\nimport java.util.function.Consumer;\n\n/**\n * Provides utility methods for logging.\n */\n@UtilityClass\npublic class LogUtils {\n\n    /**\n     * Attach a log consumer to a container's log outputs in follow mode. The consumer will receive all previous\n     * and all future log frames of the specified type(s).\n     *\n     * @param dockerClient a Docker client\n     * @param containerId  container ID to attach to\n     * @param consumer     a consumer of {@link OutputFrame}s\n     * @param types        types of {@link OutputFrame} to receive\n     */\n    public void followOutput(\n        DockerClient dockerClient,\n        String containerId,\n        Consumer<OutputFrame> consumer,\n        OutputFrame.OutputType... types\n    ) {\n        attachConsumer(dockerClient, containerId, consumer, true, types);\n    }\n\n    /**\n     * Attach a log consumer to a container's log outputs in follow mode. The consumer will receive all previous\n     * and all future log frames (both stdout and stderr).\n     *\n     * @param dockerClient a Docker client\n     * @param containerId  container ID to attach to\n     * @param consumer     a consumer of {@link OutputFrame}s\n     */\n    public void followOutput(DockerClient dockerClient, String containerId, Consumer<OutputFrame> consumer) {\n        followOutput(dockerClient, containerId, consumer, OutputFrame.OutputType.STDOUT, OutputFrame.OutputType.STDERR);\n    }\n\n    /**\n     * Retrieve all previous log outputs for a container of the specified type(s).\n     *\n     * @param dockerClient a Docker client\n     * @param containerId  container ID to attach to\n     * @param types        types of {@link OutputFrame} to receive\n     * @return all previous output frames (stdout/stderr being separated by newline characters)\n     */\n    @SneakyThrows(IOException.class)\n    public String getOutput(DockerClient dockerClient, String containerId, OutputFrame.OutputType... types) {\n        if (containerId == null) {\n            return \"\";\n        }\n\n        if (types.length == 0) {\n            types = new OutputFrame.OutputType[] { OutputFrame.OutputType.STDOUT, OutputFrame.OutputType.STDERR };\n        }\n\n        final ToStringConsumer consumer = new ToStringConsumer();\n        final WaitingConsumer wait = new WaitingConsumer();\n        try (Closeable closeable = attachConsumer(dockerClient, containerId, consumer.andThen(wait), false, types)) {\n            wait.waitUntilEnd();\n            return consumer.toUtf8String();\n        }\n    }\n\n    private static Closeable attachConsumer(\n        DockerClient dockerClient,\n        String containerId,\n        Consumer<OutputFrame> consumer,\n        boolean followStream,\n        OutputFrame.OutputType... types\n    ) {\n        final LogContainerCmd cmd = dockerClient\n            .logContainerCmd(containerId)\n            .withFollowStream(followStream)\n            .withSince(0);\n\n        final FrameConsumerResultCallback callback = new FrameConsumerResultCallback();\n        for (OutputFrame.OutputType type : types) {\n            callback.addConsumer(type, consumer);\n            if (type == OutputFrame.OutputType.STDOUT) {\n                cmd.withStdOut(true);\n            }\n            if (type == OutputFrame.OutputType.STDERR) {\n                cmd.withStdErr(true);\n            }\n        }\n\n        return cmd.exec(callback);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/utility/MountableFile.java",
    "content": "package org.testcontainers.utility;\n\nimport com.google.common.base.Charsets;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.RequiredArgsConstructor;\nimport lombok.SneakyThrows;\nimport lombok.extern.slf4j.Slf4j;\nimport org.apache.commons.compress.archivers.tar.TarArchiveEntry;\nimport org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;\nimport org.apache.commons.compress.archivers.tar.TarConstants;\nimport org.apache.commons.io.FileUtils;\nimport org.apache.commons.lang3.SystemUtils;\nimport org.jetbrains.annotations.NotNull;\nimport org.testcontainers.DockerClientFactory;\nimport org.testcontainers.UnstableAPI;\nimport org.testcontainers.images.builder.Transferable;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.UncheckedIOException;\nimport java.io.UnsupportedEncodingException;\nimport java.net.URL;\nimport java.net.URLDecoder;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.Enumeration;\nimport java.util.HashSet;\nimport java.util.Set;\nimport java.util.jar.JarEntry;\nimport java.util.jar.JarFile;\nimport java.util.stream.Stream;\nimport java.util.zip.Checksum;\n\n/**\n * An abstraction over files and classpath resources aimed at encapsulating all the complexity of generating\n * a path that the Docker daemon is about to create a volume mount for.\n */\n@RequiredArgsConstructor(access = AccessLevel.PACKAGE)\n@Slf4j\npublic class MountableFile implements Transferable {\n\n    private static final String TESTCONTAINERS_TMP_DIR_PREFIX = \".testcontainers-tmp-\";\n\n    private static final String OS_MAC_TMP_DIR = \"/tmp\";\n\n    private static final int BASE_FILE_MODE = 0100000;\n\n    private static final int BASE_DIR_MODE = 0040000;\n\n    private final String path;\n\n    private final Integer forcedFileMode;\n\n    @Getter(lazy = true)\n    private final String resolvedPath = resolvePath();\n\n    @Getter(lazy = true)\n    private final String filesystemPath = resolveFilesystemPath();\n\n    private String resourcePath;\n\n    /**\n     * Obtains a {@link MountableFile} corresponding to a resource on the classpath (including resources in JAR files)\n     *\n     * @param resourceName the classpath path to the resource\n     * @return a {@link MountableFile} that may be used to obtain a mountable path\n     */\n    public static MountableFile forClasspathResource(@NotNull final String resourceName) {\n        return forClasspathResource(resourceName, null);\n    }\n\n    /**\n     * Obtains a {@link MountableFile} corresponding to a file on the docker host filesystem.\n     *\n     * @param path the path to the resource\n     * @return a {@link MountableFile} that may be used to obtain a mountable path\n     */\n    public static MountableFile forHostPath(@NotNull final String path) {\n        return forHostPath(path, null);\n    }\n\n    /**\n     * Obtains a {@link MountableFile} corresponding to a file on the docker host filesystem.\n     *\n     * @param path the path to the resource\n     * @return a {@link MountableFile} that may be used to obtain a mountable path\n     */\n    public static MountableFile forHostPath(final Path path) {\n        return forHostPath(path, null);\n    }\n\n    /**\n     * Obtains a {@link MountableFile} corresponding to a resource on the classpath (including resources in JAR files)\n     *\n     * @param resourceName the classpath path to the resource\n     * @param mode octal value of posix file mode (000..777)\n     * @return a {@link MountableFile} that may be used to obtain a mountable path\n     */\n    public static MountableFile forClasspathResource(@NotNull final String resourceName, Integer mode) {\n        return new MountableFile(getClasspathResource(resourceName, new HashSet<>()).toString(), mode);\n    }\n\n    /**\n     * Obtains a {@link MountableFile} corresponding to a file on the docker host filesystem.\n     *\n     * @param path the path to the resource\n     * @param mode octal value of posix file mode (000..777)\n     * @return a {@link MountableFile} that may be used to obtain a mountable path\n     */\n    public static MountableFile forHostPath(@NotNull final String path, Integer mode) {\n        return forHostPath(Paths.get(path), mode);\n    }\n\n    /**\n     * Obtains a {@link MountableFile} corresponding to a file on the docker host filesystem.\n     *\n     * @param path the path to the resource\n     * @param mode octal value of posix file mode (000..777)\n     * @return a {@link MountableFile} that may be used to obtain a mountable path\n     */\n    public static MountableFile forHostPath(final Path path, Integer mode) {\n        return new MountableFile(path.toAbsolutePath().toString(), mode);\n    }\n\n    @NotNull\n    private static URL getClasspathResource(\n        @NotNull final String resourcePath,\n        @NotNull final Set<ClassLoader> classLoaders\n    ) {\n        final Set<ClassLoader> classLoadersToSearch = new HashSet<>(classLoaders);\n        // try context and system classloaders as well\n        classLoadersToSearch.add(Thread.currentThread().getContextClassLoader());\n        classLoadersToSearch.add(ClassLoader.getSystemClassLoader());\n        classLoadersToSearch.add(MountableFile.class.getClassLoader());\n\n        for (final ClassLoader classLoader : classLoadersToSearch) {\n            if (classLoader == null) {\n                continue;\n            }\n\n            URL resource = classLoader.getResource(resourcePath);\n            if (resource != null) {\n                return resource;\n            }\n\n            // Be lenient if an absolute path was given\n            if (resourcePath.startsWith(\"/\")) {\n                resource = classLoader.getResource(resourcePath.replaceFirst(\"/\", \"\"));\n                if (resource != null) {\n                    return resource;\n                }\n            }\n        }\n\n        throw new IllegalArgumentException(\n            \"Resource with path \" +\n            resourcePath +\n            \" could not be found on any of these classloaders: \" +\n            classLoadersToSearch\n        );\n    }\n\n    private static String unencodeResourceURIToFilePath(@NotNull final String resource) {\n        try {\n            // Convert any url-encoded characters (e.g. spaces) back into unencoded form\n            return URLDecoder\n                .decode(resource.replaceAll(\"\\\\+\", \"%2B\"), Charsets.UTF_8.name())\n                .replaceFirst(\"jar:\", \"\")\n                .replaceFirst(\"file:\", \"\")\n                .replaceAll(\"!.*\", \"\");\n        } catch (UnsupportedEncodingException e) {\n            throw new IllegalStateException(e);\n        }\n    }\n\n    /**\n     * Obtain a path that the Docker daemon should be able to use to volume mount a file/resource\n     * into a container. If this is a classpath resource residing in a JAR, it will be extracted to\n     * a temporary location so that the Docker daemon is able to access it.\n     *\n     * @return a volume-mountable path.\n     */\n    private String resolvePath() {\n        String result = getResourcePath();\n\n        // Special case for Windows\n        if (SystemUtils.IS_OS_WINDOWS && result.startsWith(\"/\")) {\n            // Remove leading /\n            result = result.substring(1);\n        }\n\n        return result;\n    }\n\n    /**\n     * Obtain a path in local filesystem that the Docker daemon should be able to use to volume mount a file/resource\n     * into a container. If this is a classpath resource residing in a JAR, it will be extracted to\n     * a temporary location so that the Docker daemon is able to access it.\n     *\n     * TODO: rename method accordingly and check if really needed like this\n     *\n     * @return\n     */\n    private String resolveFilesystemPath() {\n        String result = getResourcePath();\n\n        if (SystemUtils.IS_OS_WINDOWS && result.startsWith(\"/\")) {\n            result = PathUtils.createMinGWPath(result).substring(1);\n        }\n\n        return result;\n    }\n\n    private String getResourcePath() {\n        if (path.contains(\".jar!\")) {\n            resourcePath = extractClassPathResourceToTempLocation(this.path);\n        } else {\n            resourcePath = unencodeResourceURIToFilePath(path);\n        }\n        return resourcePath;\n    }\n\n    /**\n     * Extract a file or directory tree from a JAR file to a temporary location.\n     * This allows Docker to mount classpath resources as files.\n     *\n     * @param hostPath the path on the host, expected to be of the format 'file:/path/to/some.jar!/classpath/path/to/resource'\n     * @return the path of the temporary file/directory\n     */\n    private String extractClassPathResourceToTempLocation(final String hostPath) {\n        File tmpLocation = createTempDirectory();\n        //noinspection ResultOfMethodCallIgnored\n        tmpLocation.delete();\n\n        String urldecodedJarPath = unencodeResourceURIToFilePath(hostPath);\n        String internalPath = hostPath.replaceAll(\"[^!]*!/\", \"\");\n\n        try (JarFile jarFile = new JarFile(urldecodedJarPath)) {\n            Enumeration<JarEntry> entries = jarFile.entries();\n\n            while (entries.hasMoreElements()) {\n                JarEntry entry = entries.nextElement();\n                final String name = entry.getName();\n                if (name.startsWith(internalPath)) {\n                    log.debug(\n                        \"Copying classpath resource(s) from {} to {} to permit Docker to bind\",\n                        hostPath,\n                        tmpLocation\n                    );\n                    copyFromJarToLocation(jarFile, entry, internalPath, tmpLocation);\n                }\n            }\n        } catch (IOException e) {\n            throw new IllegalStateException(\n                \"Failed to process JAR file when extracting classpath resource: \" + hostPath,\n                e\n            );\n        }\n\n        // Mark temporary files/dirs for deletion at JVM shutdown\n        deleteOnExit(tmpLocation.toPath());\n\n        try {\n            return tmpLocation.getCanonicalPath();\n        } catch (IOException e) {\n            throw new IllegalStateException(e);\n        }\n    }\n\n    private File createTempDirectory() {\n        try {\n            if (SystemUtils.IS_OS_MAC) {\n                return Files.createTempDirectory(Paths.get(OS_MAC_TMP_DIR), TESTCONTAINERS_TMP_DIR_PREFIX).toFile();\n            }\n            return Files.createTempDirectory(TESTCONTAINERS_TMP_DIR_PREFIX).toFile();\n        } catch (IOException e) {\n            return new File(TESTCONTAINERS_TMP_DIR_PREFIX + Base58.randomString(5));\n        }\n    }\n\n    @SuppressWarnings(\"ResultOfMethodCallIgnored\")\n    private void copyFromJarToLocation(\n        final JarFile jarFile,\n        final JarEntry entry,\n        final String fromRoot,\n        final File toRoot\n    ) throws IOException {\n        String destinationName = entry.getName().replaceFirst(fromRoot, \"\");\n        File newFile = new File(toRoot, destinationName);\n\n        log.debug(\"Copying resource {} from JAR file {}\", fromRoot, jarFile.getName());\n\n        if (!entry.isDirectory()) {\n            // Create parent directories\n            Path parent = newFile.getAbsoluteFile().toPath().getParent();\n            parent.toFile().mkdirs();\n            newFile.deleteOnExit();\n\n            try (InputStream is = jarFile.getInputStream(entry)) {\n                Files.copy(is, newFile.toPath());\n            } catch (IOException e) {\n                log.error(\n                    \"Failed to extract classpath resource \" + entry.getName() + \" from JAR file \" + jarFile.getName(),\n                    e\n                );\n                throw e;\n            }\n        }\n    }\n\n    private void deleteOnExit(final Path path) {\n        Runtime\n            .getRuntime()\n            .addShutdownHook(\n                new Thread(DockerClientFactory.TESTCONTAINERS_THREAD_GROUP, () -> PathUtils.recursiveDeleteDir(path))\n            );\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    public void transferTo(final TarArchiveOutputStream outputStream, String destinationPathInTar) {\n        recursiveTar(destinationPathInTar, this.getResolvedPath(), this.getResolvedPath(), outputStream);\n    }\n\n    /*\n     * Recursively copies a file/directory into a TarArchiveOutputStream\n     */\n    private void recursiveTar(\n        String entryFilename,\n        String rootPath,\n        String itemPath,\n        TarArchiveOutputStream tarArchive\n    ) {\n        try {\n            final File sourceFile = new File(itemPath).getCanonicalFile(); // e.g. /foo/bar/baz\n            final File sourceRootFile = new File(rootPath).getCanonicalFile(); // e.g. /foo\n            final String relativePathToSourceFile = sourceRootFile\n                .toPath()\n                .relativize(sourceFile.toPath())\n                .toFile()\n                .toString(); // e.g. /bar/baz\n\n            final String tarEntryFilename;\n            if (relativePathToSourceFile.isEmpty()) {\n                tarEntryFilename = entryFilename; // entry filename e.g. xyz => xyz\n            } else {\n                tarEntryFilename = entryFilename + \"/\" + relativePathToSourceFile; // entry filename e.g. /xyz/bar/baz => /foo/bar/baz\n            }\n\n            final TarArchiveEntry tarEntry = new TarArchiveEntry(sourceFile, tarEntryFilename.replaceAll(\"^/\", \"\"));\n\n            // TarArchiveEntry automatically sets the mode for file/directory, but we can update to ensure that the mode is set exactly (inc executable bits)\n            tarEntry.setMode(getUnixFileMode(itemPath));\n            tarArchive.putArchiveEntry(tarEntry);\n\n            if (sourceFile.isFile()) {\n                Files.copy(sourceFile.toPath(), tarArchive);\n            }\n            // a directory entry merely needs to exist in the TAR file - there is no data stored yet\n            tarArchive.closeArchiveEntry();\n\n            final File[] children = sourceFile.listFiles();\n            if (children != null) {\n                // recurse into child files/directories\n                for (final File child : children) {\n                    recursiveTar(\n                        entryFilename,\n                        sourceRootFile.getCanonicalPath(),\n                        child.getCanonicalPath(),\n                        tarArchive\n                    );\n                }\n            }\n        } catch (IOException e) {\n            log.error(\"Error when copying TAR file entry: {}\", itemPath, e);\n            throw new UncheckedIOException(e); // fail fast\n        }\n    }\n\n    @Override\n    public long getSize() {\n        final File file = new File(this.getResolvedPath());\n        if (file.isFile()) {\n            return file.length();\n        } else {\n            return 0;\n        }\n    }\n\n    @Override\n    public String getDescription() {\n        return this.getResolvedPath();\n    }\n\n    @Override\n    public void updateChecksum(Checksum checksum) {\n        File file = new File(getResolvedPath());\n        checksumFile(file, checksum);\n    }\n\n    @SneakyThrows(IOException.class)\n    private void checksumFile(File file, Checksum checksum) {\n        Path path = file.toPath();\n        checksum.update(MountableFile.getUnixFileMode(path));\n        if (file.isDirectory()) {\n            try (Stream<Path> stream = Files.walk(path)) {\n                stream\n                    .filter(it -> it != path)\n                    .forEach(it -> {\n                        checksumFile(it.toFile(), checksum);\n                    });\n            }\n        } else {\n            FileUtils.checksum(file, checksum);\n        }\n    }\n\n    @Override\n    public int getFileMode() {\n        return getUnixFileMode(this.getResolvedPath());\n    }\n\n    private int getUnixFileMode(final String pathAsString) {\n        final Path path = Paths.get(pathAsString);\n        if (this.forcedFileMode != null) {\n            return this.getModeValue(path);\n        }\n        return getUnixFileMode(path);\n    }\n\n    @UnstableAPI\n    public static int getUnixFileMode(final Path path) {\n        try {\n            int unixMode = (int) Files.readAttributes(path, \"unix:mode\").get(\"mode\");\n            // Truncate mode bits for z/OS\n            if (\n                \"OS/390\".equals(SystemUtils.OS_NAME) ||\n                \"z/OS\".equals(SystemUtils.OS_NAME) ||\n                \"zOS\".equals(SystemUtils.OS_NAME)\n            ) {\n                unixMode &= TarConstants.MAXID;\n                unixMode |= Files.isDirectory(path) ? 040000 : 0100000;\n            }\n            return unixMode;\n        } catch (IOException | UnsupportedOperationException e) {\n            // fallback for non-posix environments\n            int mode = DEFAULT_FILE_MODE;\n            if (Files.isDirectory(path)) {\n                mode = DEFAULT_DIR_MODE;\n            } else if (Files.isExecutable(path)) {\n                mode |= 0111; // equiv to +x for user/group/others\n            }\n\n            return mode;\n        }\n    }\n\n    private int getModeValue(final Path path) {\n        int result = Files.isDirectory(path) ? BASE_DIR_MODE : BASE_FILE_MODE;\n        return result | this.forcedFileMode;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/utility/PathUtils.java",
    "content": "package org.testcontainers.utility;\n\nimport lombok.NonNull;\nimport lombok.experimental.UtilityClass;\n\nimport java.io.IOException;\nimport java.nio.file.FileVisitResult;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.SimpleFileVisitor;\nimport java.nio.file.attribute.BasicFileAttributes;\n\n/**\n * Filesystem operation utility methods.\n */\n@UtilityClass\npublic class PathUtils {\n\n    /**\n     * Recursively delete a directory and all its subdirectories and files.\n     *\n     * @param directory path to the directory to delete.\n     */\n    public static void recursiveDeleteDir(final @NonNull Path directory) {\n        try {\n            Files.walkFileTree(\n                directory,\n                new SimpleFileVisitor<Path>() {\n                    @Override\n                    public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {\n                        Files.delete(dir);\n                        return FileVisitResult.CONTINUE;\n                    }\n\n                    @Override\n                    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {\n                        Files.delete(file);\n                        return FileVisitResult.CONTINUE;\n                    }\n                }\n            );\n        } catch (IOException ignored) {}\n    }\n\n    /**\n     * Make a directory, plus any required parent directories.\n     *\n     * @param directory the directory path to make\n     */\n    public static void mkdirp(Path directory) {\n        boolean result = directory.toFile().mkdirs();\n        if (!result) {\n            throw new IllegalStateException(\"Failed to create directory at: \" + directory);\n        }\n    }\n\n    /**\n     * Create a MinGW compatible path based on usual Windows path\n     *\n     * @param path a usual windows path\n     * @return a MinGW compatible path\n     */\n    public static String createMinGWPath(String path) {\n        String mingwPath = path.replace('\\\\', '/');\n        int driveLetterIndex = 1;\n        if (mingwPath.matches(\"^[a-zA-Z]:\\\\/.*\")) {\n            driveLetterIndex = 0;\n        }\n\n        // drive-letter must be lower case\n        mingwPath =\n            \"//\" +\n            Character.toLowerCase(mingwPath.charAt(driveLetterIndex)) +\n            mingwPath.substring(driveLetterIndex + 1);\n        mingwPath = mingwPath.replace(\":\", \"\");\n        return mingwPath;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/utility/PrefixingImageNameSubstitutor.java",
    "content": "package org.testcontainers.utility;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport lombok.NoArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\n\n/**\n * An {@link ImageNameSubstitutor} which applies a prefix to all image names, e.g. a private registry host and path.\n * The prefix may be set via an environment variable (<code>TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX</code>) or an equivalent\n * configuration file entry (see {@link TestcontainersConfiguration}).\n */\n@NoArgsConstructor\n@Slf4j\nfinal class PrefixingImageNameSubstitutor extends ImageNameSubstitutor {\n\n    @VisibleForTesting\n    static final String PREFIX_PROPERTY_KEY = \"hub.image.name.prefix\";\n\n    private TestcontainersConfiguration configuration = TestcontainersConfiguration.getInstance();\n\n    @VisibleForTesting\n    PrefixingImageNameSubstitutor(final TestcontainersConfiguration configuration) {\n        this.configuration = configuration;\n    }\n\n    @Override\n    public DockerImageName apply(DockerImageName original) {\n        final String configuredPrefix = configuration.getEnvVarOrProperty(PREFIX_PROPERTY_KEY, \"\");\n\n        if (configuredPrefix.isEmpty()) {\n            log.debug(\"No prefix is configured\");\n            return original;\n        }\n\n        boolean isAHubImage = original.getRegistry().isEmpty();\n        if (!isAHubImage) {\n            log.debug(\"Image {} is not a Docker Hub image - not applying registry/repository change\", original);\n            return original;\n        }\n\n        log.debug(\"Applying changes to image name {}: applying prefix '{}'\", original, configuredPrefix);\n\n        DockerImageName prefixAsImage = DockerImageName.parse(configuredPrefix);\n\n        return original\n            .withRegistry(prefixAsImage.getRegistry())\n            .withRepository(prefixAsImage.getRepository() + original.getRepository());\n    }\n\n    @Override\n    protected String getDescription() {\n        return getClass().getSimpleName();\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/utility/RegistryAuthLocator.java",
    "content": "package org.testcontainers.utility;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.github.dockerjava.api.exception.NotFoundException;\nimport com.github.dockerjava.api.model.AuthConfig;\nimport com.google.common.annotations.VisibleForTesting;\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.commons.lang3.SystemUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.testcontainers.DockerClientFactory;\nimport org.zeroturnaround.exec.InvalidResultException;\nimport org.zeroturnaround.exec.ProcessExecutor;\nimport org.zeroturnaround.exec.ProcessResult;\nimport org.zeroturnaround.exec.stream.LogOutputStream;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.Base64;\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\n\n/**\n * Utility to look up registry authentication information for an image.\n */\npublic class RegistryAuthLocator {\n\n    private static final Logger log = LoggerFactory.getLogger(RegistryAuthLocator.class);\n\n    private static final String DEFAULT_REGISTRY_NAME = \"https://index.docker.io/v1/\";\n\n    private static final String DOCKER_AUTH_ENV_VAR = \"DOCKER_AUTH_CONFIG\";\n\n    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();\n\n    private static RegistryAuthLocator instance;\n\n    private final String commandPathPrefix;\n\n    private final String commandExtension;\n\n    private final File configFile;\n\n    private final String configEnv;\n\n    private final Map<String, Optional<AuthConfig>> cache = new ConcurrentHashMap<>();\n\n    /**\n     * key - credential helper's name\n     * value - helper's response for \"credentials not found\" use case\n     */\n    private final Map<String, String> CREDENTIALS_HELPERS_NOT_FOUND_MESSAGE_CACHE;\n\n    @VisibleForTesting\n    RegistryAuthLocator(\n        File configFile,\n        String configEnv,\n        String commandPathPrefix,\n        String commandExtension,\n        Map<String, String> notFoundMessageHolderReference\n    ) {\n        this.configFile = configFile;\n        this.configEnv = configEnv;\n        this.commandPathPrefix = commandPathPrefix;\n        this.commandExtension = commandExtension;\n\n        this.CREDENTIALS_HELPERS_NOT_FOUND_MESSAGE_CACHE = notFoundMessageHolderReference;\n    }\n\n    /**\n     */\n    protected RegistryAuthLocator() {\n        final String dockerConfigLocation = System\n            .getenv()\n            .getOrDefault(\"DOCKER_CONFIG\", System.getProperty(\"user.home\") + \"/.docker\");\n        this.configFile = new File(dockerConfigLocation + \"/config.json\");\n        this.configEnv = System.getenv(DOCKER_AUTH_ENV_VAR);\n        this.commandPathPrefix = \"\";\n        this.commandExtension = \"\";\n\n        this.CREDENTIALS_HELPERS_NOT_FOUND_MESSAGE_CACHE = new HashMap<>();\n    }\n\n    public static synchronized RegistryAuthLocator instance() {\n        if (instance == null) {\n            instance = new RegistryAuthLocator();\n        }\n\n        return instance;\n    }\n\n    @VisibleForTesting\n    static void setInstance(RegistryAuthLocator overrideInstance) {\n        instance = overrideInstance;\n    }\n\n    /**\n     * Looks up an AuthConfig for a given image name.\n     * <p>\n     * Lookup is performed in following order, as per\n     * https://docs.docker.com/engine/reference/commandline/cli/:\n     * <ol>\n     *     <li>{@code credHelpers}</li>\n     *     <li>{@code credsStore}</li>\n     *     <li>Hard-coded Base64 encoded auth in {@code auths}</li>\n     *     <li>otherwise, if no credentials have been found then behaviour falls back to docker-java's\n     *     implementation</li>\n     * </ol>\n     *\n     * @param dockerImageName image name to be looked up (potentially including a registry URL part)\n     * @param defaultAuthConfig an AuthConfig object that should be returned if there is no overriding authentication available for images that are looked up\n     * @return an AuthConfig that is applicable to this specific image OR the defaultAuthConfig.\n     */\n    public AuthConfig lookupAuthConfig(DockerImageName dockerImageName, AuthConfig defaultAuthConfig) {\n        final String registryName = effectiveRegistryName(dockerImageName);\n        log.debug(\"Looking up auth config for image: {} at registry: {}\", dockerImageName, registryName);\n\n        final Optional<AuthConfig> cachedAuth = cache.computeIfAbsent(\n            registryName,\n            __ -> lookupUncachedAuthConfig(registryName, dockerImageName)\n        );\n\n        if (cachedAuth.isPresent()) {\n            log.debug(\"Cached auth found: [{}]\", AuthConfigUtil.toSafeString(cachedAuth.get()));\n            return cachedAuth.get();\n        } else {\n            log.debug(\n                \"No matching Auth Configs - falling back to defaultAuthConfig [{}]\",\n                AuthConfigUtil.toSafeString(defaultAuthConfig)\n            );\n            // otherwise, defaultAuthConfig should already contain any credentials available\n            return defaultAuthConfig;\n        }\n    }\n\n    private Optional<AuthConfig> lookupUncachedAuthConfig(String registryName, DockerImageName dockerImageName) {\n        try {\n            final JsonNode config = getDockerAuthConfig();\n            log.debug(\"registryName [{}] for dockerImageName [{}]\", registryName, dockerImageName);\n\n            // use helper preferentially (per https://docs.docker.com/engine/reference/commandline/cli/)\n            final AuthConfig helperAuthConfig = authConfigUsingHelper(config, registryName);\n            if (helperAuthConfig != null) {\n                log.debug(\"found helper auth config [{}]\", AuthConfigUtil.toSafeString(helperAuthConfig));\n                return Optional.of(helperAuthConfig);\n            }\n            // no credsHelper to use, using credsStore:\n            final AuthConfig storeAuthConfig = authConfigUsingStore(config, registryName);\n            if (storeAuthConfig != null) {\n                log.debug(\"found creds store auth config [{}]\", AuthConfigUtil.toSafeString(storeAuthConfig));\n                return Optional.of(storeAuthConfig);\n            }\n            // fall back to base64 encoded auth hardcoded in config file\n            final AuthConfig existingAuthConfig = findExistingAuthConfig(config, registryName);\n            if (existingAuthConfig != null) {\n                log.debug(\"found existing auth config [{}]\", AuthConfigUtil.toSafeString(existingAuthConfig));\n                return Optional.of(existingAuthConfig);\n            }\n        } catch (Exception e) {\n            log.info(\n                \"Failure when attempting to lookup auth config. Please ignore if you don't have images in an authenticated registry. Details: (dockerImageName: {}, configFile: {}, configEnv: {}). Falling back to docker-java default behaviour. Exception message: {}\",\n                dockerImageName,\n                configFile,\n                DOCKER_AUTH_ENV_VAR,\n                e.getMessage()\n            );\n        }\n        return Optional.empty();\n    }\n\n    private JsonNode getDockerAuthConfig() throws Exception {\n        log.debug(\n            \"RegistryAuthLocator has configFile: {} ({}) configEnv: {} ({}) and commandPathPrefix: {}\",\n            configFile,\n            configFile.exists() ? \"exists\" : \"does not exist\",\n            DOCKER_AUTH_ENV_VAR,\n            configEnv != null ? \"exists\" : \"does not exist\",\n            commandPathPrefix\n        );\n\n        if (configEnv != null) {\n            log.debug(\"RegistryAuthLocator reading from environment variable: {}\", DOCKER_AUTH_ENV_VAR);\n            return OBJECT_MAPPER.readTree(configEnv);\n        } else if (configFile.exists()) {\n            log.debug(\"RegistryAuthLocator reading from configFile: {}\", configFile);\n            return OBJECT_MAPPER.readTree(configFile);\n        }\n\n        throw new NotFoundException(\n            \"No config supplied. Checked in order: \" +\n            configFile +\n            \" (file not found), \" +\n            DOCKER_AUTH_ENV_VAR +\n            \" (not set)\"\n        );\n    }\n\n    private AuthConfig findExistingAuthConfig(final JsonNode config, final String reposName) throws Exception {\n        final Map.Entry<String, JsonNode> entry = findAuthNode(config, reposName);\n\n        if (entry != null && entry.getValue() != null && entry.getValue().size() > 0) {\n            final AuthConfig deserializedAuth = OBJECT_MAPPER\n                .treeToValue(entry.getValue(), AuthConfig.class)\n                .withRegistryAddress(entry.getKey());\n\n            if (\n                StringUtils.isBlank(deserializedAuth.getUsername()) &&\n                StringUtils.isBlank(deserializedAuth.getPassword()) &&\n                !StringUtils.isBlank(deserializedAuth.getAuth())\n            ) {\n                final String rawAuth = new String(Base64.getDecoder().decode(deserializedAuth.getAuth()));\n                final String[] splitRawAuth = rawAuth.split(\":\", 2);\n\n                if (splitRawAuth.length == 2) {\n                    deserializedAuth.withUsername(splitRawAuth[0]);\n                    deserializedAuth.withPassword(splitRawAuth[1]);\n                }\n            }\n\n            return deserializedAuth;\n        }\n        return null;\n    }\n\n    private AuthConfig authConfigUsingHelper(final JsonNode config, final String reposName) throws Exception {\n        final JsonNode credHelpers = config.get(\"credHelpers\");\n        if (credHelpers != null && credHelpers.size() > 0) {\n            final JsonNode helperNode = credHelpers.get(reposName);\n            if (helperNode != null && helperNode.isTextual()) {\n                final String helper = helperNode.asText();\n                return runCredentialProvider(reposName, helper);\n            }\n        }\n        return null;\n    }\n\n    private AuthConfig authConfigUsingStore(final JsonNode config, final String reposName) throws Exception {\n        final JsonNode credsStoreNode = config.get(\"credsStore\");\n        if (credsStoreNode != null && !credsStoreNode.isMissingNode() && credsStoreNode.isTextual()) {\n            final String credsStore = credsStoreNode.asText();\n            if (StringUtils.isBlank(credsStore)) {\n                log.warn(\"Docker auth config credsStore field will be ignored, because value is blank\");\n                return null;\n            }\n            return runCredentialProvider(reposName, credsStore);\n        }\n        return null;\n    }\n\n    private Map.Entry<String, JsonNode> findAuthNode(final JsonNode config, final String reposName) {\n        final JsonNode auths = config.get(\"auths\");\n        if (auths != null && auths.size() > 0) {\n            final Iterator<Map.Entry<String, JsonNode>> fields = auths.fields();\n            while (fields.hasNext()) {\n                final Map.Entry<String, JsonNode> entry = fields.next();\n                if (entry.getKey().endsWith(\"://\" + reposName) || entry.getKey().equals(reposName)) {\n                    return entry;\n                }\n            }\n        }\n        return null;\n    }\n\n    private AuthConfig runCredentialProvider(String hostName, String helperOrStoreName) throws Exception {\n        if (StringUtils.isBlank(hostName)) {\n            log.debug(\"There is no point in locating AuthConfig for blank hostName. Returning NULL to allow fallback\");\n            return null;\n        }\n\n        final String credentialProgramName = getCredentialProgramName(helperOrStoreName);\n        final CredentialOutput data;\n\n        log.debug(\n            \"Executing docker credential provider: {} to locate auth config for: {}\",\n            credentialProgramName,\n            hostName\n        );\n\n        try {\n            data = runCredentialProgram(hostName, credentialProgramName);\n            if (data.getExitValue() == 1) {\n                final String responseErrorMsg = data.getStdout();\n\n                if (!StringUtils.isBlank(responseErrorMsg)) {\n                    String credentialsNotFoundMsg = getGenericCredentialsNotFoundMsg(\n                        responseErrorMsg,\n                        credentialProgramName\n                    );\n                    if (credentialsNotFoundMsg != null && credentialsNotFoundMsg.equals(responseErrorMsg)) {\n                        log.info(\n                            \"Credential helper/store ({}) does not have credentials for {}\",\n                            credentialProgramName,\n                            hostName\n                        );\n\n                        return null;\n                    }\n\n                    log.debug(\n                        \"Failure running docker credential helper/store ({}) with output '{}' and error '{}'\",\n                        credentialProgramName,\n                        responseErrorMsg,\n                        data.getStderr()\n                    );\n                } else {\n                    log.debug(\"Failure running docker credential helper/store ({})\", credentialProgramName);\n                }\n\n                throw new InvalidResultException(data.getStdout(), null);\n            }\n        } catch (Exception e) {\n            log.debug(\"Failure running docker credential helper/store ({})\", credentialProgramName);\n            throw e;\n        }\n\n        final JsonNode helperResponse = OBJECT_MAPPER.readTree(data.getStdout());\n        log.debug(\"Credential helper/store provided auth config for: {}\", hostName);\n\n        final String username = helperResponse.at(\"/Username\").asText();\n        final String password = helperResponse.at(\"/Secret\").asText();\n        final String serverUrl = helperResponse.at(\"/ServerURL\").asText();\n        final AuthConfig authConfig = new AuthConfig().withRegistryAddress(serverUrl.isEmpty() ? hostName : serverUrl);\n        if (\"<token>\".equals(username)) {\n            return authConfig.withIdentityToken(password);\n        } else {\n            return authConfig.withUsername(username).withPassword(password);\n        }\n    }\n\n    private String getCredentialProgramName(String credHelper) {\n        return commandPathPrefix + \"docker-credential-\" + credHelper + commandExtension;\n    }\n\n    private String effectiveRegistryName(DockerImageName dockerImageName) {\n        final String registry = dockerImageName.getRegistry();\n        if (!StringUtils.isEmpty(registry)) {\n            return registry;\n        }\n        return StringUtils.defaultString(\n            DockerClientFactory.instance().getInfo().getIndexServerAddress(),\n            DEFAULT_REGISTRY_NAME\n        );\n    }\n\n    private String getGenericCredentialsNotFoundMsg(String credentialsNotFoundMsg, String credentialHelperName) {\n        if (!CREDENTIALS_HELPERS_NOT_FOUND_MESSAGE_CACHE.containsKey(credentialHelperName)) {\n            CREDENTIALS_HELPERS_NOT_FOUND_MESSAGE_CACHE.put(credentialHelperName, credentialsNotFoundMsg);\n        }\n        return CREDENTIALS_HELPERS_NOT_FOUND_MESSAGE_CACHE.get(credentialHelperName);\n    }\n\n    private CredentialOutput runCredentialProgram(String hostName, String credentialHelperName)\n        throws InterruptedException, TimeoutException, IOException {\n        String[] command = SystemUtils.IS_OS_WINDOWS\n            ? new String[] { \"cmd\", \"/c\", credentialHelperName, \"get\" }\n            : new String[] { credentialHelperName, \"get\" };\n\n        StringBuffer stdout = new StringBuffer();\n        StringBuffer stderr = new StringBuffer();\n\n        ProcessResult processResult = new ProcessExecutor()\n            .command(command)\n            .redirectInput(new ByteArrayInputStream(hostName.getBytes()))\n            .redirectOutput(\n                new LogOutputStream() {\n                    @Override\n                    protected void processLine(String line) {\n                        stdout.append(line).append(System.lineSeparator());\n                    }\n                }\n            )\n            .redirectError(\n                new LogOutputStream() {\n                    @Override\n                    protected void processLine(String line) {\n                        stderr.append(line).append(System.lineSeparator());\n                    }\n                }\n            )\n            .timeout(30, TimeUnit.SECONDS)\n            .execute();\n        int exitValue = processResult.getExitValue();\n\n        return new CredentialOutput(exitValue, stdout.toString(), stderr.toString());\n    }\n\n    static class CredentialOutput {\n\n        private final int exitValue;\n\n        private final String stdout;\n\n        private final String stderr;\n\n        public CredentialOutput(int exitValue, String stdout, String stderr) {\n            this.exitValue = exitValue;\n            this.stdout = stdout.trim();\n            this.stderr = stderr.trim();\n        }\n\n        int getExitValue() {\n            return this.exitValue;\n        }\n\n        String getStdout() {\n            return this.stdout;\n        }\n\n        String getStderr() {\n            return this.stderr;\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/utility/ResourceReaper.java",
    "content": "package org.testcontainers.utility;\n\nimport com.github.dockerjava.api.DockerClient;\nimport com.github.dockerjava.api.command.CreateContainerCmd;\nimport com.github.dockerjava.api.command.InspectContainerResponse;\nimport com.github.dockerjava.api.exception.NotFoundException;\nimport com.github.dockerjava.api.model.Network;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Throwables;\nimport com.google.common.collect.Sets;\nimport lombok.extern.slf4j.Slf4j;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.testcontainers.DockerClientFactory;\nimport org.testcontainers.containers.GenericContainer;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.io.OutputStream;\nimport java.io.UnsupportedEncodingException;\nimport java.net.URLEncoder;\nimport java.util.AbstractMap.SimpleEntry;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\n/**\n * Component that responsible for container removal and automatic cleanup of dead containers at JVM shutdown.\n */\n@Slf4j\npublic class ResourceReaper {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(ResourceReaper.class);\n\n    private static final Map<String, String> MARKER_LABELS = Collections.singletonMap(\n        DockerClientFactory.TESTCONTAINERS_SESSION_ID_LABEL,\n        DockerClientFactory.SESSION_ID\n    );\n\n    static final List<List<Map.Entry<String, String>>> DEATH_NOTE = new ArrayList<>(\n        Arrays.asList(\n            Stream\n                .concat(DockerClientFactory.DEFAULT_LABELS.entrySet().stream(), MARKER_LABELS.entrySet().stream())\n                .<Map.Entry<String, String>>map(it -> new SimpleEntry<>(\"label\", it.getKey() + \"=\" + it.getValue()))\n                .collect(Collectors.toList())\n        )\n    );\n\n    private static ResourceReaper instance;\n\n    final DockerClient dockerClient = DockerClientFactory.lazyClient();\n\n    private Map<String, String> registeredContainers = new ConcurrentHashMap<>();\n\n    private Set<String> registeredNetworks = Sets.newConcurrentHashSet();\n\n    private Set<String> registeredImages = Sets.newConcurrentHashSet();\n\n    private AtomicBoolean hookIsSet = new AtomicBoolean(false);\n\n    /**\n     * Internal constructor to avoid custom implementations\n     */\n    ResourceReaper() {}\n\n    public static synchronized ResourceReaper instance() {\n        if (instance == null) {\n            boolean useRyuk = !Boolean.parseBoolean(System.getenv(\"TESTCONTAINERS_RYUK_DISABLED\"));\n            if (useRyuk) {\n                //noinspection deprecation\n                instance = new RyukResourceReaper();\n            } else {\n                String ryukDisabledMessage =\n                    \"\\n\" +\n                    \"********************************************************************************\" +\n                    \"\\n\" +\n                    \"Ryuk has been disabled. This can cause unexpected behavior in your environment.\" +\n                    \"\\n\" +\n                    \"********************************************************************************\";\n                LOGGER.warn(ryukDisabledMessage);\n\n                instance = new JVMHookResourceReaper();\n            }\n        }\n\n        return instance;\n    }\n\n    /**\n     * Perform a cleanup.\n     * @deprecated no longer supported API, use {@link DockerClient} directly\n     */\n    @Deprecated\n    public void performCleanup() {\n        registeredContainers.forEach(this::removeContainer);\n        registeredNetworks.forEach(this::removeNetwork);\n        registeredImages.forEach(this::removeImage);\n    }\n\n    /**\n     * Register a filter to be cleaned up.\n     *\n     * @param filter the filter\n     * @deprecated only label filter is supported by the prune API, use {@link #registerLabelsFilterForCleanup(Map)}\n     */\n    @Deprecated\n    public void registerFilterForCleanup(List<Map.Entry<String, String>> filter) {\n        synchronized (DEATH_NOTE) {\n            DEATH_NOTE.add(filter);\n            DEATH_NOTE.notifyAll();\n        }\n    }\n\n    /**\n     * Register a label to be cleaned up.\n     *\n     * @param labels the filter\n     */\n    public void registerLabelsFilterForCleanup(Map<String, String> labels) {\n        registerFilterForCleanup(\n            labels\n                .entrySet()\n                .stream()\n                .map(it -> new SimpleEntry<>(\"label\", it.getKey() + \"=\" + it.getValue()))\n                .collect(Collectors.toList())\n        );\n    }\n\n    /**\n     * Register a container to be cleaned up, either on explicit call to stopAndRemoveContainer, or at JVM shutdown.\n     *\n     * @param containerId the ID of the container\n     * @param imageName   the image name of the container (used for logging)\n     * @deprecated no longer supported API\n     */\n    @Deprecated\n    public void registerContainerForCleanup(String containerId, String imageName) {\n        setHook();\n        registeredContainers.put(containerId, imageName);\n    }\n\n    /**\n     * Stop a potentially running container and remove it, including associated volumes.\n     *\n     * @param containerId the ID of the container\n     * @deprecated use {@link DockerClient} directly\n     */\n    @Deprecated\n    public void stopAndRemoveContainer(String containerId) {\n        removeContainer(containerId, registeredContainers.get(containerId));\n\n        registeredContainers.remove(containerId);\n    }\n\n    /**\n     * Stop a potentially running container and remove it, including associated volumes.\n     *\n     * @param containerId the ID of the container\n     * @param imageName   the image name of the container (used for logging)\n     * @deprecated use {@link DockerClient} directly\n     */\n    @Deprecated\n    public void stopAndRemoveContainer(String containerId, String imageName) {\n        removeContainer(containerId, imageName);\n\n        registeredContainers.remove(containerId);\n    }\n\n    private void removeContainer(String containerId, String imageName) {\n        boolean running;\n        try {\n            InspectContainerResponse containerInfo = dockerClient.inspectContainerCmd(containerId).exec();\n            running = containerInfo.getState() != null && Boolean.TRUE.equals(containerInfo.getState().getRunning());\n        } catch (NotFoundException e) {\n            LOGGER.trace(\"Was going to stop container but it apparently no longer exists: {}\", containerId);\n            return;\n        } catch (Exception e) {\n            LOGGER.trace(\n                \"Error encountered when checking container for shutdown (ID: {}) - it may not have been stopped, or may already be stopped. Root cause: {}\",\n                containerId,\n                Throwables.getRootCause(e).getMessage()\n            );\n            return;\n        }\n\n        if (running) {\n            try {\n                LOGGER.trace(\"Stopping container: {}\", containerId);\n                dockerClient.killContainerCmd(containerId).exec();\n                LOGGER.trace(\"Stopped container: {}\", imageName);\n            } catch (Exception e) {\n                LOGGER.trace(\n                    \"Error encountered shutting down container (ID: {}) - it may not have been stopped, or may already be stopped. Root cause: {}\",\n                    containerId,\n                    Throwables.getRootCause(e).getMessage()\n                );\n            }\n        }\n\n        try {\n            dockerClient.inspectContainerCmd(containerId).exec();\n        } catch (Exception e) {\n            LOGGER.trace(\"Was going to remove container but it apparently no longer exists: {}\", containerId);\n            return;\n        }\n\n        try {\n            LOGGER.trace(\"Removing container: {}\", containerId);\n            dockerClient.removeContainerCmd(containerId).withRemoveVolumes(true).withForce(true).exec();\n            LOGGER.debug(\"Removed container and associated volume(s): {}\", imageName);\n        } catch (Exception e) {\n            LOGGER.trace(\n                \"Error encountered shutting down container (ID: {}) - it may not have been stopped, or may already be stopped. Root cause: {}\",\n                containerId,\n                Throwables.getRootCause(e).getMessage()\n            );\n        }\n    }\n\n    /**\n     * Register a network to be cleaned up at JVM shutdown.\n     *\n     * @param id   the ID of the network\n     * @deprecated no longer supported API\n     */\n    @Deprecated\n    public void registerNetworkIdForCleanup(String id) {\n        setHook();\n        registeredNetworks.add(id);\n    }\n\n    /**\n     * Removes a network by ID.\n     * @param id\n     * @deprecated use {@link DockerClient} directly\n     */\n    @Deprecated\n    public void removeNetworkById(String id) {\n        removeNetwork(id);\n    }\n\n    private void removeNetwork(String id) {\n        try {\n            List<Network> networks;\n            try {\n                // Try to find the network if it still exists\n                // Listing by ID first prevents docker-java logging an error if we just go blindly into removeNetworkCmd\n                networks = dockerClient.listNetworksCmd().withIdFilter(id).exec();\n            } catch (Exception e) {\n                LOGGER.trace(\n                    \"Error encountered when looking up network for removal (name: {}) - it may not have been removed\",\n                    id\n                );\n                return;\n            }\n\n            // at this point networks should contain either 0 or 1 entries, depending on whether the network exists\n            // using a for loop we essentially treat the network like an optional, only applying the removal if it exists\n            for (Network network : networks) {\n                try {\n                    dockerClient.removeNetworkCmd(network.getId()).exec();\n                    registeredNetworks.remove(network.getId());\n                    LOGGER.debug(\"Removed network: {}\", id);\n                } catch (Exception e) {\n                    LOGGER.trace(\n                        \"Error encountered removing network (name: {}) - it may not have been removed\",\n                        network.getName()\n                    );\n                }\n            }\n        } finally {\n            registeredNetworks.remove(id);\n        }\n    }\n\n    /**\n     * @deprecated no longer supported API\n     */\n    @Deprecated\n    public void unregisterNetwork(String identifier) {\n        registeredNetworks.remove(identifier);\n    }\n\n    /**\n     * @deprecated no longer supported API\n     */\n    @Deprecated\n    public void unregisterContainer(String identifier) {\n        registeredContainers.remove(identifier);\n    }\n\n    /**\n     * @deprecated no longer supported API\n     */\n    @Deprecated\n    public void registerImageForCleanup(String dockerImageName) {\n        setHook();\n        registeredImages.add(dockerImageName);\n    }\n\n    private void removeImage(String dockerImageName) {\n        LOGGER.trace(\"Removing image tagged {}\", dockerImageName);\n        try {\n            dockerClient.removeImageCmd(dockerImageName).withForce(true).exec();\n        } catch (Throwable e) {\n            LOGGER.warn(\"Unable to delete image \" + dockerImageName, e);\n        }\n    }\n\n    void setHook() {\n        if (hookIsSet.compareAndSet(false, true)) {\n            // If the JVM stops without containers being stopped, try and stop the container.\n            Runtime\n                .getRuntime()\n                .addShutdownHook(new Thread(DockerClientFactory.TESTCONTAINERS_THREAD_GROUP, this::performCleanup));\n        }\n    }\n\n    /**\n     *\n     * @deprecated internal API\n     */\n    @Deprecated\n    public Map<String, String> getLabels() {\n        return MARKER_LABELS;\n    }\n\n    /**\n     *\n     * @deprecated internal API\n     */\n    @Deprecated\n    public CreateContainerCmd register(GenericContainer<?> container, CreateContainerCmd cmd) {\n        cmd.getLabels().putAll(getLabels());\n        return cmd;\n    }\n\n    /**\n     * @deprecated internal API\n     */\n    @Deprecated\n    public void init() {}\n\n    static class FilterRegistry {\n\n        @VisibleForTesting\n        static final String ACKNOWLEDGMENT = \"ACK\";\n\n        private final BufferedReader in;\n\n        private final OutputStream out;\n\n        FilterRegistry(InputStream ryukInputStream, OutputStream ryukOutputStream) {\n            this.in = new BufferedReader(new InputStreamReader(ryukInputStream));\n            this.out = ryukOutputStream;\n        }\n\n        /**\n         * Registers the given filters with Ryuk\n         *\n         * @param filters the filter to register\n         * @return true if the filters have been registered successfully, false otherwise\n         * @throws IOException if communication with Ryuk fails\n         */\n        protected boolean register(List<Map.Entry<String, String>> filters) throws IOException {\n            String query = filters\n                .stream()\n                .map(it -> {\n                    try {\n                        return (\n                            URLEncoder.encode(it.getKey(), \"UTF-8\") + \"=\" + URLEncoder.encode(it.getValue(), \"UTF-8\")\n                        );\n                    } catch (UnsupportedEncodingException e) {\n                        throw new RuntimeException(e);\n                    }\n                })\n                .collect(Collectors.joining(\"&\"));\n\n            log.debug(\"Sending '{}' to Ryuk\", query);\n            out.write(query.getBytes());\n            out.write('\\n');\n            out.flush();\n\n            return waitForAcknowledgment(in);\n        }\n\n        private static boolean waitForAcknowledgment(BufferedReader in) throws IOException {\n            String line = in.readLine();\n            while (line != null && !ACKNOWLEDGMENT.equalsIgnoreCase(line)) {\n                line = in.readLine();\n            }\n            return ACKNOWLEDGMENT.equalsIgnoreCase(line);\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/utility/RyukContainer.java",
    "content": "package org.testcontainers.utility;\n\nimport com.github.dockerjava.api.model.Bind;\nimport com.github.dockerjava.api.model.Volume;\nimport org.testcontainers.DockerClientFactory;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\n\nclass RyukContainer extends GenericContainer<RyukContainer> {\n\n    RyukContainer() {\n        super(\"testcontainers/ryuk:0.14.0\");\n        withExposedPorts(8080);\n        withCreateContainerCmdModifier(cmd -> {\n            cmd.withName(\"testcontainers-ryuk-\" + DockerClientFactory.SESSION_ID);\n            cmd.withHostConfig(\n                cmd\n                    .getHostConfig()\n                    .withAutoRemove(true)\n                    .withPrivileged(TestcontainersConfiguration.getInstance().isRyukPrivileged())\n                    .withBinds(\n                        new Bind(\n                            DockerClientFactory.instance().getRemoteDockerUnixSocketPath(),\n                            new Volume(\"/var/run/docker.sock\")\n                        )\n                    )\n            );\n        });\n\n        waitingFor(Wait.forLogMessage(\".*Started.*\", 1));\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/utility/RyukResourceReaper.java",
    "content": "package org.testcontainers.utility;\n\nimport com.github.dockerjava.api.command.CreateContainerCmd;\nimport lombok.SneakyThrows;\nimport lombok.extern.slf4j.Slf4j;\nimport org.rnorth.ducttape.ratelimits.RateLimiter;\nimport org.rnorth.ducttape.ratelimits.RateLimiterBuilder;\nimport org.testcontainers.DockerClientFactory;\nimport org.testcontainers.containers.GenericContainer;\n\nimport java.io.IOException;\nimport java.net.InetSocketAddress;\nimport java.net.Socket;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\n/**\n * Ryuk-based {@link ResourceReaper} implementation.\n *\n * @see <a href=\"https://github.com/testcontainers/moby-ryuk\">moby-ryuk</a>\n */\n@Slf4j\nclass RyukResourceReaper extends ResourceReaper {\n\n    private static final RateLimiter RYUK_ACK_RATE_LIMITER = RateLimiterBuilder\n        .newBuilder()\n        .withRate(4, TimeUnit.SECONDS)\n        .withConstantThroughput()\n        .build();\n\n    private final AtomicBoolean started = new AtomicBoolean(false);\n\n    private final RyukContainer ryukContainer = new RyukContainer();\n\n    @Override\n    public void init() {\n        if (!TestcontainersConfiguration.getInstance().environmentSupportsReuse()) {\n            log.debug(\"Ryuk is enabled\");\n            maybeStart();\n            log.info(\"Ryuk started - will monitor and terminate Testcontainers containers on JVM exit\");\n        } else {\n            log.debug(\"Ryuk is enabled but will be started on demand\");\n        }\n    }\n\n    @Override\n    public void registerLabelsFilterForCleanup(Map<String, String> labels) {\n        maybeStart();\n        super.registerLabelsFilterForCleanup(labels);\n    }\n\n    @Override\n    public Map<String, String> getLabels() {\n        maybeStart();\n        return super.getLabels();\n    }\n\n    @Override\n    public CreateContainerCmd register(GenericContainer<?> container, CreateContainerCmd cmd) {\n        if (container == ryukContainer) {\n            // Do not register Ryuk container to avoid self-pruning\n            return cmd;\n        }\n\n        maybeStart();\n        return super.register(container, cmd);\n    }\n\n    @SneakyThrows(InterruptedException.class)\n    private synchronized void maybeStart() {\n        if (!started.compareAndSet(false, true)) {\n            return;\n        }\n\n        ryukContainer.start();\n\n        CountDownLatch ryukScheduledLatch = new CountDownLatch(1);\n\n        String host = ryukContainer.getHost();\n        Integer ryukPort = ryukContainer.getFirstMappedPort();\n        Thread kiraThread = new Thread(\n            DockerClientFactory.TESTCONTAINERS_THREAD_GROUP,\n            () -> {\n                while (true) {\n                    RYUK_ACK_RATE_LIMITER.doWhenReady(() -> {\n                        int index = 0;\n                        // not set the read timeout, as Ryuk would not send anything unless a new filter is submitted, meaning that we would get a timeout exception pretty quick\n                        try (Socket clientSocket = new Socket()) {\n                            clientSocket.connect(new InetSocketAddress(host, ryukPort), 5 * 1000);\n                            ResourceReaper.FilterRegistry registry = new ResourceReaper.FilterRegistry(\n                                clientSocket.getInputStream(),\n                                clientSocket.getOutputStream()\n                            );\n\n                            synchronized (ResourceReaper.DEATH_NOTE) {\n                                while (true) {\n                                    if (ResourceReaper.DEATH_NOTE.size() <= index) {\n                                        try {\n                                            ResourceReaper.DEATH_NOTE.wait(1_000);\n                                            continue;\n                                        } catch (InterruptedException e) {\n                                            throw new RuntimeException(e);\n                                        }\n                                    }\n                                    List<Map.Entry<String, String>> filters = ResourceReaper.DEATH_NOTE.get(index);\n                                    boolean isAcknowledged = registry.register(filters);\n                                    if (isAcknowledged) {\n                                        log.debug(\"Received 'ACK' from Ryuk\");\n                                        ryukScheduledLatch.countDown();\n                                        index++;\n                                    } else {\n                                        log.debug(\"Didn't receive 'ACK' from Ryuk. Will retry to send filters.\");\n                                    }\n                                }\n                            }\n                        } catch (IOException e) {\n                            log.warn(\"Can not connect to Ryuk at {}:{}\", host, ryukPort, e);\n                        }\n                    });\n                }\n            },\n            \"testcontainers-ryuk\"\n        );\n        kiraThread.setDaemon(true);\n        kiraThread.start();\n        // We need to wait before we can start any containers to make sure that we delete them\n        if (!ryukScheduledLatch.await(TestcontainersConfiguration.getInstance().getRyukTimeout(), TimeUnit.SECONDS)) {\n            log.error(\"Timed out waiting for Ryuk container to start. Ryuk's logs:\\n{}\", ryukContainer.getLogs());\n            throw new IllegalStateException(String.format(\"Could not connect to Ryuk at %s:%s\", host, ryukPort));\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/utility/TestEnvironment.java",
    "content": "package org.testcontainers.utility;\n\nimport org.testcontainers.DockerClientFactory;\nimport org.testcontainers.dockerclient.DockerMachineClientProviderStrategy;\n\n/**\n * Provides utility methods for determining facts about the test environment.\n */\npublic class TestEnvironment {\n\n    private TestEnvironment() {}\n\n    public static boolean dockerApiAtLeast(String minimumVersion) {\n        ComparableVersion min = new ComparableVersion(minimumVersion);\n        ComparableVersion current = new ComparableVersion(DockerClientFactory.instance().getActiveApiVersion());\n\n        return current.compareTo(min) >= 0;\n    }\n\n    public static boolean dockerExecutionDriverSupportsExec() {\n        String executionDriver = DockerClientFactory.instance().getActiveExecutionDriver();\n\n        // Could be null starting from Docker 1.13\n        return executionDriver == null || !executionDriver.startsWith(\"lxc\");\n    }\n\n    public static boolean dockerIsDockerMachine() {\n        return DockerClientFactory.instance().isUsing(DockerMachineClientProviderStrategy.class);\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/utility/TestcontainersConfiguration.java",
    "content": "package org.testcontainers.utility;\n\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.collect.ImmutableMap;\nimport lombok.Data;\nimport lombok.Getter;\nimport lombok.NonNull;\nimport lombok.SneakyThrows;\nimport lombok.Synchronized;\nimport lombok.extern.slf4j.Slf4j;\nimport org.apache.commons.lang3.exception.ExceptionUtils;\nimport org.jetbrains.annotations.Contract;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\nimport org.testcontainers.UnstableAPI;\n\nimport java.io.File;\nimport java.io.FileNotFoundException;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Properties;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.stream.Stream;\n\n/**\n * Provides a mechanism for fetching configuration/default settings.\n * <p>\n * Configuration may be provided in:\n * <ul>\n *     <li>A file in the user's home directory named <code>.testcontainers.properties</code></li>\n *     <li>A file in the classpath named <code>testcontainers.properties</code></li>\n *     <li>Environment variables</li>\n * </ul>\n * <p>\n * Note that, if using environment variables, property names are in upper case separated by underscores, preceded by\n * <code>TESTCONTAINERS_</code>.\n */\n@Data\n@Slf4j\npublic class TestcontainersConfiguration {\n\n    private static String PROPERTIES_FILE_NAME = \"testcontainers.properties\";\n\n    private static File USER_CONFIG_FILE = new File(System.getProperty(\"user.home\"), \".\" + PROPERTIES_FILE_NAME);\n\n    private static final String AMBASSADOR_IMAGE = \"richnorth/ambassador\";\n\n    private static final String SOCAT_IMAGE = \"alpine/socat\";\n\n    private static final String VNC_RECORDER_IMAGE = \"testcontainers/vnc-recorder\";\n\n    private static final String COMPOSE_IMAGE = \"docker/compose\";\n\n    private static final String ALPINE_IMAGE = \"alpine\";\n\n    private static final String RYUK_IMAGE = \"testcontainers/ryuk\";\n\n    private static final String KAFKA_IMAGE = \"confluentinc/cp-kafka\";\n\n    private static final String PULSAR_IMAGE = \"apachepulsar/pulsar\";\n\n    private static final String LOCALSTACK_IMAGE = \"localstack/localstack\";\n\n    private static final String SSHD_IMAGE = \"testcontainers/sshd\";\n\n    private static final String ORACLE_IMAGE = \"gvenzl/oracle-xe\";\n\n    private static final ImmutableMap<DockerImageName, String> CONTAINER_MAPPING = ImmutableMap\n        .<DockerImageName, String>builder()\n        .put(DockerImageName.parse(AMBASSADOR_IMAGE), \"ambassador.container.image\")\n        .put(DockerImageName.parse(SOCAT_IMAGE), \"socat.container.image\")\n        .put(DockerImageName.parse(VNC_RECORDER_IMAGE), \"vncrecorder.container.image\")\n        .put(DockerImageName.parse(COMPOSE_IMAGE), \"compose.container.image\")\n        .put(DockerImageName.parse(ALPINE_IMAGE), \"tinyimage.container.image\")\n        .put(DockerImageName.parse(RYUK_IMAGE), \"ryuk.container.image\")\n        .put(DockerImageName.parse(KAFKA_IMAGE), \"kafka.container.image\")\n        .put(DockerImageName.parse(PULSAR_IMAGE), \"pulsar.container.image\")\n        .put(DockerImageName.parse(LOCALSTACK_IMAGE), \"localstack.container.image\")\n        .put(DockerImageName.parse(SSHD_IMAGE), \"sshd.container.image\")\n        .put(DockerImageName.parse(ORACLE_IMAGE), \"oracle.container.image\")\n        .build();\n\n    @Getter(lazy = true)\n    private static final TestcontainersConfiguration instance = loadConfiguration();\n\n    @SuppressWarnings({ \"ConstantConditions\", \"unchecked\", \"rawtypes\" })\n    @VisibleForTesting\n    static AtomicReference<TestcontainersConfiguration> getInstanceField() {\n        // Lazy Getter from Lombok changes the field's type to AtomicReference\n        return (AtomicReference) (Object) instance;\n    }\n\n    private final Properties userProperties;\n\n    private final Properties classpathProperties;\n\n    private final Map<String, String> environment;\n\n    TestcontainersConfiguration(\n        Properties userProperties,\n        Properties classpathProperties,\n        final Map<String, String> environment\n    ) {\n        this.userProperties = userProperties;\n        this.classpathProperties = classpathProperties;\n        this.environment = environment;\n    }\n\n    @Deprecated\n    public String getAmbassadorContainerImage() {\n        return getImage(AMBASSADOR_IMAGE).asCanonicalNameString();\n    }\n\n    @Deprecated\n    public String getSocatContainerImage() {\n        return getImage(SOCAT_IMAGE).asCanonicalNameString();\n    }\n\n    @Deprecated\n    public String getVncRecordedContainerImage() {\n        return getImage(VNC_RECORDER_IMAGE).asCanonicalNameString();\n    }\n\n    @Deprecated\n    public String getDockerComposeContainerImage() {\n        return getImage(COMPOSE_IMAGE).asCanonicalNameString();\n    }\n\n    @Deprecated\n    public String getTinyImage() {\n        return getImage(ALPINE_IMAGE).asCanonicalNameString();\n    }\n\n    public boolean isRyukPrivileged() {\n        return Boolean.parseBoolean(getEnvVarOrProperty(\"ryuk.container.privileged\", \"true\"));\n    }\n\n    @Deprecated\n    public String getRyukImage() {\n        return getImage(RYUK_IMAGE).asCanonicalNameString();\n    }\n\n    @Deprecated\n    public String getSSHdImage() {\n        return getImage(SSHD_IMAGE).asCanonicalNameString();\n    }\n\n    public Integer getRyukTimeout() {\n        return Integer.parseInt(getEnvVarOrProperty(\"ryuk.container.timeout\", \"30\"));\n    }\n\n    @Deprecated\n    public String getKafkaImage() {\n        return getImage(KAFKA_IMAGE).asCanonicalNameString();\n    }\n\n    @Deprecated\n    public String getOracleImage() {\n        return getImage(ORACLE_IMAGE).asCanonicalNameString();\n    }\n\n    @Deprecated\n    public String getPulsarImage() {\n        return getImage(PULSAR_IMAGE).asCanonicalNameString();\n    }\n\n    @Deprecated\n    public String getLocalStackImage() {\n        return getImage(LOCALSTACK_IMAGE).asCanonicalNameString();\n    }\n\n    public boolean isDisableChecks() {\n        return Boolean.parseBoolean(getEnvVarOrUserProperty(\"checks.disable\", \"false\"));\n    }\n\n    @UnstableAPI\n    public boolean environmentSupportsReuse() {\n        return Boolean.parseBoolean(getEnvVarOrUserProperty(\"testcontainers.reuse.enable\", \"false\"));\n    }\n\n    public String getDockerClientStrategyClassName() {\n        // getConfigurable won't apply the TESTCONTAINERS_ prefix when looking for env vars if DOCKER_ appears at the beginning.\n        // Because of this overlap, and the desire to not change this specific TESTCONTAINERS_DOCKER_CLIENT_STRATEGY setting,\n        // we special-case the logic here so that docker.client.strategy is used when reading properties files and\n        // TESTCONTAINERS_DOCKER_CLIENT_STRATEGY is used when searching environment variables.\n\n        // looks for TESTCONTAINERS_ prefixed env var only\n        String prefixedEnvVarStrategy = environment.get(\"TESTCONTAINERS_DOCKER_CLIENT_STRATEGY\");\n        if (prefixedEnvVarStrategy != null) {\n            return prefixedEnvVarStrategy;\n        }\n\n        // looks for unprefixed env var or unprefixed property, or null if the strategy is not set at all\n        return getEnvVarOrUserProperty(\"docker.client.strategy\", null);\n    }\n\n    public String getTransportType() {\n        return getEnvVarOrProperty(\"transport.type\", \"httpclient5\");\n    }\n\n    public Integer getImagePullPauseTimeout() {\n        return Integer.parseInt(getEnvVarOrProperty(\"pull.pause.timeout\", \"30\"));\n    }\n\n    public Integer getImagePullTimeout() {\n        return Integer.parseInt(getEnvVarOrProperty(\"pull.timeout\", \"120\"));\n    }\n\n    public String getImageSubstitutorClassName() {\n        return getEnvVarOrProperty(\"image.substitutor\", null);\n    }\n\n    public String getImagePullPolicy() {\n        return getEnvVarOrProperty(\"pull.policy\", null);\n    }\n\n    public Integer getClientPingTimeout() {\n        return Integer.parseInt(getEnvVarOrProperty(\"client.ping.timeout\", \"10\"));\n    }\n\n    @Nullable\n    @Contract(\"_, !null, _ -> !null\")\n    private String getConfigurable(\n        @NotNull final String propertyName,\n        @Nullable final String defaultValue,\n        Properties... propertiesSources\n    ) {\n        String envVarName = propertyName.replaceAll(\"\\\\.\", \"_\").toUpperCase();\n        if (!envVarName.startsWith(\"TESTCONTAINERS_\") && !envVarName.startsWith(\"DOCKER_\")) {\n            envVarName = \"TESTCONTAINERS_\" + envVarName;\n        }\n\n        if (environment.containsKey(envVarName)) {\n            String value = environment.get(envVarName);\n            if (!value.isEmpty()) {\n                return value;\n            }\n        }\n\n        for (final Properties properties : propertiesSources) {\n            if (properties.get(propertyName) != null) {\n                return (String) properties.get(propertyName);\n            }\n        }\n\n        return defaultValue;\n    }\n\n    /**\n     * Gets a configured setting from an environment variable (if present) or a configuration file property otherwise.\n     * The configuration file will be the <code>.testcontainers.properties</code> file in the user's home directory or\n     * a <code>testcontainers.properties</code> found on the classpath.\n     * <p>\n     * Note that when searching environment variables, the prefix `TESTCONTAINERS_` will usually be applied to the\n     * property name, which will be converted to upper-case with underscore separators. This prefix will not be added\n     * if the property name begins `docker.`.\n     *\n     * @param propertyName name of configuration file property (dot-separated lower case)\n     * @return the found value, or null if not set\n     */\n    @Contract(\"_, !null -> !null\")\n    public String getEnvVarOrProperty(@NotNull final String propertyName, @Nullable final String defaultValue) {\n        return getConfigurable(propertyName, defaultValue, userProperties, classpathProperties);\n    }\n\n    /**\n     * Gets a configured setting from an environment variable (if present) or a configuration file property otherwise.\n     * The configuration file will be the <code>.testcontainers.properties</code> file in the user's home directory.\n     * <p>\n     * Note that when searching environment variables, the prefix `TESTCONTAINERS_` will usually be applied to the\n     * property name, which will be converted to upper-case with underscore separators. This prefix will not be added\n     * if the property name begins `docker.`.\n     *\n     * @param propertyName name of configuration file property (dot-separated lower case)\n     * @return the found value, or null if not set\n     */\n    @Contract(\"_, !null -> !null\")\n    public String getEnvVarOrUserProperty(@NotNull final String propertyName, @Nullable final String defaultValue) {\n        return getConfigurable(propertyName, defaultValue, userProperties);\n    }\n\n    /**\n     * Gets a configured setting from <code>~/.testcontainers.properties</code>.\n     *\n     * @param propertyName name of configuration file property (dot-separated lower case)\n     * @return the found value, or null if not set\n     */\n    @Contract(\"_, !null -> !null\")\n    public String getUserProperty(@NotNull final String propertyName, @Nullable final String defaultValue) {\n        return this.userProperties.get(propertyName) != null\n            ? (String) this.userProperties.get(propertyName)\n            : defaultValue;\n    }\n\n    /**\n     * @return properties values available from user properties and classpath properties. Values set by environment\n     * variable are NOT included.\n     * @deprecated usages should be removed ASAP. See {@link TestcontainersConfiguration#getEnvVarOrProperty(String, String)},\n     * {@link TestcontainersConfiguration#getEnvVarOrUserProperty(String, String)} or {@link TestcontainersConfiguration#getUserProperty(String, String)}\n     * for suitable replacements.\n     */\n    @Deprecated\n    public Properties getProperties() {\n        return Stream\n            .of(userProperties, classpathProperties)\n            .reduce(\n                new Properties(),\n                (a, b) -> {\n                    a.putAll(b);\n                    return a;\n                }\n            );\n    }\n\n    @Deprecated\n    public boolean updateGlobalConfig(@NonNull String prop, @NonNull String value) {\n        return updateUserConfig(prop, value);\n    }\n\n    @Synchronized\n    public boolean updateUserConfig(@NonNull String prop, @NonNull String value) {\n        try {\n            if (value.equals(userProperties.get(prop))) {\n                return false;\n            }\n\n            userProperties.setProperty(prop, value);\n\n            USER_CONFIG_FILE.createNewFile();\n            try (OutputStream outputStream = new FileOutputStream(USER_CONFIG_FILE)) {\n                userProperties.store(outputStream, \"Modified by Testcontainers\");\n            }\n\n            // Update internal state only if environment config was successfully updated\n            userProperties.setProperty(prop, value);\n            return true;\n        } catch (Exception e) {\n            log.debug(\"Can't store environment property {} in {}\", prop, USER_CONFIG_FILE);\n            return false;\n        }\n    }\n\n    @SneakyThrows(MalformedURLException.class)\n    private static TestcontainersConfiguration loadConfiguration() {\n        return new TestcontainersConfiguration(\n            readProperties(USER_CONFIG_FILE.toURI().toURL()),\n            ClasspathScanner\n                .scanFor(PROPERTIES_FILE_NAME)\n                .map(TestcontainersConfiguration::readProperties)\n                .reduce(\n                    new Properties(),\n                    (a, b) -> {\n                        // first-write-wins merging - URLs appearing first on the classpath alphabetically will take priority.\n                        // Note that this means that file: URLs will always take priority over jar: URLs.\n                        b.putAll(a);\n                        return b;\n                    }\n                ),\n            System.getenv()\n        );\n    }\n\n    private static Properties readProperties(URL url) {\n        log.debug(\"Testcontainers configuration overrides will be loaded from {}\", url);\n        Properties properties = new Properties();\n        try (InputStream inputStream = url.openStream()) {\n            properties.load(inputStream);\n        } catch (FileNotFoundException e) {\n            log.debug(\n                \"Attempted to read Testcontainers configuration file at {} but the file was not found. Exception message: {}\",\n                url,\n                ExceptionUtils.getRootCauseMessage(e)\n            );\n        } catch (IOException e) {\n            log.debug(\n                \"Attempted to read Testcontainers configuration file at {} but could it not be loaded. Exception message: {}\",\n                url,\n                ExceptionUtils.getRootCauseMessage(e)\n            );\n        }\n        return properties;\n    }\n\n    private DockerImageName getImage(final String defaultValue) {\n        return getConfiguredSubstituteImage(DockerImageName.parse(defaultValue));\n    }\n\n    DockerImageName getConfiguredSubstituteImage(DockerImageName original) {\n        for (final Map.Entry<DockerImageName, String> entry : CONTAINER_MAPPING.entrySet()) {\n            if (original.isCompatibleWith(entry.getKey())) {\n                return Optional\n                    .ofNullable(entry.getValue())\n                    .map(propertyName -> getEnvVarOrProperty(propertyName, null))\n                    .map(String::valueOf)\n                    .map(String::trim)\n                    .map(DockerImageName::parse)\n                    .orElse(original)\n                    .asCompatibleSubstituteFor(original);\n            }\n        }\n        return original;\n    }\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/utility/ThrowingFunction.java",
    "content": "package org.testcontainers.utility;\n\npublic interface ThrowingFunction<T, R> {\n    R apply(T t) throws Exception;\n}\n"
  },
  {
    "path": "core/src/main/java/org/testcontainers/utility/Versioning.java",
    "content": "package org.testcontainers.utility;\n\nimport lombok.AccessLevel;\nimport lombok.EqualsAndHashCode;\nimport lombok.NoArgsConstructor;\n\n/**\n * Represents mechanisms for versioning docker images.\n */\ninterface Versioning {\n    AnyVersion ANY = new AnyVersion();\n\n    boolean isValid();\n\n    String getSeparator();\n\n    @NoArgsConstructor(access = AccessLevel.PRIVATE)\n    class AnyVersion implements Versioning {\n\n        @Override\n        public boolean isValid() {\n            return true;\n        }\n\n        @Override\n        public String getSeparator() {\n            return \":\";\n        }\n\n        @Override\n        public String toString() {\n            return \"latest\";\n        }\n\n        @Override\n        public boolean equals(final Object obj) {\n            return obj instanceof Versioning;\n        }\n\n        @Override\n        public int hashCode() {\n            return super.hashCode();\n        }\n    }\n\n    @EqualsAndHashCode\n    class TagVersioning implements Versioning {\n\n        public static final String TAG_REGEX = \"[\\\\w][\\\\w.\\\\-]{0,127}\";\n\n        static final TagVersioning LATEST = new TagVersioning(\"latest\");\n\n        private final String tag;\n\n        TagVersioning(String tag) {\n            this.tag = tag;\n        }\n\n        @Override\n        public boolean isValid() {\n            return tag.matches(TAG_REGEX);\n        }\n\n        @Override\n        public String getSeparator() {\n            return \":\";\n        }\n\n        @Override\n        public String toString() {\n            return tag;\n        }\n    }\n\n    @EqualsAndHashCode\n    class Sha256Versioning implements Versioning {\n\n        public static final String HASH_REGEX = \"[0-9a-fA-F]{32,}\";\n\n        private final String hash;\n\n        Sha256Versioning(String hash) {\n            this.hash = hash;\n        }\n\n        @Override\n        public boolean isValid() {\n            return hash.matches(HASH_REGEX);\n        }\n\n        @Override\n        public String getSeparator() {\n            return \"@\";\n        }\n\n        @Override\n        public String toString() {\n            return \"sha256:\" + hash;\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/main/resources/META-INF/native-image/org.testcontainers/testcontainers/native-image.properties",
    "content": "Args = --enable-http --enable-https\n"
  },
  {
    "path": "core/src/main/resources/META-INF/services/org.testcontainers.dockerclient.DockerClientProviderStrategy",
    "content": "org.testcontainers.dockerclient.TestcontainersHostPropertyClientProviderStrategy\norg.testcontainers.dockerclient.EnvironmentAndSystemPropertyClientProviderStrategy\norg.testcontainers.dockerclient.UnixSocketClientProviderStrategy\norg.testcontainers.dockerclient.DockerMachineClientProviderStrategy\norg.testcontainers.dockerclient.NpipeSocketClientProviderStrategy\norg.testcontainers.dockerclient.RootlessDockerClientProviderStrategy\norg.testcontainers.dockerclient.DockerDesktopClientProviderStrategy\n"
  },
  {
    "path": "core/src/test/java/alt/testcontainers/README.md",
    "content": "Useful place for running tests that need to be outside of the `org.testcontainers` package.\n"
  },
  {
    "path": "core/src/test/java/alt/testcontainers/images/OutOfPackageImagePullPolicyTest.java",
    "content": "package alt.testcontainers.images;\n\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.TestImages;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.startupcheck.OneShotStartupCheckStrategy;\nimport org.testcontainers.images.AbstractImagePullPolicy;\nimport org.testcontainers.images.ImageData;\nimport org.testcontainers.utility.DockerImageName;\n\nclass OutOfPackageImagePullPolicyTest {\n\n    @Test\n    void shouldSupportCustomPoliciesOutOfTestcontainersPackage() {\n        try (\n            GenericContainer<?> container = new GenericContainer<>(TestImages.TINY_IMAGE)\n                .withImagePullPolicy(\n                    new AbstractImagePullPolicy() {\n                        @Override\n                        protected boolean shouldPullCached(DockerImageName imageName, ImageData localImageData) {\n                            return false;\n                        }\n                    }\n                )\n        ) {\n            container.withStartupCheckStrategy(new OneShotStartupCheckStrategy());\n            container.start();\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/DaemonTest.java",
    "content": "package org.testcontainers;\n\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.GenericContainer;\n\nimport java.io.File;\nimport java.util.HashSet;\nimport java.util.Set;\nimport java.util.stream.Collectors;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.fail;\n\n/**\n * This test forks a new JVM, otherwise it's not possible to reliably diff the threads\n */\nclass DaemonTest {\n\n    public static void main(String[] args) {\n        Thread mainThread = Thread.currentThread();\n\n        GenericContainer<?> genericContainer = null;\n\n        try {\n            genericContainer = new GenericContainer<>(TestImages.TINY_IMAGE).withCommand(\"top\");\n            genericContainer.start();\n\n            Set<Thread> threads = new HashSet<>(Thread.getAllStackTraces().keySet());\n            threads.remove(mainThread);\n\n            Set<Thread> nonDaemonThreads = threads.stream().filter(it -> !it.isDaemon()).collect(Collectors.toSet());\n\n            if (!nonDaemonThreads.isEmpty()) {\n                String nonDaemonThreadNames = nonDaemonThreads\n                    .stream()\n                    .map(Thread::getName)\n                    .collect(Collectors.joining(\"\\n\", \"\\n\", \"\"));\n\n                fail(\"Expected all threads to be daemons but the following are not:\\n\" + nonDaemonThreadNames);\n            }\n        } finally {\n            if (genericContainer != null) {\n                genericContainer.stop();\n            }\n        }\n    }\n\n    @Test\n    void testThatAllThreadsAreDaemons() throws Exception {\n        ProcessBuilder processBuilder = new ProcessBuilder(\n            new File(System.getProperty(\"java.home\")).toPath().resolve(\"bin\").resolve(\"java\").toString(),\n            \"-ea\",\n            \"-classpath\",\n            System.getProperty(\"java.class.path\"),\n            DaemonTest.class.getCanonicalName()\n        );\n\n        assertThat(processBuilder.inheritIO().start().waitFor()).isZero();\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/DockerClientFactoryTest.java",
    "content": "package org.testcontainers;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.testcontainers.dockerclient.LogToStringContainerCallback;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.MockTestcontainersConfigurationExtension;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\n\n/**\n * Test for {@link DockerClientFactory}.\n */\n@ExtendWith(MockTestcontainersConfigurationExtension.class)\nclass DockerClientFactoryTest {\n\n    @Test\n    void runCommandInsideDockerShouldNotFailIfImageDoesNotExistsLocally() {\n        try (DockerRegistryContainer registryContainer = new DockerRegistryContainer()) {\n            registryContainer.start();\n            DockerImageName imageName = registryContainer.createImage();\n\n            DockerClientFactory dockFactory = DockerClientFactory.instance();\n\n            dockFactory.runInsideDocker(\n                imageName,\n                cmd -> cmd.withCmd(\"sh\", \"-c\", \"echo 'SUCCESS'\"),\n                (client, id) -> {\n                    return client\n                        .logContainerCmd(id)\n                        .withStdOut(true)\n                        .exec(new LogToStringContainerCallback())\n                        .toString();\n                }\n            );\n        }\n    }\n\n    @Test\n    void dockerHostIpAddress() {\n        DockerClientFactory instance = new DockerClientFactory();\n        instance.strategy = null;\n        assertThat(instance.dockerHostIpAddress()).isNotNull();\n    }\n\n    @Test\n    void failedChecksFailFast() {\n        DockerClientFactory instance = DockerClientFactory.instance();\n        assertThat(instance.client()).isNotNull();\n        assertThat(instance.cachedClientFailure).isNull();\n        try {\n            RuntimeException failure = new IllegalStateException(\"Boom!\");\n            instance.cachedClientFailure = failure;\n            // Fail fast\n            assertThatThrownBy(instance::client).isEqualTo(failure);\n        } finally {\n            instance.cachedClientFailure = null;\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/DockerRegistryContainer.java",
    "content": "package org.testcontainers;\n\nimport com.github.dockerjava.api.DockerClient;\nimport com.github.dockerjava.api.async.ResultCallback;\nimport com.github.dockerjava.api.command.InspectContainerResponse;\nimport com.github.dockerjava.api.command.PullImageResultCallback;\nimport lombok.Getter;\nimport lombok.NonNull;\nimport lombok.SneakyThrows;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.output.FrameConsumerResultCallback;\nimport org.testcontainers.containers.output.OutputFrame;\nimport org.testcontainers.containers.output.WaitingConsumer;\nimport org.testcontainers.utility.Base58;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.util.UUID;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\npublic class DockerRegistryContainer extends GenericContainer<DockerRegistryContainer> {\n\n    @Getter\n    String endpoint;\n\n    public DockerRegistryContainer() {\n        super(TestImages.DOCKER_REGISTRY_IMAGE);\n    }\n\n    public DockerRegistryContainer(@NonNull Future<String> image) {\n        super(image);\n    }\n\n    @Override\n    protected void configure() {\n        super.configure();\n        withEnv(\"REGISTRY_HTTP_ADDR\", \"127.0.0.1:0\");\n        withCreateContainerCmdModifier(cmd -> {\n            cmd.getHostConfig().withNetworkMode(\"host\");\n        });\n    }\n\n    @Override\n    @SneakyThrows\n    protected void containerIsStarting(InspectContainerResponse containerInfo) {\n        AtomicInteger port = new AtomicInteger(-1);\n        try (FrameConsumerResultCallback resultCallback = new FrameConsumerResultCallback()) {\n            WaitingConsumer waitingConsumer = new WaitingConsumer();\n            resultCallback.addConsumer(OutputFrame.OutputType.STDERR, waitingConsumer);\n\n            dockerClient\n                .logContainerCmd(containerInfo.getId())\n                .withStdErr(true)\n                .withFollowStream(true)\n                .exec(resultCallback);\n\n            Pattern pattern = Pattern.compile(\n                \".*listening on .*:(\\\\d+).*\",\n                Pattern.DOTALL | Pattern.CASE_INSENSITIVE | Pattern.MULTILINE\n            );\n            waitingConsumer.waitUntil(\n                it -> {\n                    String s = it.getUtf8String();\n                    Matcher matcher = pattern.matcher(s);\n                    if (matcher.matches()) {\n                        port.set(Integer.parseInt(matcher.group(1)));\n                        return true;\n                    } else {\n                        return false;\n                    }\n                },\n                10,\n                TimeUnit.SECONDS\n            );\n        }\n\n        endpoint = getHost() + \":\" + port.get();\n    }\n\n    public DockerImageName createImage() {\n        return createImage(UUID.randomUUID().toString());\n    }\n\n    public DockerImageName createImage(String tag) {\n        return createImage(\"testcontainers/helloworld:latest\", tag);\n    }\n\n    @SneakyThrows(InterruptedException.class)\n    public DockerImageName createImage(String originalImage, String tag) {\n        DockerClient client = getDockerClient();\n        client.pullImageCmd(originalImage).exec(new PullImageResultCallback()).awaitCompletion();\n\n        String dummyImageId = client.inspectImageCmd(originalImage).exec().getId();\n\n        DockerImageName imageName = DockerImageName\n            .parse(getEndpoint() + \"/\" + Base58.randomString(6).toLowerCase())\n            .withTag(tag);\n\n        // push the image to the registry\n        client.tagImageCmd(dummyImageId, imageName.getUnversionedPart(), tag).exec();\n\n        client\n            .pushImageCmd(imageName.asCanonicalNameString())\n            .exec(new ResultCallback.Adapter<>())\n            .awaitCompletion(1, TimeUnit.MINUTES);\n\n        // Remove from local cache, tests should pull the image themselves\n        client.removeImageCmd(imageName.asCanonicalNameString()).exec();\n\n        return imageName;\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/TestImages.java",
    "content": "package org.testcontainers;\n\nimport org.testcontainers.utility.DockerImageName;\n\npublic interface TestImages {\n    DockerImageName REDIS_IMAGE = DockerImageName.parse(\"redis:6-alpine\");\n\n    DockerImageName RABBITMQ_IMAGE = DockerImageName.parse(\"rabbitmq:3.7.25\");\n\n    DockerImageName MONGODB_IMAGE = DockerImageName.parse(\"mongo:4.4\");\n\n    DockerImageName ALPINE_IMAGE = DockerImageName.parse(\"alpine:3.17\");\n\n    DockerImageName DOCKER_REGISTRY_IMAGE = DockerImageName.parse(\"registry:2.7.0\");\n\n    DockerImageName TINY_IMAGE = DockerImageName.parse(\"alpine:3.17\");\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/containers/ComposeContainerTest.java",
    "content": "package org.testcontainers.containers;\n\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.File;\nimport java.util.Optional;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass ComposeContainerTest {\n\n    public static final String DOCKER_IMAGE = \"docker:25.0.2\";\n\n    private static final String COMPOSE_FILE_PATH = \"src/test/resources/v2-compose-test.yml\";\n\n    @Test\n    void testWithCustomDockerImage() {\n        ComposeContainer composeContainer = new ComposeContainer(\n            DockerImageName.parse(DOCKER_IMAGE),\n            new File(COMPOSE_FILE_PATH)\n        );\n        composeContainer.start();\n        verifyContainerCreation(composeContainer);\n        composeContainer.stop();\n    }\n\n    @Test\n    void testWithCustomDockerImageAndIdentifier() {\n        ComposeContainer composeContainer = new ComposeContainer(\n            DockerImageName.parse(DOCKER_IMAGE),\n            \"myidentifier\",\n            new File(COMPOSE_FILE_PATH)\n        );\n        composeContainer.start();\n        verifyContainerCreation(composeContainer);\n        composeContainer.stop();\n    }\n\n    private void verifyContainerCreation(ComposeContainer composeContainer) {\n        Optional<ContainerState> redis = composeContainer.getContainerByServiceName(\"redis\");\n        assertThat(redis)\n            .hasValueSatisfying(container -> {\n                assertThat(container.isRunning()).isTrue();\n                assertThat(container.getContainerInfo().getConfig().getLabels())\n                    .containsEntry(\"com.docker.compose.version\", \"2.24.5\");\n            });\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/containers/ComposeContainerWithServicesTest.java",
    "content": "package org.testcontainers.containers;\n\nimport org.junit.jupiter.api.Test;\nimport org.rnorth.ducttape.TimeoutException;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.File;\nimport java.time.Duration;\nimport java.util.List;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.catchThrowable;\n\nclass ComposeContainerWithServicesTest {\n\n    public static final File SIMPLE_COMPOSE_FILE = new File(\n        \"src/test/resources/compose-scaling-multiple-containers.yml\"\n    );\n\n    public static final File COMPOSE_FILE_WITH_INLINE_SCALE = new File(\n        \"src/test/resources/compose-with-inline-scale-test.yml\"\n    );\n\n    public static final File COMPOSE_FILE_WITH_HEALTHCHECK = new File(\n        \"src/test/resources/docker-compose-healthcheck.yml\"\n    );\n\n    @Test\n    void testDesiredSubsetOfServicesAreStarted() {\n        try (\n            ComposeContainer compose = new ComposeContainer(DockerImageName.parse(\"docker:25.0.5\"), SIMPLE_COMPOSE_FILE)\n                .withServices(\"redis\")\n        ) {\n            compose.start();\n\n            verifyStartedContainers(compose, \"redis-1\");\n        }\n    }\n\n    @Test\n    void testDesiredSubsetOfScaledServicesAreStarted() {\n        try (\n            ComposeContainer compose = new ComposeContainer(DockerImageName.parse(\"docker:25.0.5\"), SIMPLE_COMPOSE_FILE)\n                .withScaledService(\"redis\", 2)\n        ) {\n            compose.start();\n\n            verifyStartedContainers(compose, \"redis-1\", \"redis-2\");\n        }\n    }\n\n    @Test\n    void testDesiredSubsetOfSpecifiedAndScaledServicesAreStarted() {\n        try (\n            ComposeContainer compose = new ComposeContainer(DockerImageName.parse(\"docker:25.0.5\"), SIMPLE_COMPOSE_FILE)\n                .withServices(\"redis\")\n                .withScaledService(\"redis\", 2)\n        ) {\n            compose.start();\n\n            verifyStartedContainers(compose, \"redis-1\", \"redis-2\");\n        }\n    }\n\n    @Test\n    void testDesiredSubsetOfSpecifiedOrScaledServicesAreStarted() {\n        try (\n            ComposeContainer compose = new ComposeContainer(DockerImageName.parse(\"docker:25.0.5\"), SIMPLE_COMPOSE_FILE)\n                .withServices(\"other\")\n                .withScaledService(\"redis\", 2)\n        ) {\n            compose.start();\n\n            verifyStartedContainers(compose, \"redis-1\", \"redis-2\", \"other-1\");\n        }\n    }\n\n    @Test\n    void testAllServicesAreStartedIfNotSpecified() {\n        try (\n            ComposeContainer compose = new ComposeContainer(DockerImageName.parse(\"docker:25.0.5\"), SIMPLE_COMPOSE_FILE)\n        ) {\n            compose.start();\n\n            verifyStartedContainers(compose, \"redis-1\", \"other-1\");\n        }\n    }\n\n    @Test\n    void testScaleInComposeFileIsRespected() {\n        try (\n            ComposeContainer compose = new ComposeContainer(\n                DockerImageName.parse(\"docker:25.0.5\"),\n                COMPOSE_FILE_WITH_INLINE_SCALE\n            )\n        ) {\n            compose.start();\n\n            // the compose file includes `scale: 3` for the redis container\n            verifyStartedContainers(compose, \"redis-1\", \"redis-2\", \"redis-3\");\n        }\n    }\n\n    @Test\n    void testStartupTimeoutSetsTheHighestTimeout() {\n        assertThat(\n            catchThrowable(() -> {\n                try (\n                    ComposeContainer compose = new ComposeContainer(\n                        DockerImageName.parse(\"docker:25.0.5\"),\n                        SIMPLE_COMPOSE_FILE\n                    )\n                        .withServices(\"redis\")\n                        .withStartupTimeout(Duration.ofMillis(1))\n                        .withExposedService(\n                            \"redis\",\n                            80,\n                            Wait.forListeningPort().withStartupTimeout(Duration.ofMinutes(1))\n                        );\n                ) {\n                    compose.start();\n                }\n            })\n        )\n            .as(\"We expect a timeout from the startup timeout\")\n            .isInstanceOf(TimeoutException.class);\n    }\n\n    @Test\n    void testWaitingForHealthcheck() {\n        try (\n            ComposeContainer compose = new ComposeContainer(\n                DockerImageName.parse(\"docker:25.0.5\"),\n                COMPOSE_FILE_WITH_HEALTHCHECK\n            )\n                .waitingFor(\"redis\", Wait.forHealthcheck().withStartupTimeout(Duration.ofMinutes(2)))\n        ) {\n            compose.start();\n\n            verifyStartedContainers(compose, \"redis-1\");\n        }\n    }\n\n    @Test\n    void testWaitingForHealthcheckWithRestartDoesNotCrash() {\n        try (\n            ComposeContainer compose = new ComposeContainer(\n                DockerImageName.parse(\"docker:25.0.5\"),\n                COMPOSE_FILE_WITH_HEALTHCHECK\n            )\n                .waitingFor(\"redis\", Wait.forHealthcheck().withStartupTimeout(Duration.ofMinutes(1)))\n        ) {\n            compose.start();\n            compose.stop();\n            compose.start();\n\n            verifyStartedContainers(compose, \"redis-1\");\n        }\n    }\n\n    private void verifyStartedContainers(final ComposeContainer compose, final String... names) {\n        final List<String> containerNames = compose\n            .listChildContainers()\n            .stream()\n            .flatMap(container -> Stream.of(container.getNames()))\n            .collect(Collectors.toList());\n\n        assertThat(containerNames)\n            .as(\"number of running services of docker-compose is the same as length of listOfServices\")\n            .hasSize(names.length);\n\n        for (final String expectedName : names) {\n            final long matches = containerNames.stream().filter(foundName -> foundName.endsWith(expectedName)).count();\n\n            assertThat(matches)\n                .as(\"container with name starting '\" + expectedName + \"' should be running\")\n                .isEqualTo(1L);\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/containers/ComposeOverridesTest.java",
    "content": "package org.testcontainers.containers;\n\nimport com.google.common.util.concurrent.Uninterruptibles;\nimport org.apache.commons.lang3.SystemUtils;\nimport org.assertj.core.api.Assumptions;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.rnorth.ducttape.unreliables.Unreliables;\nimport org.testcontainers.utility.CommandLine;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.BufferedReader;\nimport java.io.File;\nimport java.io.InputStreamReader;\nimport java.net.Socket;\nimport java.util.Arrays;\nimport java.util.concurrent.TimeUnit;\n\nclass ComposeOverridesTest {\n\n    private static final String DOCKER_EXECUTABLE = SystemUtils.IS_OS_WINDOWS ? \"docker.exe\" : \"docker\";\n\n    private static final File BASE_COMPOSE_FILE = new File(\"src/test/resources/docker-compose-base.yml\");\n\n    private static final String BASE_ENV_VAR = \"bar=base\";\n\n    private static final File OVERRIDE_COMPOSE_FILE = new File(\n        \"src/test/resources/docker-compose-non-default-override.yml\"\n    );\n\n    private static final String OVERRIDE_ENV_VAR = \"bar=overwritten\";\n\n    private static final int SERVICE_PORT = 3000;\n\n    private static final String SERVICE_NAME = \"alpine-1\";\n\n    public static Iterable<Object[]> data() {\n        return Arrays.asList(\n            new Object[][] {\n                { true, BASE_ENV_VAR, new File[] { BASE_COMPOSE_FILE } },\n                { true, OVERRIDE_ENV_VAR, new File[] { BASE_COMPOSE_FILE, OVERRIDE_COMPOSE_FILE } },\n                { false, BASE_ENV_VAR, new File[] { BASE_COMPOSE_FILE } },\n                { false, OVERRIDE_ENV_VAR, new File[] { BASE_COMPOSE_FILE, OVERRIDE_COMPOSE_FILE } },\n            }\n        );\n    }\n\n    @ParameterizedTest(name = \"{index}: local[{0}], composeFiles[{2}], expectedEnvVar[{1}]\")\n    @MethodSource(\"data\")\n    void test(boolean localMode, String expectedEnvVar, File... composeFiles) {\n        ComposeContainer compose;\n        if (localMode) {\n            Assumptions\n                .assumeThat(CommandLine.executableExists(DOCKER_EXECUTABLE))\n                .as(\"docker executable exists\")\n                .isTrue();\n            compose = new ComposeContainer(composeFiles).withExposedService(SERVICE_NAME, SERVICE_PORT);\n        } else {\n            compose =\n                new ComposeContainer(DockerImageName.parse(\"docker:25.0.2\"), composeFiles)\n                    .withExposedService(SERVICE_NAME, SERVICE_PORT);\n        }\n\n        compose.start();\n\n        BufferedReader br = Unreliables.retryUntilSuccess(\n            10,\n            TimeUnit.SECONDS,\n            () -> {\n                Uninterruptibles.sleepUninterruptibly(1, TimeUnit.SECONDS);\n\n                Socket socket = new Socket(\n                    compose.getServiceHost(SERVICE_NAME, SERVICE_PORT),\n                    compose.getServicePort(SERVICE_NAME, SERVICE_PORT)\n                );\n                return new BufferedReader(new InputStreamReader(socket.getInputStream()));\n            }\n        );\n\n        Unreliables.retryUntilTrue(\n            10,\n            TimeUnit.SECONDS,\n            () -> {\n                while (br.ready()) {\n                    String line = br.readLine();\n                    if (line.contains(expectedEnvVar)) {\n                        return true;\n                    }\n                }\n                Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS);\n                return false;\n            }\n        );\n        compose.stop();\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/containers/ComposeProfilesOptionTest.java",
    "content": "package org.testcontainers.containers;\n\nimport org.assertj.core.api.Assumptions;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.testcontainers.utility.CommandLine;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.File;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass ComposeProfilesOptionTest {\n\n    public static Boolean[] local() {\n        return new Boolean[] { Boolean.TRUE, Boolean.FALSE };\n    }\n\n    public static final File COMPOSE_FILE = new File(\"src/test/resources/compose-profile-option/compose-test.yml\");\n\n    @ParameterizedTest\n    @MethodSource(\"local\")\n    void testProfileOption(boolean localMode) {\n        ComposeContainer compose;\n        if (localMode) {\n            Assumptions\n                .assumeThat(CommandLine.executableExists(ComposeContainer.COMPOSE_EXECUTABLE))\n                .as(\"docker executable exists\")\n                .isTrue();\n            // composeContainerWithLocalCompose {\n            compose =\n                new ComposeContainer(COMPOSE_FILE)\n                    // }\n                    .withOptions(\"--profile=cache\");\n        } else {\n            compose =\n                new ComposeContainer(DockerImageName.parse(\"docker:25.0.2\"), COMPOSE_FILE)\n                    .withOptions(\"--profile=cache\");\n        }\n        compose.start();\n        assertThat(compose.listChildContainers()).hasSize(1);\n        compose.stop();\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/containers/ContainerStateTest.java",
    "content": "package org.testcontainers.containers;\n\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport java.util.Collections;\nimport java.util.List;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.doCallRealMethod;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nclass ContainerStateTest {\n\n    public static Object[][] params() {\n        return new Object[][] {\n            new Object[] { \"regular mapping\", \"80:8080/tcp\", Collections.singletonList(80) },\n            new Object[] { \"regular mapping with host\", \"127.0.0.1:80:8080/tcp\", Collections.singletonList(80) },\n            new Object[] { \"zero port without host\", \":0:8080/tcp\", Collections.emptyList() },\n            new Object[] { \"missing port with host\", \"0.0.0.0:0:8080/tcp\", Collections.emptyList() },\n            new Object[] { \"zero port (synthetic case)\", \"0:8080/tcp\", Collections.emptyList() },\n            new Object[] { \"missing port\", \":8080/tcp\", Collections.emptyList() },\n        };\n    }\n\n    @ParameterizedTest(name = \"{0} ({1} -> {2})\")\n    @MethodSource(\"params\")\n    void test(String name, String testSet, List<Integer> expectedResult) {\n        ContainerState containerState = mock(ContainerState.class);\n        doCallRealMethod().when(containerState).getBoundPortNumbers();\n\n        when(containerState.getPortBindings()).thenReturn(Collections.singletonList(testSet));\n\n        List<Integer> result = containerState.getBoundPortNumbers();\n        assertThat(result).hasSameElementsAs(expectedResult);\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/containers/DockerComposeContainerCustomImageTest.java",
    "content": "package org.testcontainers.containers;\n\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.File;\nimport java.util.Optional;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass DockerComposeContainerCustomImageTest {\n\n    public static final String DOCKER_IMAGE = \"docker/compose:debian-1.29.2\";\n\n    private static final String COMPOSE_FILE_PATH = \"src/test/resources/scaled-compose-test.yml\";\n\n    @Test\n    void testWithCustomDockerImage() {\n        DockerComposeContainer<?> composeContainer = new DockerComposeContainer<>(\n            DockerImageName.parse(DOCKER_IMAGE),\n            \"testing\",\n            new File(COMPOSE_FILE_PATH)\n        );\n        composeContainer.start();\n        verifyContainerCreation(composeContainer);\n        composeContainer.stop();\n    }\n\n    @Test\n    void testWithCustomDockerImageAndIdentifier() {\n        DockerComposeContainer<?> composeContainer = new DockerComposeContainer(\n            DockerImageName.parse(DOCKER_IMAGE),\n            \"myidentifier\",\n            new File(COMPOSE_FILE_PATH)\n        );\n        composeContainer.start();\n        verifyContainerCreation(composeContainer);\n        composeContainer.stop();\n    }\n\n    private void verifyContainerCreation(DockerComposeContainer<?> composeContainer) {\n        Optional<ContainerState> redis = composeContainer.getContainerByServiceName(\"redis\");\n        assertThat(redis)\n            .hasValueSatisfying(container -> {\n                assertThat(container.isRunning()).isTrue();\n                assertThat(container.getContainerInfo().getConfig().getLabels())\n                    .containsEntry(\"com.docker.compose.version\", \"1.29.2\");\n            });\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/containers/DockerComposeContainerWithServicesTest.java",
    "content": "package org.testcontainers.containers;\n\nimport org.junit.jupiter.api.Test;\nimport org.rnorth.ducttape.TimeoutException;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.File;\nimport java.time.Duration;\nimport java.util.List;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.catchThrowable;\n\nclass DockerComposeContainerWithServicesTest {\n\n    public static final File SIMPLE_COMPOSE_FILE = new File(\n        \"src/test/resources/compose-scaling-multiple-containers.yml\"\n    );\n\n    public static final File COMPOSE_FILE_WITH_INLINE_SCALE = new File(\n        \"src/test/resources/compose-with-inline-scale-test.yml\"\n    );\n\n    public static final File COMPOSE_FILE_WITH_HEALTHCHECK = new File(\n        \"src/test/resources/docker-compose-healthcheck.yml\"\n    );\n\n    @Test\n    void testDesiredSubsetOfServicesAreStarted() {\n        try (\n            DockerComposeContainer<?> compose = new DockerComposeContainer<>(\n                DockerImageName.parse(\"docker/compose:debian-1.29.2\"),\n                SIMPLE_COMPOSE_FILE\n            )\n                .withServices(\"redis\")\n        ) {\n            compose.start();\n\n            verifyStartedContainers(compose, \"redis_1\");\n        }\n    }\n\n    @Test\n    void testDesiredSubsetOfScaledServicesAreStarted() {\n        try (\n            DockerComposeContainer<?> compose = new DockerComposeContainer<>(\n                DockerImageName.parse(\"docker/compose:debian-1.29.2\"),\n                SIMPLE_COMPOSE_FILE\n            )\n                .withScaledService(\"redis\", 2)\n        ) {\n            compose.start();\n\n            verifyStartedContainers(compose, \"redis_1\", \"redis_2\");\n        }\n    }\n\n    @Test\n    void testDesiredSubsetOfSpecifiedAndScaledServicesAreStarted() {\n        try (\n            DockerComposeContainer<?> compose = new DockerComposeContainer<>(\n                DockerImageName.parse(\"docker/compose:debian-1.29.2\"),\n                SIMPLE_COMPOSE_FILE\n            )\n                .withServices(\"redis\")\n                .withScaledService(\"redis\", 2)\n        ) {\n            compose.start();\n\n            verifyStartedContainers(compose, \"redis_1\", \"redis_2\");\n        }\n    }\n\n    @Test\n    void testDesiredSubsetOfSpecifiedOrScaledServicesAreStarted() {\n        try (\n            DockerComposeContainer<?> compose = new DockerComposeContainer<>(\n                DockerImageName.parse(\"docker/compose:debian-1.29.2\"),\n                SIMPLE_COMPOSE_FILE\n            )\n                .withServices(\"other\")\n                .withScaledService(\"redis\", 2)\n        ) {\n            compose.start();\n\n            verifyStartedContainers(compose, \"redis_1\", \"redis_2\", \"other_1\");\n        }\n    }\n\n    @Test\n    void testAllServicesAreStartedIfNotSpecified() {\n        try (\n            DockerComposeContainer<?> compose = new DockerComposeContainer<>(\n                DockerImageName.parse(\"docker/compose:debian-1.29.2\"),\n                SIMPLE_COMPOSE_FILE\n            )\n        ) {\n            compose.start();\n\n            verifyStartedContainers(compose, \"redis_1\", \"other_1\");\n        }\n    }\n\n    @Test\n    void testScaleInComposeFileIsRespected() {\n        try (\n            DockerComposeContainer<?> compose = new DockerComposeContainer<>(\n                DockerImageName.parse(\"docker/compose:debian-1.29.2\"),\n                COMPOSE_FILE_WITH_INLINE_SCALE\n            )\n        ) {\n            compose.start();\n\n            // the compose file includes `scale: 3` for the redis container\n            verifyStartedContainers(compose, \"redis_1\", \"redis_2\", \"redis_3\");\n        }\n    }\n\n    @Test\n    void testStartupTimeoutSetsTheHighestTimeout() {\n        assertThat(\n            catchThrowable(() -> {\n                try (\n                    DockerComposeContainer<?> compose = new DockerComposeContainer<>(\n                        DockerImageName.parse(\"docker/compose:debian-1.29.2\"),\n                        SIMPLE_COMPOSE_FILE\n                    )\n                        .withServices(\"redis\")\n                        .withStartupTimeout(Duration.ofMillis(1))\n                        .withExposedService(\n                            \"redis\",\n                            80,\n                            Wait.forListeningPort().withStartupTimeout(Duration.ofMinutes(1))\n                        );\n                ) {\n                    compose.start();\n                }\n            })\n        )\n            .as(\"We expect a timeout from the startup timeout\")\n            .isInstanceOf(TimeoutException.class);\n    }\n\n    @Test\n    void testWaitingForHealthcheck() {\n        try (\n            DockerComposeContainer<?> compose = new DockerComposeContainer<>(\n                DockerImageName.parse(\"docker/compose:debian-1.29.2\"),\n                COMPOSE_FILE_WITH_HEALTHCHECK\n            )\n                .waitingFor(\"redis\", Wait.forHealthcheck().withStartupTimeout(Duration.ofMinutes(2)))\n        ) {\n            compose.start();\n\n            verifyStartedContainers(compose, \"redis_1\");\n        }\n    }\n\n    @Test\n    void testWaitingForHealthcheckWithRestartDoesNotCrash() {\n        try (\n            DockerComposeContainer<?> compose = new DockerComposeContainer<>(\n                DockerImageName.parse(\"docker/compose:debian-1.29.2\"),\n                COMPOSE_FILE_WITH_HEALTHCHECK\n            )\n                .waitingFor(\"redis\", Wait.forHealthcheck().withStartupTimeout(Duration.ofMinutes(1)))\n        ) {\n            compose.start();\n            compose.stop();\n            compose.start();\n\n            verifyStartedContainers(compose, \"redis_1\");\n        }\n    }\n\n    private void verifyStartedContainers(final DockerComposeContainer<?> compose, final String... names) {\n        final List<String> containerNames = compose\n            .listChildContainers()\n            .stream()\n            .flatMap(container -> Stream.of(container.getNames()))\n            .collect(Collectors.toList());\n\n        assertThat(containerNames)\n            .as(\"number of running services of docker-compose is the same as length of listOfServices\")\n            .hasSize(names.length);\n\n        for (final String expectedName : names) {\n            final long matches = containerNames.stream().filter(foundName -> foundName.endsWith(expectedName)).count();\n\n            assertThat(matches)\n                .as(\"container with name starting '\" + expectedName + \"' should be running\")\n                .isEqualTo(1L);\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/containers/DockerComposeFilesTest.java",
    "content": "package org.testcontainers.containers;\n\nimport com.google.common.collect.Lists;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.File;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass DockerComposeFilesTest {\n\n    @Test\n    void shouldGetDependencyImages() {\n        DockerComposeFiles dockerComposeFiles = new DockerComposeFiles(\n            Lists.newArrayList(new File(\"src/test/resources/docker-compose-imagename-parsing-v2.yml\"))\n        );\n        assertThat(dockerComposeFiles.getDependencyImages())\n            .containsExactlyInAnyOrder(\"postgres:latest\", \"redis:latest\", \"mysql:latest\");\n    }\n\n    @Test\n    void shouldGetDependencyImagesWhenOverriding() {\n        DockerComposeFiles dockerComposeFiles = new DockerComposeFiles(\n            Lists.newArrayList(\n                new File(\"src/test/resources/docker-compose-imagename-overriding-a.yml\"),\n                new File(\"src/test/resources/docker-compose-imagename-overriding-b.yml\")\n            )\n        );\n        assertThat(dockerComposeFiles.getDependencyImages())\n            .containsExactlyInAnyOrder(\"alpine:3.17\", \"redis:b\", \"mysql:b\", \"aservice:latest\");\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/containers/DockerComposeOverridesTest.java",
    "content": "package org.testcontainers.containers;\n\nimport com.google.common.util.concurrent.Uninterruptibles;\nimport org.assertj.core.api.Assumptions;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.rnorth.ducttape.unreliables.Unreliables;\nimport org.testcontainers.utility.CommandLine;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.BufferedReader;\nimport java.io.File;\nimport java.io.InputStreamReader;\nimport java.net.Socket;\nimport java.util.Arrays;\nimport java.util.concurrent.TimeUnit;\n\nclass DockerComposeOverridesTest {\n\n    private static final File BASE_COMPOSE_FILE = new File(\"src/test/resources/docker-compose-base.yml\");\n\n    private static final String BASE_ENV_VAR = \"bar=base\";\n\n    private static final File OVERRIDE_COMPOSE_FILE = new File(\n        \"src/test/resources/docker-compose-non-default-override.yml\"\n    );\n\n    private static final String OVERRIDE_ENV_VAR = \"bar=overwritten\";\n\n    private static final int SERVICE_PORT = 3000;\n\n    private static final String SERVICE_NAME = \"alpine_1\";\n\n    public static Iterable<Object[]> data() {\n        return Arrays.asList(\n            new Object[][] {\n                { true, BASE_ENV_VAR, new File[] { BASE_COMPOSE_FILE } },\n                { true, OVERRIDE_ENV_VAR, new File[] { BASE_COMPOSE_FILE, OVERRIDE_COMPOSE_FILE } },\n                { false, BASE_ENV_VAR, new File[] { BASE_COMPOSE_FILE } },\n                { false, OVERRIDE_ENV_VAR, new File[] { BASE_COMPOSE_FILE, OVERRIDE_COMPOSE_FILE } },\n            }\n        );\n    }\n\n    @ParameterizedTest(name = \"{index}: local[{0}], composeFiles[{2}], expectedEnvVar[{1}]\")\n    @MethodSource(\"data\")\n    void test(boolean localMode, String expectedEnvVar, File... composeFiles) {\n        DockerComposeContainer compose;\n        if (localMode) {\n            Assumptions\n                .assumeThat(CommandLine.executableExists(DockerComposeContainer.COMPOSE_EXECUTABLE))\n                .as(\"docker-compose executable exists\")\n                .isTrue();\n            Assumptions\n                .assumeThat(CommandLine.runShellCommand(\"docker-compose\", \"--version\"))\n                .doesNotStartWith(\"Docker Compose version v2\");\n\n            compose = new DockerComposeContainer(composeFiles).withExposedService(SERVICE_NAME, SERVICE_PORT);\n        } else {\n            compose =\n                new DockerComposeContainer(DockerImageName.parse(\"docker/compose:debian-1.29.2\"), composeFiles)\n                    .withExposedService(SERVICE_NAME, SERVICE_PORT);\n        }\n\n        compose.start();\n\n        BufferedReader br = Unreliables.retryUntilSuccess(\n            10,\n            TimeUnit.SECONDS,\n            () -> {\n                Uninterruptibles.sleepUninterruptibly(1, TimeUnit.SECONDS);\n\n                Socket socket = new Socket(\n                    compose.getServiceHost(SERVICE_NAME, SERVICE_PORT),\n                    compose.getServicePort(SERVICE_NAME, SERVICE_PORT)\n                );\n                return new BufferedReader(new InputStreamReader(socket.getInputStream()));\n            }\n        );\n\n        Unreliables.retryUntilTrue(\n            10,\n            TimeUnit.SECONDS,\n            () -> {\n                while (br.ready()) {\n                    String line = br.readLine();\n                    if (line.contains(expectedEnvVar)) {\n                        return true;\n                    }\n                }\n                Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS);\n                return false;\n            }\n        );\n        compose.stop();\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/containers/DockerComposeProfilesOptionTest.java",
    "content": "package org.testcontainers.containers;\n\nimport org.assertj.core.api.Assumptions;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.testcontainers.utility.CommandLine;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.File;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass DockerComposeProfilesOptionTest {\n\n    public static Boolean[] local() {\n        return new Boolean[] { Boolean.TRUE, Boolean.FALSE };\n    }\n\n    public static final File COMPOSE_FILE = new File(\"src/test/resources/compose-profile-option/compose-test.yml\");\n\n    @ParameterizedTest(name = \"{0}\")\n    @MethodSource(\"local\")\n    void testProfileOption(boolean localMode) {\n        DockerComposeContainer<?> compose;\n        if (localMode) {\n            Assumptions\n                .assumeThat(CommandLine.executableExists(DockerComposeContainer.COMPOSE_EXECUTABLE))\n                .as(\"docker-compose executable exists\")\n                .isTrue();\n            Assumptions\n                .assumeThat(CommandLine.runShellCommand(\"docker-compose\", \"--version\"))\n                .doesNotStartWith(\"Docker Compose version v2\");\n\n            compose = new DockerComposeContainer<>(COMPOSE_FILE).withOptions(\"--profile=cache\");\n        } else {\n            compose =\n                new DockerComposeContainer<>(DockerImageName.parse(\"docker/compose:debian-1.29.2\"), COMPOSE_FILE)\n                    .withOptions(\"--profile=cache\");\n        }\n\n        compose.start();\n        assertThat(compose.listChildContainers()).hasSize(1);\n        compose.stop();\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/containers/DockerMcpGatewayContainerTest.java",
    "content": "package org.testcontainers.containers;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.util.Collections;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass DockerMcpGatewayContainerTest {\n\n    @Test\n    void serviceSuccessfullyStarts() {\n        try (DockerMcpGatewayContainer gateway = new DockerMcpGatewayContainer(\"docker/mcp-gateway:latest\")) {\n            gateway.start();\n\n            assertThat(gateway.isRunning()).isTrue();\n        }\n    }\n\n    @Test\n    void gatewayStartsWithServers() {\n        try (\n            // container {\n            DockerMcpGatewayContainer gateway = new DockerMcpGatewayContainer(\"docker/mcp-gateway:latest\")\n                .withServer(\"curl\", \"curl\")\n                .withServer(\"brave\", \"brave_local_search\", \"brave_web_search\")\n                .withServer(\"github-official\", Collections.singletonList(\"add_issue_comment\"))\n                .withSecret(\"brave.api_key\", \"test_key\")\n                .withSecrets(Collections.singletonMap(\"github.personal_access_token\", \"test_token\"))\n            // }\n        ) {\n            gateway.start();\n\n            assertThat(gateway.getLogs()).contains(\"4 tools listed\");\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/containers/DockerModelRunnerContainerTest.java",
    "content": "package org.testcontainers.containers;\n\nimport io.restassured.RestAssured;\nimport io.restassured.response.Response;\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assumptions.assumeThat;\n\nclass DockerModelRunnerContainerTest {\n\n    @Test\n    void checkStatus() {\n        assumeThat(System.getenv(\"CI\")).isNull();\n\n        try (\n            // container {\n            DockerModelRunnerContainer dmr = new DockerModelRunnerContainer(\"alpine/socat:1.7.4.3-r0\")\n            // }\n        ) {\n            dmr.start();\n\n            Response modelResponse = RestAssured.get(dmr.getBaseEndpoint() + \"/status\").thenReturn();\n            assertThat(modelResponse.body().asString()).contains(\"The service is running\");\n        }\n    }\n\n    @Test\n    void pullsModelAndExposesInference() {\n        assumeThat(System.getenv(\"CI\")).isNull();\n\n        String modelName = \"ai/smollm2:360M-Q4_K_M\";\n\n        try (\n            // pullModel {\n            DockerModelRunnerContainer dmr = new DockerModelRunnerContainer(\"alpine/socat:1.7.4.3-r0\")\n                .withModel(modelName)\n            // }\n        ) {\n            dmr.start();\n\n            Response modelResponse = RestAssured.get(dmr.getBaseEndpoint() + \"/models\").thenReturn();\n            assertThat(modelResponse.body().jsonPath().getList(\"tags.flatten()\")).contains(modelName);\n\n            Response openAiResponse = RestAssured.get(dmr.getOpenAIEndpoint() + \"/v1/models\").prettyPeek().thenReturn();\n            assertThat(openAiResponse.body().jsonPath().getList(\"data.id\")).contains(modelName);\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/containers/ExposedHostTest.java",
    "content": "package org.testcontainers.containers;\n\nimport com.google.common.collect.ImmutableMap;\nimport com.sun.net.httpserver.HttpServer;\nimport lombok.SneakyThrows;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.DockerClientFactory;\nimport org.testcontainers.TestImages;\nimport org.testcontainers.Testcontainers;\nimport org.testcontainers.utility.TestcontainersConfiguration;\n\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.net.InetSocketAddress;\nimport java.util.List;\nimport java.util.UUID;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assumptions.assumeThat;\n\nclass ExposedHostTest {\n\n    private static HttpServer server;\n\n    @BeforeAll\n    public static void setUpClass() throws Exception {\n        server = HttpServer.create(new InetSocketAddress(0), 0);\n        server.createContext(\n            \"/\",\n            exchange -> {\n                byte[] content = \"Hello World!\".getBytes();\n                exchange.sendResponseHeaders(200, content.length);\n                try (OutputStream responseBody = exchange.getResponseBody()) {\n                    responseBody.write(content);\n                    responseBody.flush();\n                }\n            }\n        );\n        server.start();\n    }\n\n    @AfterAll\n    public static void tearDownClass() {\n        server.stop(0);\n    }\n\n    @AfterEach\n    public void tearDown() {\n        PortForwardingContainer.INSTANCE.reset();\n    }\n\n    @Test\n    void testExposedHostAfterContainerIsStarted() {\n        try (GenericContainer<?> container = new GenericContainer<>(tinyContainerDef()).withAccessToHost(true)) {\n            container.start();\n            Testcontainers.exposeHostPorts(server.getAddress().getPort());\n            assertResponse(container, server.getAddress().getPort());\n        }\n    }\n\n    @Test\n    void testExposedHost() {\n        Testcontainers.exposeHostPorts(server.getAddress().getPort());\n        assertResponse(new GenericContainer<>(tinyContainerDef()), server.getAddress().getPort());\n    }\n\n    @Test\n    void testExposedHostWithNetwork() {\n        Testcontainers.exposeHostPorts(server.getAddress().getPort());\n        try (Network network = Network.newNetwork()) {\n            assertResponse(\n                new GenericContainer<>(tinyContainerDef()).withNetwork(network),\n                server.getAddress().getPort()\n            );\n        }\n    }\n\n    @Test\n    void testExposedHostPortOnFixedInternalPorts() {\n        Testcontainers.exposeHostPorts(ImmutableMap.of(server.getAddress().getPort(), 80));\n        Testcontainers.exposeHostPorts(ImmutableMap.of(server.getAddress().getPort(), 81));\n\n        assertResponse(new GenericContainer<>(tinyContainerDef()), 80);\n        assertResponse(new GenericContainer<>(tinyContainerDef()), 81);\n    }\n\n    @Test\n    void testExposedHostWithReusableContainerAndFixedNetworkName() throws IOException, InterruptedException {\n        assumeThat(TestcontainersConfiguration.getInstance().environmentSupportsReuse()).isTrue();\n        Network network = createReusableNetwork(UUID.randomUUID());\n        Testcontainers.exposeHostPorts(server.getAddress().getPort());\n\n        GenericContainer<?> container = new GenericContainer<>(tinyContainerDef()).withReuse(true).withNetwork(network);\n        container.start();\n\n        assertHttpResponseFromHost(container, server.getAddress().getPort());\n\n        PortForwardingContainer.INSTANCE.reset();\n        Testcontainers.exposeHostPorts(server.getAddress().getPort());\n\n        GenericContainer<?> reusedContainer = new GenericContainer<>(tinyContainerDef())\n            .withReuse(true)\n            .withNetwork(network);\n        reusedContainer.start();\n\n        assertThat(reusedContainer.getContainerId()).isEqualTo(container.getContainerId());\n        assertHttpResponseFromHost(reusedContainer, server.getAddress().getPort());\n\n        container.stop();\n        reusedContainer.stop();\n        DockerClientFactory.lazyClient().removeNetworkCmd(network.getId()).exec();\n    }\n\n    @Test\n    void testExposedHostOnFixedInternalPortsWithReusableContainerAndFixedNetworkName()\n        throws IOException, InterruptedException {\n        assumeThat(TestcontainersConfiguration.getInstance().environmentSupportsReuse()).isTrue();\n        Network network = createReusableNetwork(UUID.randomUUID());\n        Testcontainers.exposeHostPorts(ImmutableMap.of(server.getAddress().getPort(), 1234));\n\n        GenericContainer<?> container = new GenericContainer<>(tinyContainerDef()).withReuse(true).withNetwork(network);\n        container.start();\n\n        assertHttpResponseFromHost(container, 1234);\n\n        PortForwardingContainer.INSTANCE.reset();\n        Testcontainers.exposeHostPorts(ImmutableMap.of(server.getAddress().getPort(), 1234));\n\n        GenericContainer<?> reusedContainer = new GenericContainer<>(tinyContainerDef())\n            .withReuse(true)\n            .withNetwork(network);\n        reusedContainer.start();\n\n        assertThat(reusedContainer.getContainerId()).isEqualTo(container.getContainerId());\n        assertHttpResponseFromHost(reusedContainer, 1234);\n\n        container.stop();\n        reusedContainer.stop();\n        DockerClientFactory.lazyClient().removeNetworkCmd(network.getId()).exec();\n    }\n\n    @SneakyThrows\n    protected void assertResponse(GenericContainer<?> container, int port) {\n        try {\n            container.start();\n\n            String response = container\n                .execInContainer(\"wget\", \"-O\", \"-\", \"http://host.testcontainers.internal:\" + port)\n                .getStdout();\n\n            assertThat(response).as(\"received response\").isEqualTo(\"Hello World!\");\n        } finally {\n            container.stop();\n        }\n    }\n\n    private ContainerDef tinyContainerDef() {\n        return new TinyContainerDef();\n    }\n\n    private static class TinyContainerDef extends ContainerDef {\n\n        TinyContainerDef() {\n            setImage(TestImages.TINY_IMAGE);\n            setCommand(\"top\");\n        }\n    }\n\n    private void assertHttpResponseFromHost(GenericContainer<?> container, int port)\n        throws IOException, InterruptedException {\n        String httpResponseFromHost = container\n            .execInContainer(\"wget\", \"-O\", \"-\", \"http://host.testcontainers.internal:\" + port)\n            .getStdout();\n        assertThat(httpResponseFromHost).isEqualTo(\"Hello World!\");\n    }\n\n    private static Network createReusableNetwork(UUID name) {\n        String networkName = name.toString();\n        Network network = new Network() {\n            @Override\n            public String getId() {\n                return networkName;\n            }\n\n            @Override\n            public void close() {}\n        };\n\n        List<com.github.dockerjava.api.model.Network> networks = DockerClientFactory\n            .lazyClient()\n            .listNetworksCmd()\n            .withNameFilter(networkName)\n            .exec();\n        if (networks.isEmpty()) {\n            Network.builder().createNetworkCmdModifier(cmd -> cmd.withName(networkName)).build().getId();\n        }\n        return network;\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/containers/GenericContainerTest.java",
    "content": "package org.testcontainers.containers;\n\nimport ch.qos.logback.classic.spi.ILoggingEvent;\nimport ch.qos.logback.core.read.ListAppender;\nimport com.github.dockerjava.api.DockerClient;\nimport com.github.dockerjava.api.command.InspectContainerResponse;\nimport com.github.dockerjava.api.command.InspectContainerResponse.ContainerState;\nimport com.github.dockerjava.api.model.Container;\nimport com.github.dockerjava.api.model.ExposedPort;\nimport com.github.dockerjava.api.model.Info;\nimport com.github.dockerjava.api.model.Ports;\nimport com.google.common.base.MoreObjects;\nimport com.google.common.collect.ImmutableList;\nimport lombok.RequiredArgsConstructor;\nimport lombok.SneakyThrows;\nimport lombok.experimental.FieldDefaults;\nimport lombok.extern.slf4j.Slf4j;\nimport org.apache.commons.io.FileUtils;\nimport org.assertj.core.api.Assumptions;\nimport org.junit.jupiter.api.Test;\nimport org.rnorth.ducttape.unreliables.Unreliables;\nimport org.testcontainers.DockerClientFactory;\nimport org.testcontainers.TestImages;\nimport org.testcontainers.containers.startupcheck.StartupCheckStrategy;\nimport org.testcontainers.containers.wait.strategy.AbstractWaitStrategy;\nimport org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.images.RemoteDockerImage;\nimport org.testcontainers.images.builder.ImageFromDockerfile;\nimport org.testcontainers.images.builder.Transferable;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.MountableFile;\n\nimport java.time.Duration;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.Predicate;\nimport java.util.stream.Collectors;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\nimport static org.assertj.core.api.Assertions.catchThrowable;\nimport static org.assertj.core.api.Assumptions.assumeThat;\n\nclass GenericContainerTest {\n\n    @Test\n    void shouldReportOOMAfterWait() {\n        Info info = DockerClientFactory.instance().client().infoCmd().exec();\n        // Poor man's rootless Docker detection :D\n        Assumptions.assumeThat(info.getSecurityOptions()).doesNotContain(\"name=rootless\");\n        try (\n            GenericContainer<?> container = new GenericContainer<>(TestImages.TINY_IMAGE)\n                .withStartupCheckStrategy(new NoopStartupCheckStrategy())\n                .waitingFor(new WaitForExitedState(ContainerState::getOOMKilled))\n                .withCreateContainerCmdModifier(it -> {\n                    it\n                        .getHostConfig()\n                        .withMemory(20 * FileUtils.ONE_MB)\n                        .withMemorySwappiness(0L)\n                        .withMemorySwap(0L)\n                        .withMemoryReservation(0L)\n                        .withKernelMemory(16 * FileUtils.ONE_MB);\n                })\n                .withCommand(\"sh\", \"-c\", \"A='0123456789'; for i in $(seq 0 32); do A=$A$A; done; sleep 10m\")\n        ) {\n            assertThatThrownBy(container::start)\n                .hasStackTraceContaining(\"Wait strategy failed. Container crashed with out-of-memory (OOMKilled)\")\n                .hasStackTraceContaining(\"Nope!\");\n        }\n    }\n\n    @Test\n    void shouldReportErrorAfterWait() {\n        try (\n            GenericContainer<?> container = new GenericContainer<>(TestImages.TINY_IMAGE)\n                .withStartupCheckStrategy(new NoopStartupCheckStrategy())\n                .waitingFor(new WaitForExitedState(state -> state.getExitCode() > 0))\n                .withCommand(\"sh\", \"-c\", \"usleep 100; exit 123\")\n        ) {\n            assertThatThrownBy(container::start)\n                .hasStackTraceContaining(\"Container startup failed for image \" + TestImages.TINY_IMAGE)\n                .hasStackTraceContaining(\"Wait strategy failed. Container exited with code 123\")\n                .hasStackTraceContaining(\"Nope!\");\n        }\n    }\n\n    @Test\n    void shouldCopyTransferableAsFile() {\n        try (\n            // transferableFile {\n            GenericContainer<?> container = new GenericContainer<>(TestImages.TINY_IMAGE)\n                .withStartupCheckStrategy(new NoopStartupCheckStrategy())\n                .withCopyToContainer(Transferable.of(\"test\"), \"/tmp/test\")\n                .waitingFor(new WaitForExitedState(state -> state.getExitCodeLong() > 0))\n                .withCommand(\"sh\", \"-c\", \"grep -q test /tmp/test && exit 100\")\n            // }\n        ) {\n            assertThatThrownBy(container::start)\n                .hasStackTraceContaining(\"Wait strategy failed. Container exited with code 100\")\n                .hasStackTraceContaining(\"Nope!\");\n        }\n    }\n\n    @Test\n    void shouldCopyTransferableAsFileWithFileMode() {\n        try (\n            // transferableWithFileMode {\n            GenericContainer<?> container = new GenericContainer<>(TestImages.TINY_IMAGE)\n                .withStartupCheckStrategy(new NoopStartupCheckStrategy())\n                .withCopyToContainer(Transferable.of(\"test\", 0777), \"/tmp/test\")\n                .waitingFor(new WaitForExitedState(state -> state.getExitCodeLong() > 0))\n                .withCommand(\"sh\", \"-c\", \"ls -ll /tmp | grep '\\\\-rwxrwxrwx\\\\|test' && exit 100\")\n            // }\n        ) {\n            assertThatThrownBy(container::start)\n                .hasStackTraceContaining(\"Wait strategy failed. Container exited with code 100\")\n                .hasStackTraceContaining(\"Nope!\");\n        }\n    }\n\n    @Test\n    void shouldCopyTransferableAfterMountableFile() {\n        try (\n            GenericContainer<?> container = new GenericContainer<>(TestImages.TINY_IMAGE)\n                .withStartupCheckStrategy(new NoopStartupCheckStrategy())\n                .withCopyFileToContainer(MountableFile.forClasspathResource(\"test_copy_to_container.txt\"), \"/tmp/test\")\n                .withCopyToContainer(Transferable.of(\"test\"), \"/tmp/test\")\n                .waitingFor(new WaitForExitedState(state -> state.getExitCodeLong() > 0))\n                .withCommand(\"sh\", \"-c\", \"grep -q test /tmp/test && exit 100\")\n        ) {\n            assertThatThrownBy(container::start)\n                .hasStackTraceContaining(\"Wait strategy failed. Container exited with code 100\")\n                .hasStackTraceContaining(\"Nope!\");\n        }\n    }\n\n    @Test\n    void shouldOnlyPublishExposedPorts() {\n        ImageFromDockerfile image = new ImageFromDockerfile(\"publish-multiple\")\n            .withDockerfileFromBuilder(builder -> {\n                builder\n                    .from(\"testcontainers/helloworld:1.1.0\") //\n                    .expose(8080, 8081)\n                    .build();\n            });\n        try (GenericContainer<?> container = new GenericContainer<>(image).withExposedPorts(8080)) {\n            container.start();\n\n            InspectContainerResponse inspectedContainer = container.getContainerInfo();\n\n            List<Integer> exposedPorts = Arrays\n                .stream(inspectedContainer.getConfig().getExposedPorts())\n                .map(ExposedPort::getPort)\n                .collect(Collectors.toList());\n\n            assertThat(exposedPorts).as(\"the exposed ports are all of those EXPOSEd by the image\").contains(8080, 8081);\n\n            Map<ExposedPort, Ports.Binding[]> hostBindings = inspectedContainer\n                .getHostConfig()\n                .getPortBindings()\n                .getBindings();\n            assertThat(hostBindings).as(\"only 1 port is bound on the host (published)\").hasSize(1);\n\n            Integer mappedPort = container.getMappedPort(8080);\n            assertThat(mappedPort != 8080).as(\"port 8080 is bound to a different port on the host\").isTrue();\n\n            assertThat(catchThrowable(() -> container.getMappedPort(8081)))\n                .as(\"trying to get a non-bound port mapping fails\")\n                .isInstanceOf(IllegalArgumentException.class);\n        }\n    }\n\n    @Test\n    void shouldWaitUntilExposedPortIsMapped() {\n        ImageFromDockerfile image = new ImageFromDockerfile(\"publish-multiple\")\n            .withDockerfileFromBuilder(builder -> {\n                builder\n                    .from(\"testcontainers/helloworld:1.1.0\")\n                    .expose(8080, 8081) // one additional port exposed in image\n                    .build();\n            });\n\n        try (\n            GenericContainer container = new GenericContainer<>(image)\n                .withExposedPorts(8080)\n                .withCreateContainerCmdModifier(it -> it.withExposedPorts(ExposedPort.tcp(8082))) // another port exposed by modifier\n        ) {\n            container.start();\n\n            assertThat(container.getExposedPorts()).as(\"Only withExposedPort should be exposed\").hasSize(1);\n            assertThat(container.getExposedPorts()).as(\"withExposedPort should be exposed\").contains(8080);\n        }\n    }\n\n    @Test\n    void testArchitectureCheck() {\n        assumeThat(DockerClientFactory.instance().client().versionCmd().exec().getArch()).isNotEqualTo(\"amd64\");\n        // Choose an image that is *different* from the server architecture--this ensures we always get a warning.\n        final String image;\n        if (DockerClientFactory.instance().client().versionCmd().exec().getArch().equals(\"amd64\")) {\n            // arm64 image\n            image = \"testcontainers/sshd@sha256:f701fa4ae2cd25ad2b2ea2df1aad00980f67bacdd03958a2d7d52ee63d7fb3e8\";\n        } else {\n            // amd64 image\n            image = \"testcontainers/sshd@sha256:7879c6c99eeab01f1c6beb2c240d49a70430ef2d52f454765ec9707f547ef6f1\";\n        }\n\n        try (GenericContainer container = new GenericContainer<>(image)) {\n            // Grab a copy of everything that is logged when we start the container\n            ch.qos.logback.classic.Logger logger = (ch.qos.logback.classic.Logger) container.logger();\n            ListAppender<ILoggingEvent> listAppender = new ListAppender<>();\n            listAppender.start();\n            logger.addAppender(listAppender);\n\n            container.start();\n\n            String regexMatch = \"The architecture '\\\\S+' for image .*\";\n            assertThat(listAppender.list)\n                .describedAs(\n                    \"Received log list does not have a message matching '\" +\n                    regexMatch +\n                    \"': \" +\n                    listAppender.list.toString()\n                )\n                .filteredOn(event -> event.getMessage().matches(regexMatch))\n                .isNotEmpty();\n        }\n    }\n\n    @Test\n    void shouldReturnTheProvidedImage() {\n        GenericContainer container = new GenericContainer(TestImages.REDIS_IMAGE);\n        assertThat(container.getImage().get()).isEqualTo(\"redis:6-alpine\");\n        container.setImage(new RemoteDockerImage(TestImages.ALPINE_IMAGE));\n        assertThat(container.getImage().get()).isEqualTo(\"alpine:3.17\");\n    }\n\n    @Test\n    void shouldContainDefaultNetworkAlias() {\n        try (GenericContainer<?> container = new GenericContainer<>(\"testcontainers/helloworld:1.1.0\")) {\n            container.start();\n            assertThat(container.getNetworkAliases()).hasSize(1);\n        }\n    }\n\n    @Test\n    void shouldContainDefaultNetworkAliasWhenUsingGenericContainer() {\n        try (HelloWorldContainer container = new HelloWorldContainer(\"testcontainers/helloworld:1.1.0\")) {\n            container.start();\n            assertThat(container.getNetworkAliases()).hasSize(1);\n        }\n    }\n\n    @Test\n    void shouldContainDefaultNetworkAliasWhenUsingContainerDef() {\n        try (TcHelloWorldContainer container = new TcHelloWorldContainer(\"testcontainers/helloworld:1.1.0\")) {\n            container.start();\n            assertThat(container.getNetworkAliases()).hasSize(1);\n        }\n    }\n\n    @Test\n    void shouldRespectWaitStrategy() {\n        try (\n            HelloWorldLogStrategyContainer container = new HelloWorldLogStrategyContainer(\n                \"testcontainers/helloworld:1.1.0\"\n            )\n        ) {\n            container.setWaitStrategy(Wait.forLogMessage(\".*Starting server on port.*\", 1));\n            container.start();\n            assertThat((LogMessageWaitStrategy) container.getWaitStrategy())\n                .extracting(\"regEx\", \"times\")\n                .containsExactly(\".*Starting server on port.*\", 1);\n        }\n    }\n\n    @Test\n    void testStartupAttemptsDoesNotLeaveContainersRunningWhenWrongWaitStrategyIsUsed() {\n        try (\n            GenericContainer<?> container = new GenericContainer<>(TestImages.TINY_IMAGE)\n                .withLabel(\"waitstrategy\", \"wrong\")\n                .withStartupAttempts(3)\n                .waitingFor(\n                    Wait.forLogMessage(\"this text does not exist in logs\", 1).withStartupTimeout(Duration.ofMillis(1))\n                )\n                .withCommand(\"tail\", \"-f\", \"/dev/null\");\n        ) {\n            assertThatThrownBy(container::start).hasStackTraceContaining(\"Retry limit hit with exception\");\n        }\n        assertThat(reportLeakedContainers()).isEmpty();\n    }\n\n    private static Optional<String> reportLeakedContainers() {\n        @SuppressWarnings(\"resource\") // Throws when close is attempted, as this is a global instance.\n        DockerClient dockerClient = DockerClientFactory.lazyClient();\n\n        List<Container> containers = dockerClient\n            .listContainersCmd()\n            .withAncestorFilter(Collections.singletonList(\"alpine:3.17\"))\n            .withLabelFilter(\n                Arrays.asList(\n                    DockerClientFactory.TESTCONTAINERS_SESSION_ID_LABEL + \"=\" + DockerClientFactory.SESSION_ID,\n                    \"waitstrategy=wrong\"\n                )\n            )\n            // ignore status \"exited\" - for example, failed containers after using `withStartupAttempts()`\n            .withStatusFilter(Arrays.asList(\"created\", \"restarting\", \"running\", \"paused\"))\n            .exec()\n            .stream()\n            .collect(ImmutableList.toImmutableList());\n\n        if (containers.isEmpty()) {\n            return Optional.empty();\n        }\n\n        return Optional.of(\n            String.format(\n                \"Leaked containers: %s\",\n                containers\n                    .stream()\n                    .map(container -> {\n                        return MoreObjects\n                            .toStringHelper(\"container\")\n                            .add(\"id\", container.getId())\n                            .add(\"image\", container.getImage())\n                            .add(\"imageId\", container.getImageId())\n                            .toString();\n                    })\n                    .collect(Collectors.joining(\", \", \"[\", \"]\"))\n            )\n        );\n    }\n\n    static class NoopStartupCheckStrategy extends StartupCheckStrategy {\n\n        @Override\n        public StartupStatus checkStartupState(DockerClient dockerClient, String containerId) {\n            return StartupStatus.SUCCESSFUL;\n        }\n    }\n\n    @RequiredArgsConstructor\n    @FieldDefaults(makeFinal = true)\n    @Slf4j\n    static class WaitForExitedState extends AbstractWaitStrategy {\n\n        Predicate<ContainerState> predicate;\n\n        @Override\n        @SneakyThrows\n        protected void waitUntilReady() {\n            Unreliables.retryUntilTrue(\n                5,\n                TimeUnit.SECONDS,\n                () -> {\n                    ContainerState state = waitStrategyTarget.getCurrentContainerInfo().getState();\n\n                    log.debug(\"Current state: {}\", state);\n                    if (!\"exited\".equalsIgnoreCase(state.getStatus())) {\n                        Thread.sleep(100);\n                        return false;\n                    }\n                    return predicate.test(state);\n                }\n            );\n\n            throw new IllegalStateException(\"Nope!\");\n        }\n    }\n\n    static class HelloWorldContainer extends GenericContainer<HelloWorldContainer> {\n\n        public HelloWorldContainer(String image) {\n            super(DockerImageName.parse(image));\n            withExposedPorts(8080);\n        }\n    }\n\n    static class TcHelloWorldContainer extends GenericContainer<HelloWorldContainer> {\n\n        public TcHelloWorldContainer(String image) {\n            super(DockerImageName.parse(image));\n        }\n\n        @Override\n        ContainerDef createContainerDef() {\n            return new HelloWorldContainerDef();\n        }\n\n        class HelloWorldContainerDef extends ContainerDef {\n\n            HelloWorldContainerDef() {\n                addExposedTcpPort(8080);\n            }\n        }\n    }\n\n    static class HelloWorldLogStrategyContainer extends GenericContainer<HelloWorldContainer> {\n\n        public HelloWorldLogStrategyContainer(String image) {\n            super(DockerImageName.parse(image));\n            withExposedPorts(8080);\n            waitingFor(Wait.forLogMessage(\".*Starting server on port.*\", 2));\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/containers/JibTest.java",
    "content": "package org.testcontainers.containers;\n\nimport com.github.dockerjava.api.command.InspectImageResponse;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.DockerClientFactory;\nimport org.testcontainers.containers.output.OutputFrame.OutputType;\nimport org.testcontainers.containers.startupcheck.OneShotStartupCheckStrategy;\nimport org.testcontainers.jib.JibImage;\n\nimport java.time.Duration;\nimport java.util.Collections;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass JibTest {\n\n    @Test\n    void buildImage() {\n        try (\n            // jibContainerUsage {\n            GenericContainer<?> busybox = new GenericContainer<>(\n                new JibImage(\n                    \"busybox:1.35\",\n                    jibContainerBuilder -> {\n                        return jibContainerBuilder.setEntrypoint(\"echo\", \"Hello World\");\n                    }\n                )\n            )\n                .withStartupCheckStrategy(new OneShotStartupCheckStrategy().withTimeout(Duration.ofSeconds(3)))\n            // }\n        ) {\n            busybox.start();\n            String logs = busybox.getLogs(OutputType.STDOUT);\n            assertThat(logs).contains(\"Hello World\");\n        }\n    }\n\n    @Test\n    void standardLabelsAreAddedWhenUsingJibSetLabels() {\n        try (\n            GenericContainer<?> busybox = new GenericContainer<>(\n                new JibImage(\n                    \"busybox:1.35\",\n                    jibContainerBuilder -> {\n                        return jibContainerBuilder\n                            .setEntrypoint(\"echo\", \"Hello World\")\n                            .setLabels(Collections.singletonMap(\"foo\", \"bar\"));\n                    }\n                )\n            )\n                .withStartupCheckStrategy(new OneShotStartupCheckStrategy().withTimeout(Duration.ofSeconds(3)))\n        ) {\n            busybox.start();\n            assertImageLabels(busybox);\n        }\n    }\n\n    @Test\n    void standardLabelsAreAddedWhenUsingJibAddLabel() {\n        try (\n            GenericContainer<?> busybox = new GenericContainer<>(\n                new JibImage(\n                    \"busybox:1.35\",\n                    jibContainerBuilder -> {\n                        return jibContainerBuilder.setEntrypoint(\"echo\", \"Hello World\").addLabel(\"foo\", \"bar\");\n                    }\n                )\n            )\n                .withStartupCheckStrategy(new OneShotStartupCheckStrategy().withTimeout(Duration.ofSeconds(3)))\n        ) {\n            busybox.start();\n            assertImageLabels(busybox);\n        }\n    }\n\n    private static void assertImageLabels(GenericContainer<?> busybox) {\n        String image = busybox.getContainerInfo().getConfig().getImage();\n        InspectImageResponse imageResponse = DockerClientFactory.lazyClient().inspectImageCmd(image).exec();\n        assertThat(imageResponse.getConfig().getLabels())\n            .containsEntry(\"foo\", \"bar\")\n            .containsKeys(\n                \"org.testcontainers\",\n                \"org.testcontainers.sessionId\",\n                \"org.testcontainers.lang\",\n                \"org.testcontainers.version\"\n            );\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/containers/MultiStageBuildTest.java",
    "content": "package org.testcontainers.containers;\n\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.images.builder.ImageFromDockerfile;\n\nimport java.io.IOException;\nimport java.nio.file.Paths;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass MultiStageBuildTest {\n\n    @Test\n    void testDockerMultistageBuild() throws IOException, InterruptedException {\n        try (\n            GenericContainer<?> container = new GenericContainer<>(\n                new ImageFromDockerfile()\n                    .withDockerfile(Paths.get(\"src/test/resources/Dockerfile-multistage\"))\n                    .withTarget(\"builder\")\n            )\n                .withCommand(\"/bin/sh\", \"-c\", \"sleep 10\")\n        ) {\n            container.start();\n            assertThat(container.execInContainer(\"pwd\").getStdout()).contains(\"/my-files\");\n            assertThat(container.execInContainer(\"ls\").getStdout()).contains(\"hello.txt\");\n        }\n    }\n\n    @Test\n    void shouldBuildMultistageBuildWithBuildImageCmdModifier() throws IOException, InterruptedException {\n        try (\n            GenericContainer<?> container = new GenericContainer<>(\n                new ImageFromDockerfile()\n                    .withDockerfile(Paths.get(\"src/test/resources/Dockerfile-multistage\"))\n                    .withBuildImageCmdModifier(cmd -> cmd.withTarget(\"builder\"))\n            )\n                .withCommand(\"/bin/sh\", \"-c\", \"sleep 10\")\n        ) {\n            container.start();\n            assertThat(container.execInContainer(\"pwd\").getStdout()).contains(\"/my-files\");\n            assertThat(container.execInContainer(\"ls\").getStdout()).contains(\"hello.txt\");\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/containers/NetworkTest.java",
    "content": "package org.testcontainers.containers;\n\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.DockerClientFactory;\nimport org.testcontainers.TestImages;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass NetworkTest {\n\n    @Nested\n    class WithRules {\n\n        public Network network = Network.newNetwork();\n\n        public GenericContainer<?> foo = new GenericContainer<>(TestImages.TINY_IMAGE)\n            .withNetwork(network)\n            .withNetworkAliases(\"foo\")\n            .withCommand(\"/bin/sh\", \"-c\", \"while true ; do printf 'HTTP/1.1 200 OK\\\\n\\\\nyay' | nc -l -p 8080; done\");\n\n        public GenericContainer<?> bar = new GenericContainer<>(TestImages.TINY_IMAGE)\n            .withNetwork(network)\n            .withCommand(\"top\");\n\n        void testNetworkSupport() throws Exception {\n            foo.start();\n            bar.start();\n            String response = bar.execInContainer(\"wget\", \"-O\", \"-\", \"http://foo:8080\").getStdout();\n            assertThat(response).as(\"received response\").isEqualTo(\"yay\");\n        }\n    }\n\n    @Nested\n    class WithoutRules {\n\n        @Test\n        void testNetworkSupport() throws Exception {\n            // useCustomNetwork {\n            try (\n                Network network = Network.newNetwork();\n                GenericContainer<?> foo = new GenericContainer<>(TestImages.TINY_IMAGE)\n                    .withNetwork(network)\n                    .withNetworkAliases(\"foo\")\n                    .withCommand(\n                        \"/bin/sh\",\n                        \"-c\",\n                        \"while true ; do printf 'HTTP/1.1 200 OK\\\\n\\\\nyay' | nc -l -p 8080; done\"\n                    );\n                GenericContainer<?> bar = new GenericContainer<>(TestImages.TINY_IMAGE)\n                    .withNetwork(network)\n                    .withCommand(\"top\")\n            ) {\n                foo.start();\n                bar.start();\n\n                String response = bar.execInContainer(\"wget\", \"-O\", \"-\", \"http://foo:8080\").getStdout();\n                assertThat(response).as(\"received response\").isEqualTo(\"yay\");\n            }\n            // }\n        }\n\n        @Test\n        void testBuilder() {\n            try (Network network = Network.builder().driver(\"macvlan\").build()) {\n                String id = network.getId();\n                assertThat(\n                    DockerClientFactory.instance().client().inspectNetworkCmd().withNetworkId(id).exec().getDriver()\n                )\n                    .as(\"Flag is set\")\n                    .isEqualTo(\"macvlan\");\n            }\n        }\n\n        @Test\n        void testModifiers() {\n            try (\n                Network network = Network.builder().createNetworkCmdModifier(cmd -> cmd.withDriver(\"macvlan\")).build()\n            ) {\n                String id = network.getId();\n                assertThat(\n                    DockerClientFactory.instance().client().inspectNetworkCmd().withNetworkId(id).exec().getDriver()\n                )\n                    .as(\"Flag is set\")\n                    .isEqualTo(\"macvlan\");\n            }\n        }\n\n        @Test\n        void testReusability() {\n            try (Network network = Network.newNetwork()) {\n                String firstId = network.getId();\n                assertThat(DockerClientFactory.instance().client().inspectNetworkCmd().withNetworkId(firstId).exec())\n                    .as(\"Network exists\")\n                    .isNotNull();\n\n                network.close();\n\n                assertThat(\n                    DockerClientFactory\n                        .instance()\n                        .client()\n                        .inspectNetworkCmd()\n                        .withNetworkId(network.getId())\n                        .exec()\n                        .getId()\n                )\n                    .as(\"New network created\")\n                    .isNotEqualTo(firstId);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/containers/ParsedDockerComposeFileBean.java",
    "content": "package org.testcontainers.containers;\n\npublic class ParsedDockerComposeFileBean {\n\n    public String foo;\n\n    public ParsedDockerComposeFileBean(String foo) {\n        this.foo = foo;\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/containers/ParsedDockerComposeFileValidationTest.java",
    "content": "package org.testcontainers.containers;\n\nimport com.google.common.collect.ImmutableMap;\nimport com.google.common.collect.Sets;\nimport lombok.SneakyThrows;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\n\nimport java.io.File;\nimport java.io.PrintWriter;\nimport java.nio.file.Path;\nimport java.util.Collections;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatNoException;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\nimport static org.assertj.core.api.Assertions.entry;\n\nclass ParsedDockerComposeFileValidationTest {\n\n    @TempDir\n    public Path temporaryFolder;\n\n    @Test\n    void shouldValidate() {\n        File file = new File(\"src/test/resources/docker-compose-container-name-v1.yml\");\n        assertThatThrownBy(() -> {\n                new ParsedDockerComposeFile(file);\n            })\n            .hasMessageContaining(file.getAbsolutePath())\n            .hasMessageContaining(\"'container_name' property set for service 'redis'\");\n    }\n\n    @Test\n    void shouldRejectContainerNameV1() {\n        assertThatThrownBy(() -> {\n                new ParsedDockerComposeFile(ImmutableMap.of(\"redis\", ImmutableMap.of(\"container_name\", \"redis\")));\n            })\n            .hasMessageContaining(\"'container_name' property set for service 'redis'\");\n    }\n\n    @Test\n    void shouldRejectContainerNameV2() {\n        assertThatThrownBy(() -> {\n                new ParsedDockerComposeFile(\n                    ImmutableMap.of(\n                        \"version\",\n                        \"2\",\n                        \"services\",\n                        ImmutableMap.of(\"redis\", ImmutableMap.of(\"container_name\", \"redis\"))\n                    )\n                );\n            })\n            .hasMessageContaining(\"'container_name' property set for service 'redis'\");\n    }\n\n    @Test\n    void shouldIgnoreUnknownStructure() {\n        // Everything is a list\n        new ParsedDockerComposeFile(Collections.emptyMap());\n\n        // services is not a map but List\n        new ParsedDockerComposeFile(ImmutableMap.of(\"version\", \"2\", \"services\", Collections.emptyList()));\n\n        // services is not a collection\n        new ParsedDockerComposeFile(ImmutableMap.of(\"version\", \"2\", \"services\", true));\n\n        // no services while version is defined\n        new ParsedDockerComposeFile(ImmutableMap.of(\"version\", \"9000\"));\n    }\n\n    @Test\n    @SneakyThrows\n    void shouldRejectDeserializationOfArbitraryClasses() {\n        // Reject deserialization gadget chain attacks: https://nvd.nist.gov/vuln/detail/CVE-2022-1471\n        // https://raw.githubusercontent.com/mbechler/marshalsec/master/marshalsec.pdf\n\n        File file = new File(\"src/test/resources/docker-compose-deserialization.yml\");\n\n        // ParsedDockerComposeFile should reject deserialization of ParsedDockerComposeFileBean\n        assertThatThrownBy(() -> {\n                new ParsedDockerComposeFile(file);\n            })\n            .hasMessageContaining(file.getAbsolutePath())\n            .hasMessageContaining(\"Unable to parse YAML file\");\n    }\n\n    @Test\n    void shouldObtainImageNamesV1() {\n        File file = new File(\"src/test/resources/docker-compose-imagename-parsing-v1.yml\");\n        ParsedDockerComposeFile parsedFile = new ParsedDockerComposeFile(file);\n        assertThat(parsedFile.getServiceNameToImageNames())\n            .as(\"all defined images are found\")\n            .contains(\n                entry(\"mysql\", Sets.newHashSet(\"mysql\")),\n                entry(\"redis\", Sets.newHashSet(\"redis\")),\n                entry(\"custom\", Sets.newHashSet(\"postgres\"))\n            ); // redis, mysql from compose file, postgres from Dockerfile build\n    }\n\n    @Test\n    void shouldObtainImageNamesV2() {\n        File file = new File(\"src/test/resources/docker-compose-imagename-parsing-v2.yml\");\n        ParsedDockerComposeFile parsedFile = new ParsedDockerComposeFile(file);\n        assertThat(parsedFile.getServiceNameToImageNames())\n            .as(\"all defined images are found\")\n            .contains(\n                entry(\"mysql\", Sets.newHashSet(\"mysql\")),\n                entry(\"redis\", Sets.newHashSet(\"redis\")),\n                entry(\"custom\", Sets.newHashSet(\"postgres\"))\n            );\n    }\n\n    @Test\n    void shouldObtainImageNamesV2WithNoVersionTag() {\n        File file = new File(\"src/test/resources/docker-compose-imagename-parsing-v2-no-version.yml\");\n        ParsedDockerComposeFile parsedFile = new ParsedDockerComposeFile(file);\n        assertThat(parsedFile.getServiceNameToImageNames())\n            .as(\"all defined images are found\")\n            .contains(\n                entry(\"mysql\", Sets.newHashSet(\"mysql\")),\n                entry(\"redis\", Sets.newHashSet(\"redis\")),\n                entry(\"custom\", Sets.newHashSet(\"postgres\"))\n            );\n    }\n\n    @Test\n    void shouldObtainImageFromDockerfileBuild() {\n        File file = new File(\"src/test/resources/docker-compose-imagename-parsing-dockerfile.yml\");\n        ParsedDockerComposeFile parsedFile = new ParsedDockerComposeFile(file);\n        assertThat(parsedFile.getServiceNameToImageNames())\n            .as(\"all defined images are found\")\n            .contains(\n                entry(\"mysql\", Sets.newHashSet(\"mysql\")),\n                entry(\"redis\", Sets.newHashSet(\"redis\")),\n                entry(\"custom\", Sets.newHashSet(\"alpine:3.17\"))\n            ); // r/ redis, mysql from compose file, alpine:3.17 from Dockerfile build\n    }\n\n    @Test\n    void shouldObtainImageFromDockerfileBuildWithContext() {\n        File file = new File(\"src/test/resources/docker-compose-imagename-parsing-dockerfile-with-context.yml\");\n        ParsedDockerComposeFile parsedFile = new ParsedDockerComposeFile(file);\n        assertThat(parsedFile.getServiceNameToImageNames())\n            .as(\"all defined images are found\")\n            .contains(\n                entry(\"mysql\", Sets.newHashSet(\"mysql\")),\n                entry(\"redis\", Sets.newHashSet(\"redis\")),\n                entry(\"custom\", Sets.newHashSet(\"alpine:3.17\"))\n            ); // redis, mysql from compose file, alpine:3.17 from Dockerfile build\n    }\n\n    @Test\n    void shouldSupportALotOfAliases() throws Exception {\n        File file = temporaryFolder.resolve(\"tmp-docker-compose.yml\").toFile();\n        try (PrintWriter writer = new PrintWriter(file)) {\n            writer.println(\"x-entry: &entry\");\n            writer.println(\"  key: value\");\n            writer.println();\n            writer.println(\"services:\");\n            for (int i = 0; i < 1_000; i++) {\n                writer.println(\"  service\" + i + \":\");\n                writer.println(\"    image: busybox\");\n                writer.println(\"    environment:\");\n                writer.println(\"      <<: *entry\");\n            }\n        }\n        assertThatNoException().isThrownBy(() -> new ParsedDockerComposeFile(file));\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/containers/ReusabilityUnitTests.java",
    "content": "package org.testcontainers.containers;\n\nimport com.fasterxml.jackson.core.type.TypeReference;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.github.dockerjava.api.DockerClient;\nimport com.github.dockerjava.api.command.CreateContainerCmd;\nimport com.github.dockerjava.api.command.CreateContainerResponse;\nimport com.github.dockerjava.api.command.InspectContainerCmd;\nimport com.github.dockerjava.api.command.InspectContainerResponse;\nimport com.github.dockerjava.api.command.ListContainersCmd;\nimport com.github.dockerjava.api.command.StartContainerCmd;\nimport com.github.dockerjava.api.model.Container;\nimport com.github.dockerjava.core.command.CreateContainerCmdImpl;\nimport com.github.dockerjava.core.command.InspectContainerCmdImpl;\nimport com.github.dockerjava.core.command.ListContainersCmdImpl;\nimport com.github.dockerjava.core.command.StartContainerCmdImpl;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.mockito.Answers;\nimport org.mockito.Mockito;\nimport org.mockito.stubbing.Answer;\nimport org.testcontainers.DockerClientFactory;\nimport org.testcontainers.TestImages;\nimport org.testcontainers.containers.startupcheck.StartupCheckStrategy;\nimport org.testcontainers.containers.wait.strategy.AbstractWaitStrategy;\nimport org.testcontainers.utility.MockTestcontainersConfigurationExtension;\nimport org.testcontainers.utility.MountableFile;\nimport org.testcontainers.utility.TestcontainersConfiguration;\n\nimport java.io.File;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.function.Consumer;\nimport java.util.function.Function;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assumptions.assumeThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.when;\n\nclass ReusabilityUnitTests {\n\n    @Nested\n    class CanBeReusedTest {\n\n        public static Object[][] data() {\n            return new Object[][] {\n                { \"generic\", new GenericContainer<>(TestImages.TINY_IMAGE), true },\n                { \"anonymous generic\", new GenericContainer(TestImages.TINY_IMAGE) {}, true },\n                { \"custom\", new CustomContainer(), true },\n                { \"anonymous custom\", new CustomContainer() {}, true },\n                { \"custom with containerIsCreated\", new CustomContainerWithContainerIsCreated(), false },\n            };\n        }\n\n        @ParameterizedTest(name = \"{0}\")\n        @MethodSource(\"data\")\n        void shouldBeReusable(String name, GenericContainer container, boolean reusable) {\n            if (reusable) {\n                assertThat(container.canBeReused()).as(\"Is reusable\").isTrue();\n            } else {\n                assertThat(container.canBeReused()).as(\"Is not reusable\").isFalse();\n            }\n        }\n\n        static class CustomContainer extends GenericContainer<CustomContainer> {\n\n            CustomContainer() {\n                super(TestImages.TINY_IMAGE);\n            }\n        }\n\n        static class CustomContainerWithContainerIsCreated\n            extends GenericContainer<CustomContainerWithContainerIsCreated> {\n\n            CustomContainerWithContainerIsCreated() {\n                super(TestImages.TINY_IMAGE);\n            }\n\n            @Override\n            protected void containerIsCreated(String containerId) {\n                super.containerIsCreated(containerId);\n            }\n        }\n    }\n\n    @Nested\n    class HooksTest extends AbstractReusabilityTest {\n\n        List<String> script = new ArrayList<>();\n\n        GenericContainer<?> container = makeReusable(\n            new GenericContainer(TestImages.TINY_IMAGE) {\n                @Override\n                protected boolean canBeReused() {\n                    // Because we override \"containerIsCreated\"\n                    return true;\n                }\n\n                @Override\n                protected void containerIsCreated(String containerId) {\n                    script.add(\"containerIsCreated\");\n                }\n\n                @Override\n                protected void containerIsStarting(InspectContainerResponse containerInfo, boolean reused) {\n                    script.add(\"containerIsStarting(reused=\" + reused + \")\");\n                }\n\n                @Override\n                protected void containerIsStarted(InspectContainerResponse containerInfo, boolean reused) {\n                    script.add(\"containerIsStarted(reused=\" + reused + \")\");\n                }\n            }\n        );\n\n        @Test\n        void shouldSetLabelsIfEnvironmentDoesNotSupportReuse() {\n            Mockito.doReturn(false).when(TestcontainersConfiguration.getInstance()).environmentSupportsReuse();\n\n            String containerId = randomContainerId();\n            when(client.createContainerCmd(any())).then(createContainerAnswer(containerId));\n            when(client.listContainersCmd()).then(listContainersAnswer());\n            when(client.startContainerCmd(containerId)).then(startContainerAnswer());\n            when(client.inspectContainerCmd(containerId)).then(inspectContainerAnswer());\n\n            container.start();\n            assertThat(script)\n                .containsExactly(\n                    \"containerIsCreated\",\n                    \"containerIsStarting(reused=false)\",\n                    \"containerIsStarted(reused=false)\"\n                );\n        }\n\n        @Test\n        void shouldCallHookIfReused() {\n            Mockito.doReturn(true).when(TestcontainersConfiguration.getInstance()).environmentSupportsReuse();\n            String containerId = randomContainerId();\n            when(client.createContainerCmd(any())).then(createContainerAnswer(containerId));\n            String existingContainerId = randomContainerId();\n            when(client.listContainersCmd()).then(listContainersAnswer(existingContainerId));\n            when(client.inspectContainerCmd(existingContainerId)).then(inspectContainerAnswer());\n\n            container.start();\n            assertThat(script).containsExactly(\"containerIsStarting(reused=true)\", \"containerIsStarted(reused=true)\");\n        }\n\n        @Test\n        void shouldNotCallHookIfNotReused() {\n            String containerId = randomContainerId();\n            when(client.createContainerCmd(any())).then(createContainerAnswer(containerId));\n            when(client.listContainersCmd()).then(listContainersAnswer());\n            when(client.startContainerCmd(containerId)).then(startContainerAnswer());\n            when(client.inspectContainerCmd(containerId)).then(inspectContainerAnswer());\n\n            container.start();\n            assertThat(script)\n                .containsExactly(\n                    \"containerIsCreated\",\n                    \"containerIsStarting(reused=false)\",\n                    \"containerIsStarted(reused=false)\"\n                );\n        }\n    }\n\n    @Nested\n    class HashTest extends AbstractReusabilityTest {\n\n        protected GenericContainer<?> container = makeReusable(\n            new GenericContainer(TestImages.TINY_IMAGE) {\n                @Override\n                public void copyFileToContainer(MountableFile mountableFile, String containerPath) {\n                    // NOOP\n                }\n            }\n        );\n\n        @Test\n        void shouldStartIfListReturnsEmpty() {\n            String containerId = randomContainerId();\n            when(client.createContainerCmd(any())).then(createContainerAnswer(containerId));\n            when(client.listContainersCmd()).then(listContainersAnswer());\n            when(client.startContainerCmd(containerId)).then(startContainerAnswer());\n            when(client.inspectContainerCmd(containerId)).then(inspectContainerAnswer());\n\n            container.start();\n\n            Mockito.verify(client, Mockito.atLeastOnce()).startContainerCmd(containerId);\n        }\n\n        @Test\n        void shouldReuseIfListReturnsID() {\n            Mockito.doReturn(true).when(TestcontainersConfiguration.getInstance()).environmentSupportsReuse();\n            String containerId = randomContainerId();\n            when(client.createContainerCmd(any())).then(createContainerAnswer(containerId));\n            String existingContainerId = randomContainerId();\n            when(client.listContainersCmd()).then(listContainersAnswer(existingContainerId));\n            when(client.inspectContainerCmd(existingContainerId)).then(inspectContainerAnswer());\n\n            container.start();\n\n            Mockito.verify(client, Mockito.never()).startContainerCmd(containerId);\n            Mockito.verify(client, Mockito.never()).startContainerCmd(existingContainerId);\n        }\n\n        @Test\n        void shouldSetLabelsIfEnvironmentDoesNotSupportReuse() {\n            Mockito.doReturn(false).when(TestcontainersConfiguration.getInstance()).environmentSupportsReuse();\n            AtomicReference<CreateContainerCmd> commandRef = new AtomicReference<>();\n            String containerId = randomContainerId();\n            when(client.createContainerCmd(any())).then(createContainerAnswer(containerId, commandRef::set));\n            when(client.startContainerCmd(containerId)).then(startContainerAnswer());\n            when(client.inspectContainerCmd(containerId)).then(inspectContainerAnswer());\n\n            container.start();\n\n            assertThat(commandRef)\n                .isNotNull()\n                .satisfies(command -> {\n                    assertThat(command.get().getLabels())\n                        .containsKeys(DockerClientFactory.TESTCONTAINERS_SESSION_ID_LABEL);\n                });\n        }\n\n        @Test\n        void shouldSetCopiedFilesHashLabel() {\n            Mockito.doReturn(true).when(TestcontainersConfiguration.getInstance()).environmentSupportsReuse();\n            AtomicReference<CreateContainerCmd> commandRef = new AtomicReference<>();\n            String containerId = randomContainerId();\n            when(client.createContainerCmd(any())).then(createContainerAnswer(containerId, commandRef::set));\n            when(client.listContainersCmd()).then(listContainersAnswer());\n            when(client.startContainerCmd(containerId)).then(startContainerAnswer());\n            when(client.inspectContainerCmd(containerId)).then(inspectContainerAnswer());\n\n            container.start();\n\n            assertThat(commandRef).isNotNull();\n            assertThat(commandRef.get().getLabels()).containsKeys(GenericContainer.COPIED_FILES_HASH_LABEL);\n        }\n\n        @Test\n        void shouldHashCopiedFiles() {\n            Mockito.doReturn(true).when(TestcontainersConfiguration.getInstance()).environmentSupportsReuse();\n            AtomicReference<CreateContainerCmd> commandRef = new AtomicReference<>();\n            String containerId = randomContainerId();\n            when(client.createContainerCmd(any())).then(createContainerAnswer(containerId, commandRef::set));\n            when(client.listContainersCmd()).then(listContainersAnswer());\n            when(client.startContainerCmd(containerId)).then(startContainerAnswer());\n            when(client.inspectContainerCmd(containerId)).then(inspectContainerAnswer());\n\n            container.start();\n\n            assertThat(commandRef).isNotNull();\n\n            Map<String, String> labels = commandRef.get().getLabels();\n            assertThat(labels).containsKeys(GenericContainer.COPIED_FILES_HASH_LABEL);\n\n            String oldHash = labels.get(GenericContainer.COPIED_FILES_HASH_LABEL);\n\n            // Simulate stop\n            container.containerId = null;\n\n            container.withCopyFileToContainer(\n                MountableFile.forClasspathResource(\"test_copy_to_container.txt\"),\n                \"/foo/bar\"\n            );\n            container.start();\n\n            assertThat(commandRef.get().getLabels())\n                .hasEntrySatisfying(\n                    GenericContainer.COPIED_FILES_HASH_LABEL,\n                    newHash -> {\n                        assertThat(newHash).as(\"new hash\").isNotEqualTo(oldHash);\n                    }\n                );\n        }\n    }\n\n    @Nested\n    @ParameterizedClass\n    @MethodSource(\"strategies\")\n    class CopyFilesHashTest {\n\n        private final TestStrategy strategy;\n\n        interface TestStrategy {\n            void withCopyFileToContainer(MountableFile mountableFile, String path);\n\n            void clear();\n        }\n\n        private static class MountableFileTestStrategy implements TestStrategy {\n\n            private final GenericContainer<?> container;\n\n            private MountableFileTestStrategy(GenericContainer<?> container) {\n                this.container = container;\n            }\n\n            @Override\n            public void withCopyFileToContainer(MountableFile mountableFile, String path) {\n                container.withCopyFileToContainer(mountableFile, path);\n            }\n\n            @Override\n            public void clear() {\n                container.getCopyToFileContainerPathMap().clear();\n            }\n        }\n\n        private static class TransferableTestStrategy implements TestStrategy {\n\n            private final GenericContainer<?> container;\n\n            private TransferableTestStrategy(GenericContainer<?> container) {\n                this.container = container;\n            }\n\n            @Override\n            public void withCopyFileToContainer(MountableFile mountableFile, String path) {\n                container.withCopyToContainer(mountableFile, path);\n            }\n\n            @Override\n            public void clear() {\n                container.getCopyToTransferableContainerPathMap().clear();\n            }\n        }\n\n        public static List<Function<GenericContainer<?>, TestStrategy>> strategies() {\n            return Arrays.asList(MountableFileTestStrategy::new, TransferableTestStrategy::new);\n        }\n\n        GenericContainer<?> container = new GenericContainer<>(TestImages.TINY_IMAGE);\n\n        public CopyFilesHashTest(Function<GenericContainer<?>, TestStrategy> strategyFactory) {\n            this.strategy = strategyFactory.apply(container);\n        }\n\n        @Test\n        void empty() {\n            assertThat(container.hashCopiedFiles()).isNotNull();\n        }\n\n        @Test\n        void oneFile() {\n            long emptyHash = container.hashCopiedFiles().getValue();\n\n            strategy.withCopyFileToContainer(\n                MountableFile.forClasspathResource(\"test_copy_to_container.txt\"),\n                \"/foo/bar\"\n            );\n\n            assertThat(container.hashCopiedFiles().getValue()).isNotEqualTo(emptyHash);\n        }\n\n        @Test\n        void differentPath() {\n            MountableFile mountableFile = MountableFile.forClasspathResource(\"test_copy_to_container.txt\");\n            strategy.withCopyFileToContainer(mountableFile, \"/foo/bar\");\n\n            long hash1 = container.hashCopiedFiles().getValue();\n\n            strategy.clear();\n\n            strategy.withCopyFileToContainer(mountableFile, \"/foo/baz\");\n\n            assertThat(container.hashCopiedFiles().getValue()).isNotEqualTo(hash1);\n        }\n\n        @Test\n        void detectsChangesInFile() throws Exception {\n            Path path = File.createTempFile(\"reusable_test\", \".txt\").toPath();\n            MountableFile mountableFile = MountableFile.forHostPath(path);\n            strategy.withCopyFileToContainer(mountableFile, \"/foo/bar\");\n\n            long hash1 = container.hashCopiedFiles().getValue();\n\n            Files.write(path, UUID.randomUUID().toString().getBytes());\n\n            assertThat(container.hashCopiedFiles().getValue()).isNotEqualTo(hash1);\n        }\n\n        @Test\n        void multipleFiles() {\n            strategy.withCopyFileToContainer(\n                MountableFile.forClasspathResource(\"test_copy_to_container.txt\"),\n                \"/foo/bar\"\n            );\n            long hash1 = container.hashCopiedFiles().getValue();\n\n            strategy.withCopyFileToContainer(\n                MountableFile.forClasspathResource(\"mappable-resource/test-resource.txt\"),\n                \"/foo/baz\"\n            );\n\n            assertThat(container.hashCopiedFiles().getValue()).isNotEqualTo(hash1);\n        }\n\n        @Test\n        void folder() throws Exception {\n            long emptyHash = container.hashCopiedFiles().getValue();\n\n            Path tempDirectory = Files.createTempDirectory(\"reusable_test\");\n            MountableFile mountableFile = MountableFile.forHostPath(tempDirectory);\n            strategy.withCopyFileToContainer(mountableFile, \"/foo/bar/\");\n\n            assertThat(container.hashCopiedFiles().getValue()).isNotEqualTo(emptyHash);\n        }\n\n        @Test\n        void changesInFolder() throws Exception {\n            Path tempDirectory = Files.createTempDirectory(\"reusable_test\");\n            MountableFile mountableFile = MountableFile.forHostPath(tempDirectory);\n            assertThat(new File(mountableFile.getResolvedPath())).isDirectory();\n            strategy.withCopyFileToContainer(mountableFile, \"/foo/bar/\");\n\n            long hash1 = container.hashCopiedFiles().getValue();\n\n            Path fileInFolder = Files.createFile(\n                // Create file in the sub-folder\n                Files.createDirectory(tempDirectory.resolve(\"sub\")).resolve(\"test.txt\")\n            );\n            assertThat(fileInFolder).exists();\n            Files.write(fileInFolder, UUID.randomUUID().toString().getBytes());\n\n            assertThat(container.hashCopiedFiles().getValue()).isNotEqualTo(hash1);\n        }\n\n        @Test\n        void folderAndFile() throws Exception {\n            Path tempDirectory = Files.createTempDirectory(\"reusable_test\");\n            MountableFile mountableFile = MountableFile.forHostPath(tempDirectory);\n            assertThat(new File(mountableFile.getResolvedPath())).isDirectory();\n            strategy.withCopyFileToContainer(mountableFile, \"/foo/bar/\");\n\n            long hash1 = container.hashCopiedFiles().getValue();\n\n            strategy.withCopyFileToContainer(\n                MountableFile.forClasspathResource(\"test_copy_to_container.txt\"),\n                \"/foo/baz\"\n            );\n\n            assertThat(container.hashCopiedFiles().getValue()).isNotEqualTo(hash1);\n        }\n\n        @Test\n        void filePermissions() throws Exception {\n            Path path = File.createTempFile(\"reusable_test\", \".txt\").toPath();\n            path.toFile().setExecutable(false);\n            MountableFile mountableFile = MountableFile.forHostPath(path);\n            strategy.withCopyFileToContainer(mountableFile, \"/foo/bar\");\n\n            long hash1 = container.hashCopiedFiles().getValue();\n\n            assumeThat(path.toFile().canExecute()).isFalse();\n            path.toFile().setExecutable(true);\n\n            assertThat(container.hashCopiedFiles().getValue()).isNotEqualTo(hash1);\n        }\n\n        @Test\n        void folderPermissions() throws Exception {\n            Path tempDirectory = Files.createTempDirectory(\"reusable_test\");\n            MountableFile mountableFile = MountableFile.forHostPath(tempDirectory);\n            assertThat(new File(mountableFile.getResolvedPath())).isDirectory();\n            Path subDir = Files.createDirectory(tempDirectory.resolve(\"sub\"));\n            subDir.toFile().setWritable(false);\n            assumeThat(subDir.toFile().canWrite()).isFalse();\n            strategy.withCopyFileToContainer(mountableFile, \"/foo/bar/\");\n\n            long hash1 = container.hashCopiedFiles().getValue();\n\n            subDir.toFile().setWritable(true);\n            assumeThat(subDir.toFile()).canWrite();\n\n            assertThat(container.hashCopiedFiles().getValue()).isNotEqualTo(hash1);\n        }\n    }\n\n    @ExtendWith(MockTestcontainersConfigurationExtension.class)\n    public abstract static class AbstractReusabilityTest {\n\n        protected DockerClient client = Mockito.mock(DockerClient.class);\n\n        protected <T extends GenericContainer<?>> T makeReusable(T container) {\n            container.dockerClient = client;\n            container.withNetworkMode(\"none\"); // to disable the port forwarding\n            container.withStartupCheckStrategy(\n                new StartupCheckStrategy() {\n                    @Override\n                    public boolean waitUntilStartupSuccessful(DockerClient dockerClient, String containerId) {\n                        // Skip DockerClient rate limiter\n                        return true;\n                    }\n\n                    @Override\n                    public StartupStatus checkStartupState(DockerClient dockerClient, String containerId) {\n                        return StartupStatus.SUCCESSFUL;\n                    }\n                }\n            );\n            container.waitingFor(\n                new AbstractWaitStrategy() {\n                    @Override\n                    protected void waitUntilReady() {}\n                }\n            );\n            container.withReuse(true);\n            return container;\n        }\n\n        protected String randomContainerId() {\n            return UUID.randomUUID().toString();\n        }\n\n        protected Answer<ListContainersCmd> listContainersAnswer(String... ids) {\n            return invocation -> {\n                ListContainersCmd.Exec exec = command -> {\n                    return new ObjectMapper()\n                        .convertValue(\n                            Stream.of(ids).map(id -> Collections.singletonMap(\"Id\", id)).collect(Collectors.toList()),\n                            new TypeReference<List<Container>>() {}\n                        );\n                };\n                return new ListContainersCmdImpl(exec);\n            };\n        }\n\n        protected Answer<CreateContainerCmd> createContainerAnswer(String containerId) {\n            return createContainerAnswer(containerId, command -> {});\n        }\n\n        protected Answer<CreateContainerCmd> createContainerAnswer(\n            String containerId,\n            Consumer<CreateContainerCmd> cmdConsumer\n        ) {\n            return invocation -> {\n                CreateContainerCmd.Exec exec = command -> {\n                    cmdConsumer.accept(command);\n                    CreateContainerResponse response = new CreateContainerResponse();\n                    response.setId(containerId);\n                    return response;\n                };\n                return new CreateContainerCmdImpl(exec, null, \"image:latest\");\n            };\n        }\n\n        protected Answer<StartContainerCmd> startContainerAnswer() {\n            return invocation -> {\n                StartContainerCmd.Exec exec = command -> {\n                    return null;\n                };\n                return new StartContainerCmdImpl(exec, invocation.getArgument(0));\n            };\n        }\n\n        protected Answer<InspectContainerCmd> inspectContainerAnswer() {\n            return invocation -> {\n                InspectContainerCmd.Exec exec = command -> {\n                    InspectContainerResponse stubResponse = Mockito.mock(\n                        InspectContainerResponse.class,\n                        Answers.RETURNS_DEEP_STUBS\n                    );\n                    when(stubResponse.getNetworkSettings().getPorts().getBindings()).thenReturn(Collections.emptyMap());\n                    return stubResponse;\n                };\n                return new InspectContainerCmdImpl(exec, invocation.getArgument(0));\n            };\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/containers/output/ContainerLogsTest.java",
    "content": "package org.testcontainers.containers.output;\n\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.TestImages;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.startupcheck.OneShotStartupCheckStrategy;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass ContainerLogsTest {\n\n    @Test\n    @Disabled(\"fails due to the timing of the shell's decision to flush\")\n    void getLogsReturnsAllLogsToDate() {\n        try (GenericContainer<?> container = shortLivedContainer()) {\n            container.start();\n\n            final String logs = container.getLogs();\n            assertThat(logs).as(\"stdout and stderr are reflected in the returned logs\").isEqualTo(\"stdout\\nstderr\");\n        }\n    }\n\n    @Test\n    void getLogsContainsBothOutputTypes() {\n        try (GenericContainer<?> container = shortLivedContainer()) {\n            container.start();\n\n            // docsGetAllLogs {\n            final String logs = container.getLogs();\n            // }\n            assertThat(logs).as(\"stdout is reflected in the returned logs\").contains(\"stdout\");\n            assertThat(logs).as(\"stderr is reflected in the returned logs\").contains(\"stderr\");\n        }\n    }\n\n    @Test\n    void getLogsReturnsStdOutToDate() {\n        try (GenericContainer<?> container = shortLivedContainer()) {\n            container.start();\n\n            // docsGetStdOut {\n            final String logs = container.getLogs(OutputFrame.OutputType.STDOUT);\n            // }\n            assertThat(logs).as(\"stdout is reflected in the returned logs\").contains(\"stdout\");\n        }\n    }\n\n    @Test\n    void getLogsReturnsStdErrToDate() {\n        try (GenericContainer<?> container = shortLivedContainer()) {\n            container.start();\n\n            // docsGetStdErr {\n            final String logs = container.getLogs(OutputFrame.OutputType.STDERR);\n            // }\n            assertThat(logs).as(\"stderr is reflected in the returned logs\").contains(\"stderr\");\n        }\n    }\n\n    @Test\n    void getLogsForLongRunningContainer() throws InterruptedException {\n        try (GenericContainer<?> container = longRunningContainer()) {\n            container.start();\n\n            Thread.sleep(1000L);\n\n            final String logs = container.getLogs(OutputFrame.OutputType.STDOUT);\n            assertThat(logs).as(\"stdout is reflected in the returned logs for a running container\").contains(\"seq=0\");\n        }\n    }\n\n    private static GenericContainer<?> shortLivedContainer() {\n        return new GenericContainer<>(TestImages.ALPINE_IMAGE)\n            .withCommand(\"/bin/sh\", \"-c\", \"echo -n 'stdout' && echo -n 'stderr' 1>&2\")\n            .withStartupCheckStrategy(new OneShotStartupCheckStrategy());\n    }\n\n    private static GenericContainer<?> longRunningContainer() {\n        return new GenericContainer<>(TestImages.ALPINE_IMAGE).withCommand(\"ping -c 100 127.0.0.1\");\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/containers/output/FrameConsumerResultCallbackTest.java",
    "content": "package org.testcontainers.containers.output;\n\nimport com.github.dockerjava.api.model.Frame;\nimport com.github.dockerjava.api.model.StreamType;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\nimport java.util.function.Consumer;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass FrameConsumerResultCallbackTest {\n\n    private static final String FRAME_PAYLOAD =\n        \"\\u001B[0;32mТест1\\u001B[0m\\n\\u001B[1;33mTest2\\u001B[0m\\n\\u001B[0;31mTest3\\u001B[0m\";\n\n    private static final String LOG_RESULT = \"Тест1\\nTest2\\nTest3\";\n\n    @Test\n    void passStderrFrameWithoutColors() throws IOException {\n        FrameConsumerResultCallback callback = new FrameConsumerResultCallback();\n        ToStringConsumer consumer = new ToStringConsumer();\n        callback.addConsumer(OutputFrame.OutputType.STDERR, consumer);\n        callback.onNext(new Frame(StreamType.STDERR, FRAME_PAYLOAD.getBytes()));\n        callback.close();\n        assertThat(consumer.toUtf8String()).isEqualTo(LOG_RESULT);\n    }\n\n    @Test\n    void passStderrFrameWithColors() throws IOException {\n        FrameConsumerResultCallback callback = new FrameConsumerResultCallback();\n        ToStringConsumer consumer = new ToStringConsumer().withRemoveAnsiCodes(false);\n        callback.addConsumer(OutputFrame.OutputType.STDERR, consumer);\n        callback.onNext(new Frame(StreamType.STDERR, FRAME_PAYLOAD.getBytes()));\n        callback.close();\n        assertThat(consumer.toUtf8String()).isEqualTo(FRAME_PAYLOAD);\n    }\n\n    @Test\n    void passStdoutFrameWithoutColors() throws IOException {\n        FrameConsumerResultCallback callback = new FrameConsumerResultCallback();\n        ToStringConsumer consumer = new ToStringConsumer();\n        callback.addConsumer(OutputFrame.OutputType.STDOUT, consumer);\n        callback.onNext(new Frame(StreamType.STDOUT, FRAME_PAYLOAD.getBytes()));\n        callback.close();\n        assertThat(consumer.toUtf8String()).isEqualTo(LOG_RESULT);\n    }\n\n    @Test\n    void passStdoutFrameWithColors() throws IOException {\n        FrameConsumerResultCallback callback = new FrameConsumerResultCallback();\n        ToStringConsumer consumer = new ToStringConsumer().withRemoveAnsiCodes(false);\n        callback.addConsumer(OutputFrame.OutputType.STDOUT, consumer);\n        callback.onNext(new Frame(StreamType.STDOUT, FRAME_PAYLOAD.getBytes()));\n        callback.close();\n        assertThat(consumer.toUtf8String()).isEqualTo(FRAME_PAYLOAD);\n    }\n\n    @Test\n    void basicConsumer() throws IOException {\n        FrameConsumerResultCallback callback = new FrameConsumerResultCallback();\n        BasicConsumer consumer = new BasicConsumer();\n        callback.addConsumer(OutputFrame.OutputType.STDOUT, consumer);\n        callback.onNext(new Frame(StreamType.STDOUT, FRAME_PAYLOAD.getBytes()));\n        callback.close();\n        assertThat(consumer.toString()).isEqualTo(LOG_RESULT);\n    }\n\n    @Test\n    void passStdoutNull() throws IOException {\n        FrameConsumerResultCallback callback = new FrameConsumerResultCallback();\n        ToStringConsumer consumer = new ToStringConsumer().withRemoveAnsiCodes(false);\n        callback.addConsumer(OutputFrame.OutputType.STDOUT, consumer);\n        callback.onNext(new Frame(StreamType.STDOUT, null));\n        callback.close();\n        assertThat(consumer.toUtf8String()).isEqualTo(\"\");\n    }\n\n    @Test\n    void passStdoutEmptyLine() throws IOException {\n        String payload = \"\";\n        FrameConsumerResultCallback callback = new FrameConsumerResultCallback();\n        ToStringConsumer consumer = new ToStringConsumer().withRemoveAnsiCodes(false);\n        callback.addConsumer(OutputFrame.OutputType.STDOUT, consumer);\n        callback.onNext(new Frame(StreamType.STDOUT, payload.getBytes()));\n        callback.close();\n        assertThat(consumer.toUtf8String()).isEqualTo(payload);\n    }\n\n    @Test\n    void passStdoutSingleLine() throws IOException {\n        String payload = \"Test\";\n        FrameConsumerResultCallback callback = new FrameConsumerResultCallback();\n        ToStringConsumer consumer = new ToStringConsumer().withRemoveAnsiCodes(false);\n        callback.addConsumer(OutputFrame.OutputType.STDOUT, consumer);\n        callback.onNext(new Frame(StreamType.STDOUT, payload.getBytes()));\n        callback.close();\n        assertThat(consumer.toUtf8String()).isEqualTo(payload);\n    }\n\n    @Test\n    void passStdoutSingleLineWithNewline() throws IOException {\n        String payload = \"Test\\n\";\n        FrameConsumerResultCallback callback = new FrameConsumerResultCallback();\n        ToStringConsumer consumer = new ToStringConsumer().withRemoveAnsiCodes(false);\n        callback.addConsumer(OutputFrame.OutputType.STDOUT, consumer);\n        callback.onNext(new Frame(StreamType.STDOUT, payload.getBytes()));\n        callback.close();\n        assertThat(consumer.toUtf8String()).isEqualTo(payload);\n    }\n\n    @Test\n    void passRawFrameWithoutColors() throws TimeoutException, IOException {\n        FrameConsumerResultCallback callback = new FrameConsumerResultCallback();\n        WaitingConsumer waitConsumer = new WaitingConsumer();\n        callback.addConsumer(OutputFrame.OutputType.STDOUT, waitConsumer);\n        callback.onNext(new Frame(StreamType.RAW, FRAME_PAYLOAD.getBytes()));\n        waitConsumer.waitUntil(\n            frame -> frame.getType() == OutputFrame.OutputType.STDOUT && frame.getUtf8String().equals(\"Test2\\n\"),\n            1,\n            TimeUnit.SECONDS\n        );\n        waitConsumer.waitUntil(\n            frame -> frame.getType() == OutputFrame.OutputType.STDOUT && frame.getUtf8String().equals(\"Тест1\\n\"),\n            1,\n            TimeUnit.SECONDS\n        );\n        Exception exception = null;\n        try {\n            waitConsumer.waitUntil(\n                frame -> frame.getType() == OutputFrame.OutputType.STDOUT && frame.getUtf8String().equals(\"Test3\"),\n                1,\n                TimeUnit.SECONDS\n            );\n        } catch (Exception e) {\n            exception = e;\n        }\n        assertThat(exception instanceof TimeoutException).isTrue();\n        callback.close();\n        waitConsumer.waitUntil(\n            frame -> frame.getType() == OutputFrame.OutputType.STDOUT && frame.getUtf8String().equals(\"Test3\"),\n            1,\n            TimeUnit.SECONDS\n        );\n    }\n\n    @Test\n    void passRawFrameWithColors() throws TimeoutException, IOException {\n        FrameConsumerResultCallback callback = new FrameConsumerResultCallback();\n        WaitingConsumer waitConsumer = new WaitingConsumer().withRemoveAnsiCodes(false);\n        callback.addConsumer(OutputFrame.OutputType.STDOUT, waitConsumer);\n        callback.onNext(new Frame(StreamType.RAW, FRAME_PAYLOAD.getBytes()));\n        waitConsumer.waitUntil(\n            frame -> {\n                return (\n                    frame.getType() == OutputFrame.OutputType.STDOUT &&\n                    frame.getUtf8String().equals(\"\\u001B[1;33mTest2\\u001B[0m\\n\")\n                );\n            },\n            1,\n            TimeUnit.SECONDS\n        );\n        waitConsumer.waitUntil(\n            frame -> {\n                return (\n                    frame.getType() == OutputFrame.OutputType.STDOUT &&\n                    frame.getUtf8String().equals(\"\\u001B[0;32mТест1\\u001B[0m\\n\")\n                );\n            },\n            1,\n            TimeUnit.SECONDS\n        );\n        Exception exception = null;\n        try {\n            waitConsumer.waitUntil(\n                frame -> {\n                    return (\n                        frame.getType() == OutputFrame.OutputType.STDOUT &&\n                        frame.getUtf8String().equals(\"\\u001B[0;31mTest3\\u001B[0m\")\n                    );\n                },\n                1,\n                TimeUnit.SECONDS\n            );\n        } catch (Exception e) {\n            exception = e;\n        }\n        assertThat(exception instanceof TimeoutException).isTrue();\n        callback.close();\n        waitConsumer.waitUntil(\n            frame -> {\n                return (\n                    frame.getType() == OutputFrame.OutputType.STDOUT &&\n                    frame.getUtf8String().equals(\"\\u001B[0;31mTest3\\u001B[0m\")\n                );\n            },\n            1,\n            TimeUnit.SECONDS\n        );\n    }\n\n    @Test\n    void reconstructBreakedUnicode() throws IOException {\n        String payload = \"Тест\";\n        byte[] payloadBytes = payload.getBytes(StandardCharsets.UTF_8);\n        byte[] bytes1 = new byte[(int) (payloadBytes.length * 0.6)];\n        byte[] bytes2 = new byte[payloadBytes.length - bytes1.length];\n        System.arraycopy(payloadBytes, 0, bytes1, 0, bytes1.length);\n        System.arraycopy(payloadBytes, bytes1.length, bytes2, 0, bytes2.length);\n        FrameConsumerResultCallback callback = new FrameConsumerResultCallback();\n        ToStringConsumer consumer = new ToStringConsumer().withRemoveAnsiCodes(false);\n        callback.addConsumer(OutputFrame.OutputType.STDOUT, consumer);\n        callback.onNext(new Frame(StreamType.RAW, bytes1));\n        callback.onNext(new Frame(StreamType.RAW, bytes2));\n        callback.close();\n        assertThat(consumer.toUtf8String()).isEqualTo(payload);\n    }\n\n    private static class BasicConsumer implements Consumer<OutputFrame> {\n\n        private StringBuilder input = new StringBuilder();\n\n        @Override\n        public void accept(OutputFrame outputFrame) {\n            input.append(outputFrame.getUtf8String());\n        }\n\n        @Override\n        public String toString() {\n            return input.toString();\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/containers/output/ToStringConsumerTest.java",
    "content": "package org.testcontainers.containers.output;\n\nimport org.apache.commons.lang3.RandomStringUtils;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Timeout;\nimport org.testcontainers.containers.Container.ExecResult;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.startupcheck.OneShotStartupCheckStrategy;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass ToStringConsumerTest {\n\n    private static final String LARGE_PAYLOAD;\n\n    static {\n        StringBuilder builder = new StringBuilder(10_003 * 10);\n        for (int i = 0; i < 10; i++) {\n            builder.append(' ').append(i).append(RandomStringUtils.randomAlphabetic(10000));\n        }\n        LARGE_PAYLOAD = builder.toString();\n        assertThat(LARGE_PAYLOAD).doesNotContain(\"\\n\");\n    }\n\n    @Test\n    void newlines_are_not_added_to_exec_output() throws Exception {\n        try (GenericContainer<?> container = new GenericContainer<>(\"alpine:3.17\")) {\n            container.withCommand(\"sleep\", \"2m\");\n            container.start();\n\n            ExecResult build = container.execInContainer(\"echo\", \"-n\", LARGE_PAYLOAD);\n            assertThat(build.getStdout()).doesNotContain(\"\\n\").isEqualTo(LARGE_PAYLOAD);\n        }\n    }\n\n    @Test\n    @Timeout(60)\n    void newlines_are_not_added_to_exec_output_with_tty() throws Exception {\n        try (GenericContainer<?> container = new GenericContainer<>(\"alpine:3.17\")) {\n            container.withCreateContainerCmdModifier(cmd -> {\n                cmd.withAttachStdin(true).withStdinOpen(true).withTty(true);\n            });\n            container.withCommand(\"sleep\", \"2m\");\n            container.start();\n\n            ExecResult build = container.execInContainer(\"echo\", \"-n\", LARGE_PAYLOAD);\n            assertThat(build.getStdout()).isEqualTo(LARGE_PAYLOAD).doesNotContain(\"\\n\");\n        }\n    }\n\n    @Test\n    void newlines_are_not_added_to_container_output() {\n        try (GenericContainer<?> container = new GenericContainer<>(\"alpine:3.17\")) {\n            container.withCommand(\"echo\", \"-n\", LARGE_PAYLOAD);\n            container.setStartupCheckStrategy(new OneShotStartupCheckStrategy());\n            container.start();\n\n            container.getDockerClient().waitContainerCmd(container.getContainerId()).start().awaitStatusCode();\n\n            assertThat(container.getLogs()).isEqualTo(LARGE_PAYLOAD).doesNotContain(\"\\n\");\n        }\n    }\n\n    @Test\n    void newlines_are_not_added_to_container_output_with_tty() {\n        try (GenericContainer<?> container = new GenericContainer<>(\"alpine:3.17\")) {\n            container.withCreateContainerCmdModifier(cmd -> {\n                cmd.withTty(true);\n            });\n            container.withCommand(\"echo\", \"-n\", LARGE_PAYLOAD);\n            container.setStartupCheckStrategy(new OneShotStartupCheckStrategy());\n            container.start();\n\n            container.getDockerClient().waitContainerCmd(container.getContainerId()).start().awaitStatusCode();\n\n            assertThat(container.getLogs()).isEqualTo(LARGE_PAYLOAD).doesNotContain(\"\\n\");\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/containers/startupcheck/IsRunningStartupCheckStrategyTest.java",
    "content": "package org.testcontainers.containers.startupcheck;\n\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.TestImages;\nimport org.testcontainers.containers.GenericContainer;\n\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\n\nclass IsRunningStartupCheckStrategyTest {\n\n    @Test\n    void testCommandQuickExitSuccess() {\n        try (GenericContainer container = new GenericContainer<>(TestImages.TINY_IMAGE).withCommand(\"/bin/true\")) {\n            container.start(); // should start with no Exception\n        }\n    }\n\n    @Test\n    @Disabled(\"This test can fail to throw an AssertionError if the container doesn't fail quickly enough\")\n    void testCommandQuickExitFailure() {\n        try (GenericContainer container = new GenericContainer<>(TestImages.TINY_IMAGE).withCommand(\"/bin/false\")) {\n            assertThatThrownBy(container::start)\n                .hasStackTraceContaining(\"Container startup failed\")\n                .hasStackTraceContaining(\"Container did not start correctly\");\n        }\n    }\n\n    @Test\n    void testCommandStaysRunning() {\n        try (\n            GenericContainer container = new GenericContainer<>(TestImages.TINY_IMAGE).withCommand(\"/bin/sleep\", \"60\")\n        ) {\n            container.start(); // should start with no Exception\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/containers/wait/internal/ExternalPortListeningCheckTest.java",
    "content": "package org.testcontainers.containers.wait.internal;\n\nimport com.google.common.collect.ImmutableSet;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.wait.strategy.WaitStrategyTarget;\n\nimport java.net.ServerSocket;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.catchThrowable;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nclass ExternalPortListeningCheckTest {\n\n    private ServerSocket listeningSocket1;\n\n    private ServerSocket listeningSocket2;\n\n    private ServerSocket nonListeningSocket;\n\n    private WaitStrategyTarget mockContainer;\n\n    @BeforeEach\n    public void setUp() throws Exception {\n        listeningSocket1 = new ServerSocket(0);\n        listeningSocket2 = new ServerSocket(0);\n\n        nonListeningSocket = new ServerSocket(0);\n        nonListeningSocket.close();\n\n        mockContainer = mock(WaitStrategyTarget.class);\n        when(mockContainer.getHost()).thenReturn(\"127.0.0.1\");\n    }\n\n    @Test\n    void singleListening() {\n        final ExternalPortListeningCheck check = new ExternalPortListeningCheck(\n            mockContainer,\n            ImmutableSet.of(listeningSocket1.getLocalPort())\n        );\n\n        final Boolean result = check.call();\n\n        assertThat(result).as(\"ExternalPortListeningCheck identifies a single listening port\").isTrue();\n    }\n\n    @Test\n    void multipleListening() {\n        final ExternalPortListeningCheck check = new ExternalPortListeningCheck(\n            mockContainer,\n            ImmutableSet.of(listeningSocket1.getLocalPort(), listeningSocket2.getLocalPort())\n        );\n\n        final Boolean result = check.call();\n\n        assertThat(result).as(\"ExternalPortListeningCheck identifies multiple listening port\").isTrue();\n    }\n\n    @Test\n    void oneNotListening() {\n        final ExternalPortListeningCheck check = new ExternalPortListeningCheck(\n            mockContainer,\n            ImmutableSet.of(listeningSocket1.getLocalPort(), nonListeningSocket.getLocalPort())\n        );\n\n        assertThat(catchThrowable(check::call))\n            .as(\"ExternalPortListeningCheck detects a non-listening port among many\")\n            .isInstanceOf(IllegalStateException.class);\n    }\n\n    @AfterEach\n    public void tearDown() throws Exception {\n        listeningSocket1.close();\n        listeningSocket2.close();\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/containers/wait/internal/InternalCommandPortListeningCheckTest.java",
    "content": "package org.testcontainers.containers.wait.internal;\n\nimport com.google.common.collect.ImmutableSet;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.rnorth.ducttape.TimeoutException;\nimport org.rnorth.ducttape.unreliables.Unreliables;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.images.builder.ImageFromDockerfile;\n\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.concurrent.TimeUnit;\n\nimport static org.assertj.core.api.Assertions.fail;\n\n@ParameterizedClass(name = \"{index} - {0}\")\n@MethodSource(\"data\")\nclass InternalCommandPortListeningCheckTest {\n\n    public static List<String> data() {\n        return Arrays.asList(\n            \"internal-port-check-dockerfile/Dockerfile-tcp\",\n            \"internal-port-check-dockerfile/Dockerfile-nc\",\n            \"internal-port-check-dockerfile/Dockerfile-bash\"\n        );\n    }\n\n    public GenericContainer<?> container;\n\n    public InternalCommandPortListeningCheckTest(String dockerfile) {\n        container =\n            new GenericContainer<>(\n                new ImageFromDockerfile()\n                    .withFileFromClasspath(\"Dockerfile\", dockerfile)\n                    .withFileFromClasspath(\"nginx.conf\", \"internal-port-check-dockerfile/nginx.conf\")\n            );\n        container.start();\n    }\n\n    @Test\n    void singleListening() {\n        final InternalCommandPortListeningCheck check = new InternalCommandPortListeningCheck(\n            container,\n            ImmutableSet.of(8080)\n        );\n\n        Unreliables.retryUntilTrue(5, TimeUnit.SECONDS, check);\n    }\n\n    @Test\n    void nonListening() {\n        final InternalCommandPortListeningCheck check = new InternalCommandPortListeningCheck(\n            container,\n            ImmutableSet.of(8080, 1234)\n        );\n\n        try {\n            Unreliables.retryUntilTrue(5, TimeUnit.SECONDS, check);\n            fail(\"expected to fail\");\n        } catch (TimeoutException e) {}\n    }\n\n    @Test\n    void lowAndHighPortListening() {\n        final InternalCommandPortListeningCheck check = new InternalCommandPortListeningCheck(\n            container,\n            ImmutableSet.of(100, 8080)\n        );\n\n        Unreliables.retryUntilTrue(5, TimeUnit.SECONDS, check);\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/containers/wait/strategy/DockerHealthcheckWaitStrategyTest.java",
    "content": "package org.testcontainers.containers.wait.strategy;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.ContainerLaunchException;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.images.builder.ImageFromDockerfile;\n\nimport java.time.Duration;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.catchThrowable;\n\nclass DockerHealthcheckWaitStrategyTest {\n\n    private GenericContainer container;\n\n    @BeforeEach\n    public void setUp() {\n        // Using a Dockerfile here, since Dockerfile builder DSL doesn't support HEALTHCHECK\n        container =\n            new GenericContainer(\n                new ImageFromDockerfile()\n                    .withFileFromClasspath(\n                        \"write_file_and_loop.sh\",\n                        \"health-wait-strategy-dockerfile/write_file_and_loop.sh\"\n                    )\n                    .withFileFromClasspath(\"Dockerfile\", \"health-wait-strategy-dockerfile/Dockerfile\")\n            )\n                .waitingFor(Wait.forHealthcheck().withStartupTimeout(Duration.ofSeconds(3)));\n    }\n\n    @Test\n    void startsOnceHealthy() {\n        container.start();\n    }\n\n    @Test\n    void containerStartFailsIfContainerIsUnhealthy() {\n        container.withCommand(\"tail\", \"-f\", \"/dev/null\");\n        assertThat(catchThrowable(container::start))\n            .as(\"Container launch fails when unhealthy\")\n            .isInstanceOf(ContainerLaunchException.class);\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/containers/wait/strategy/WaitAllStrategyTest.java",
    "content": "package org.testcontainers.containers.wait.strategy;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.InOrder;\nimport org.mockito.Mock;\nimport org.mockito.MockitoAnnotations;\nimport org.rnorth.ducttape.TimeoutException;\nimport org.testcontainers.containers.GenericContainer;\n\nimport java.time.Duration;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.catchThrowable;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.doNothing;\nimport static org.mockito.Mockito.doThrow;\nimport static org.mockito.Mockito.inOrder;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.verify;\n\nclass WaitAllStrategyTest {\n\n    @Mock\n    private GenericContainer container;\n\n    @Mock\n    private WaitStrategy strategy1;\n\n    @Mock\n    private WaitStrategy strategy2;\n\n    @Mock\n    private WaitStrategy strategy3;\n\n    @BeforeEach\n    public void setUp() {\n        MockitoAnnotations.initMocks(this);\n    }\n\n    /*\n     * Dummy-based tests, to check that timeout values are propagated correctly, without involving actual timing-sensitive code\n     */\n    @Test\n    void parentTimeoutApplies() {\n        DummyStrategy child1 = new DummyStrategy(Duration.ofMillis(10));\n        child1.withStartupTimeout(Duration.ofMillis(20));\n\n        assertThat(child1.startupTimeout.toMillis()).as(\"withStartupTimeout directly sets the timeout\").isEqualTo(20L);\n\n        new WaitAllStrategy().withStrategy(child1).withStartupTimeout(Duration.ofMillis(30));\n\n        assertThat(child1.startupTimeout.toMillis()).as(\"WaitAllStrategy overrides a child's timeout\").isEqualTo(30L);\n    }\n\n    @Test\n    void parentTimeoutAppliesToMultipleChildren() {\n        Duration defaultInnerWait = Duration.ofMillis(2);\n        Duration outerWait = Duration.ofMillis(6);\n\n        DummyStrategy child1 = new DummyStrategy(defaultInnerWait);\n        DummyStrategy child2 = new DummyStrategy(defaultInnerWait);\n\n        new WaitAllStrategy().withStrategy(child1).withStrategy(child2).withStartupTimeout(outerWait);\n\n        assertThat(child1.startupTimeout.toMillis())\n            .as(\"WaitAllStrategy overrides a child's timeout (1st)\")\n            .isEqualTo(6L);\n        assertThat(child2.startupTimeout.toMillis())\n            .as(\"WaitAllStrategy overrides a child's timeout (2nd)\")\n            .isEqualTo(6L);\n    }\n\n    @Test\n    void parentTimeoutAppliesToAdditionalChildren() {\n        Duration defaultInnerWait = Duration.ofMillis(2);\n        Duration outerWait = Duration.ofMillis(20);\n\n        DummyStrategy child1 = new DummyStrategy(defaultInnerWait);\n        DummyStrategy child2 = new DummyStrategy(defaultInnerWait);\n\n        new WaitAllStrategy().withStrategy(child1).withStartupTimeout(outerWait).withStrategy(child2);\n\n        assertThat(child1.startupTimeout.toMillis())\n            .as(\"WaitAllStrategy overrides a child's timeout (1st)\")\n            .isEqualTo(20L);\n        assertThat(child2.startupTimeout.toMillis())\n            .as(\"WaitAllStrategy overrides a child's timeout (2nd, additional)\")\n            .isEqualTo(20L);\n    }\n\n    /*\n     * Mock-based tests to check overall behaviour, without involving timing-sensitive code\n     */\n    @Test\n    void childExecutionTest() {\n        final WaitStrategy underTest = new WaitAllStrategy().withStrategy(strategy1).withStrategy(strategy2);\n\n        doNothing().when(strategy1).waitUntilReady(eq(container));\n        doNothing().when(strategy2).waitUntilReady(eq(container));\n\n        underTest.waitUntilReady(container);\n\n        InOrder inOrder = inOrder(strategy1, strategy2);\n        inOrder.verify(strategy1).waitUntilReady(any());\n        inOrder.verify(strategy2).waitUntilReady(any());\n    }\n\n    @Test\n    void withoutOuterTimeoutShouldRelyOnInnerStrategies() {\n        final WaitStrategy underTest = new WaitAllStrategy(WaitAllStrategy.Mode.WITH_INDIVIDUAL_TIMEOUTS_ONLY)\n            .withStrategy(strategy1)\n            .withStrategy(strategy2)\n            .withStrategy(strategy3);\n\n        doNothing().when(strategy1).waitUntilReady(eq(container));\n        doThrow(TimeoutException.class).when(strategy2).waitUntilReady(eq(container));\n\n        assertThat(\n            catchThrowable(() -> {\n                underTest.waitUntilReady(container);\n            })\n        )\n            .as(\"The outer strategy timeout applies\")\n            .isInstanceOf(TimeoutException.class);\n\n        InOrder inOrder = inOrder(strategy1, strategy2, strategy3);\n        inOrder.verify(strategy1).waitUntilReady(any());\n        inOrder.verify(strategy2).waitUntilReady(any());\n        inOrder.verify(strategy3, never()).waitUntilReady(any());\n    }\n\n    @Test\n    void timeoutChangeShouldNotBePossibleWithIndividualTimeoutMode() {\n        final WaitStrategy underTest = new WaitAllStrategy(WaitAllStrategy.Mode.WITH_INDIVIDUAL_TIMEOUTS_ONLY);\n\n        assertThat(\n            catchThrowable(() -> {\n                underTest.withStartupTimeout(Duration.ofSeconds(42));\n            })\n        )\n            .as(\"Cannot change timeout for individual timeouts\")\n            .isInstanceOf(IllegalStateException.class);\n    }\n\n    @Test\n    void shouldNotMessWithIndividualTimeouts() {\n        new WaitAllStrategy(WaitAllStrategy.Mode.WITH_INDIVIDUAL_TIMEOUTS_ONLY)\n            .withStrategy(strategy1)\n            .withStrategy(strategy2);\n\n        verify(strategy1, never()).withStartupTimeout(any());\n        verify(strategy1, never()).withStartupTimeout(any());\n    }\n\n    @Test\n    void shouldOverwriteIndividualTimeouts() {\n        Duration someSeconds = Duration.ofSeconds(23);\n        new WaitAllStrategy().withStartupTimeout(someSeconds).withStrategy(strategy1).withStrategy(strategy2);\n\n        verify(strategy1).withStartupTimeout(someSeconds);\n        verify(strategy1).withStartupTimeout(someSeconds);\n    }\n\n    static class DummyStrategy extends AbstractWaitStrategy {\n\n        DummyStrategy(Duration defaultInnerWait) {\n            super.startupTimeout = defaultInnerWait;\n        }\n\n        @Override\n        protected void waitUntilReady() {\n            // no-op\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/custom/TestCreateContainerCmdModifier.java",
    "content": "package org.testcontainers.custom;\n\nimport com.github.dockerjava.api.command.CreateContainerCmd;\nimport org.testcontainers.core.CreateContainerCmdModifier;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic class TestCreateContainerCmdModifier implements CreateContainerCmdModifier {\n\n    @Override\n    public CreateContainerCmd modify(CreateContainerCmd createContainerCmd) {\n        Map<String, String> labels = new HashMap<>();\n        labels.put(\"project\", \"testcontainers-java\");\n        labels.put(\"scope\", \"global\");\n        createContainerCmd.getLabels().putAll(labels);\n        return createContainerCmd;\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/dockerclient/AmbiguousImagePullTest.java",
    "content": "package org.testcontainers.dockerclient;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Timeout;\nimport org.testcontainers.DockerRegistryContainer;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.utility.DockerImageName;\n\nclass AmbiguousImagePullTest {\n\n    @Test\n    @Timeout(30)\n    void testNotUsingParse() {\n        try (DockerRegistryContainer registryContainer = new DockerRegistryContainer()) {\n            registryContainer.start();\n            DockerImageName imageName = registryContainer.createImage(\"latest\");\n            String imageNameWithoutTag = imageName.getRegistry() + \"/\" + imageName.getRepository();\n            try (\n                final GenericContainer<?> container = new GenericContainer<>(imageNameWithoutTag).withExposedPorts(8080)\n            ) {\n                container.start();\n                // do nothing other than start and stop\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/dockerclient/DockerClientConfigUtilsTest.java",
    "content": "package org.testcontainers.dockerclient;\n\nimport com.github.dockerjava.api.DockerClient;\nimport org.assertj.core.api.Assumptions;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Timeout;\nimport org.testcontainers.DockerClientFactory;\n\nimport java.net.URI;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass DockerClientConfigUtilsTest {\n\n    DockerClient client = DockerClientFactory.lazyClient();\n\n    @Test\n    void getDockerHostIpAddressShouldReturnLocalhostWhenUnixSocket() {\n        Assumptions.assumeThat(DockerClientConfigUtils.IN_A_CONTAINER).as(\"in a container\").isFalse();\n\n        String actual = DockerClientProviderStrategy.resolveDockerHostIpAddress(\n            client,\n            URI.create(\"unix:///var/run/docker.sock\"),\n            true\n        );\n        assertThat(actual).isEqualTo(\"localhost\");\n    }\n\n    @Test\n    void getDockerHostIpAddressShouldReturnDockerHostIpWhenHttpsUri() {\n        String actual = DockerClientProviderStrategy.resolveDockerHostIpAddress(\n            client,\n            URI.create(\"http://12.23.34.45\"),\n            true\n        );\n        assertThat(actual).isEqualTo(\"12.23.34.45\");\n    }\n\n    @Test\n    void getDockerHostIpAddressShouldReturnDockerHostIpWhenTcpUri() {\n        String actual = DockerClientProviderStrategy.resolveDockerHostIpAddress(\n            client,\n            URI.create(\"tcp://12.23.34.45\"),\n            true\n        );\n        assertThat(actual).isEqualTo(\"12.23.34.45\");\n    }\n\n    @Test\n    void getDockerHostIpAddressShouldReturnNullWhenUnsupportedUriScheme() {\n        String actual = DockerClientProviderStrategy.resolveDockerHostIpAddress(\n            client,\n            URI.create(\"gopher://12.23.34.45\"),\n            true\n        );\n        assertThat(actual).isNull();\n    }\n\n    @Test\n    @Timeout(5)\n    void getDefaultGateway() {\n        assertThat(DockerClientConfigUtils.getDefaultGateway()).isNotNull();\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/dockerclient/EnvironmentAndSystemPropertyClientProviderStrategyTest.java",
    "content": "package org.testcontainers.dockerclient;\n\nimport com.github.dockerjava.core.DefaultDockerClientConfig;\nimport com.github.dockerjava.transport.SSLConfig;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.Mockito;\nimport org.testcontainers.utility.MockTestcontainersConfigurationExtension;\nimport org.testcontainers.utility.TestcontainersConfiguration;\n\nimport java.io.IOException;\nimport java.net.URI;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.Properties;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assumptions.assumeThat;\nimport static org.mockito.ArgumentMatchers.anyString;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.ArgumentMatchers.isNull;\n\n/**\n * Test that we can use Testcontainers configuration file to override settings. We assume that docker-java has test\n * coverage for detection of environment variables (e.g. DOCKER_HOST) and its own properties config file.\n */\n@ExtendWith(MockTestcontainersConfigurationExtension.class)\nclass EnvironmentAndSystemPropertyClientProviderStrategyTest {\n\n    private URI defaultDockerHost;\n\n    private com.github.dockerjava.core.SSLConfig defaultSSLConfig;\n\n    @BeforeEach\n    public void checkEnvironmentClear() {\n        // If docker-java picks up non-default settings from the environment, our test needs to know to expect those\n        DefaultDockerClientConfig defaultConfig = DefaultDockerClientConfig.createDefaultConfigBuilder().build();\n        defaultDockerHost = defaultConfig.getDockerHost();\n        defaultSSLConfig = defaultConfig.getSSLConfig();\n    }\n\n    @Test\n    void testWhenConfigAbsent() {\n        Mockito\n            .doReturn(\"auto\")\n            .when(TestcontainersConfiguration.getInstance())\n            .getEnvVarOrProperty(eq(\"dockerconfig.source\"), anyString());\n        Mockito\n            .doReturn(null)\n            .when(TestcontainersConfiguration.getInstance())\n            .getEnvVarOrUserProperty(eq(\"docker.host\"), isNull());\n        Mockito\n            .doReturn(null)\n            .when(TestcontainersConfiguration.getInstance())\n            .getEnvVarOrUserProperty(eq(\"docker.tls.verify\"), isNull());\n        Mockito\n            .doReturn(null)\n            .when(TestcontainersConfiguration.getInstance())\n            .getEnvVarOrUserProperty(eq(\"docker.cert.path\"), isNull());\n\n        EnvironmentAndSystemPropertyClientProviderStrategy strategy = new EnvironmentAndSystemPropertyClientProviderStrategy();\n\n        TransportConfig transportConfig = strategy.getTransportConfig();\n        assertThat(transportConfig.getDockerHost()).isEqualTo(defaultDockerHost);\n        assertThat(transportConfig.getSslConfig()).isEqualTo(defaultSSLConfig);\n    }\n\n    @Test\n    void testWhenDockerHostPresent() {\n        Mockito\n            .doReturn(\"auto\")\n            .when(TestcontainersConfiguration.getInstance())\n            .getEnvVarOrProperty(eq(\"dockerconfig.source\"), anyString());\n        Mockito\n            .doReturn(\"tcp://1.2.3.4:2375\")\n            .when(TestcontainersConfiguration.getInstance())\n            .getEnvVarOrUserProperty(eq(\"docker.host\"), isNull());\n        Mockito\n            .doReturn(null)\n            .when(TestcontainersConfiguration.getInstance())\n            .getEnvVarOrUserProperty(eq(\"docker.tls.verify\"), isNull());\n        Mockito\n            .doReturn(null)\n            .when(TestcontainersConfiguration.getInstance())\n            .getEnvVarOrUserProperty(eq(\"docker.cert.path\"), isNull());\n\n        EnvironmentAndSystemPropertyClientProviderStrategy strategy = new EnvironmentAndSystemPropertyClientProviderStrategy();\n\n        TransportConfig transportConfig = strategy.getTransportConfig();\n        assertThat(transportConfig.getDockerHost().toString()).isEqualTo(\"tcp://1.2.3.4:2375\");\n        assertThat(transportConfig.getSslConfig()).isEqualTo(defaultSSLConfig);\n    }\n\n    @Test\n    void testWhenDockerHostAndSSLConfigPresent() throws IOException {\n        Path tempDir = Files.createTempDirectory(\"testcontainers-test\");\n        String tempDirPath = tempDir.toAbsolutePath().toString();\n\n        Mockito\n            .doReturn(\"auto\")\n            .when(TestcontainersConfiguration.getInstance())\n            .getEnvVarOrProperty(eq(\"dockerconfig.source\"), anyString());\n        Mockito\n            .doReturn(\"tcp://1.2.3.4:2375\")\n            .when(TestcontainersConfiguration.getInstance())\n            .getEnvVarOrUserProperty(eq(\"docker.host\"), isNull());\n        Mockito\n            .doReturn(\"1\")\n            .when(TestcontainersConfiguration.getInstance())\n            .getEnvVarOrUserProperty(eq(\"docker.tls.verify\"), isNull());\n        Mockito\n            .doReturn(tempDirPath)\n            .when(TestcontainersConfiguration.getInstance())\n            .getEnvVarOrUserProperty(eq(\"docker.cert.path\"), isNull());\n\n        EnvironmentAndSystemPropertyClientProviderStrategy strategy = new EnvironmentAndSystemPropertyClientProviderStrategy();\n\n        TransportConfig transportConfig = strategy.getTransportConfig();\n        assertThat(transportConfig.getDockerHost().toString()).isEqualTo(\"tcp://1.2.3.4:2375\");\n\n        SSLConfig sslConfig = transportConfig.getSslConfig();\n        assertThat(sslConfig).extracting(\"dockerCertPath\").isEqualTo(tempDirPath);\n    }\n\n    @Test\n    void applicableWhenIgnoringUserPropertiesAndConfigured() {\n        Mockito\n            .doReturn(\"autoIgnoringUserProperties\")\n            .when(TestcontainersConfiguration.getInstance())\n            .getEnvVarOrProperty(eq(\"dockerconfig.source\"), anyString());\n\n        Properties oldProperties = System.getProperties();\n        try {\n            System.setProperty(\"DOCKER_HOST\", \"tcp://1.2.3.4:2375\");\n            EnvironmentAndSystemPropertyClientProviderStrategy strategy = new EnvironmentAndSystemPropertyClientProviderStrategy();\n\n            assertThat(strategy.isApplicable()).isTrue();\n        } finally {\n            System.setProperties(oldProperties);\n        }\n    }\n\n    @Test\n    void notApplicableWhenIgnoringUserPropertiesAndNotConfigured() {\n        assumeThat(System.getenv(\"DOCKER_HOST\")).isNull();\n\n        Mockito\n            .doReturn(\"autoIgnoringUserProperties\")\n            .when(TestcontainersConfiguration.getInstance())\n            .getEnvVarOrProperty(eq(\"dockerconfig.source\"), anyString());\n\n        EnvironmentAndSystemPropertyClientProviderStrategy strategy = new EnvironmentAndSystemPropertyClientProviderStrategy();\n\n        assertThat(strategy.isApplicable()).isFalse();\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/dockerclient/EventStreamTest.java",
    "content": "package org.testcontainers.dockerclient;\n\nimport com.github.dockerjava.api.DockerClient;\nimport com.github.dockerjava.api.model.Event;\nimport com.github.dockerjava.core.command.EventsResultCallback;\nimport org.jetbrains.annotations.NotNull;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Timeout;\nimport org.testcontainers.DockerClientFactory;\nimport org.testcontainers.TestImages;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.startupcheck.OneShotStartupCheckStrategy;\n\nimport java.io.IOException;\nimport java.time.Instant;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * Test that event streaming from the {@link DockerClient} works correctly\n */\n@Timeout(10)\nclass EventStreamTest {\n\n    /**\n     * Test that docker events can be streamed from the client.\n     */\n    @Test\n    void test() throws IOException, InterruptedException {\n        CountDownLatch latch = new CountDownLatch(1);\n\n        try (\n            GenericContainer<?> container = new GenericContainer<>(TestImages.TINY_IMAGE)\n                .withCommand(\"true\")\n                .withStartupCheckStrategy(new OneShotStartupCheckStrategy())\n        ) {\n            container.start();\n            String createdAt = container.getContainerInfo().getCreated();\n\n            // Request all events between startTime and endTime for the container\n            try (\n                EventsResultCallback response = DockerClientFactory\n                    .instance()\n                    .client()\n                    .eventsCmd()\n                    .withContainerFilter(container.getContainerId())\n                    .withEventFilter(\"create\")\n                    .withSince(Instant.parse(createdAt).getEpochSecond() + \"\")\n                    .exec(\n                        new EventsResultCallback() {\n                            @Override\n                            public void onNext(@NotNull Event event) {\n                                // Check that a create event for the container is received\n                                if (\n                                    event.getId().equals(container.getContainerId()) &&\n                                    event.getStatus().equals(\"create\")\n                                ) {\n                                    latch.countDown();\n                                }\n                            }\n                        }\n                    )\n            ) {\n                response.awaitStarted(5, TimeUnit.SECONDS);\n                latch.await(5, TimeUnit.SECONDS);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/dockerclient/ImagePullTest.java",
    "content": "package org.testcontainers.dockerclient;\n\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.startupcheck.OneShotStartupCheckStrategy;\nimport org.testcontainers.utility.DockerImageName;\n\nclass ImagePullTest {\n\n    public static String[] parameters() {\n        return new String[] {\n            \"alpine:latest\",\n            \"alpine:3.17\",\n            \"alpine\", // omitting the tag should work and default to latest\n            \"alpine@sha256:1775bebec23e1f3ce486989bfc9ff3c4e951690df84aa9f926497d82f2ffca9d\",\n            \"docker.io/testcontainers/ryuk:latest\",\n            \"docker.io/testcontainers/ryuk:0.7.0\",\n            \"docker.io/testcontainers/ryuk@sha256:bcbee39cd601396958ba1bd06ea14ad64ce0ea709de29a427d741d1f5262080a\",\n            //            \"ibmcom/db2express-c\", // Big image for testing with slow networks\n        };\n    }\n\n    @ParameterizedTest(name = \"{0}\")\n    @MethodSource(\"parameters\")\n    void test(String image) {\n        try (\n            final GenericContainer<?> container = new GenericContainer<>(DockerImageName.parse(image))\n                .withCommand(\"/bin/sh\", \"-c\", \"sleep 0\")\n                .withStartupCheckStrategy(new OneShotStartupCheckStrategy())\n        ) {\n            container.start();\n            // do nothing other than start and stop\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/dockerclient/TestcontainersHostPropertyClientProviderStrategyTest.java",
    "content": "package org.testcontainers.dockerclient;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.Mockito;\nimport org.testcontainers.utility.MockTestcontainersConfigurationExtension;\nimport org.testcontainers.utility.TestcontainersConfiguration;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.ArgumentMatchers.isNull;\n\n@ExtendWith(MockTestcontainersConfigurationExtension.class)\nclass TestcontainersHostPropertyClientProviderStrategyTest {\n\n    @Test\n    void tcHostPropertyIsProvided() {\n        Mockito\n            .doReturn(\"tcp://127.0.0.1:9000\")\n            .when(TestcontainersConfiguration.getInstance())\n            .getUserProperty(eq(\"tc.host\"), isNull());\n\n        TestcontainersHostPropertyClientProviderStrategy strategy = new TestcontainersHostPropertyClientProviderStrategy();\n\n        assertThat(strategy.isApplicable()).isTrue();\n        TransportConfig transportConfig = strategy.getTransportConfig();\n        assertThat(transportConfig.getDockerHost().toString()).isEqualTo(\"tcp://127.0.0.1:9000\");\n    }\n\n    @Test\n    void tcHostPropertyIsNotProvided() {\n        Mockito.doReturn(null).when(TestcontainersConfiguration.getInstance()).getUserProperty(eq(\"tc.host\"), isNull());\n\n        TestcontainersHostPropertyClientProviderStrategy strategy = new TestcontainersHostPropertyClientProviderStrategy();\n\n        assertThat(strategy.isApplicable()).isFalse();\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/images/AgeBasedPullPolicyTest.java",
    "content": "package org.testcontainers.images;\n\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.time.temporal.ChronoUnit;\nimport java.util.UUID;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass AgeBasedPullPolicyTest {\n\n    final DockerImageName imageName = DockerImageName.parse(UUID.randomUUID().toString());\n\n    @Test\n    void shouldPull() {\n        ImageData imageData = ImageData.builder().createdAt(Instant.now().minus(2, ChronoUnit.HOURS)).build();\n\n        AgeBasedPullPolicy oneHour = new AgeBasedPullPolicy(Duration.of(1L, ChronoUnit.HOURS));\n        assertThat(oneHour.shouldPullCached(imageName, imageData)).isTrue();\n\n        AgeBasedPullPolicy fiveHours = new AgeBasedPullPolicy(Duration.of(5L, ChronoUnit.HOURS));\n        assertThat(fiveHours.shouldPullCached(imageName, imageData)).isFalse();\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/images/ImageDataTest.java",
    "content": "package org.testcontainers.images;\n\nimport com.github.dockerjava.api.command.InspectImageResponse;\nimport org.junit.jupiter.api.Test;\n\nimport java.time.Instant;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass ImageDataTest {\n\n    @Test\n    void shouldReadTimestampWithoutOffsetFromInspectImageResponse() {\n        final String timestamp = \"2020-07-27T18:23:31.365190246Z\";\n        final ImageData imageData = ImageData.from(new InspectImageResponse().withCreated(timestamp));\n        assertThat(imageData.getCreatedAt()).isEqualTo(Instant.parse(timestamp));\n    }\n\n    @Test\n    void shouldReadTimestampWithOffsetFromInspectImageResponse() {\n        final String timestamp = \"2020-07-27T18:23:31.365190246+02:00\";\n        final ImageData imageData = ImageData.from(new InspectImageResponse().withCreated(timestamp));\n        assertThat(imageData.getCreatedAt()).isEqualTo(Instant.parse(\"2020-07-27T16:23:31.365190246Z\"));\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/images/ImagePullPolicyTest.java",
    "content": "package org.testcontainers.images;\n\nimport com.github.dockerjava.api.exception.NotFoundException;\nimport org.junit.jupiter.api.AutoClose;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.Mockito;\nimport org.testcontainers.DockerClientFactory;\nimport org.testcontainers.DockerRegistryContainer;\nimport org.testcontainers.containers.ContainerLaunchException;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.startupcheck.OneShotStartupCheckStrategy;\nimport org.testcontainers.utility.DockerImageName;\n\nimport static org.assertj.core.api.Assertions.fail;\nimport static org.mockito.ArgumentMatchers.any;\n\nclass ImagePullPolicyTest {\n\n    @AutoClose\n    public static DockerRegistryContainer registry = new DockerRegistryContainer();\n\n    private final DockerImageName imageName = registry.createImage();\n\n    static {\n        registry.start();\n    }\n\n    @Test\n    void pullsByDefault() {\n        try (GenericContainer<?> container = new GenericContainer<>(imageName).withExposedPorts(8080)) {\n            container.start();\n        }\n    }\n\n    @Test\n    void shouldAlwaysPull() {\n        try (GenericContainer<?> container = new GenericContainer<>(imageName).withExposedPorts(8080)) {\n            container.start();\n        }\n\n        removeImage();\n\n        try (GenericContainer<?> container = new GenericContainer<>(imageName).withExposedPorts(8080)) {\n            expectToFailWithNotFoundException(container);\n        }\n\n        try (\n            // built_in_image_pull_policy {\n            GenericContainer<?> container = new GenericContainer<>(imageName)\n                .withImagePullPolicy(PullPolicy.alwaysPull())\n            // }\n        ) {\n            container.withExposedPorts(8080);\n            container.start();\n        }\n    }\n\n    @Test\n    void shouldSupportCustomPolicies() {\n        try (\n            // custom_image_pull_policy {\n            GenericContainer<?> container = new GenericContainer<>(imageName)\n                .withImagePullPolicy(\n                    new AbstractImagePullPolicy() {\n                        @Override\n                        protected boolean shouldPullCached(DockerImageName imageName, ImageData localImageData) {\n                            return System.getenv(\"ALWAYS_PULL_IMAGE\") != null;\n                        }\n                    }\n                )\n            // }\n        ) {\n            container.withExposedPorts(8080);\n            container.start();\n        }\n    }\n\n    @Test\n    void shouldCheckPolicy() {\n        ImagePullPolicy policy = Mockito.spy(\n            new AbstractImagePullPolicy() {\n                @Override\n                protected boolean shouldPullCached(DockerImageName imageName, ImageData localImageData) {\n                    return false;\n                }\n            }\n        );\n        try (\n            GenericContainer<?> container = new GenericContainer<>(imageName)\n                .withImagePullPolicy(policy)\n                .withExposedPorts(8080)\n        ) {\n            container.start();\n\n            Mockito.verify(policy).shouldPull(any());\n        }\n    }\n\n    @Test\n    void shouldNotForcePulling() {\n        try (\n            GenericContainer<?> container = new GenericContainer<>(imageName)\n                .withImagePullPolicy(__ -> false)\n                .withStartupCheckStrategy(new OneShotStartupCheckStrategy())\n        ) {\n            expectToFailWithNotFoundException(container);\n        }\n    }\n\n    private void expectToFailWithNotFoundException(GenericContainer<?> container) {\n        try {\n            container.start();\n            fail(\"Should fail\");\n        } catch (ContainerLaunchException e) {\n            Throwable throwable = e;\n            while (throwable.getCause() != null) {\n                throwable = throwable.getCause();\n                if (throwable.getCause() instanceof NotFoundException) {\n                    return;\n                }\n            }\n            fail(\"Caused by NotFoundException\");\n        }\n    }\n\n    private void removeImage() {\n        try {\n            DockerClientFactory\n                .instance()\n                .client()\n                .removeImageCmd(imageName.asCanonicalNameString())\n                .withForce(true)\n                .exec();\n        } catch (NotFoundException ignored) {}\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/images/LocalImagesCacheAccessor.java",
    "content": "package org.testcontainers.images;\n\npublic final class LocalImagesCacheAccessor {\n\n    public static synchronized void clearCache() {\n        LocalImagesCache.INSTANCE.cache.clear();\n        LocalImagesCache.INSTANCE.initialized.set(false);\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/images/OverrideImagePullPolicyTest.java",
    "content": "package org.testcontainers.images;\n\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.Mockito;\nimport org.testcontainers.DockerRegistryContainer;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.utility.FakeImagePullPolicy;\nimport org.testcontainers.utility.MockTestcontainersConfigurationExtension;\nimport org.testcontainers.utility.TestcontainersConfiguration;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n@ExtendWith(MockTestcontainersConfigurationExtension.class)\nclass OverrideImagePullPolicyTest {\n\n    private ImagePullPolicy originalInstance;\n\n    private ImagePullPolicy originalDefaultImplementation;\n\n    @BeforeEach\n    public void setUp() {\n        this.originalInstance = PullPolicy.instance;\n        this.originalDefaultImplementation = PullPolicy.defaultImplementation;\n        PullPolicy.instance = null;\n        PullPolicy.defaultImplementation = Mockito.mock(ImagePullPolicy.class);\n    }\n\n    @AfterEach\n    public void tearDown() {\n        PullPolicy.instance = originalInstance;\n        PullPolicy.defaultImplementation = originalDefaultImplementation;\n    }\n\n    @Test\n    void simpleConfigurationTest() {\n        Mockito\n            .doReturn(FakeImagePullPolicy.class.getCanonicalName())\n            .when(TestcontainersConfiguration.getInstance())\n            .getImagePullPolicy();\n\n        try (DockerRegistryContainer registry = new DockerRegistryContainer()) {\n            registry.start();\n            GenericContainer<?> container = new GenericContainer<>(registry.createImage()).withExposedPorts(8080);\n            container.start();\n            assertThat(container.getImage().imagePullPolicy).isInstanceOf(FakeImagePullPolicy.class);\n            container.stop();\n        }\n    }\n\n    @Test\n    void alwaysPullConfigurationTest() {\n        Mockito\n            .doReturn(AlwaysPullPolicy.class.getCanonicalName())\n            .when(TestcontainersConfiguration.getInstance())\n            .getImagePullPolicy();\n\n        try (DockerRegistryContainer registry = new DockerRegistryContainer()) {\n            registry.start();\n            GenericContainer<?> container = new GenericContainer<>(registry.createImage()).withExposedPorts(8080);\n            container.start();\n            assertThat(container.getImage().imagePullPolicy).isInstanceOf(AlwaysPullPolicy.class);\n            container.stop();\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/images/ParsedDockerfileTest.java",
    "content": "package org.testcontainers.images;\n\nimport com.google.common.collect.Sets;\nimport org.junit.jupiter.api.Test;\n\nimport java.nio.file.Paths;\nimport java.util.Arrays;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass ParsedDockerfileTest {\n\n    @Test\n    void doesSimpleParsing() {\n        final ParsedDockerfile parsedDockerfile = new ParsedDockerfile(\n            Arrays.asList(\"FROM someimage\", \"RUN something\")\n        );\n        assertThat(parsedDockerfile.getDependencyImageNames())\n            .as(\"extracts a single image name\")\n            .isEqualTo(Sets.newHashSet(\"someimage\"));\n    }\n\n    @Test\n    void isCaseInsensitive() {\n        final ParsedDockerfile parsedDockerfile = new ParsedDockerfile(\n            Arrays.asList(\"from someimage\", \"RUN something\")\n        );\n        assertThat(parsedDockerfile.getDependencyImageNames())\n            .as(\"extracts a single image name\")\n            .isEqualTo(Sets.newHashSet(\"someimage\"));\n    }\n\n    @Test\n    void handlesTags() {\n        final ParsedDockerfile parsedDockerfile = new ParsedDockerfile(\n            Arrays.asList(\"FROM someimage:tag\", \"RUN something\")\n        );\n        assertThat(parsedDockerfile.getDependencyImageNames())\n            .as(\"retains tags in image names\")\n            .isEqualTo(Sets.newHashSet(\"someimage:tag\"));\n    }\n\n    @Test\n    void handlesDigests() {\n        final ParsedDockerfile parsedDockerfile = new ParsedDockerfile(\n            Arrays.asList(\"FROM someimage@sha256:abc123\", \"RUN something\")\n        );\n        assertThat(parsedDockerfile.getDependencyImageNames())\n            .as(\"retains digests in image names\")\n            .isEqualTo(Sets.newHashSet(\"someimage@sha256:abc123\"));\n    }\n\n    @Test\n    void ignoringCommentedFromLines() {\n        final ParsedDockerfile parsedDockerfile = new ParsedDockerfile(\n            Arrays.asList(\"FROM someimage\", \"#FROM somethingelse\")\n        );\n        assertThat(parsedDockerfile.getDependencyImageNames())\n            .as(\"ignores commented from lines\")\n            .isEqualTo(Sets.newHashSet(\"someimage\"));\n    }\n\n    @Test\n    void ignoringBuildStageNames() {\n        final ParsedDockerfile parsedDockerfile = new ParsedDockerfile(\n            Arrays.asList(\"FROM someimage --as=base\", \"RUN something\", \"FROM nextimage\", \"RUN something\")\n        );\n        assertThat(parsedDockerfile.getDependencyImageNames())\n            .as(\"ignores build stage names and allows multiple images to be extracted\")\n            .isEqualTo(Sets.newHashSet(\"someimage\", \"nextimage\"));\n    }\n\n    @Test\n    void ignoringPlatformArgs() {\n        final ParsedDockerfile parsedDockerfile = new ParsedDockerfile(\n            Arrays.asList(\"FROM --platform=linux/amd64 someimage\", \"RUN something\")\n        );\n        assertThat(parsedDockerfile.getDependencyImageNames())\n            .as(\"ignores platform args\")\n            .isEqualTo(Sets.newHashSet(\"someimage\"));\n    }\n\n    @Test\n    void ignoringExtraPlatformArgs() {\n        final ParsedDockerfile parsedDockerfile = new ParsedDockerfile(\n            Arrays.asList(\"FROM --platform=linux/amd64 --somethingelse=value someimage\", \"RUN something\")\n        );\n        assertThat(parsedDockerfile.getDependencyImageNames())\n            .as(\"ignores platform args\")\n            .isEqualTo(Sets.newHashSet(\"someimage\"));\n    }\n\n    @Test\n    void handlesGracefullyIfNoFromLine() {\n        final ParsedDockerfile parsedDockerfile = new ParsedDockerfile(\n            Arrays.asList(\"RUN something\", \"# is this even a valid Dockerfile?\")\n        );\n        assertThat(parsedDockerfile.getDependencyImageNames())\n            .as(\"handles invalid Dockerfiles gracefully\")\n            .isEqualTo(Sets.newHashSet());\n    }\n\n    @Test\n    void handlesGracefullyIfDockerfileNotFound() {\n        final ParsedDockerfile parsedDockerfile = new ParsedDockerfile(Paths.get(\"nonexistent.Dockerfile\"));\n        assertThat(parsedDockerfile.getDependencyImageNames())\n            .as(\"handles missing Dockerfiles gracefully\")\n            .isEqualTo(Sets.newHashSet());\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/images/RemoteDockerImageTest.java",
    "content": "package org.testcontainers.images;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Timeout;\nimport org.testcontainers.utility.Base58;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.LazyFuture;\n\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass RemoteDockerImageTest {\n\n    @Test\n    void toStringContainsOnlyImageName() {\n        String imageName = Base58.randomString(8).toLowerCase();\n        RemoteDockerImage remoteDockerImage = new RemoteDockerImage(DockerImageName.parse(imageName));\n        assertThat(remoteDockerImage.toString()).contains(\"imageName=\" + imageName);\n    }\n\n    @Test\n    void toStringWithExceptionContainsOnlyImageNameFuture() {\n        CompletableFuture<String> imageNameFuture = new CompletableFuture<>();\n        imageNameFuture.completeExceptionally(new RuntimeException(\"arbitrary\"));\n\n        RemoteDockerImage remoteDockerImage = new RemoteDockerImage(imageNameFuture);\n        assertThat(remoteDockerImage.toString()).contains(\"imageName=java.lang.RuntimeException: arbitrary\");\n    }\n\n    @Test\n    @Timeout(5)\n    void toStringDoesntResolveImageNameFuture() {\n        CompletableFuture<String> imageNameFuture = new CompletableFuture<>();\n\n        // verify that we've set up the test properly\n        assertThat(imageNameFuture).isNotDone();\n\n        RemoteDockerImage remoteDockerImage = new RemoteDockerImage(imageNameFuture);\n        assertThat(remoteDockerImage.toString()).contains(\"imageName=<resolving>\");\n\n        // Make sure the act of calling toString doesn't resolve the imageNameFuture\n        assertThat(imageNameFuture).isNotDone();\n\n        String imageName = Base58.randomString(8).toLowerCase();\n        imageNameFuture.complete(imageName);\n        assertThat(remoteDockerImage.toString()).contains(\"imageName=\" + imageName);\n    }\n\n    @Test\n    @Timeout(5)\n    void toStringDoesntResolveLazyFuture() throws Exception {\n        String imageName = Base58.randomString(8).toLowerCase();\n        AtomicBoolean resolved = new AtomicBoolean(false);\n        Future<String> imageNameFuture = new LazyFuture<String>() {\n            @Override\n            protected String resolve() {\n                resolved.set(true);\n                return imageName;\n            }\n        };\n\n        // verify that we've set up the test properly\n        assertThat(imageNameFuture).isNotDone();\n\n        RemoteDockerImage remoteDockerImage = new RemoteDockerImage(imageNameFuture);\n        assertThat(remoteDockerImage.toString()).contains(\"imageName=<resolving>\");\n\n        // Make sure the act of calling toString doesn't resolve the imageNameFuture\n        assertThat(imageNameFuture).isNotDone();\n        assertThat(resolved).isFalse();\n\n        // Trigger resolve\n        imageNameFuture.get();\n        assertThat(remoteDockerImage.toString()).contains(\"imageName=\" + imageName);\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/images/builder/DockerfileBuildTest.java",
    "content": "package org.testcontainers.images.builder;\n\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.startupcheck.OneShotStartupCheckStrategy;\n\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.stream.Stream;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass DockerfileBuildTest {\n\n    static final Path RESOURCE_PATH = Paths.get(\"src/test/resources/dockerfile-build-test\");\n\n    public static Stream<Arguments> parameters() {\n        Map<String, String> buildArgs = new HashMap<>(4);\n        buildArgs.put(\"BUILD_IMAGE\", \"alpine:3.16\");\n        buildArgs.put(\"BASE_IMAGE\", \"alpine\");\n        buildArgs.put(\"BASE_IMAGE_TAG\", \"3.12\");\n        buildArgs.put(\"UNUSED\", \"ignored\");\n\n        //noinspection deprecation\n        return Stream.of(\n            // Dockerfile build without explicit per-file inclusion\n            Arguments.of(\n                \"test1234\",\n                // spotless:off\n                // docsShowRecursiveFileInclusion {\n                new ImageFromDockerfile()\n                    .withFileFromPath(\".\", RESOURCE_PATH)),\n                // }\n                // spotless:on\n            // Dockerfile build using a non-standard Dockerfile\n            Arguments.of(\n                \"test4567\",\n                new ImageFromDockerfile().withFileFromPath(\".\", RESOURCE_PATH).withDockerfilePath(\"./Dockerfile-alt\")\n            ),\n            // Dockerfile build using withBuildArg()\n            Arguments.of(\n                \"test7890\",\n                new ImageFromDockerfile()\n                    .withFileFromPath(\".\", RESOURCE_PATH)\n                    .withDockerfilePath(\"./Dockerfile-buildarg\")\n                    .withBuildArg(\"CUSTOM_ARG\", \"test7890\")\n            ),\n            // Dockerfile build using withBuildArgs() with build args in FROM statement\n            Arguments.of(\n                \"test1234\",\n                new ImageFromDockerfile()\n                    .withFileFromPath(\".\", RESOURCE_PATH)\n                    .withDockerfile(RESOURCE_PATH.resolve(\"Dockerfile-from-buildarg\"))\n                    .withBuildArgs(buildArgs)\n            ),\n            // Dockerfile build using withDockerfile(File)\n            Arguments.of(\n                \"test4567\",\n                new ImageFromDockerfile()\n                    .withFileFromPath(\".\", RESOURCE_PATH)\n                    .withDockerfile(RESOURCE_PATH.resolve(\"Dockerfile-alt\"))\n            )\n        );\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"parameters\")\n    void performTest(String expectedFileContent, ImageFromDockerfile image) {\n        try (\n            final GenericContainer<?> container = new GenericContainer<>(image)\n                .withStartupCheckStrategy(new OneShotStartupCheckStrategy())\n                .withCommand(\"cat\", \"/test.txt\")\n        ) {\n            container.start();\n\n            final String logs = container.getLogs();\n            assertThat(logs)\n                .as(\"expected file content indicates that dockerfile build steps have been run\")\n                .contains(expectedFileContent);\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/images/builder/DockerignoreTest.java",
    "content": "package org.testcontainers.images.builder;\n\nimport com.github.dockerjava.api.exception.DockerClientException;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.startupcheck.OneShotStartupCheckStrategy;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.fail;\n\nclass DockerignoreTest {\n\n    private static final Path INVALID_DOCKERIGNORE_PATH = Paths.get(\"src/test/resources/dockerfile-build-invalid\");\n\n    @Test\n    void testInvalidDockerignore() throws Exception {\n        try {\n            new ImageFromDockerfile()\n                .withFileFromPath(\".\", INVALID_DOCKERIGNORE_PATH)\n                .withDockerfile(INVALID_DOCKERIGNORE_PATH.resolve(\"Dockerfile\"))\n                .get();\n            fail(\"Should not be able to build an image with an invalid .dockerignore file\");\n        } catch (DockerClientException e) {\n            if (!e.getMessage().contains(\"Invalid pattern\")) {\n                throw e;\n            }\n        }\n    }\n\n    @SuppressWarnings(\"resource\")\n    @Test\n    void testValidDockerignore() throws Exception {\n        ImageFromDockerfile img = new ImageFromDockerfile()\n            .withFileFromPath(\".\", DockerfileBuildTest.RESOURCE_PATH)\n            .withDockerfile(DockerfileBuildTest.RESOURCE_PATH.resolve(\"Dockerfile-currentdir\"));\n        try (\n            final GenericContainer<?> container = new GenericContainer(DockerImageName.parse(img.get()))\n                .withStartupCheckStrategy(new OneShotStartupCheckStrategy())\n                .withCommand(\"ls\", \"/\")\n        ) {\n            container.start();\n\n            final String logs = container.getLogs();\n            assertThat(logs)\n                .as(\"Files in the container indicated the .dockerignore was not applied. Output was: \" + logs)\n                .contains(\"should_not_be_ignored.txt\");\n            assertThat(logs)\n                .as(\"Files in the container indicated the .dockerignore was not applied. Output was: \" + logs)\n                .doesNotContain(\"should_be_ignored.txt\");\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/images/builder/ImageFromDockerfileTest.java",
    "content": "package org.testcontainers.images.builder;\n\nimport com.github.dockerjava.api.DockerClient;\nimport com.github.dockerjava.api.command.InspectImageResponse;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.DockerClientFactory;\nimport org.testcontainers.utility.Base58;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass ImageFromDockerfileTest {\n\n    @Test\n    void shouldAddDefaultLabels() {\n        ImageFromDockerfile image = new ImageFromDockerfile().withDockerfileFromBuilder(it -> it.from(\"scratch\"));\n\n        String imageId = image.resolve();\n\n        DockerClient dockerClient = DockerClientFactory.instance().client();\n\n        InspectImageResponse inspectImageResponse = dockerClient.inspectImageCmd(imageId).exec();\n\n        assertThat(inspectImageResponse.getConfig().getLabels())\n            .containsAllEntriesOf(DockerClientFactory.DEFAULT_LABELS);\n    }\n\n    @Test\n    void shouldNotAddSessionLabelIfDeleteOnExitIsFalse() {\n        ImageFromDockerfile image = new ImageFromDockerfile(\n            \"localhost/testcontainers/\" + Base58.randomString(16).toLowerCase(),\n            false\n        )\n            .withDockerfileFromBuilder(it -> it.from(\"scratch\"));\n        String imageId = image.resolve();\n\n        DockerClient dockerClient = DockerClientFactory.instance().client();\n\n        try {\n            InspectImageResponse inspectImageResponse = dockerClient.inspectImageCmd(imageId).exec();\n            assertThat(inspectImageResponse.getConfig().getLabels())\n                .doesNotContainKey(DockerClientFactory.TESTCONTAINERS_SESSION_ID_LABEL);\n        } finally {\n            // ensure the image is deleted, even if the test fails\n            dockerClient.removeImageCmd(imageId).exec();\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/images/builder/dockerfile/statement/AbstractStatementTest.java",
    "content": "package org.testcontainers.images.builder.dockerfile.statement;\n\nimport org.apache.commons.io.IOUtils;\nimport org.apache.commons.lang3.StringUtils;\nimport org.apache.commons.lang3.exception.ExceptionUtils;\nimport org.junit.jupiter.api.TestInfo;\nimport org.rnorth.ducttape.Preconditions;\n\nimport java.io.InputStream;\nimport java.util.Arrays;\n\nimport static org.assertj.core.api.Assertions.fail;\n\npublic abstract class AbstractStatementTest {\n\n    private final TestInfo testInfo;\n\n    AbstractStatementTest(TestInfo testInfo) {\n        this.testInfo = testInfo;\n    }\n\n    protected void assertStatement(Statement statement) {\n        String testName = testInfo.getTestMethod().get().getName();\n        String[] expectedLines = new String[0];\n        try {\n            String path = \"fixtures/statements/\" + getClass().getSimpleName() + \"/\" + testName;\n            InputStream inputStream = getClass().getClassLoader().getResourceAsStream(path);\n\n            Preconditions.check(\"inputStream is null for path \" + path, inputStream != null);\n\n            String content = IOUtils.toString(inputStream);\n            IOUtils.closeQuietly(inputStream);\n            expectedLines = StringUtils.chomp(content.replaceAll(\"\\r\\n\", \"\\n\").trim()).split(\"\\n\");\n        } catch (Exception e) {\n            fail(\"can't load fixture '\" + testName + \"'\\n\" + ExceptionUtils.getStackTrace(e));\n        }\n\n        StringBuilder builder = new StringBuilder();\n        statement.appendArguments(builder);\n        String[] resultLines = StringUtils.chomp(builder.toString().trim()).split(\"\\n\");\n\n        if (expectedLines.length != resultLines.length) {\n            fail(\n                \"number of lines is not the same. Expected \" + expectedLines.length + \" but got \" + resultLines.length\n            );\n        }\n\n        if (!Arrays.equals(expectedLines, resultLines)) {\n            StringBuilder failureBuilder = new StringBuilder();\n            failureBuilder.append(\"Invalid statement!\\n\");\n\n            for (int i = 0; i < expectedLines.length; i++) {\n                String expectedLine = expectedLines[i];\n                String actualLine = resultLines[i];\n\n                if (!expectedLine.equals(actualLine)) {\n                    failureBuilder.append(\"Invalid line #\");\n                    failureBuilder.append(i);\n                    failureBuilder.append(\":\\n\\tActual:   <\");\n                    failureBuilder.append(actualLine);\n                    failureBuilder.append(\">\\n\\tExpected: <\");\n                    failureBuilder.append(expectedLine);\n                    failureBuilder.append(\">\\n\");\n                }\n            }\n\n            fail(failureBuilder.toString());\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/images/builder/dockerfile/statement/KeyValuesStatementTest.java",
    "content": "package org.testcontainers.images.builder.dockerfile.statement;\n\nimport com.google.common.collect.ImmutableMap;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.TestInfo;\n\nimport java.util.Collections;\n\nclass KeyValuesStatementTest extends AbstractStatementTest {\n\n    KeyValuesStatementTest(TestInfo testInfo) {\n        super(testInfo);\n    }\n\n    @Test\n    void multilineTest() throws Exception {\n        ImmutableMap<String, String> pairs = ImmutableMap\n            .<String, String>builder()\n            .put(\"line1\", \"1\")\n            .put(\"line2\", \"2\")\n            .put(\"line3\", \"3\")\n            .build();\n\n        assertStatement(new KeyValuesStatement(\"TEST\", pairs));\n    }\n\n    @Test\n    void keyWithSpacesTest() throws Exception {\n        assertStatement(new KeyValuesStatement(\"TEST\", Collections.singletonMap(\"key with spaces\", \"1\")));\n    }\n\n    @Test\n    void keyWithNewLinesTest() throws Exception {\n        assertStatement(new KeyValuesStatement(\"TEST\", Collections.singletonMap(\"key\\nwith\\nnewlines\", \"1\")));\n    }\n\n    @Test\n    void keyWithTabsTest() throws Exception {\n        assertStatement(new KeyValuesStatement(\"TEST\", Collections.singletonMap(\"key\\twith\\ttab\", \"1\")));\n    }\n\n    @Test\n    void valueIsEscapedTest() throws Exception {\n        ImmutableMap<String, String> pairs = ImmutableMap\n            .<String, String>builder()\n            .put(\"1\", \"value with spaces\")\n            .put(\"2\", \"value\\nwith\\nnewlines\")\n            .put(\"3\", \"value\\twith\\ttab\")\n            .build();\n\n        assertStatement(new KeyValuesStatement(\"TEST\", pairs));\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/images/builder/dockerfile/statement/MultiArgsStatementTest.java",
    "content": "package org.testcontainers.images.builder.dockerfile.statement;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.TestInfo;\n\nclass MultiArgsStatementTest extends AbstractStatementTest {\n\n    MultiArgsStatementTest(TestInfo testInfo) {\n        super(testInfo);\n    }\n\n    @Test\n    void simpleTest() {\n        assertStatement(new MultiArgsStatement(\"TEST\", \"a\", \"b\", \"c\"));\n    }\n\n    @Test\n    void multilineTest() {\n        assertStatement(new MultiArgsStatement(\"TEST\", \"some\\nmultiline\\nargument\"));\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/images/builder/dockerfile/statement/RawStatementTest.java",
    "content": "package org.testcontainers.images.builder.dockerfile.statement;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.TestInfo;\n\nclass RawStatementTest extends AbstractStatementTest {\n\n    RawStatementTest(TestInfo testInfo) {\n        super(testInfo);\n    }\n\n    @Test\n    void simpleTest() throws Exception {\n        assertStatement(new RawStatement(\"TEST\", \"value\\nas\\t\\\\\\nis\"));\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/images/builder/dockerfile/statement/SingleArgumentStatementTest.java",
    "content": "package org.testcontainers.images.builder.dockerfile.statement;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.TestInfo;\n\nclass SingleArgumentStatementTest extends AbstractStatementTest {\n\n    SingleArgumentStatementTest(TestInfo testInfo) {\n        super(testInfo);\n    }\n\n    @Test\n    void simpleTest() throws Exception {\n        assertStatement(new SingleArgumentStatement(\"TEST\", \"hello\"));\n    }\n\n    @Test\n    void multilineTest() throws Exception {\n        assertStatement(new SingleArgumentStatement(\"TEST\", \"hello\\nworld\"));\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/BaseComposeTest.java",
    "content": "package org.testcontainers.junit;\n\nimport com.github.dockerjava.api.model.Network;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Assumptions;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.DockerClientFactory;\nimport org.testcontainers.containers.ComposeContainer;\nimport org.testcontainers.utility.TestEnvironment;\nimport redis.clients.jedis.Jedis;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\npublic abstract class BaseComposeTest {\n\n    protected static final int REDIS_PORT = 6379;\n\n    protected abstract ComposeContainer getEnvironment();\n\n    private List<String> existingNetworks = new ArrayList<>();\n\n    @BeforeAll\n    public static void checkVersion() {\n        Assumptions.assumeTrue(TestEnvironment.dockerApiAtLeast(\"1.22\"));\n    }\n\n    @Test\n    void simpleTest() {\n        Jedis jedis = new Jedis(\n            getEnvironment().getServiceHost(\"redis-1\", REDIS_PORT),\n            getEnvironment().getServicePort(\"redis-1\", REDIS_PORT)\n        );\n\n        jedis.incr(\"test\");\n        jedis.incr(\"test\");\n        jedis.incr(\"test\");\n\n        assertThat(jedis.get(\"test\")).as(\"A redis instance defined in compose can be used in isolation\").isEqualTo(\"3\");\n    }\n\n    @Test\n    void secondTest() {\n        // used in manual checking for cleanup in between tests\n        Jedis jedis = new Jedis(\n            getEnvironment().getServiceHost(\"redis-1\", REDIS_PORT),\n            getEnvironment().getServicePort(\"redis-1\", REDIS_PORT)\n        );\n\n        jedis.incr(\"test\");\n        jedis.incr(\"test\");\n        jedis.incr(\"test\");\n\n        assertThat(jedis.get(\"test\")).as(\"Tests use fresh container instances\").isEqualTo(\"3\");\n        // if these end up using the same container one of the test methods will fail.\n        // However, @Rule creates a separate ComposeContainer instance per test, so this just shouldn't happen\n    }\n\n    @BeforeEach\n    public void captureNetworks() {\n        existingNetworks.addAll(findAllNetworks());\n    }\n\n    @AfterEach\n    public void verifyNoNetworks() {\n        assertThat(findAllNetworks()).as(\"The networks\").isEqualTo(existingNetworks);\n    }\n\n    private List<String> findAllNetworks() {\n        return DockerClientFactory\n            .instance()\n            .client()\n            .listNetworksCmd()\n            .exec()\n            .stream()\n            .map(Network::getName)\n            .sorted()\n            .collect(Collectors.toList());\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/BaseDockerComposeTest.java",
    "content": "package org.testcontainers.junit;\n\nimport com.github.dockerjava.api.model.Network;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.DockerClientFactory;\nimport org.testcontainers.containers.DockerComposeContainer;\nimport org.testcontainers.utility.TestEnvironment;\nimport redis.clients.jedis.Jedis;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assumptions.assumeThat;\n\n/**\n * Created by rnorth on 21/05/2016.\n */\npublic abstract class BaseDockerComposeTest {\n\n    protected static final int REDIS_PORT = 6379;\n\n    protected abstract DockerComposeContainer getEnvironment();\n\n    private List<String> existingNetworks = new ArrayList<>();\n\n    @BeforeAll\n    public static void checkVersion() {\n        assumeThat(TestEnvironment.dockerApiAtLeast(\"1.22\")).isTrue();\n    }\n\n    @Test\n    void simpleTest() {\n        Jedis jedis = new Jedis(\n            getEnvironment().getServiceHost(\"redis_1\", REDIS_PORT),\n            getEnvironment().getServicePort(\"redis_1\", REDIS_PORT)\n        );\n\n        jedis.incr(\"test\");\n        jedis.incr(\"test\");\n        jedis.incr(\"test\");\n\n        assertThat(jedis.get(\"test\")).as(\"A redis instance defined in compose can be used in isolation\").isEqualTo(\"3\");\n    }\n\n    @Test\n    void secondTest() {\n        // used in manual checking for cleanup in between tests\n        Jedis jedis = new Jedis(\n            getEnvironment().getServiceHost(\"redis_1\", REDIS_PORT),\n            getEnvironment().getServicePort(\"redis_1\", REDIS_PORT)\n        );\n\n        jedis.incr(\"test\");\n        jedis.incr(\"test\");\n        jedis.incr(\"test\");\n\n        assertThat(jedis.get(\"test\")).as(\"Tests use fresh container instances\").isEqualTo(\"3\");\n        // if these end up using the same container one of the test methods will fail.\n        // However, @Rule creates a separate DockerComposeContainer instance per test, so this just shouldn't happen\n    }\n\n    @BeforeEach\n    public void captureNetworks() {\n        existingNetworks.addAll(findAllNetworks());\n    }\n\n    @AfterEach\n    public void verifyNoNetworks() {\n        assertThat(findAllNetworks()).as(\"The networks\").isEqualTo(existingNetworks);\n    }\n\n    private List<String> findAllNetworks() {\n        return DockerClientFactory\n            .instance()\n            .client()\n            .listNetworksCmd()\n            .exec()\n            .stream()\n            .map(Network::getName)\n            .sorted()\n            .collect(Collectors.toList());\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/ComposeContainerOverrideTest.java",
    "content": "package org.testcontainers.junit;\n\nimport com.github.dockerjava.api.command.InspectContainerResponse;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.ComposeContainer;\nimport org.testcontainers.containers.ContainerState;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.File;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass ComposeContainerOverrideTest {\n\n    private static final File BASE = new File(\"src/test/resources/compose-override/compose.yml\");\n\n    private static final File OVERRIDE = new File(\"src/test/resources/compose-override/compose-override.yml\");\n\n    @Test\n    void readEnvironment() {\n        try (\n            ComposeContainer compose = new ComposeContainer(DockerImageName.parse(\"docker:25.0.5\"), BASE)\n                .withExposedService(\"redis\", 6379)\n        ) {\n            compose.start();\n            InspectContainerResponse container = compose\n                .getContainerByServiceName(\"redis-1\")\n                .map(ContainerState::getContainerInfo)\n                .get();\n            assertThat(container.getConfig().getEnv()).contains(\"foo=bar\");\n        }\n    }\n\n    @Test\n    void resetEnvironment() {\n        try (\n            ComposeContainer compose = new ComposeContainer(DockerImageName.parse(\"docker:25.0.5\"), BASE, OVERRIDE)\n                .withExposedService(\"redis\", 6379)\n        ) {\n            compose.start();\n            InspectContainerResponse container = compose\n                .getContainerByServiceName(\"redis-1\")\n                .map(ContainerState::getContainerInfo)\n                .get();\n            assertThat(container.getConfig().getEnv()).doesNotContain(\"foo=bar\");\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/ComposeContainerPortViaEnvTest.java",
    "content": "package org.testcontainers.junit;\n\nimport org.junit.jupiter.api.AutoClose;\nimport org.testcontainers.containers.ComposeContainer;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.File;\n\nclass ComposeContainerPortViaEnvTest extends BaseComposeTest {\n\n    @AutoClose\n    public ComposeContainer environment = new ComposeContainer(\n        DockerImageName.parse(\"docker:25.0.5\"),\n        new File(\"src/test/resources/v2-compose-test-port-via-env.yml\")\n    )\n        .withExposedService(\"redis-1\", REDIS_PORT)\n        .withEnv(\"REDIS_PORT\", String.valueOf(REDIS_PORT));\n\n    ComposeContainerPortViaEnvTest() {\n        this.environment.start();\n    }\n\n    @Override\n    protected ComposeContainer getEnvironment() {\n        return environment;\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/ComposeContainerScalingTest.java",
    "content": "package org.testcontainers.junit;\n\nimport org.assertj.core.api.Assumptions;\nimport org.junit.jupiter.api.AutoClose;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.ComposeContainer;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.TestEnvironment;\nimport redis.clients.jedis.Jedis;\n\nimport java.io.File;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass ComposeContainerScalingTest {\n\n    private static final int REDIS_PORT = 6379;\n\n    private Jedis[] clients = new Jedis[3];\n\n    @BeforeAll\n    public static void checkVersion() {\n        Assumptions.assumeThat(TestEnvironment.dockerApiAtLeast(\"1.22\")).isTrue();\n    }\n\n    @AutoClose\n    public ComposeContainer environment = new ComposeContainer(\n        DockerImageName.parse(\"docker:25.0.5\"),\n        new File(\"src/test/resources/composev2/scaled-compose-test.yml\")\n    )\n        .withScaledService(\"redis\", 3)\n        .withExposedService(\"redis\", REDIS_PORT) // implicit '-1'\n        .withExposedService(\"redis-2\", REDIS_PORT) // explicit service index\n        .withExposedService(\"redis\", 3, REDIS_PORT); // explicit service index via parameter\n\n    ComposeContainerScalingTest() {\n        environment.start();\n    }\n\n    @BeforeEach\n    public void setupClients() {\n        for (int i = 0; i < 3; i++) {\n            String name = String.format(\"redis-%d\", i + 1);\n\n            clients[i] =\n                new Jedis(environment.getServiceHost(name, REDIS_PORT), environment.getServicePort(name, REDIS_PORT));\n        }\n    }\n\n    @Test\n    void simpleTest() {\n        for (int i = 0; i < 3; i++) {\n            clients[i].incr(\"somekey\");\n\n            assertThat(clients[i].get(\"somekey\")).as(\"Each redis instance is separate\").isEqualTo(\"1\");\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/ComposeContainerTest.java",
    "content": "package org.testcontainers.junit;\n\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.ComposeContainer;\nimport org.testcontainers.containers.ContainerState;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.util.Collections;\nimport java.util.Optional;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass ComposeContainerTest extends BaseComposeTest {\n\n    // composeContainerConstructor {\n    public ComposeContainer environment = new ComposeContainer(\n        DockerImageName.parse(\"docker:25.0.5\"),\n        new File(\"src/test/resources/composev2/compose-test.yml\")\n    )\n        .withExposedService(\"redis-1\", REDIS_PORT)\n        .withExposedService(\"db-1\", 3306);\n\n    // }\n\n    ComposeContainerTest() {\n        environment.start();\n    }\n\n    @Override\n    protected ComposeContainer getEnvironment() {\n        return environment;\n    }\n\n    @Test\n    void testGetServiceHostAndPort() {\n        // getServiceHostAndPort {\n        String serviceHost = environment.getServiceHost(\"redis-1\", REDIS_PORT);\n        int serviceWithInstancePort = environment.getServicePort(\"redis-1\", REDIS_PORT);\n        // }\n\n        assertThat(serviceHost).as(\"Service host is not blank\").isNotBlank();\n        assertThat(serviceWithInstancePort).as(\"Port is set for service with instance number\").isNotNull();\n\n        int serviceWithoutInstancePort = environment.getServicePort(\"redis\", REDIS_PORT);\n        assertThat(serviceWithoutInstancePort).as(\"Port is set for service with instance number\").isNotNull();\n        assertThat(serviceWithoutInstancePort).as(\"Service ports are the same\").isEqualTo(serviceWithInstancePort);\n    }\n\n    @Test\n    void shouldRetrieveContainerByServiceName() {\n        String existingServiceName = \"db-1\";\n        Optional<ContainerState> result = environment.getContainerByServiceName(existingServiceName);\n        assertThat(result)\n            .as(String.format(\"Container should be found by service name %s\", existingServiceName))\n            .isPresent();\n        assertThat(Collections.singletonList(3306))\n            .as(\"Mapped port for result container was wrong, probably wrong container found\")\n            .isEqualTo(result.get().getExposedPorts());\n    }\n\n    @Test\n    void shouldReturnEmptyResultOnNoneExistingService() {\n        String notExistingServiceName = \"db-256\";\n        Optional<ContainerState> result = environment.getContainerByServiceName(notExistingServiceName);\n        assertThat(result)\n            .as(String.format(\"No container should be found under service name %s\", notExistingServiceName))\n            .isNotPresent();\n    }\n\n    @Test\n    void shouldCreateContainerWhenFileNotPrefixedWithPath() throws IOException {\n        String validYaml =\n            \"version: '2.2'\\n\" +\n            \"services:\\n\" +\n            \"  http:\\n\" +\n            \"    build: .\\n\" +\n            \"    image: python:latest\\n\" +\n            \"    ports:\\n\" +\n            \"    - 8080:8080\";\n\n        File filePathNotStartWithDotSlash = new File(\"docker-compose-test.yml\");\n        filePathNotStartWithDotSlash.createNewFile();\n        filePathNotStartWithDotSlash.deleteOnExit();\n        Files.write(filePathNotStartWithDotSlash.toPath(), validYaml.getBytes(StandardCharsets.UTF_8));\n\n        final ComposeContainer dockerComposeContainer = new ComposeContainer(\n            DockerImageName.parse(\"docker:25.0.5\"),\n            filePathNotStartWithDotSlash\n        );\n        assertThat(dockerComposeContainer).as(\"Container created using docker compose file\").isNotNull();\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/ComposeContainerVolumeRemovalTest.java",
    "content": "package org.testcontainers.junit;\n\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.testcontainers.DockerClientFactory;\nimport org.testcontainers.containers.ComposeContainer;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.File;\nimport java.util.LinkedHashSet;\nimport java.util.Set;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.stream.Stream;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\n\n@Disabled\nclass ComposeContainerVolumeRemovalTest {\n\n    public static Stream<Arguments> params() {\n        return Stream.of(Arguments.of(true, false), Arguments.of(false, true));\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"params\")\n    void performTest(boolean removeVolumes, boolean shouldVolumesBePresentAfterRunning) {\n        final File composeFile = new File(\"src/test/resources/v2-compose-test.yml\");\n\n        final AtomicReference<String> volumeName = new AtomicReference<>(\"\");\n        try (\n            ComposeContainer environment = new ComposeContainer(DockerImageName.parse(\"docker:25.0.5\"), composeFile)\n                .withExposedService(\"redis\", 6379)\n                .withRemoveVolumes(removeVolumes)\n                .withRemoveImages(ComposeContainer.RemoveImages.ALL)\n        ) {\n            environment.start();\n\n            volumeName.set(volumeNameForRunningContainer(\"-redis-1\"));\n            final boolean isVolumePresentWhileRunning = isVolumePresent(volumeName.get());\n            assertThat(isVolumePresentWhileRunning).as(\"the container volume is present while running\").isEqualTo(true);\n        }\n\n        await()\n            .untilAsserted(() -> {\n                final boolean isVolumePresentAfterRunning = isVolumePresent(volumeName.get());\n                assertThat(isVolumePresentAfterRunning)\n                    .as(\"the container volume is present after running\")\n                    .isEqualTo(shouldVolumesBePresentAfterRunning);\n            });\n    }\n\n    private String volumeNameForRunningContainer(final String containerNameSuffix) {\n        return DockerClientFactory\n            .instance()\n            .client()\n            .listContainersCmd()\n            .exec()\n            .stream()\n            .filter(it -> Stream.of(it.getNames()).anyMatch(name -> name.endsWith(containerNameSuffix)))\n            .findFirst()\n            .map(container -> container.getMounts().get(0).getName())\n            .orElseThrow(IllegalStateException::new);\n    }\n\n    private boolean isVolumePresent(final String volumeName) {\n        Set<String> nameFilter = new LinkedHashSet<>(1);\n        nameFilter.add(volumeName);\n        return DockerClientFactory\n            .instance()\n            .client()\n            .listVolumesCmd()\n            .withFilter(\"name\", nameFilter)\n            .exec()\n            .getVolumes()\n            .stream()\n            .findFirst()\n            .isPresent();\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/ComposeContainerWithBuildTest.java",
    "content": "package org.testcontainers.junit;\n\nimport com.github.dockerjava.api.model.Container;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.rnorth.ducttape.unreliables.Unreliables;\nimport org.testcontainers.DockerClientFactory;\nimport org.testcontainers.containers.ComposeContainer;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.File;\nimport java.util.Collections;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.stream.Stream;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass ComposeContainerWithBuildTest {\n\n    public static Stream<Arguments> params() {\n        return Stream.of(\n            Arguments.of(null, true, true),\n            Arguments.of(ComposeContainer.RemoveImages.LOCAL, false, true),\n            Arguments.of(ComposeContainer.RemoveImages.ALL, false, false)\n        );\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"params\")\n    void performTest(\n        ComposeContainer.RemoveImages removeMode,\n        boolean shouldBuiltImageBePresentAfterRunning,\n        boolean shouldPulledImageBePresentAfterRunning\n    ) {\n        final File composeFile = new File(\"src/test/resources/compose-v2-build-test/docker-compose.yml\");\n\n        final AtomicReference<String> builtImageName = new AtomicReference<>(\"\");\n        final AtomicReference<String> pulledImageName = new AtomicReference<>(\"\");\n        try (\n            ComposeContainer environment = new ComposeContainer(DockerImageName.parse(\"docker:25.0.5\"), composeFile)\n                .withExposedService(\"customredis\", 6379)\n                .withBuild(true)\n                .withRemoveImages(removeMode)\n        ) {\n            environment.start();\n\n            builtImageName.set(imageNameForRunningContainer(\"-customredis-1\"));\n            final boolean isBuiltImagePresentWhileRunning = isImagePresent(builtImageName.get());\n            assertThat(isBuiltImagePresentWhileRunning).as(\"the built image is present while running\").isTrue();\n\n            pulledImageName.set(imageNameForRunningContainer(\"-normalredis-1\"));\n            final boolean isPulledImagePresentWhileRunning = isImagePresent(pulledImageName.get());\n            assertThat(isPulledImagePresentWhileRunning).as(\"the pulled image is present while running\").isTrue();\n        }\n\n        Unreliables.retryUntilSuccess(\n            10,\n            TimeUnit.SECONDS,\n            () -> {\n                final boolean isBuiltImagePresentAfterRunning = isImagePresent(builtImageName.get());\n                assertThat(isBuiltImagePresentAfterRunning)\n                    .as(\"the built image is not present after running\")\n                    .isEqualTo(shouldBuiltImageBePresentAfterRunning);\n                return null;\n            }\n        );\n\n        Unreliables.retryUntilSuccess(\n            10,\n            TimeUnit.SECONDS,\n            () -> {\n                final boolean isPulledImagePresentAfterRunning = isImagePresent(pulledImageName.get());\n                assertThat(isPulledImagePresentAfterRunning)\n                    .as(\"the pulled image is present after running\")\n                    .isEqualTo(shouldPulledImageBePresentAfterRunning);\n                return null;\n            }\n        );\n    }\n\n    private String imageNameForRunningContainer(final String containerNameSuffix) {\n        return DockerClientFactory\n            .instance()\n            .client()\n            .listContainersCmd()\n            .exec()\n            .stream()\n            .filter(it -> Stream.of(it.getNames()).anyMatch(name -> name.endsWith(containerNameSuffix)))\n            .findFirst()\n            .map(Container::getImage)\n            .orElseThrow(IllegalStateException::new);\n    }\n\n    private boolean isImagePresent(final String imageName) {\n        return DockerClientFactory\n            .instance()\n            .client()\n            .listImagesCmd()\n            .withFilter(\"reference\", Collections.singletonList(imageName))\n            .exec()\n            .stream()\n            .findFirst()\n            .isPresent();\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/ComposeContainerWithCopyFilesTest.java",
    "content": "package org.testcontainers.junit;\n\nimport io.restassured.RestAssured;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.ComposeContainer;\nimport org.testcontainers.containers.ContainerLaunchException;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.File;\nimport java.io.IOException;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatExceptionOfType;\n\nclass ComposeContainerWithCopyFilesTest {\n\n    @Test\n    void testShouldCopyAllFilesByDefault() throws IOException {\n        try (\n            ComposeContainer environment = new ComposeContainer(\n                DockerImageName.parse(\"docker:25.0.5\"),\n                new File(\"src/test/resources/compose-file-copy-inclusions/compose.yml\")\n            )\n                .withExposedService(\"app\", 8080)\n        ) {\n            environment.start();\n\n            String response = readStringFromURL(environment);\n            assertThat(response).isEqualTo(\"MY_ENV_VARIABLE: override\");\n        }\n    }\n\n    @Test\n    void testWithFileCopyInclusionUsingFilePath() throws IOException {\n        try (\n            ComposeContainer environment = new ComposeContainer(\n                DockerImageName.parse(\"docker:25.0.5\"),\n                new File(\"src/test/resources/compose-file-copy-inclusions/compose-root-only.yml\")\n            )\n                .withExposedService(\"app\", 8080)\n                .withCopyFilesInContainer(\"Dockerfile\", \"EnvVariableRestEndpoint.java\", \".env\")\n        ) {\n            environment.start();\n\n            String response = readStringFromURL(environment);\n\n            // The `test/.env` file is not copied, now so we get the original value\n            assertThat(response).isEqualTo(\"MY_ENV_VARIABLE: original\");\n        }\n    }\n\n    @Test\n    void testWithFileCopyInclusionUsingDirectoryPath() throws IOException {\n        try (\n            // composeContainerWithCopyFiles {\n            ComposeContainer environment = new ComposeContainer(\n                DockerImageName.parse(\"docker:25.0.5\"),\n                new File(\"src/test/resources/compose-file-copy-inclusions/compose-test-only.yml\")\n            )\n                .withExposedService(\"app\", 8080)\n                .withCopyFilesInContainer(\"Dockerfile\", \"EnvVariableRestEndpoint.java\", \"test\")\n            // }\n        ) {\n            environment.start();\n\n            String response = readStringFromURL(environment);\n            // The test directory (with its contents) is copied, so we get the override\n            assertThat(response).isEqualTo(\"MY_ENV_VARIABLE: override\");\n        }\n    }\n\n    @Test\n    void testShouldNotBeAbleToStartIfNeededEnvFileIsNotCopied() {\n        try (\n            ComposeContainer environment = new ComposeContainer(\n                DockerImageName.parse(\"docker:25.0.5\"),\n                new File(\"src/test/resources/compose-file-copy-inclusions/compose-test-only.yml\")\n            )\n                .withExposedService(\"app\", 8080)\n                .withCopyFilesInContainer(\"Dockerfile\", \"EnvVariableRestEndpoint.java\")\n        ) {\n            assertThatExceptionOfType(ContainerLaunchException.class)\n                .isThrownBy(environment::start)\n                .withMessageContaining(\"Container startup failed for image docker\");\n        }\n    }\n\n    private static String readStringFromURL(ComposeContainer container) throws IOException {\n        Integer servicePort = container.getServicePort(\"app-1\", 8080);\n        String serviceHost = container.getServiceHost(\"app-1\", 8080);\n        String requestURL = \"http://\" + serviceHost + \":\" + servicePort + \"/env\";\n        return RestAssured.get(requestURL).thenReturn().body().asString();\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/ComposeContainerWithOptionsTest.java",
    "content": "package org.testcontainers.junit;\n\nimport com.google.common.collect.ImmutableSet;\nimport org.assertj.core.api.Assumptions;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.testcontainers.containers.ComposeContainer;\nimport org.testcontainers.utility.CommandLine;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.File;\nimport java.util.Set;\nimport java.util.stream.Stream;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Tests the options associated with the docker-compose command.\n */\nclass ComposeContainerWithOptionsTest {\n\n    public static Stream<Arguments> params() {\n        return Stream.of(\n            // Test the happy day case. The compatibility option should be accepted by docker-compose.\n            Arguments.of(\n                new File(\"src/test/resources/compose-options-test/with-deploy-block.yml\"),\n                false,\n                ImmutableSet.of(\"--compatibility\"),\n                false\n            ),\n            // Test with flags absent. Docker compose will warn but continue, ignoring the deploy block.\n            Arguments.of(\n                new File(\"src/test/resources/compose-options-test/with-deploy-block.yml\"),\n                false,\n                ImmutableSet.of(\"\"),\n                false\n            ),\n            // Test with a bad option. Compose will complain.\n            Arguments.of(\n                new File(\"src/test/resources/compose-options-test/with-deploy-block.yml\"),\n                false,\n                ImmutableSet.of(\"--bad-option\"),\n                true\n            ),\n            // Local compose\n            Arguments.of(\n                new File(\"src/test/resources/compose-options-test/with-deploy-block.yml\"),\n                true,\n                ImmutableSet.of(\"--compatibility\"),\n                false\n            )\n        );\n    }\n\n    @ParameterizedTest(name = \"docker-compose test [compose file: {0}, local: {1}, options: {2}, expected result: {3}]\")\n    @MethodSource(\"params\")\n    void performTest(File composeFile, boolean localMode, Set<String> options, boolean expectError) {\n        ComposeContainer environment;\n        if (localMode) {\n            Assumptions\n                .assumeThat(CommandLine.executableExists(ComposeContainer.COMPOSE_EXECUTABLE))\n                .as(\"docker executable exists\")\n                .isTrue();\n            environment = new ComposeContainer(composeFile).withOptions(options.stream().toArray(String[]::new));\n        } else {\n            environment =\n                new ComposeContainer(DockerImageName.parse(\"docker:25.0.2\"), composeFile)\n                    .withOptions(options.stream().toArray(String[]::new));\n        }\n\n        try {\n            environment.start();\n            assertThat(expectError).isFalse();\n            environment.stop();\n        } catch (Exception e) {\n            assertThat(expectError).isTrue();\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/ComposeContainerWithWaitStrategiesTest.java",
    "content": "package org.testcontainers.junit;\n\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.ComposeContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.File;\nimport java.time.Duration;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass ComposeContainerWithWaitStrategiesTest {\n\n    private static final int REDIS_PORT = 6379;\n\n    @Test\n    void testComposeContainerConstructor() {\n        try (\n            // composeContainerWithCombinedWaitStrategies {\n            ComposeContainer compose = new ComposeContainer(\n                DockerImageName.parse(\"docker:25.0.5\"),\n                new File(\"src/test/resources/composev2/compose-test.yml\")\n            )\n                .withExposedService(\"redis-1\", REDIS_PORT, Wait.forSuccessfulCommand(\"redis-cli ping\"))\n                .withExposedService(\"db-1\", 3306, Wait.forLogMessage(\".*ready for connections.*\\\\n\", 1))\n            // }\n        ) {\n            compose.start();\n            containsStartedServices(compose, \"redis-1\", \"db-1\");\n        }\n    }\n\n    @Test\n    void testComposeContainerWaitForPortWithTimeout() {\n        try (\n            // composeContainerWaitForPortWithTimeout {\n            ComposeContainer compose = new ComposeContainer(\n                DockerImageName.parse(\"docker:25.0.5\"),\n                new File(\"src/test/resources/composev2/compose-test.yml\")\n            )\n                .withExposedService(\n                    \"redis-1\",\n                    REDIS_PORT,\n                    Wait.forListeningPort().withStartupTimeout(Duration.ofSeconds(30))\n                )\n            // }\n        ) {\n            compose.start();\n            containsStartedServices(compose, \"redis-1\");\n        }\n    }\n\n    private void containsStartedServices(ComposeContainer compose, String... expectedServices) {\n        for (String serviceName : expectedServices) {\n            assertThat(compose.getContainerByServiceName(serviceName))\n                .as(\"Container should be found by service name %s\", serviceName)\n                .isPresent();\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/ComposeErrorHandlingTest.java",
    "content": "package org.testcontainers.junit;\n\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.ComposeContainer;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.File;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.catchThrowable;\n\nclass ComposeErrorHandlingTest {\n\n    @Test\n    void simpleTest() {\n        assertThat(\n            catchThrowable(() -> {\n                ComposeContainer environment = new ComposeContainer(\n                    DockerImageName.parse(\"docker:25.0.5\"),\n                    new File(\"src/test/resources/invalid-compose.yml\")\n                )\n                    .withExposedService(\"something\", 123);\n            })\n        )\n            .as(\"starting with an invalid docker-compose file throws an exception\")\n            .isInstanceOf(IllegalArgumentException.class);\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/ComposePassthroughTest.java",
    "content": "package org.testcontainers.junit;\n\nimport org.assertj.core.api.Assumptions;\nimport org.junit.jupiter.api.AutoClose;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.ComposeContainer;\nimport org.testcontainers.containers.ContainerState;\nimport org.testcontainers.containers.wait.strategy.HostPortWaitStrategy;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.TestEnvironment;\n\nimport java.io.File;\nimport java.util.Arrays;\nimport java.util.Objects;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass ComposePassthroughTest {\n\n    private final TestWaitStrategy waitStrategy = new TestWaitStrategy();\n\n    @BeforeAll\n    public static void checkVersion() {\n        Assumptions.assumeThat(TestEnvironment.dockerApiAtLeast(\"1.22\")).isTrue();\n    }\n\n    @AutoClose\n    public ComposeContainer compose = new ComposeContainer(\n        DockerImageName.parse(\"docker:25.0.5\"),\n        new File(\"src/test/resources/v2-compose-test-passthrough.yml\")\n    )\n        .withEnv(\"foo\", \"bar\")\n        .withExposedService(\"alpine-1\", 3000, waitStrategy);\n\n    ComposePassthroughTest() {\n        compose.start();\n    }\n\n    @Test\n    void testContainerInstanceProperties() {\n        final ContainerState container = waitStrategy.getContainer();\n\n        //check environment variable was set\n        assertThat(Arrays.asList(Objects.requireNonNull(container.getContainerInfo().getConfig().getEnv())))\n            .as(\"Environment variable set correctly\")\n            .containsOnlyOnce(\"bar=bar\");\n\n        //check other container properties\n        assertThat(container.getContainerId()).as(\"Container id is not null\").isNotNull();\n        assertThat(container.getMappedPort(3000)).as(\"Port mapped\").isNotNull();\n        assertThat(container.getExposedPorts()).containsExactly(3000);\n    }\n\n    /*\n     * WaitStrategy is the only class that has access to the DockerComposeServiceInstance reference\n     * Using a custom WaitStrategy to expose the reference for testability\n     */\n    class TestWaitStrategy extends HostPortWaitStrategy {\n\n        @SuppressWarnings(\"unchecked\")\n        public ContainerState getContainer() {\n            return this.waitStrategyTarget;\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/ComposeWaitStrategyTest.java",
    "content": "package org.testcontainers.junit;\n\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.Mockito;\nimport org.testcontainers.containers.ComposeContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.containers.wait.strategy.WaitStrategy;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.File;\nimport java.time.Duration;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.catchThrowable;\nimport static org.assertj.core.api.Assertions.fail;\n\nclass ComposeWaitStrategyTest {\n\n    private static final int REDIS_PORT = 6379;\n\n    private ComposeContainer environment;\n\n    @BeforeEach\n    public final void setUp() {\n        environment =\n            new ComposeContainer(\n                DockerImageName.parse(\"docker:25.0.5\"),\n                new File(\"src/test/resources/composev2/compose-test.yml\")\n            );\n    }\n\n    @AfterEach\n    public final void cleanUp() {\n        environment.stop();\n    }\n\n    @Test\n    void testWaitOnListeningPort() {\n        environment.withExposedService(\"redis-1\", REDIS_PORT, Wait.forListeningPort());\n\n        try {\n            environment.start();\n        } catch (RuntimeException e) {\n            fail(\"Docker compose should start after waiting for listening port with failed with: \" + e);\n        }\n    }\n\n    @Test\n    void testWaitOnMultipleStrategiesPassing() {\n        environment\n            .withExposedService(\"redis-1\", REDIS_PORT, Wait.forListeningPort())\n            .withExposedService(\"db-1\", 3306, Wait.forLogMessage(\".*ready for connections.*\\\\s\", 1))\n            .withTailChildContainers(true);\n\n        try {\n            environment.start();\n        } catch (RuntimeException e) {\n            fail(\"Docker compose should start after waiting for listening port with failed with: \" + e);\n        }\n    }\n\n    @Test\n    void testWaitingFails() {\n        environment.withExposedService(\n            \"redis-1\",\n            REDIS_PORT,\n            Wait.forHttp(\"/test\").withStartupTimeout(Duration.ofSeconds(10))\n        );\n        assertThat(catchThrowable(() -> environment.start()))\n            .as(\"waiting on an invalid http path times out\")\n            .isInstanceOf(RuntimeException.class);\n    }\n\n    @Test\n    void testWaitOnOneOfMultipleStrategiesFailing() {\n        environment\n            .withExposedService(\n                \"redis-1\",\n                REDIS_PORT,\n                Wait.forListeningPort().withStartupTimeout(Duration.ofSeconds(10))\n            )\n            .waitingFor(\n                \"db-1\",\n                Wait.forLogMessage(\".*test test test.*\\\\s\", 1).withStartupTimeout(Duration.ofSeconds(10))\n            )\n            .withTailChildContainers(true);\n\n        assertThat(catchThrowable(() -> environment.start()))\n            .as(\"waiting on one failing strategy to time out\")\n            .isInstanceOf(RuntimeException.class);\n    }\n\n    @Test\n    void testWaitingForNonexistentServices() {\n        String existentServiceName = \"db-1\";\n        String nonexistentServiceName1 = \"some-nonexistent_service-1\";\n        String nonexistentServiceName2 = \"some-nonexistent_service-2\";\n        WaitStrategy someWaitStrategy = Mockito.mock(WaitStrategy.class);\n\n        environment\n            .waitingFor(existentServiceName, someWaitStrategy)\n            .waitingFor(nonexistentServiceName1, someWaitStrategy)\n            .waitingFor(nonexistentServiceName2, someWaitStrategy);\n\n        Throwable thrownWhenRequestedToWaitForNonexistentService = catchThrowable(environment::start);\n\n        assertThat(thrownWhenRequestedToWaitForNonexistentService)\n            .isInstanceOf(IllegalStateException.class)\n            .hasMessageContaining(nonexistentServiceName1, nonexistentServiceName2)\n            .hasMessageNotContaining(existentServiceName);\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/ComposeWithIdentifierTest.java",
    "content": "package org.testcontainers.junit;\n\nimport org.junit.jupiter.api.AutoClose;\nimport org.testcontainers.containers.ComposeContainer;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.File;\n\nclass ComposeWithIdentifierTest extends BaseComposeTest {\n\n    @AutoClose\n    public ComposeContainer environment = new ComposeContainer(\n        DockerImageName.parse(\"docker:25.0.5\"),\n        \"TEST\",\n        new File(\"src/test/resources/v2-compose-test.yml\")\n    )\n        .withExposedService(\"redis-1\", REDIS_PORT);\n\n    ComposeWithIdentifierTest() {\n        environment.start();\n    }\n\n    @Override\n    protected ComposeContainer getEnvironment() {\n        return this.environment;\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/ComposeWithNetworkTest.java",
    "content": "package org.testcontainers.junit;\n\nimport org.junit.jupiter.api.AutoClose;\nimport org.testcontainers.containers.ComposeContainer;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.File;\n\nclass ComposeWithNetworkTest extends BaseComposeTest {\n\n    @AutoClose\n    public ComposeContainer environment = new ComposeContainer(\n        DockerImageName.parse(\"docker:25.0.5\"),\n        new File(\"src/test/resources/v2-compose-test-with-network.yml\")\n    )\n        .withExposedService(\"redis-1\", REDIS_PORT);\n\n    ComposeWithNetworkTest() {\n        environment.start();\n    }\n\n    @Override\n    protected ComposeContainer getEnvironment() {\n        return environment;\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/CopyFileToContainerTest.java",
    "content": "package org.testcontainers.junit;\n\nimport com.google.common.io.Files;\nimport com.google.common.io.Resources;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.TestImages;\nimport org.testcontainers.containers.BindMode;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.SelinuxContext;\nimport org.testcontainers.utility.MountableFile;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.Map;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass CopyFileToContainerTest {\n\n    private static String destinationOnHost;\n\n    private static String directoryInContainer = \"/tmp/mappable-resource/\";\n\n    private static String fileName = \"test-resource.txt\";\n\n    @BeforeEach\n    public void setup() throws IOException {\n        destinationOnHost = File.createTempFile(\"testcontainers\", null).getAbsolutePath();\n    }\n\n    @Test\n    void checkFileCopied() throws IOException, InterruptedException {\n        try (\n            // copyToContainer {\n            GenericContainer<?> container = new GenericContainer<>(TestImages.TINY_IMAGE)\n                .withCommand(\"sleep\", \"3000\")\n                .withCopyFileToContainer(\n                    MountableFile.forClasspathResource(\"/mappable-resource/\"),\n                    directoryInContainer\n                )\n            // }\n        ) {\n            container.start();\n            String filesList = container.execInContainer(\"ls\", directoryInContainer).getStdout();\n            assertThat(filesList).as(\"file list contains the file\").contains(fileName);\n\n            // copyFileFromContainer {\n            container.copyFileFromContainer(directoryInContainer + fileName, destinationOnHost);\n            // }\n        }\n\n        assertThat(Files.toByteArray(new File(destinationOnHost)))\n            .isEqualTo(Resources.toByteArray(getClass().getResource(\"/mappable-resource/\" + fileName)));\n    }\n\n    @Test\n    void shouldUseCopyForReadOnlyClasspathResources() throws Exception {\n        try (\n            GenericContainer<?> container = new GenericContainer<>(TestImages.TINY_IMAGE)\n                .withCommand(\"sleep\", \"3000\")\n                .withClasspathResourceMapping(\"/mappable-resource/\", directoryInContainer, BindMode.READ_ONLY)\n        ) {\n            container.start();\n            String filesList = container.execInContainer(\"ls\", \"/tmp/mappable-resource\").getStdout();\n            assertThat(filesList).as(\"file list contains the file\").contains(fileName);\n        }\n    }\n\n    @Test\n    void shouldUseCopyOnlyWithReadOnlyClasspathResources() {\n        String resource = \"/test_copy_to_container.txt\";\n        GenericContainer<?> container = new GenericContainer<>(TestImages.TINY_IMAGE)\n            .withClasspathResourceMapping(resource, \"/readOnly\", BindMode.READ_ONLY)\n            .withClasspathResourceMapping(resource, \"/readOnlyShared\", BindMode.READ_ONLY, SelinuxContext.SHARED)\n            .withClasspathResourceMapping(resource, \"/readWrite\", BindMode.READ_WRITE);\n\n        Map<MountableFile, String> copyMap = container.getCopyToFileContainerPathMap();\n        assertThat(copyMap).as(\"uses copy for read-only\").containsValue(\"/readOnly\");\n        assertThat(copyMap).as(\"uses copy for read-only with Selinux\").containsValue(\"/readOnlyShared\");\n        assertThat(copyMap).as(\"uses mount for read-write\").doesNotContainValue(\"/readWrite\");\n    }\n\n    @Test\n    void shouldCreateFoldersStructureWithCopy() throws Exception {\n        String resource = \"/test_copy_to_container.txt\";\n        try (\n            GenericContainer<?> container = new GenericContainer<>(TestImages.TINY_IMAGE)\n                .withCommand(\"sleep\", \"3000\")\n                .withClasspathResourceMapping(resource, \"/a/b/c/file\", BindMode.READ_ONLY)\n        ) {\n            container.start();\n            String filesList = container.execInContainer(\"ls\", \"/a/b/c/\").getStdout();\n            assertThat(filesList).as(\"file list contains the file\").contains(\"file\");\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/DependenciesTest.java",
    "content": "package org.testcontainers.junit;\n\nimport lombok.Getter;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.TestImages;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.startupcheck.OneShotStartupCheckStrategy;\nimport org.testcontainers.lifecycle.Startable;\nimport org.testcontainers.lifecycle.Startables;\n\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicLong;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass DependenciesTest {\n\n    @Test\n    void shouldWorkWithSimpleDependency() {\n        InvocationCountingStartable startable = new InvocationCountingStartable();\n\n        try (\n            GenericContainer<?> container = new GenericContainer<>(TestImages.TINY_IMAGE)\n                .withStartupCheckStrategy(new OneShotStartupCheckStrategy())\n                .dependsOn(startable)\n        ) {\n            container.start();\n        }\n\n        assertThat(startable.getStartInvocationCount().intValue()).as(\"Started once\").isEqualTo(1);\n        assertThat(startable.getStopInvocationCount().intValue()).as(\"Does not trigger .stop()\").isZero();\n    }\n\n    @Test\n    void shouldWorkWithMultipleDependencies() {\n        InvocationCountingStartable startable1 = new InvocationCountingStartable();\n        InvocationCountingStartable startable2 = new InvocationCountingStartable();\n\n        try (\n            GenericContainer<?> container = new GenericContainer<>(TestImages.TINY_IMAGE)\n                .withStartupCheckStrategy(new OneShotStartupCheckStrategy())\n                .dependsOn(startable1, startable2)\n        ) {\n            container.start();\n        }\n\n        assertThat(startable1.getStartInvocationCount().intValue()).as(\"Startable1 started once\").isEqualTo(1);\n        assertThat(startable2.getStartInvocationCount().intValue()).as(\"Startable2 started once\").isEqualTo(1);\n    }\n\n    @Test\n    void shouldStartEveryTime() {\n        InvocationCountingStartable startable = new InvocationCountingStartable();\n\n        try (\n            GenericContainer<?> container = new GenericContainer<>(TestImages.TINY_IMAGE)\n                .withStartupCheckStrategy(new OneShotStartupCheckStrategy())\n                .dependsOn(startable)\n        ) {\n            container.start();\n            container.stop();\n\n            container.start();\n            container.stop();\n\n            container.start();\n        }\n\n        assertThat(startable.getStartInvocationCount().intValue()).as(\"Started multiple times\").isEqualTo(3);\n        assertThat(startable.getStopInvocationCount().intValue()).as(\"Does not trigger .stop()\").isZero();\n    }\n\n    @Test\n    void shouldStartTransitiveDependencies() {\n        InvocationCountingStartable transitiveOfTransitiveStartable = new InvocationCountingStartable();\n        InvocationCountingStartable transitiveStartable = new InvocationCountingStartable();\n        transitiveStartable.getDependencies().add(transitiveOfTransitiveStartable);\n\n        InvocationCountingStartable startable = new InvocationCountingStartable();\n        startable.getDependencies().add(transitiveStartable);\n\n        try (\n            GenericContainer<?> container = new GenericContainer<>(TestImages.TINY_IMAGE)\n                .withStartupCheckStrategy(new OneShotStartupCheckStrategy())\n                .dependsOn(startable)\n        ) {\n            container.start();\n            container.stop();\n        }\n\n        assertThat(startable.getStartInvocationCount().intValue()).as(\"Root started\").isEqualTo(1);\n        assertThat(transitiveStartable.getStartInvocationCount().intValue()).as(\"Transitive started\").isEqualTo(1);\n        assertThat(transitiveOfTransitiveStartable.getStartInvocationCount().intValue())\n            .as(\"Transitive of transitive started\")\n            .isEqualTo(1);\n    }\n\n    @Test\n    void shouldHandleDiamondDependencies() throws Exception {\n        InvocationCountingStartable a = new InvocationCountingStartable();\n        InvocationCountingStartable b = new InvocationCountingStartable();\n        InvocationCountingStartable c = new InvocationCountingStartable();\n        InvocationCountingStartable d = new InvocationCountingStartable();\n        //  / b \\\n        // a     d\n        //  \\ c /\n        b.getDependencies().add(a);\n        c.getDependencies().add(a);\n\n        d.getDependencies().add(b);\n        d.getDependencies().add(c);\n\n        Startables.deepStart(Stream.of(d)).get(1, TimeUnit.SECONDS);\n\n        assertThat(a.getStartInvocationCount().intValue()).as(\"A started\").isEqualTo(1);\n        assertThat(b.getStartInvocationCount().intValue()).as(\"B started\").isEqualTo(1);\n        assertThat(c.getStartInvocationCount().intValue()).as(\"C started\").isEqualTo(1);\n        assertThat(d.getStartInvocationCount().intValue()).as(\"D started\").isEqualTo(1);\n    }\n\n    @Test\n    void shouldHandleParallelStream() throws Exception {\n        List<Startable> startables = Stream\n            .generate(InvocationCountingStartable::new)\n            .limit(10)\n            .collect(Collectors.toList());\n\n        for (int i = 1; i < startables.size(); i++) {\n            startables.get(0).getDependencies().add(startables.get(i));\n        }\n\n        Startables.deepStart(startables.parallelStream()).get(1, TimeUnit.SECONDS);\n    }\n\n    private static class InvocationCountingStartable implements Startable {\n\n        @Getter\n        Set<Startable> dependencies = new HashSet<>();\n\n        @Getter\n        AtomicLong startInvocationCount = new AtomicLong(0);\n\n        @Getter\n        AtomicLong stopInvocationCount = new AtomicLong(0);\n\n        @Override\n        public void start() {\n            startInvocationCount.getAndIncrement();\n        }\n\n        @Override\n        public void stop() {\n            stopInvocationCount.getAndIncrement();\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/DockerComposeContainerPortViaEnvTest.java",
    "content": "package org.testcontainers.junit;\n\nimport org.junit.jupiter.api.AutoClose;\nimport org.junit.jupiter.api.Disabled;\nimport org.testcontainers.containers.DockerComposeContainer;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.File;\n\n@Disabled\nclass DockerComposeContainerPortViaEnvTest extends BaseDockerComposeTest {\n\n    @AutoClose\n    public DockerComposeContainer environment = new DockerComposeContainer(\n        DockerImageName.parse(\"docker/compose:1.29.2\"),\n        new File(\"src/test/resources/v2-compose-test-port-via-env.yml\")\n    )\n        .withExposedService(\"redis_1\", REDIS_PORT)\n        .withEnv(\"REDIS_PORT\", String.valueOf(REDIS_PORT));\n\n    DockerComposeContainerPortViaEnvTest() {\n        environment.start();\n    }\n\n    @Override\n    protected DockerComposeContainer getEnvironment() {\n        return environment;\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/DockerComposeContainerScalingTest.java",
    "content": "package org.testcontainers.junit;\n\nimport org.assertj.core.api.Assumptions;\nimport org.junit.jupiter.api.AutoClose;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.DockerComposeContainer;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.TestEnvironment;\nimport redis.clients.jedis.Jedis;\n\nimport java.io.File;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Created by rnorth on 08/08/2015.\n */\nclass DockerComposeContainerScalingTest {\n\n    private static final int REDIS_PORT = 6379;\n\n    private Jedis[] clients = new Jedis[3];\n\n    @BeforeAll\n    public static void checkVersion() {\n        Assumptions.assumeThat(TestEnvironment.dockerApiAtLeast(\"1.22\")).isTrue();\n    }\n\n    @AutoClose\n    public DockerComposeContainer environment = new DockerComposeContainer(\n        DockerImageName.parse(\"docker/compose:1.29.2\"),\n        new File(\"src/test/resources/scaled-compose-test.yml\")\n    )\n        .withScaledService(\"redis\", 3)\n        .withExposedService(\"redis\", REDIS_PORT) // implicit '_1'\n        .withExposedService(\"redis_2\", REDIS_PORT) // explicit service index\n        .withExposedService(\"redis\", 3, REDIS_PORT); // explicit service index via parameter\n\n    @BeforeEach\n    public void setupClients() {\n        this.environment.start();\n        for (int i = 0; i < 3; i++) {\n            String name = String.format(\"redis_%d\", i + 1);\n\n            clients[i] =\n                new Jedis(environment.getServiceHost(name, REDIS_PORT), environment.getServicePort(name, REDIS_PORT));\n        }\n    }\n\n    @Test\n    void simpleTest() {\n        for (int i = 0; i < 3; i++) {\n            clients[i].incr(\"somekey\");\n\n            assertThat(clients[i].get(\"somekey\")).as(\"Each redis instance is separate\").isEqualTo(\"1\");\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/DockerComposeContainerTest.java",
    "content": "package org.testcontainers.junit;\n\nimport org.junit.jupiter.api.AutoClose;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.ContainerState;\nimport org.testcontainers.containers.DockerComposeContainer;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.util.Collections;\nimport java.util.Optional;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Created by rnorth on 08/08/2015.\n */\n@Disabled\nclass DockerComposeContainerTest extends BaseDockerComposeTest {\n\n    @AutoClose\n    public DockerComposeContainer environment = new DockerComposeContainer(\n        DockerImageName.parse(\"docker/compose:1.29.2\"),\n        new File(\"src/test/resources/compose-test.yml\")\n    )\n        .withExposedService(\"redis_1\", REDIS_PORT)\n        .withExposedService(\"db_1\", 3306);\n\n    DockerComposeContainerTest() {\n        environment.start();\n    }\n\n    @Override\n    protected DockerComposeContainer getEnvironment() {\n        return environment;\n    }\n\n    @Test\n    void testGetServicePort() {\n        int serviceWithInstancePort = environment.getServicePort(\"redis_1\", REDIS_PORT);\n        assertThat(serviceWithInstancePort).as(\"Port is set for service with instance number\").isNotNull();\n        int serviceWithoutInstancePort = environment.getServicePort(\"redis\", REDIS_PORT);\n        assertThat(serviceWithoutInstancePort).as(\"Port is set for service with instance number\").isNotNull();\n        assertThat(serviceWithoutInstancePort).as(\"Service ports are the same\").isEqualTo(serviceWithInstancePort);\n    }\n\n    @Test\n    void shouldRetrieveContainerByServiceName() {\n        String existingServiceName = \"db_1\";\n        Optional<ContainerState> result = environment.getContainerByServiceName(existingServiceName);\n\n        assertThat(result)\n            .as(String.format(\"Container should be found by service name %s\", existingServiceName))\n            .isPresent();\n        assertThat(Collections.singletonList(3306))\n            .as(\"Mapped port for result container was wrong, probably wrong container found\")\n            .isEqualTo(result.get().getExposedPorts());\n    }\n\n    @Test\n    void shouldRetrieveContainerByServiceNameWithoutNumberedSuffix() {\n        String existingServiceName = \"db\";\n        Optional<ContainerState> result = environment.getContainerByServiceName(existingServiceName);\n\n        assertThat(result)\n            .as(String.format(\"Container should be found by service name %s\", existingServiceName))\n            .isPresent();\n        assertThat(result.get().getExposedPorts())\n            .as(\"Mapped port for result container was wrong, perhaps wrong container was found\")\n            .isEqualTo(Collections.singletonList(3306));\n    }\n\n    @Test\n    void shouldReturnEmptyResultOnNoneExistingService() {\n        String notExistingServiceName = \"db_256\";\n        Optional<ContainerState> result = environment.getContainerByServiceName(notExistingServiceName);\n        assertThat(result)\n            .as(String.format(\"No container should be found under service name %s\", notExistingServiceName))\n            .isNotPresent();\n    }\n\n    @Test\n    void shouldCreateContainerWhenFileNotPrefixedWithPath() throws IOException {\n        String validYaml =\n            \"version: '2.2'\\n\" +\n            \"services:\\n\" +\n            \"  http:\\n\" +\n            \"    build: .\\n\" +\n            \"    image: python:latest\\n\" +\n            \"    ports:\\n\" +\n            \"    - 8080:8080\";\n\n        File filePathNotStartWithDotSlash = new File(\"docker-compose-test.yml\");\n        filePathNotStartWithDotSlash.createNewFile();\n        filePathNotStartWithDotSlash.deleteOnExit();\n        Files.write(filePathNotStartWithDotSlash.toPath(), validYaml.getBytes(StandardCharsets.UTF_8));\n\n        final DockerComposeContainer<?> dockerComposeContainer = new DockerComposeContainer<>(\n            DockerImageName.parse(\"docker/compose:debian-1.29.2\"),\n            filePathNotStartWithDotSlash\n        );\n        assertThat(dockerComposeContainer).as(\"Container could not be created using docker compose file\").isNotNull();\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/DockerComposeContainerVolumeRemovalTest.java",
    "content": "package org.testcontainers.junit;\n\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.testcontainers.DockerClientFactory;\nimport org.testcontainers.containers.DockerComposeContainer;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.File;\nimport java.util.LinkedHashSet;\nimport java.util.Set;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.stream.Stream;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\n\n@Disabled\nclass DockerComposeContainerVolumeRemovalTest {\n\n    public static Object[][] params() {\n        return new Object[][] { { true, false }, { false, true } };\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"params\")\n    void performTest(boolean removeVolumes, boolean shouldVolumesBePresentAfterRunning) {\n        final File composeFile = new File(\"src/test/resources/compose-test.yml\");\n\n        final AtomicReference<String> volumeName = new AtomicReference<>(\"\");\n        try (\n            DockerComposeContainer environment = new DockerComposeContainer<>(\n                DockerImageName.parse(\"docker/compose:1.29.2\"),\n                composeFile\n            )\n                .withExposedService(\"redis\", 6379)\n                .withRemoveVolumes(removeVolumes)\n                .withRemoveImages(DockerComposeContainer.RemoveImages.ALL)\n        ) {\n            environment.start();\n\n            volumeName.set(volumeNameForRunningContainer(\"_redis_1\"));\n            final boolean isVolumePresentWhileRunning = isVolumePresent(volumeName.get());\n            assertThat(isVolumePresentWhileRunning).as(\"the container volume is present while running\").isEqualTo(true);\n        }\n\n        await()\n            .untilAsserted(() -> {\n                final boolean isVolumePresentAfterRunning = isVolumePresent(volumeName.get());\n                assertThat(isVolumePresentAfterRunning)\n                    .as(\"the container volume is present after running\")\n                    .isEqualTo(shouldVolumesBePresentAfterRunning);\n            });\n    }\n\n    private String volumeNameForRunningContainer(final String containerNameSuffix) {\n        return DockerClientFactory\n            .instance()\n            .client()\n            .listContainersCmd()\n            .exec()\n            .stream()\n            .filter(it -> Stream.of(it.getNames()).anyMatch(name -> name.endsWith(containerNameSuffix)))\n            .findFirst()\n            .map(container -> container.getMounts().get(0).getName())\n            .orElseThrow(IllegalStateException::new);\n    }\n\n    private boolean isVolumePresent(final String volumeName) {\n        Set<String> nameFilter = new LinkedHashSet<>(1);\n        nameFilter.add(volumeName);\n        return DockerClientFactory\n            .instance()\n            .client()\n            .listVolumesCmd()\n            .withFilter(\"name\", nameFilter)\n            .exec()\n            .getVolumes()\n            .stream()\n            .findFirst()\n            .isPresent();\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/DockerComposeContainerWithBuildTest.java",
    "content": "package org.testcontainers.junit;\n\nimport com.github.dockerjava.api.model.Container;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.rnorth.ducttape.unreliables.Unreliables;\nimport org.testcontainers.DockerClientFactory;\nimport org.testcontainers.containers.DockerComposeContainer;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.File;\nimport java.util.Collections;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.stream.Stream;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass DockerComposeContainerWithBuildTest {\n\n    public static Stream<Arguments> params() {\n        return Stream.of(\n            Arguments.of(null, true, true),\n            Arguments.of(DockerComposeContainer.RemoveImages.LOCAL, false, true),\n            Arguments.of(DockerComposeContainer.RemoveImages.ALL, false, false)\n        );\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"params\")\n    void performTest(\n        DockerComposeContainer.RemoveImages removeMode,\n        boolean shouldBuiltImageBePresentAfterRunning,\n        boolean shouldPulledImageBePresentAfterRunning\n    ) {\n        final File composeFile = new File(\"src/test/resources/compose-build-test/docker-compose.yml\");\n\n        final AtomicReference<String> builtImageName = new AtomicReference<>(\"\");\n        final AtomicReference<String> pulledImageName = new AtomicReference<>(\"\");\n        try (\n            DockerComposeContainer environment = new DockerComposeContainer<>(\n                DockerImageName.parse(\"docker/compose:1.29.2\"),\n                composeFile\n            )\n                .withExposedService(\"customredis\", 6379)\n                .withBuild(true)\n                .withRemoveImages(removeMode)\n        ) {\n            environment.start();\n\n            builtImageName.set(imageNameForRunningContainer(\"_customredis_1\"));\n            final boolean isBuiltImagePresentWhileRunning = isImagePresent(builtImageName.get());\n            assertThat(isBuiltImagePresentWhileRunning).as(\"the built image is present while running\").isEqualTo(true);\n\n            pulledImageName.set(imageNameForRunningContainer(\"_normalredis_1\"));\n            final boolean isPulledImagePresentWhileRunning = isImagePresent(pulledImageName.get());\n            assertThat(isPulledImagePresentWhileRunning)\n                .as(\"the pulled image is present while running\")\n                .isEqualTo(true);\n        }\n\n        Unreliables.retryUntilSuccess(\n            10,\n            TimeUnit.SECONDS,\n            () -> {\n                final boolean isBuiltImagePresentAfterRunning = isImagePresent(builtImageName.get());\n                assertThat(isBuiltImagePresentAfterRunning)\n                    .as(\"the built image is not present after running\")\n                    .isEqualTo(shouldBuiltImageBePresentAfterRunning);\n                return null;\n            }\n        );\n\n        Unreliables.retryUntilSuccess(\n            10,\n            TimeUnit.SECONDS,\n            () -> {\n                final boolean isPulledImagePresentAfterRunning = isImagePresent(pulledImageName.get());\n                assertThat(isPulledImagePresentAfterRunning)\n                    .as(\"the pulled image is present after running\")\n                    .isEqualTo(shouldPulledImageBePresentAfterRunning);\n                return null;\n            }\n        );\n    }\n\n    private String imageNameForRunningContainer(final String containerNameSuffix) {\n        return DockerClientFactory\n            .instance()\n            .client()\n            .listContainersCmd()\n            .exec()\n            .stream()\n            .filter(it -> Stream.of(it.getNames()).anyMatch(name -> name.endsWith(containerNameSuffix)))\n            .findFirst()\n            .map(Container::getImage)\n            .orElseThrow(IllegalStateException::new);\n    }\n\n    private boolean isImagePresent(final String imageName) {\n        return DockerClientFactory\n            .instance()\n            .client()\n            .listImagesCmd()\n            .withFilter(\"reference\", Collections.singletonList(imageName))\n            .exec()\n            .stream()\n            .findFirst()\n            .isPresent();\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/DockerComposeContainerWithCopyFilesTest.java",
    "content": "package org.testcontainers.junit;\n\nimport io.restassured.RestAssured;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.DockerComposeContainer;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.File;\nimport java.io.IOException;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass DockerComposeContainerWithCopyFilesTest {\n\n    @Test\n    void testShouldCopyAllFilesByDefault() throws IOException {\n        try (\n            DockerComposeContainer environment = new DockerComposeContainer(\n                DockerImageName.parse(\"docker/compose:1.29.2\"),\n                new File(\"src/test/resources/compose-file-copy-inclusions/compose.yml\")\n            )\n                .withExposedService(\"app\", 8080)\n        ) {\n            environment.start();\n\n            String response = readStringFromURL(environment);\n            assertThat(response).isEqualTo(\"MY_ENV_VARIABLE: override\");\n        }\n    }\n\n    @Test\n    void testWithFileCopyInclusionUsingFilePath() throws IOException {\n        try (\n            DockerComposeContainer environment = new DockerComposeContainer(\n                DockerImageName.parse(\"docker/compose:1.29.2\"),\n                new File(\"src/test/resources/compose-file-copy-inclusions/compose-root-only.yml\")\n            )\n                .withExposedService(\"app\", 8080)\n                .withCopyFilesInContainer(\"Dockerfile\", \"EnvVariableRestEndpoint.java\", \".env\")\n        ) {\n            environment.start();\n\n            String response = readStringFromURL(environment);\n\n            // The `test/.env` file is not copied, now so we get the original value\n            assertThat(response).isEqualTo(\"MY_ENV_VARIABLE: original\");\n        }\n    }\n\n    @Test\n    void testWithFileCopyInclusionUsingDirectoryPath() throws IOException {\n        try (\n            DockerComposeContainer environment = new DockerComposeContainer(\n                DockerImageName.parse(\"docker/compose:1.29.2\"),\n                new File(\"src/test/resources/compose-file-copy-inclusions/compose-test-only.yml\")\n            )\n                .withExposedService(\"app\", 8080)\n                .withCopyFilesInContainer(\"Dockerfile\", \"EnvVariableRestEndpoint.java\", \"test\")\n        ) {\n            environment.start();\n\n            String response = readStringFromURL(environment);\n            // The test directory (with its contents) is copied, so we get the override\n            assertThat(response).isEqualTo(\"MY_ENV_VARIABLE: override\");\n        }\n    }\n\n    private static String readStringFromURL(DockerComposeContainer container) throws IOException {\n        Integer servicePort = container.getServicePort(\"app_1\", 8080);\n        String serviceHost = container.getServiceHost(\"app_1\", 8080);\n        String requestURL = \"http://\" + serviceHost + \":\" + servicePort + \"/env\";\n        return RestAssured.get(requestURL).thenReturn().body().asString();\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/DockerComposeContainerWithOptionsTest.java",
    "content": "package org.testcontainers.junit;\n\nimport com.google.common.collect.ImmutableSet;\nimport org.assertj.core.api.Assumptions;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.testcontainers.containers.DockerComposeContainer;\nimport org.testcontainers.utility.CommandLine;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.File;\nimport java.util.Set;\nimport java.util.stream.Stream;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Tests the options associated with the docker-compose command.\n */\nclass DockerComposeContainerWithOptionsTest {\n\n    public static Stream<Arguments> params() {\n        return Stream.of(\n            // Test the happy day case. The compatibility option should be accepted by docker-compose.\n            Arguments.of(\n                new File(\"src/test/resources/compose-options-test/with-deploy-block.yml\"),\n                false,\n                ImmutableSet.of(\"--compatibility\"),\n                false\n            ),\n            // Test with flags absent. Docker compose will warn but continue, ignoring the deploy block.\n            Arguments.of(\n                new File(\"src/test/resources/compose-options-test/with-deploy-block.yml\"),\n                false,\n                ImmutableSet.of(\"\"),\n                false\n            ),\n            // Test with a bad option. Compose will complain.\n            Arguments.of(\n                new File(\"src/test/resources/compose-options-test/with-deploy-block.yml\"),\n                false,\n                ImmutableSet.of(\"--bad-option\"),\n                true\n            ),\n            // Local compose\n            Arguments.of(\n                new File(\"src/test/resources/compose-options-test/with-deploy-block.yml\"),\n                true,\n                ImmutableSet.of(\"--compatibility\"),\n                false\n            )\n        );\n    }\n\n    @ParameterizedTest(name = \"docker-compose test [compose file: {0}, local: {1}, options: {2}, expected result: {3}]\")\n    @MethodSource(\"params\")\n    void performTest(File composeFile, boolean localMode, Set<String> options, boolean expectError) {\n        DockerComposeContainer<?> environment;\n        if (localMode) {\n            Assumptions\n                .assumeThat(CommandLine.executableExists(DockerComposeContainer.COMPOSE_EXECUTABLE))\n                .as(\"docker-compose executable exists\")\n                .isTrue();\n            Assumptions\n                .assumeThat(CommandLine.runShellCommand(\"docker-compose\", \"--version\"))\n                .doesNotStartWith(\"Docker Compose version v2\");\n\n            environment =\n                new DockerComposeContainer<>(composeFile).withOptions(options.stream().toArray(String[]::new));\n        } else {\n            environment =\n                new DockerComposeContainer<>(DockerImageName.parse(\"docker/compose:debian-1.29.2\"), composeFile)\n                    .withOptions(options.stream().toArray(String[]::new));\n        }\n\n        try {\n            environment.start();\n            assertThat(expectError).isEqualTo(false);\n            environment.stop();\n        } catch (Exception e) {\n            assertThat(expectError).isEqualTo(true);\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/DockerComposeErrorHandlingTest.java",
    "content": "package org.testcontainers.junit;\n\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.DockerComposeContainer;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.File;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.catchThrowable;\n\nclass DockerComposeErrorHandlingTest {\n\n    @Test\n    void simpleTest() {\n        assertThat(\n            catchThrowable(() -> {\n                DockerComposeContainer environment = new DockerComposeContainer(\n                    DockerImageName.parse(\"docker/compose:1.29.2\"),\n                    new File(\"src/test/resources/invalid-compose.yml\")\n                )\n                    .withExposedService(\"something\", 123);\n            })\n        )\n            .as(\"starting with an invalid docker-compose file throws an exception\")\n            .isInstanceOf(IllegalArgumentException.class);\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/DockerComposeLocalImageTest.java",
    "content": "package org.testcontainers.junit;\n\nimport com.github.dockerjava.api.DockerClient;\nimport com.github.dockerjava.core.command.PullImageResultCallback;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.DockerClientFactory;\nimport org.testcontainers.containers.DockerComposeContainer;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.File;\n\nclass DockerComposeLocalImageTest {\n\n    @Test\n    void usesLocalImageEvenWhenPullFails() throws InterruptedException {\n        tagImage(\"redis:6-alpine\", \"redis-local\", \"latest\");\n\n        DockerComposeContainer composeContainer = new DockerComposeContainer(\n            DockerImageName.parse(\"docker/compose:1.29.2\"),\n            new File(\"src/test/resources/local-compose-test.yml\")\n        )\n            .withExposedService(\"redis\", 6379);\n        composeContainer.start();\n    }\n\n    private void tagImage(String sourceImage, String targetImage, String targetTag) throws InterruptedException {\n        DockerClient client = DockerClientFactory.instance().client();\n        client.pullImageCmd(sourceImage).exec(new PullImageResultCallback()).awaitCompletion();\n        client.tagImageCmd(sourceImage, targetImage, targetTag).exec();\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/DockerComposeLogConsumerTest.java",
    "content": "package org.testcontainers.junit;\n\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.DockerComposeContainer;\nimport org.testcontainers.containers.output.OutputFrame.OutputType;\nimport org.testcontainers.containers.output.WaitingConsumer;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.File;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\n\nclass DockerComposeLogConsumerTest {\n\n    @Test\n    void testLogConsumer() throws TimeoutException {\n        WaitingConsumer logConsumer = new WaitingConsumer();\n        DockerComposeContainer environment = new DockerComposeContainer(\n            DockerImageName.parse(\"docker/compose:1.29.2\"),\n            new File(\"src/test/resources/v2-compose-test.yml\")\n        )\n            .withExposedService(\"redis_1\", 6379)\n            .withLogConsumer(\"redis_1\", logConsumer);\n\n        try {\n            environment.start();\n            logConsumer.waitUntil(\n                frame -> {\n                    return (\n                        frame.getType() == OutputType.STDOUT &&\n                        frame.getUtf8String().contains(\"Ready to accept connections\")\n                    );\n                },\n                5,\n                TimeUnit.SECONDS\n            );\n        } finally {\n            environment.stop();\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/DockerComposePassthroughTest.java",
    "content": "package org.testcontainers.junit;\n\nimport org.assertj.core.api.Assumptions;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.ContainerState;\nimport org.testcontainers.containers.DockerComposeContainer;\nimport org.testcontainers.containers.wait.strategy.HostPortWaitStrategy;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.TestEnvironment;\n\nimport java.io.File;\nimport java.util.Arrays;\nimport java.util.Objects;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Created by rnorth on 11/06/2016.\n */\nclass DockerComposePassthroughTest {\n\n    @Test\n    void testContainerInstanceProperties() {\n        Assumptions.assumeThat(TestEnvironment.dockerApiAtLeast(\"1.22\")).isTrue();\n\n        TestWaitStrategy waitStrategy = new TestWaitStrategy();\n        try (\n            DockerComposeContainer compose = new DockerComposeContainer(\n                DockerImageName.parse(\"docker/compose:1.29.2\"),\n                new File(\"src/test/resources/v2-compose-test-passthrough.yml\")\n            )\n                .withEnv(\"foo\", \"bar\")\n                .withExposedService(\"alpine_1\", 3000, waitStrategy)\n        ) {\n            compose.start();\n\n            final ContainerState container = waitStrategy.getContainer();\n\n            //check environment variable was set\n            assertThat(Arrays.asList(Objects.requireNonNull(container.getContainerInfo().getConfig().getEnv())))\n                .as(\"Environment variable set correctly\")\n                .containsOnlyOnce(\"bar=bar\");\n\n            //check other container properties\n            assertThat(container.getContainerId()).as(\"Container id is not null\").isNotNull();\n            assertThat(container.getMappedPort(3000)).as(\"Port mapped\").isNotNull();\n            assertThat(container.getExposedPorts()).containsExactly(3000);\n        }\n    }\n\n    /*\n     * WaitStrategy is the only class that has access to the DockerComposeServiceInstance reference\n     * Using a custom WaitStrategy to expose the reference for testability\n     */\n    class TestWaitStrategy extends HostPortWaitStrategy {\n\n        @SuppressWarnings(\"unchecked\")\n        public ContainerState getContainer() {\n            return this.waitStrategyTarget;\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/DockerComposeServiceTest.java",
    "content": "package org.testcontainers.junit;\n\nimport org.junit.jupiter.api.AutoClose;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.DockerComposeContainer;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.File;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\n\n@Disabled\nclass DockerComposeServiceTest extends BaseDockerComposeTest {\n\n    @AutoClose\n    public DockerComposeContainer environment = new DockerComposeContainer(\n        DockerImageName.parse(\"docker/compose:1.29.2\"),\n        new File(\"src/test/resources/compose-test.yml\")\n    )\n        .withServices(\"redis\")\n        .withExposedService(\"redis_1\", REDIS_PORT);\n\n    DockerComposeServiceTest() {\n        environment.start();\n    }\n\n    @Override\n    protected DockerComposeContainer getEnvironment() {\n        return environment;\n    }\n\n    @Test\n    void testDbIsNotStarting() {\n        assertThatThrownBy(() -> {\n                environment.getServicePort(\"db_1\", 10001);\n            })\n            .isInstanceOf(IllegalArgumentException.class);\n    }\n\n    @Test\n    void testRedisIsStarting() {\n        assertThat(environment.getServicePort(\"redis_1\", REDIS_PORT)).as(\"Redis server started\").isNotNull();\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/DockerComposeV2FormatTest.java",
    "content": "package org.testcontainers.junit;\n\nimport org.junit.jupiter.api.AutoClose;\nimport org.junit.jupiter.api.Disabled;\nimport org.testcontainers.containers.DockerComposeContainer;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.File;\n\n/**\n * Created by rnorth on 21/05/2016.\n */\n@Disabled\nclass DockerComposeV2FormatTest extends BaseDockerComposeTest {\n\n    @AutoClose\n    public DockerComposeContainer environment = new DockerComposeContainer(\n        DockerImageName.parse(\"docker/compose:1.29.2\"),\n        new File(\"src/test/resources/v2-compose-test.yml\")\n    )\n        .withExposedService(\"redis_1\", REDIS_PORT);\n\n    DockerComposeV2FormatTest() {\n        environment.start();\n    }\n\n    @Override\n    protected DockerComposeContainer getEnvironment() {\n        return environment;\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/DockerComposeV2FormatWithIdentifierTest.java",
    "content": "package org.testcontainers.junit;\n\nimport org.junit.jupiter.api.AutoClose;\nimport org.junit.jupiter.api.Disabled;\nimport org.testcontainers.containers.DockerComposeContainer;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.File;\n\n@Disabled\nclass DockerComposeV2FormatWithIdentifierTest extends BaseDockerComposeTest {\n\n    @AutoClose\n    public DockerComposeContainer environment = new DockerComposeContainer(\n        DockerImageName.parse(\"docker/compose:1.29.2\"),\n        \"TEST\",\n        new File(\"src/test/resources/v2-compose-test.yml\")\n    )\n        .withExposedService(\"redis_1\", REDIS_PORT);\n\n    DockerComposeV2FormatWithIdentifierTest() {\n        environment.start();\n    }\n\n    @Override\n    protected DockerComposeContainer getEnvironment() {\n        return this.environment;\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/DockerComposeV2WithNetworkTest.java",
    "content": "package org.testcontainers.junit;\n\nimport org.junit.jupiter.api.AutoClose;\nimport org.junit.jupiter.api.Disabled;\nimport org.testcontainers.containers.DockerComposeContainer;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.File;\n\n@Disabled\nclass DockerComposeV2WithNetworkTest extends BaseDockerComposeTest {\n\n    @AutoClose\n    public DockerComposeContainer environment = new DockerComposeContainer(\n        DockerImageName.parse(\"docker/compose:1.29.2\"),\n        new File(\"src/test/resources/v2-compose-test-with-network.yml\")\n    )\n        .withExposedService(\"redis_1\", REDIS_PORT);\n\n    DockerComposeV2WithNetworkTest() {\n        environment.start();\n    }\n\n    @Override\n    protected DockerComposeContainer getEnvironment() {\n        return environment;\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/DockerComposeWaitStrategyTest.java",
    "content": "package org.testcontainers.junit;\n\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.Mockito;\nimport org.testcontainers.containers.DockerComposeContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.containers.wait.strategy.WaitStrategy;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.File;\nimport java.time.Duration;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.catchThrowable;\nimport static org.assertj.core.api.Assertions.fail;\n\nclass DockerComposeWaitStrategyTest {\n\n    private static final int REDIS_PORT = 6379;\n\n    private DockerComposeContainer<?> environment;\n\n    @BeforeEach\n    public final void setUp() {\n        environment =\n            new DockerComposeContainer<>(\n                DockerImageName.parse(\"docker/compose:1.29.2\"),\n                new File(\"src/test/resources/compose-test.yml\")\n            );\n    }\n\n    @AfterEach\n    public final void cleanUp() {\n        environment.stop();\n    }\n\n    @Test\n    void testWaitOnListeningPort() {\n        environment.withExposedService(\"redis_1\", REDIS_PORT, Wait.forListeningPort());\n\n        try {\n            environment.start();\n        } catch (RuntimeException e) {\n            fail(\"Docker compose should start after waiting for listening port with failed with: \" + e);\n        }\n    }\n\n    @Test\n    void testWaitOnMultipleStrategiesPassing() {\n        environment\n            .withExposedService(\"redis_1\", REDIS_PORT, Wait.forListeningPort())\n            .withExposedService(\"db_1\", 3306, Wait.forLogMessage(\".*ready for connections.*\\\\s\", 1))\n            .withTailChildContainers(true);\n\n        try {\n            environment.start();\n        } catch (RuntimeException e) {\n            fail(\"Docker compose should start after waiting for listening port with failed with: \" + e);\n        }\n    }\n\n    @Test\n    void testWaitingFails() {\n        environment.withExposedService(\n            \"redis_1\",\n            REDIS_PORT,\n            Wait.forHttp(\"/test\").withStartupTimeout(Duration.ofSeconds(10))\n        );\n        assertThat(catchThrowable(() -> environment.start()))\n            .as(\"waiting on an invalid http path times out\")\n            .isInstanceOf(RuntimeException.class);\n    }\n\n    @Test\n    void testWaitOnOneOfMultipleStrategiesFailing() {\n        environment\n            .withExposedService(\n                \"redis_1\",\n                REDIS_PORT,\n                Wait.forListeningPort().withStartupTimeout(Duration.ofSeconds(10))\n            )\n            .waitingFor(\n                \"db_1\",\n                Wait.forLogMessage(\".*test test test.*\\\\s\", 1).withStartupTimeout(Duration.ofSeconds(10))\n            )\n            .withTailChildContainers(true);\n\n        assertThat(catchThrowable(() -> environment.start()))\n            .as(\"waiting on one failing strategy to time out\")\n            .isInstanceOf(RuntimeException.class);\n    }\n\n    @Test\n    void testWaitingForNonexistentServices() {\n        String existentServiceName = \"db_1\";\n        String nonexistentServiceName1 = \"some_nonexistent_service_1\";\n        String nonexistentServiceName2 = \"some_nonexistent_service_2\";\n        WaitStrategy someWaitStrategy = Mockito.mock(WaitStrategy.class);\n\n        environment\n            .waitingFor(existentServiceName, someWaitStrategy)\n            .waitingFor(nonexistentServiceName1, someWaitStrategy)\n            .waitingFor(nonexistentServiceName2, someWaitStrategy);\n\n        Throwable thrownWhenRequestedToWaitForNonexistentService = catchThrowable(environment::start);\n\n        assertThat(thrownWhenRequestedToWaitForNonexistentService)\n            .isInstanceOf(IllegalStateException.class)\n            .hasMessageContaining(nonexistentServiceName1, nonexistentServiceName2)\n            .hasMessageNotContaining(existentServiceName);\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/DockerNetworkModeTest.java",
    "content": "package org.testcontainers.junit;\n\nimport com.github.dockerjava.api.model.NetworkSettings;\nimport lombok.extern.slf4j.Slf4j;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.TestImages;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.startupcheck.OneShotStartupCheckStrategy;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Simple tests of named network modes - more may be possible, but may not be reproducible\n * without other setup steps.\n */\n@Slf4j\nclass DockerNetworkModeTest {\n\n    @Test\n    void testNoNetworkContainer() {\n        try (\n            GenericContainer<?> container = new GenericContainer<>(TestImages.TINY_IMAGE)\n                .withStartupCheckStrategy(new OneShotStartupCheckStrategy())\n                .withCommand(\"true\")\n                .withNetworkMode(\"none\")\n        ) {\n            container.start();\n            NetworkSettings networkSettings = container.getContainerInfo().getNetworkSettings();\n\n            assertThat(networkSettings.getNetworks()).as(\"only one network is set\").hasSize(1);\n            assertThat(networkSettings.getNetworks()).as(\"network is 'none'\").containsKey(\"none\");\n        }\n    }\n\n    @Test\n    void testHostNetworkContainer() {\n        try (\n            GenericContainer<?> container = new GenericContainer<>(TestImages.TINY_IMAGE)\n                .withStartupCheckStrategy(new OneShotStartupCheckStrategy())\n                .withCommand(\"true\")\n                .withNetworkMode(\"host\")\n        ) {\n            container.start();\n            NetworkSettings networkSettings = container.getContainerInfo().getNetworkSettings();\n\n            assertThat(networkSettings.getNetworks()).as(\"only one network is set\").hasSize(1);\n            assertThat(networkSettings.getNetworks()).as(\"network is 'host'\").containsKey(\"host\");\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/DockerfileContainerTest.java",
    "content": "package org.testcontainers.junit;\n\nimport org.apache.http.client.methods.CloseableHttpResponse;\nimport org.apache.http.client.methods.HttpGet;\nimport org.apache.http.impl.client.CloseableHttpClient;\nimport org.apache.http.impl.client.HttpClientBuilder;\nimport org.junit.jupiter.api.AutoClose;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.images.builder.ImageFromDockerfile;\n\nimport java.io.IOException;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Simple test case / demonstration of creating a fresh container image from a Dockerfile DSL\n */\nclass DockerfileContainerTest {\n\n    @AutoClose\n    public GenericContainer dslContainer = new GenericContainer(\n        new ImageFromDockerfile(\"tcdockerfile/nginx\", false)\n            .withDockerfileFromBuilder(builder -> {\n                builder\n                    .from(\"alpine:3.2\") //\n                    .run(\"apk add --update nginx\")\n                    .cmd(\"nginx\", \"-g\", \"daemon off;\")\n                    .build();\n            })\n    )\n        .withExposedPorts(80);\n\n    @Test\n    void simpleDslTest() throws IOException {\n        dslContainer.start();\n\n        String address = String.format(\"http://%s:%s\", dslContainer.getHost(), dslContainer.getMappedPort(80));\n\n        CloseableHttpClient httpClient = HttpClientBuilder.create().build();\n        HttpGet get = new HttpGet(address);\n\n        try (CloseableHttpResponse response = httpClient.execute(get)) {\n            assertThat(response.getStatusLine().getStatusCode())\n                .as(\"A container built from a dockerfile can run nginx as expected, and returns a good status code\")\n                .isEqualTo(200);\n            assertThat(response.getHeaders(\"Server\")[0].getValue())\n                .as(\n                    \"A container built from a dockerfile can run nginx as expected, and returns an expected Server header\"\n                )\n                .contains(\"nginx\");\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/DockerfileTest.java",
    "content": "package org.testcontainers.junit;\n\nimport com.github.dockerjava.api.command.BuildImageCmd;\nimport org.junit.jupiter.api.Test;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.output.OutputFrame;\nimport org.testcontainers.containers.output.WaitingConsumer;\nimport org.testcontainers.containers.startupcheck.OneShotStartupCheckStrategy;\nimport org.testcontainers.images.builder.ImageFromDockerfile;\nimport org.testcontainers.images.builder.Transferable;\n\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\n\nclass DockerfileTest {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(DockerfileTest.class);\n\n    @Test\n    void simpleDockerfileWorks() {\n        ImageFromDockerfile image = new ImageFromDockerfile()\n            .withFileFromString(\"folder/someFile.txt\", \"hello\")\n            .withFileFromClasspath(\"test.txt\", \"mappable-resource/test-resource.txt\")\n            .withFileFromClasspath(\"Dockerfile\", \"mappable-dockerfile/Dockerfile\");\n\n        verifyImage(image);\n    }\n\n    @Test\n    void customizableImage() {\n        ImageFromDockerfile image = new ImageFromDockerfile() {\n            @Override\n            protected void configure(BuildImageCmd buildImageCmd) {\n                super.configure(buildImageCmd);\n\n                List<String> dockerfile = Arrays.asList(\n                    \"FROM alpine:3.17\",\n                    \"RUN echo 'hello from Docker build process'\",\n                    \"CMD yes\"\n                );\n                withFileFromString(\"Dockerfile\", String.join(\"\\n\", dockerfile));\n\n                buildImageCmd.withNoCache(true);\n            }\n        };\n\n        verifyImage(image);\n    }\n\n    @Test\n    void dockerfileBuilderWorks() {\n        ImageFromDockerfile image = new ImageFromDockerfile()\n            .withFileFromClasspath(\"test.txt\", \"mappable-resource/test-resource.txt\")\n            .withFileFromString(\"folder/someFile.txt\", \"hello\")\n            .withDockerfileFromBuilder(builder -> {\n                builder\n                    .from(\"alpine:3.17\")\n                    .workDir(\"/app\")\n                    .add(\"test.txt\", \"test file.txt\")\n                    .run(\"ls\", \"-la\", \"/app/test file.txt\")\n                    .copy(\"folder/someFile.txt\", \"/someFile.txt\")\n                    .expose(80, 8080)\n                    .cmd(\"while true; do cat /someFile.txt | nc -l -p 80; done\");\n            });\n\n        verifyImage(image);\n    }\n\n    @Test\n    void filePermissions() throws TimeoutException {\n        WaitingConsumer consumer = new WaitingConsumer();\n\n        ImageFromDockerfile image = new ImageFromDockerfile()\n            .withFileFromTransferable(\n                \"/someFile.txt\",\n                new Transferable() {\n                    @Override\n                    public long getSize() {\n                        return 0;\n                    }\n\n                    @Override\n                    public byte[] getBytes() {\n                        return new byte[0];\n                    }\n\n                    @Override\n                    public String getDescription() {\n                        return \"test file\";\n                    }\n\n                    @Override\n                    public int getFileMode() {\n                        return 0123;\n                    }\n                }\n            )\n            .withDockerfileFromBuilder(builder -> {\n                builder\n                    .from(\"alpine:3.17\") //\n                    .copy(\"someFile.txt\", \"/someFile.txt\")\n                    .cmd(\"stat -c \\\"%a\\\" /someFile.txt\");\n            });\n\n        GenericContainer container = new GenericContainer(image)\n            .withStartupCheckStrategy(new OneShotStartupCheckStrategy())\n            .withLogConsumer(consumer);\n\n        try {\n            container.start();\n\n            consumer.waitUntil(\n                frame -> frame.getType() == OutputFrame.OutputType.STDOUT && frame.getUtf8String().contains(\"123\"),\n                5,\n                TimeUnit.SECONDS\n            );\n        } finally {\n            container.stop();\n        }\n    }\n\n    protected void verifyImage(ImageFromDockerfile image) {\n        GenericContainer container = new GenericContainer(image);\n\n        try {\n            container.start();\n        } finally {\n            container.stop();\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/ExecInContainerTest.java",
    "content": "package org.testcontainers.junit;\n\nimport org.assertj.core.api.Assumptions;\nimport org.junit.jupiter.api.AutoClose;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.TestImages;\nimport org.testcontainers.containers.ExecConfig;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.utility.TestEnvironment;\n\nimport java.util.Collections;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass ExecInContainerTest {\n\n    @AutoClose\n    public static GenericContainer<?> redis = new GenericContainer<>(TestImages.REDIS_IMAGE).withExposedPorts(6379);\n\n    static {\n        redis.start();\n    }\n\n    @Test\n    void shouldExecuteCommand() throws Exception {\n        // The older \"lxc\" execution driver doesn't support \"exec\". At the time of writing (2016/03/29),\n        // that's the case for CircleCI.\n        // Once they resolve the issue, this clause can be removed.\n        Assumptions.assumeThat(TestEnvironment.dockerExecutionDriverSupportsExec()).isTrue();\n\n        final GenericContainer.ExecResult result = redis.execInContainer(\"redis-cli\", \"role\");\n        assertThat(result.getStdout())\n            .as(\"Output for \\\"redis-cli role\\\" command should start with \\\"master\\\"\")\n            .startsWith(\"master\");\n        assertThat(result.getStderr()).as(\"Stderr for \\\"redis-cli role\\\" command should be empty\").isEmpty();\n        // We expect to reach this point for modern Docker versions.\n    }\n\n    @Test\n    void shouldExecuteCommandWithUser() throws Exception {\n        // The older \"lxc\" execution driver doesn't support \"exec\". At the time of writing (2016/03/29),\n        // that's the case for CircleCI.\n        // Once they resolve the issue, this clause can be removed.\n        Assumptions.assumeThat(TestEnvironment.dockerExecutionDriverSupportsExec()).isTrue();\n\n        final GenericContainer.ExecResult result = redis.execInContainerWithUser(\"redis\", \"whoami\");\n        assertThat(result.getStdout())\n            .as(\"Output for \\\"whoami\\\" command should start with \\\"redis\\\"\")\n            .startsWith(\"redis\");\n        assertThat(result.getStderr()).as(\"Stderr for \\\"whoami\\\" command should be empty\").isEmpty();\n        // We expect to reach this point for modern Docker versions.\n    }\n\n    @Test\n    void shouldExecuteCommandWithWorkdir() throws Exception {\n        Assumptions.assumeThat(TestEnvironment.dockerExecutionDriverSupportsExec()).isTrue();\n\n        final GenericContainer.ExecResult result = redis.execInContainer(\n            ExecConfig.builder().workDir(\"/opt\").command(new String[] { \"pwd\" }).build()\n        );\n        assertThat(result.getStdout()).startsWith(\"/opt\");\n    }\n\n    @Test\n    void shouldExecuteCommandWithEnvVars() throws Exception {\n        Assumptions.assumeThat(TestEnvironment.dockerExecutionDriverSupportsExec()).isTrue();\n\n        final GenericContainer.ExecResult result = redis.execInContainer(\n            ExecConfig\n                .builder()\n                .envVars(Collections.singletonMap(\"TESTCONTAINERS\", \"JAVA\"))\n                .command(new String[] { \"env\" })\n                .build()\n        );\n        assertThat(result.getStdout()).contains(\"TESTCONTAINERS=JAVA\");\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/FileOperationsTest.java",
    "content": "package org.testcontainers.junit;\n\nimport com.github.dockerjava.api.exception.NotFoundException;\nimport org.apache.commons.io.FileUtils;\nimport org.apache.commons.io.IOUtils;\nimport org.apache.commons.io.output.CountingOutputStream;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.testcontainers.TestImages;\nimport org.testcontainers.containers.Container;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.startupcheck.OneShotStartupCheckStrategy;\nimport org.testcontainers.utility.MountableFile;\n\nimport java.io.BufferedOutputStream;\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.nio.file.Path;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\n\nclass FileOperationsTest {\n\n    @TempDir\n    public Path temporaryFolder;\n\n    @Test\n    void copyFileToContainerFileTest() throws Exception {\n        try (\n            GenericContainer alpineCopyToContainer = new GenericContainer(TestImages.ALPINE_IMAGE) //\n                .withCommand(\"top\")\n        ) {\n            alpineCopyToContainer.start();\n            final MountableFile mountableFile = MountableFile.forClasspathResource(\"test_copy_to_container.txt\");\n            alpineCopyToContainer.copyFileToContainer(mountableFile, \"/test.txt\");\n\n            File actualFile = new File(temporaryFolder.toFile().getAbsolutePath() + \"/test_copy_to_container.txt\");\n            alpineCopyToContainer.copyFileFromContainer(\"/test.txt\", actualFile.getPath());\n\n            File expectedFile = new File(mountableFile.getResolvedPath());\n            assertThat(FileUtils.contentEquals(expectedFile, actualFile)).as(\"Files aren't same \").isTrue();\n        }\n    }\n\n    @Test\n    void copyLargeFilesToContainer() throws Exception {\n        File tempFile = temporaryFolder.resolve(\"large-file\").toFile();\n        try (\n            GenericContainer<?> alpineCopyToContainer = new GenericContainer<>(TestImages.ALPINE_IMAGE) //\n                .withCommand(\"sleep\", \"infinity\")\n        ) {\n            alpineCopyToContainer.start();\n            final long byteCount;\n            try (\n                FileOutputStream fos = new FileOutputStream(tempFile);\n                CountingOutputStream cos = new CountingOutputStream(fos);\n                BufferedOutputStream bos = new BufferedOutputStream(cos)\n            ) {\n                for (int i = 0; i < 0x4000; i++) {\n                    byte[] bytes = new byte[0xFFFF];\n                    bos.write(bytes);\n                }\n                bos.flush();\n                byteCount = cos.getByteCount();\n            }\n            final MountableFile mountableFile = MountableFile.forHostPath(tempFile.getPath());\n            final String containerPath = \"/test.bin\";\n            alpineCopyToContainer.copyFileToContainer(mountableFile, containerPath);\n\n            final Container.ExecResult execResult = alpineCopyToContainer.execInContainer( //\n                \"stat\",\n                \"-c\",\n                \"%s\",\n                containerPath\n            );\n            assertThat(execResult.getStdout()).isEqualToIgnoringNewLines(Long.toString(byteCount));\n        } finally {\n            tempFile.delete();\n        }\n    }\n\n    @Test\n    void copyFileToContainerFolderTest() throws Exception {\n        try (\n            GenericContainer alpineCopyToContainer = new GenericContainer(TestImages.ALPINE_IMAGE) //\n                .withCommand(\"top\")\n        ) {\n            alpineCopyToContainer.start();\n            final MountableFile mountableFile = MountableFile.forClasspathResource(\"test_copy_to_container.txt\");\n            alpineCopyToContainer.copyFileToContainer(mountableFile, \"/home/\");\n\n            File actualFile = new File(temporaryFolder.toFile().getAbsolutePath() + \"/test_copy_to_container.txt\");\n            alpineCopyToContainer.copyFileFromContainer(\"/home/test_copy_to_container.txt\", actualFile.getPath());\n\n            File expectedFile = new File(mountableFile.getResolvedPath());\n            assertThat(FileUtils.contentEquals(expectedFile, actualFile)).as(\"Files aren't same \").isTrue();\n        }\n    }\n\n    @Test\n    void copyFolderToContainerFolderTest() throws Exception {\n        try (\n            GenericContainer alpineCopyToContainer = new GenericContainer(TestImages.ALPINE_IMAGE) //\n                .withCommand(\"top\")\n        ) {\n            alpineCopyToContainer.start();\n            final MountableFile mountableFile = MountableFile.forClasspathResource(\"mappable-resource/\");\n            alpineCopyToContainer.copyFileToContainer(mountableFile, \"/home/test/\");\n\n            File actualFile = new File(temporaryFolder.toFile().getAbsolutePath() + \"/test_copy_to_container.txt\");\n            alpineCopyToContainer.copyFileFromContainer(\"/home/test/test-resource.txt\", actualFile.getPath());\n\n            File expectedFile = new File(mountableFile.getResolvedPath() + \"/test-resource.txt\");\n            assertThat(FileUtils.contentEquals(expectedFile, actualFile)).as(\"Files aren't same \").isTrue();\n        }\n    }\n\n    @Test\n    void copyFromContainerShouldFailBecauseNoFileTest() {\n        assertThatThrownBy(() -> {\n                try (\n                    GenericContainer alpineCopyToContainer = new GenericContainer(TestImages.ALPINE_IMAGE) //\n                        .withCommand(\"top\")\n                ) {\n                    alpineCopyToContainer.start();\n                    alpineCopyToContainer.copyFileFromContainer(\n                        \"/home/test.txt\",\n                        \"src/test/resources/copy-from/test.txt\"\n                    );\n                }\n            })\n            .isInstanceOf(NotFoundException.class);\n    }\n\n    @Test\n    void shouldCopyFileFromContainerTest() throws IOException {\n        try (\n            GenericContainer alpineCopyToContainer = new GenericContainer(TestImages.ALPINE_IMAGE) //\n                .withCommand(\"top\")\n        ) {\n            alpineCopyToContainer.start();\n            final MountableFile mountableFile = MountableFile.forClasspathResource(\"test_copy_to_container.txt\");\n            alpineCopyToContainer.copyFileToContainer(mountableFile, \"/home/\");\n\n            File actualFile = new File(temporaryFolder.toFile().getAbsolutePath() + \"/test_copy_from_container.txt\");\n            alpineCopyToContainer.copyFileFromContainer(\"/home/test_copy_to_container.txt\", actualFile.getPath());\n\n            File expectedFile = new File(mountableFile.getResolvedPath());\n            assertThat(FileUtils.contentEquals(expectedFile, actualFile)).as(\"Files aren't same \").isTrue();\n        }\n    }\n\n    @Test\n    void copyFileOperationsShouldFailWhenNotStartedTest() {\n        try (GenericContainer<?> container = new GenericContainer<>(TestImages.ALPINE_IMAGE).withCommand(\"top\")) {\n            assertThatThrownBy(() -> {\n                    MountableFile mountableFile = MountableFile.forClasspathResource(\"test_copy_to_container.txt\");\n                    container.copyFileToContainer(mountableFile, \"/home/test.txt\");\n                })\n                .isInstanceOf(IllegalStateException.class)\n                .hasMessageContaining(\"can only be used with created / running container\");\n\n            assertThatThrownBy(() -> {\n                    container.copyFileFromContainer(\"/home/test_copy_to_container.txt\", IOUtils::toByteArray);\n                })\n                .isInstanceOf(IllegalStateException.class)\n                .hasMessageContaining(\"can only be used when the Container is created\");\n        }\n    }\n\n    @Test\n    void shouldCopyFileFromExitedContainerTest() {\n        try (\n            GenericContainer<?> container = new GenericContainer<>(TestImages.ALPINE_IMAGE)\n                .withCommand(\"sh\", \"-c\", \"echo -n 'Hello!' > /home/file_in_container.txt\")\n                .withStartupCheckStrategy(new OneShotStartupCheckStrategy())\n        ) {\n            container.start();\n            assertThat(\n                container.getDockerClient().waitContainerCmd(container.getContainerId()).start().awaitStatusCode()\n            )\n                .isZero();\n\n            container.copyFileFromContainer(\"/home/file_in_container.txt\", IOUtils::toByteArray);\n\n            container.copyFileToContainer(\n                MountableFile.forClasspathResource(\"test_copy_to_container.txt\"),\n                \"/test.txt\"\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/FixedHostPortContainerTest.java",
    "content": "package org.testcontainers.junit;\n\nimport org.awaitility.Awaitility;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.TestImages;\nimport org.testcontainers.containers.FixedHostPortGenericContainer;\nimport org.testcontainers.containers.GenericContainer;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.net.Socket;\nimport java.time.Duration;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Test of {@link FixedHostPortGenericContainer}. Note that this is not an example of typical use (usually, a container\n * should be a field on the test class annotated with @Rule or @TestRule). Instead, here, the lifecycle of the container\n * is managed completely within the test method to allow a free port to be found and assigned before the container\n * is started.\n */\nclass FixedHostPortContainerTest {\n\n    private static final String TEST_IMAGE = \"alpine:3.17\";\n\n    /**\n     * Default http server port (just something different from default)\n     */\n    private static final int TEST_PORT = 5678;\n\n    /**\n     * test response\n     */\n    private static final String TEST_RESPONSE = \"test-response\";\n\n    /**\n     * *nix pipe to fire test response on test port\n     */\n    private static final String HTTP_ECHO_CMD = String.format(\n        \"while true; do echo \\\"%s\\\" | nc -l -p %d; done\",\n        TEST_RESPONSE,\n        TEST_PORT\n    );\n\n    @Test\n    void testFixedHostPortMapping() throws IOException {\n        // first find a free port on the docker host that will work for testing\n        final Integer unusedHostPort;\n        try (\n            final GenericContainer echoServer = new GenericContainer(TestImages.TINY_IMAGE)\n                .withExposedPorts(TEST_PORT)\n                .withCommand(\"/bin/sh\", \"-c\", HTTP_ECHO_CMD)\n        ) {\n            echoServer.start();\n            unusedHostPort = echoServer.getMappedPort(TEST_PORT);\n        }\n\n        // now starting echo server container mapped to known-as-free host port\n        try (\n            final GenericContainer echoServer = new FixedHostPortGenericContainer(TEST_IMAGE)\n                // using workaround for port bind+expose\n                .withFixedExposedPort(unusedHostPort, TEST_PORT)\n                .withExposedPorts(TEST_PORT)\n                .withCommand(\"/bin/sh\", \"-c\", HTTP_ECHO_CMD)\n        ) {\n            echoServer.start();\n\n            assertThat(echoServer.getMappedPort(TEST_PORT))\n                .as(\"Port mapping does not seem to match given fixed port\")\n                .isEqualTo(unusedHostPort);\n\n            final String content = readResponse(echoServer, unusedHostPort);\n            assertThat(content).as(\"Returned echo from fixed port does not match expected\").isEqualTo(TEST_RESPONSE);\n        }\n    }\n\n    /**\n     * Simple socket content reader from given container:port\n     *\n     * @param container to query\n     * @param port      to send request to\n     * @return socket reader content\n     * @throws IOException if any\n     */\n    private String readResponse(GenericContainer container, Integer port) throws IOException {\n        try (\n            Socket socket = Awaitility\n                .await()\n                .pollDelay(Duration.ofSeconds(1))\n                .until(() -> new Socket(container.getHost(), port), Socket::isConnected);\n            BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()))\n        ) {\n            return reader.readLine();\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/GenericContainerRuleTest.java",
    "content": "package org.testcontainers.junit;\n\nimport com.github.dockerjava.api.model.HostConfig;\nimport com.google.common.collect.ImmutableMap;\nimport com.google.common.util.concurrent.Uninterruptibles;\nimport com.mongodb.MongoClient;\nimport com.mongodb.client.MongoCollection;\nimport com.mongodb.client.MongoDatabase;\nimport com.rabbitmq.client.AMQP;\nimport com.rabbitmq.client.Channel;\nimport com.rabbitmq.client.Connection;\nimport com.rabbitmq.client.ConnectionFactory;\nimport com.rabbitmq.client.DefaultConsumer;\nimport com.rabbitmq.client.Envelope;\nimport org.apache.commons.io.FileUtils;\nimport org.bson.Document;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\nimport org.rnorth.ducttape.RetryCountExceededException;\nimport org.rnorth.ducttape.unreliables.Unreliables;\nimport org.testcontainers.TestImages;\nimport org.testcontainers.containers.BindMode;\nimport org.testcontainers.containers.Container;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.SelinuxContext;\nimport org.testcontainers.containers.startupcheck.OneShotStartupCheckStrategy;\nimport org.testcontainers.utility.Base58;\n\nimport java.io.BufferedReader;\nimport java.io.File;\nimport java.io.FileNotFoundException;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.io.PrintStream;\nimport java.net.Socket;\nimport java.time.Duration;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.Map;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.catchThrowable;\n\n/**\n * Tests for GenericContainerRules\n */\nclass GenericContainerRuleTest {\n\n    private static final int REDIS_PORT = 6379;\n\n    private static final String RABBIQMQ_TEST_EXCHANGE = \"TestExchange\";\n\n    private static final String RABBITMQ_TEST_ROUTING_KEY = \"TestRoutingKey\";\n\n    private static final String RABBITMQ_TEST_MESSAGE = \"Hello world\";\n\n    private static final int RABBITMQ_PORT = 5672;\n\n    private static final int MONGO_PORT = 27017;\n\n    /*\n     * Test data setup\n     */\n    @BeforeAll\n    public static void setupContent() throws FileNotFoundException {\n        File contentFolder = new File(System.getProperty(\"user.home\") + \"/.tmp-test-container\");\n        contentFolder.mkdir();\n        writeStringToFile(contentFolder, \"file\", \"Hello world!\");\n    }\n\n    /**\n     * Redis\n     */\n    public static GenericContainer<?> redis = new GenericContainer<>(TestImages.REDIS_IMAGE)\n        .withExposedPorts(REDIS_PORT);\n\n    /**\n     * RabbitMQ\n     */\n    public static GenericContainer<?> rabbitMq = new GenericContainer<>(TestImages.RABBITMQ_IMAGE)\n        .withExposedPorts(RABBITMQ_PORT);\n\n    /**\n     * MongoDB\n     */\n    public static GenericContainer<?> mongo = new GenericContainer<>(TestImages.MONGODB_IMAGE)\n        .withExposedPorts(MONGO_PORT);\n\n    /**\n     * Pass an environment variable to the container, then run a shell script that exposes the variable in a quick and\n     * dirty way for testing.\n     */\n    public static GenericContainer<?> alpineEnvVar = new GenericContainer<>(TestImages.ALPINE_IMAGE)\n        .withExposedPorts(80)\n        .withEnv(\"MAGIC_NUMBER\", \"4\")\n        .withEnv(\"MAGIC_NUMBER\", oldValue -> oldValue.orElse(\"\") + \"2\")\n        .withCommand(\"/bin/sh\", \"-c\", \"while true; do echo \\\"$MAGIC_NUMBER\\\" | nc -l -p 80; done\");\n\n    /**\n     * Pass environment variables to the container, then run a shell script that exposes the variables in a quick and\n     * dirty way for testing.\n     */\n    public static GenericContainer<?> alpineEnvVarFromMap = new GenericContainer<>(TestImages.ALPINE_IMAGE)\n        .withExposedPorts(80)\n        .withEnv(ImmutableMap.of(\"FIRST\", \"42\", \"SECOND\", \"50\"))\n        .withCommand(\"/bin/sh\", \"-c\", \"while true; do echo \\\"$FIRST and $SECOND\\\" | nc -l -p 80; done\");\n\n    /**\n     * Map a file on the classpath to a file in the container, and then expose the content for testing.\n     */\n    public static GenericContainer<?> alpineClasspathResource = new GenericContainer<>(TestImages.ALPINE_IMAGE)\n        .withExposedPorts(80)\n        .withClasspathResourceMapping(\"mappable-resource/test-resource.txt\", \"/content.txt\", BindMode.READ_ONLY)\n        .withCommand(\"/bin/sh\", \"-c\", \"while true; do cat /content.txt | nc -l -p 80; done\");\n\n    /**\n     * Map a file on the classpath to a file in the container, and then expose the content for testing.\n     */\n    public static GenericContainer<?> alpineClasspathResourceSelinux = new GenericContainer<>(TestImages.ALPINE_IMAGE)\n        .withExposedPorts(80)\n        .withClasspathResourceMapping(\n            \"mappable-resource/test-resource.txt\",\n            \"/content.txt\",\n            BindMode.READ_WRITE,\n            SelinuxContext.SHARED\n        )\n        .withCommand(\"/bin/sh\", \"-c\", \"while true; do cat /content.txt | nc -l -p 80; done\");\n\n    /**\n     * Create a container with an extra host entry and expose the content of /etc/hosts for testing.\n     */\n    public static GenericContainer<?> alpineExtrahost = new GenericContainer<>(TestImages.ALPINE_IMAGE)\n        .withExposedPorts(80)\n        .withExtraHost(\"somehost\", \"192.168.1.10\")\n        .withCommand(\"/bin/sh\", \"-c\", \"while true; do cat /etc/hosts | nc -l -p 80; done\");\n\n    static {\n        redis.start();\n        rabbitMq.start();\n        mongo.start();\n        alpineEnvVar.start();\n        alpineEnvVarFromMap.start();\n        alpineClasspathResource.start();\n        alpineClasspathResourceSelinux.start();\n        alpineExtrahost.start();\n    }\n\n    @Test\n    void testIsRunning() {\n        try (GenericContainer<?> container = new GenericContainer<>(TestImages.TINY_IMAGE).withCommand(\"top\")) {\n            assertThat(container.isRunning()).as(\"Container is not started and not running\").isFalse();\n            container.start();\n            assertThat(container.isRunning()).as(\"Container is started and running\").isTrue();\n        }\n    }\n\n    @Test\n    void withTmpFsTest() throws Exception {\n        try (\n            GenericContainer<?> container = new GenericContainer<>(TestImages.TINY_IMAGE)\n                .withCommand(\"top\")\n                .withTmpFs(Collections.singletonMap(\"/testtmpfs\", \"rw\"))\n        ) {\n            container.start();\n            // check file doesn't exist\n            String path = \"/testtmpfs/test.file\";\n            Container.ExecResult execResult = container.execInContainer(\"ls\", path);\n            assertThat(execResult.getStderr())\n                .as(\"tmpfs inside container works fine\")\n                .isEqualTo(\"ls: /testtmpfs/test.file: No such file or directory\\n\");\n            // touch && check file does exist\n            container.execInContainer(\"touch\", path);\n            execResult = container.execInContainer(\"ls\", path);\n            assertThat(execResult.getStdout()).as(\"tmpfs inside container works fine\").isEqualTo(path + \"\\n\");\n        }\n    }\n\n    @Test\n    void simpleRabbitMqTest() throws IOException, TimeoutException {\n        ConnectionFactory factory = new ConnectionFactory();\n        factory.setHost(rabbitMq.getHost());\n        factory.setPort(rabbitMq.getMappedPort(RABBITMQ_PORT));\n        Connection connection = factory.newConnection();\n\n        Channel channel = connection.createChannel();\n        channel.exchangeDeclare(RABBIQMQ_TEST_EXCHANGE, \"direct\", true);\n        String queueName = channel.queueDeclare().getQueue();\n        channel.queueBind(queueName, RABBIQMQ_TEST_EXCHANGE, RABBITMQ_TEST_ROUTING_KEY);\n\n        // Set up a consumer on the queue\n        final boolean[] messageWasReceived = new boolean[1];\n        channel.basicConsume(\n            queueName,\n            false,\n            new DefaultConsumer(channel) {\n                @Override\n                public void handleDelivery(\n                    String consumerTag,\n                    Envelope envelope,\n                    AMQP.BasicProperties properties,\n                    byte[] body\n                ) throws IOException {\n                    messageWasReceived[0] = Arrays.equals(body, RABBITMQ_TEST_MESSAGE.getBytes());\n                }\n            }\n        );\n\n        // post a message\n        channel.basicPublish(RABBIQMQ_TEST_EXCHANGE, RABBITMQ_TEST_ROUTING_KEY, null, RABBITMQ_TEST_MESSAGE.getBytes());\n\n        // check the message was received\n        assertThat(\n            Unreliables.retryUntilSuccess(\n                5,\n                TimeUnit.SECONDS,\n                () -> {\n                    if (!messageWasReceived[0]) {\n                        throw new IllegalStateException(\"Message not received yet\");\n                    }\n                    return true;\n                }\n            )\n        )\n            .as(\"The message was received\")\n            .isTrue();\n    }\n\n    @Test\n    void simpleMongoDbTest() {\n        MongoClient mongoClient = new MongoClient(mongo.getHost(), mongo.getMappedPort(MONGO_PORT));\n        MongoDatabase database = mongoClient.getDatabase(\"test\");\n        MongoCollection<Document> collection = database.getCollection(\"testCollection\");\n\n        Document doc = new Document(\"name\", \"foo\").append(\"value\", 1);\n        collection.insertOne(doc);\n\n        Document doc2 = collection.find(new Document(\"name\", \"foo\")).first();\n        assertThat(doc2.get(\"value\")).as(\"A record can be inserted into and retrieved from MongoDB\").isEqualTo(1);\n    }\n\n    @Test\n    void environmentAndCustomCommandTest() throws IOException {\n        String line = getReaderForContainerPort80(alpineEnvVar).readLine();\n\n        assertThat(line).as(\"An environment variable can be passed into a command\").isEqualTo(\"42\");\n    }\n\n    @Test\n    void environmentFromMapTest() throws IOException {\n        String line = getReaderForContainerPort80(alpineEnvVarFromMap).readLine();\n\n        assertThat(line).as(\"Environment variables can be passed into a command from a map\").isEqualTo(\"42 and 50\");\n    }\n\n    @Test\n    void customLabelTest() {\n        try (\n            final GenericContainer alpineCustomLabel = new GenericContainer<>(TestImages.ALPINE_IMAGE)\n                .withLabel(\"our.custom\", \"label\")\n                .withCommand(\"top\")\n                .withCreateContainerCmdModifier(cmd -> cmd.getLabels().put(\"scope\", \"local\"))\n        ) {\n            alpineCustomLabel.start();\n\n            Map<String, String> labels = alpineCustomLabel.getCurrentContainerInfo().getConfig().getLabels();\n            assertThat(labels).as(\"org.testcontainers label is present\").containsKey(\"org.testcontainers\");\n            assertThat(labels).as(\"org.testcontainers.lang label is present\").containsKey(\"org.testcontainers.lang\");\n            assertThat(labels)\n                .as(\"org.testcontainers.lang label is present\")\n                .containsEntry(\"org.testcontainers.lang\", \"java\");\n            assertThat(labels)\n                .as(\"org.testcontainers.version label is present\")\n                .containsKey(\"org.testcontainers.version\");\n            assertThat(labels).as(\"our.custom label is present\").containsKey(\"our.custom\");\n            assertThat(labels).as(\"our.custom label value is label\").containsEntry(\"our.custom\", \"label\");\n            assertThat(labels)\n                .as(\"project label value is testcontainers-java\")\n                .containsEntry(\"project\", \"testcontainers-java\");\n            assertThat(labels).as(\"scope label value is local\").containsEntry(\"scope\", \"local\");\n        }\n    }\n\n    @Test\n    void exceptionThrownWhenTryingToOverrideTestcontainersLabels() {\n        assertThat(\n            catchThrowable(() -> {\n                new GenericContainer<>(TestImages.ALPINE_IMAGE).withLabel(\"org.testcontainers.foo\", \"false\");\n            })\n        )\n            .as(\"When trying to overwrite an 'org.testcontainers' label, withLabel() throws an exception\")\n            .isInstanceOf(IllegalArgumentException.class);\n    }\n\n    @Test\n    void customClasspathResourceMappingTest() throws IOException {\n        // Note: This functionality doesn't work if you are running your build inside a Docker container;\n        // in that case this test will fail.\n        String line = getReaderForContainerPort80(alpineClasspathResource).readLine();\n\n        assertThat(line)\n            .as(\"Resource on the classpath can be mapped using calls to withClasspathResourceMapping\")\n            .isEqualTo(\"FOOBAR\");\n    }\n\n    @Test\n    void customClasspathResourceMappingWithSelinuxTest() throws IOException {\n        String line = getReaderForContainerPort80(alpineClasspathResourceSelinux).readLine();\n        assertThat(line)\n            .as(\"Resource on the classpath can be mapped using calls to withClasspathResourceMappingSelinux\")\n            .isEqualTo(\"FOOBAR\");\n    }\n\n    @Test\n    void exceptionThrownWhenMappedPortNotFound() {\n        assertThat(catchThrowable(() -> redis.getMappedPort(666)))\n            .as(\"When the requested port is not mapped, getMappedPort() throws an exception\")\n            .isInstanceOf(IllegalArgumentException.class);\n    }\n\n    protected static void writeStringToFile(File contentFolder, String filename, String string)\n        throws FileNotFoundException {\n        File file = new File(contentFolder, filename);\n\n        PrintStream printStream = new PrintStream(new FileOutputStream(file));\n        printStream.println(string);\n        printStream.close();\n    }\n\n    @Test\n    @Disabled //TODO investigate intermittent failures\n    void failFastWhenContainerHaltsImmediately() {\n        long startingTimeNano = System.nanoTime();\n        final GenericContainer failsImmediately = new GenericContainer<>(TestImages.ALPINE_IMAGE)\n            .withCommand(\"/bin/sh\", \"-c\", \"return false\")\n            .withMinimumRunningDuration(Duration.ofMillis(100));\n\n        try {\n            assertThat(catchThrowable(failsImmediately::start))\n                .as(\"When we start a container that halts immediately, an exception is thrown\")\n                .isInstanceOf(RetryCountExceededException.class);\n\n            // Check how long it took, to verify that we ARE bailing out early.\n            // Want to strike a balance here; too short and this test will fail intermittently\n            // on slow systems and/or due to GC variation, too long, and we won't properly test\n            // what we're intending to test.\n            int allowedSecondsToFailure = GenericContainer.CONTAINER_RUNNING_TIMEOUT_SEC / 2;\n            long completedTimeNano = System.nanoTime();\n            assertThat(completedTimeNano - startingTimeNano < TimeUnit.SECONDS.toNanos(allowedSecondsToFailure))\n                .as(\"container should not take long to start up\")\n                .isTrue();\n        } finally {\n            failsImmediately.stop();\n        }\n    }\n\n    @Test\n    void extraHostTest() throws IOException {\n        BufferedReader br = getReaderForContainerPort80(alpineExtrahost);\n\n        // read hosts file from container\n        StringBuffer hosts = new StringBuffer();\n        String line = br.readLine();\n        while (line != null) {\n            hosts.append(line);\n            hosts.append(\"\\n\");\n            line = br.readLine();\n        }\n\n        Matcher matcher = Pattern.compile(\"^192.168.1.10\\\\s.*somehost\", Pattern.MULTILINE).matcher(hosts.toString());\n        assertThat(matcher.find()).as(\"The hosts file of container contains extra host\").isTrue();\n    }\n\n    @Test\n    void createContainerCmdHookTest() {\n        // Use random name to avoid the conflicts between the tests\n        String randomName = Base58.randomString(5);\n        try (\n            GenericContainer<?> container = new GenericContainer<>(TestImages.REDIS_IMAGE)\n                .withCommand(\"redis-server\", \"--help\")\n                .withCreateContainerCmdModifier(cmd -> cmd.withName(\"overrideMe\"))\n                // Preserves the order\n                .withCreateContainerCmdModifier(cmd -> cmd.withName(randomName))\n                // Allows to override pre-configured values by GenericContainer\n                .withCreateContainerCmdModifier(cmd -> cmd.withCmd(\"redis-server\", \"--port\", \"6379\"))\n        ) {\n            container.start();\n\n            assertThat(container.getContainerInfo().getName()).as(\"Name is configured\").isEqualTo(\"/\" + randomName);\n            assertThat(Arrays.toString(container.getContainerInfo().getConfig().getCmd()))\n                .as(\"Command is configured\")\n                .isEqualTo(\"[redis-server, --port, 6379]\");\n        }\n    }\n\n    private BufferedReader getReaderForContainerPort80(GenericContainer container) {\n        return Unreliables.retryUntilSuccess(\n            10,\n            TimeUnit.SECONDS,\n            () -> {\n                Uninterruptibles.sleepUninterruptibly(1, TimeUnit.SECONDS);\n\n                Socket socket = new Socket(container.getHost(), container.getFirstMappedPort());\n                return new BufferedReader(new InputStreamReader(socket.getInputStream()));\n            }\n        );\n    }\n\n    @Test\n    void addExposedPortAfterWithExposedPortsTest() {\n        redis.addExposedPort(8987);\n        assertThat(redis.getExposedPorts()).as(\"Both ports should be exposed\").hasSize(2);\n        assertThat(redis.getExposedPorts()).as(\"withExposedPort should be exposed\").contains(REDIS_PORT);\n        assertThat(redis.getExposedPorts()).as(\"addExposedPort should be exposed\").contains(8987);\n    }\n\n    @Test\n    void addingExposedPortTwiceShouldNotFail() {\n        redis.addExposedPort(8987);\n        redis.addExposedPort(8987);\n        assertThat(redis.getExposedPorts()).as(\"Both ports should be exposed\").hasSize(2); // 2 ports = de-duplicated port 8897 and original port 6379\n        assertThat(redis.getExposedPorts()).as(\"withExposedPort should be exposed\").contains(REDIS_PORT);\n        assertThat(redis.getExposedPorts()).as(\"addExposedPort should be exposed\").contains(8987);\n    }\n\n    @Test\n    void sharedMemorySetTest() {\n        try (\n            GenericContainer containerWithSharedMemory = new GenericContainer<>(TestImages.TINY_IMAGE)\n                .withSharedMemorySize(42L * FileUtils.ONE_MB)\n                .withStartupCheckStrategy(new OneShotStartupCheckStrategy())\n        ) {\n            containerWithSharedMemory.start();\n\n            HostConfig hostConfig = containerWithSharedMemory.getContainerInfo().getHostConfig();\n            assertThat(hostConfig.getShmSize())\n                .as(\"Shared memory not set on container\")\n                .isEqualTo(42L * FileUtils.ONE_MB);\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/NonExistentImagePullTest.java",
    "content": "package org.testcontainers.junit;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Timeout;\nimport org.testcontainers.containers.ContainerFetchException;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.utility.DockerImageName;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.catchThrowable;\n\n/**\n * Created by rnorth on 20/03/2016.\n */\nclass NonExistentImagePullTest {\n\n    @Test\n    @Timeout(60)\n    void pullingNonExistentImageFailsGracefully() {\n        assertThat(\n            catchThrowable(() -> {\n                new GenericContainer<>(DockerImageName.parse(\"testcontainers/nonexistent:latest\")).getDockerImageName();\n            })\n        )\n            .as(\"Pulling a nonexistent container will cause an exception to be thrown\")\n            .isInstanceOf(ContainerFetchException.class);\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/OutputStreamTest.java",
    "content": "package org.testcontainers.junit;\n\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Timeout;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.testcontainers.TestImages;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.output.OutputFrame;\nimport org.testcontainers.containers.output.Slf4jLogConsumer;\nimport org.testcontainers.containers.output.ToStringConsumer;\nimport org.testcontainers.containers.output.WaitingConsumer;\n\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\nimport java.util.function.Consumer;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.catchThrowable;\n\n/**\n * Simple test for following container output.\n */\nclass OutputStreamTest {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(OutputStreamTest.class);\n\n    public GenericContainer container = new GenericContainer(TestImages.ALPINE_IMAGE)\n        .withCommand(\"ping -c 5 127.0.0.1\");\n\n    @BeforeEach\n    void setUp() {\n        container.start();\n    }\n\n    @AfterEach\n    void tearDown() {\n        container.stop();\n    }\n\n    @Test\n    @Timeout(value = 60)\n    void testFetchStdout() throws TimeoutException {\n        WaitingConsumer consumer = new WaitingConsumer();\n\n        container.followOutput(consumer, OutputFrame.OutputType.STDOUT);\n\n        consumer.waitUntil(\n            frame -> frame.getType() == OutputFrame.OutputType.STDOUT && frame.getUtf8String().contains(\"seq=2\"),\n            30,\n            TimeUnit.SECONDS\n        );\n    }\n\n    @Test\n    @Timeout(value = 60)\n    void testFetchStdoutWithTimeout() {\n        WaitingConsumer consumer = new WaitingConsumer();\n\n        container.followOutput(consumer, OutputFrame.OutputType.STDOUT);\n\n        assertThat(\n            catchThrowable(() -> {\n                consumer.waitUntil(\n                    frame -> {\n                        return (\n                            frame.getType() == OutputFrame.OutputType.STDOUT && frame.getUtf8String().contains(\"seq=5\")\n                        );\n                    },\n                    2,\n                    TimeUnit.SECONDS\n                );\n            })\n        )\n            .as(\"a TimeoutException should be thrown\")\n            .isInstanceOf(TimeoutException.class);\n    }\n\n    @Test\n    @Timeout(value = 60)\n    void testFetchStdoutWithNoLimit() throws TimeoutException {\n        WaitingConsumer consumer = new WaitingConsumer();\n\n        container.followOutput(consumer, OutputFrame.OutputType.STDOUT);\n\n        consumer.waitUntil(frame -> {\n            return frame.getType() == OutputFrame.OutputType.STDOUT && frame.getUtf8String().contains(\"seq=2\");\n        });\n    }\n\n    @Test\n    @Timeout(value = 60)\n    void testLogConsumer() throws TimeoutException {\n        WaitingConsumer waitingConsumer = new WaitingConsumer();\n        Slf4jLogConsumer logConsumer = new Slf4jLogConsumer(LOGGER);\n\n        Consumer<OutputFrame> composedConsumer = logConsumer.andThen(waitingConsumer);\n        container.followOutput(composedConsumer);\n\n        waitingConsumer.waitUntil(frame -> {\n            return frame.getType() == OutputFrame.OutputType.STDOUT && frame.getUtf8String().contains(\"seq=2\");\n        });\n    }\n\n    @Test\n    @Timeout(value = 60)\n    void testToStringConsumer() throws TimeoutException {\n        WaitingConsumer waitingConsumer = new WaitingConsumer();\n        ToStringConsumer toStringConsumer = new ToStringConsumer();\n\n        Consumer<OutputFrame> composedConsumer = toStringConsumer.andThen(waitingConsumer);\n        container.followOutput(composedConsumer);\n\n        waitingConsumer.waitUntilEnd(30, TimeUnit.SECONDS);\n\n        String utf8String = toStringConsumer.toUtf8String();\n        assertThat(utf8String).as(\"the expected first value was found\").contains(\"seq=1\");\n        assertThat(utf8String).as(\"the expected last value was found\").contains(\"seq=4\");\n        assertThat(utf8String).as(\"a non-expected value was found\").doesNotContain(\"seq=42\");\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/OutputStreamWithTTYTest.java",
    "content": "package org.testcontainers.junit;\n\nimport lombok.extern.slf4j.Slf4j;\nimport org.junit.jupiter.api.AutoClose;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Timeout;\nimport org.testcontainers.TestImages;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.output.OutputFrame;\nimport org.testcontainers.containers.output.Slf4jLogConsumer;\nimport org.testcontainers.containers.output.ToStringConsumer;\nimport org.testcontainers.containers.output.WaitingConsumer;\nimport org.testcontainers.containers.startupcheck.OneShotStartupCheckStrategy;\n\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\nimport java.util.function.Consumer;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.catchThrowable;\n\n@Slf4j\n@Timeout(10)\nclass OutputStreamWithTTYTest {\n\n    @AutoClose\n    public GenericContainer<?> container = new GenericContainer<>(TestImages.ALPINE_IMAGE)\n        .withCommand(\"ls -1\")\n        .withStartupCheckStrategy(new OneShotStartupCheckStrategy())\n        .withCreateContainerCmdModifier(command -> command.withTty(true));\n\n    @BeforeEach\n    void setUp() {\n        container.start();\n    }\n\n    @Test\n    void testFetchStdout() throws TimeoutException {\n        WaitingConsumer consumer = new WaitingConsumer();\n\n        container.followOutput(consumer, OutputFrame.OutputType.STDOUT);\n\n        consumer.waitUntil(\n            frame -> {\n                return frame.getType() == OutputFrame.OutputType.STDOUT && frame.getUtf8String().contains(\"home\");\n            },\n            4,\n            TimeUnit.SECONDS\n        );\n    }\n\n    @Test\n    void testFetchStdoutWithTimeout() {\n        WaitingConsumer consumer = new WaitingConsumer();\n\n        container.followOutput(consumer, OutputFrame.OutputType.STDOUT);\n\n        assertThat(\n            catchThrowable(() -> {\n                consumer.waitUntil(\n                    frame -> {\n                        return (\n                            frame.getType() == OutputFrame.OutputType.STDOUT && frame.getUtf8String().contains(\"qqq\")\n                        );\n                    },\n                    1,\n                    TimeUnit.SECONDS\n                );\n            })\n        )\n            .as(\"a TimeoutException should be thrown\")\n            .isInstanceOf(TimeoutException.class);\n    }\n\n    @Test\n    void testFetchStdoutWithNoLimit() throws TimeoutException {\n        WaitingConsumer consumer = new WaitingConsumer();\n\n        container.followOutput(consumer, OutputFrame.OutputType.STDOUT);\n\n        consumer.waitUntil(frame -> {\n            return frame.getType() == OutputFrame.OutputType.STDOUT && frame.getUtf8String().contains(\"home\");\n        });\n    }\n\n    @Test\n    void testLogConsumer() throws TimeoutException {\n        WaitingConsumer waitingConsumer = new WaitingConsumer();\n        Slf4jLogConsumer logConsumer = new Slf4jLogConsumer(log);\n\n        Consumer<OutputFrame> composedConsumer = logConsumer.andThen(waitingConsumer);\n        container.followOutput(composedConsumer);\n\n        waitingConsumer.waitUntil(frame -> {\n            return frame.getType() == OutputFrame.OutputType.STDOUT && frame.getUtf8String().contains(\"home\");\n        });\n    }\n\n    @Test\n    void testToStringConsumer() throws TimeoutException {\n        WaitingConsumer waitingConsumer = new WaitingConsumer();\n        ToStringConsumer toStringConsumer = new ToStringConsumer();\n\n        Consumer<OutputFrame> composedConsumer = toStringConsumer.andThen(waitingConsumer);\n        container.followOutput(composedConsumer);\n\n        waitingConsumer.waitUntilEnd(4, TimeUnit.SECONDS);\n\n        String utf8String = toStringConsumer.toUtf8String();\n        assertThat(utf8String).as(\"the expected first value was found\").contains(\"home\");\n        assertThat(utf8String).as(\"the expected last value was found\").contains(\"media\");\n        assertThat(utf8String).as(\"a non-expected value was found\").doesNotContain(\"qqq\");\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/ParameterizedDockerfileContainerTest.java",
    "content": "package org.testcontainers.junit;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.images.builder.ImageFromDockerfile;\n\nimport java.util.stream.Stream;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Simple test case / demonstration of creating a fresh container image from a Dockerfile DSL when the test\n * is parameterized.\n */\n@ParameterizedClass(name = \"{0}\")\n@MethodSource(\"data\")\nclass ParameterizedDockerfileContainerTest {\n\n    private final String expectedVersion;\n\n    public GenericContainer container;\n\n    public ParameterizedDockerfileContainerTest(String baseImage, String expectedVersion) {\n        container =\n            new GenericContainer(\n                new ImageFromDockerfile()\n                    .withDockerfileFromBuilder(builder -> {\n                        builder\n                            .from(baseImage)\n                            // Could potentially customise the image here, e.g. adding files, running\n                            //  commands, etc.\n                            .build();\n                    })\n            )\n                .withCommand(\"top\");\n        container.start();\n        this.expectedVersion = expectedVersion;\n    }\n\n    public static Stream<Arguments> data() {\n        return Stream.of(\n            Arguments.of(\"alpine:3.12\", \"3.12\"),\n            Arguments.of(\"alpine:3.13\", \"3.13\"),\n            Arguments.of(\"alpine:3.14\", \"3.14\"),\n            Arguments.of(\"alpine:3.15\", \"3.15\"),\n            Arguments.of(\"alpine:3.16\", \"3.16\")\n        );\n    }\n\n    @Test\n    void simpleTest() throws Exception {\n        final String release = container.execInContainer(\"cat\", \"/etc/alpine-release\").getStdout();\n\n        assertThat(release).as(\"/etc/alpine-release starts with \" + expectedVersion).startsWith(expectedVersion);\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/WorkingDirectoryTest.java",
    "content": "package org.testcontainers.junit;\n\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.TestImages;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.startupcheck.OneShotStartupCheckStrategy;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Created by rnorth on 26/07/2016.\n */\nclass WorkingDirectoryTest {\n\n    public static GenericContainer container = new GenericContainer(TestImages.ALPINE_IMAGE)\n        .withWorkingDirectory(\"/etc\")\n        .withStartupCheckStrategy(new OneShotStartupCheckStrategy())\n        .withCommand(\"ls\", \"-al\");\n\n    static {\n        container.start();\n    }\n\n    @Test\n    void checkOutput() {\n        String listing = container.getLogs();\n\n        assertThat(listing).as(\"Directory listing contains expected /etc content\").contains(\"hostname\");\n        assertThat(listing).as(\"Directory listing contains expected /etc content\").contains(\"init.d\");\n        assertThat(listing).as(\"Directory listing contains expected /etc content\").contains(\"passwd\");\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/wait/strategy/AbstractWaitStrategyTest.java",
    "content": "package org.testcontainers.junit.wait.strategy;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.rnorth.ducttape.RetryCountExceededException;\nimport org.testcontainers.TestImages;\nimport org.testcontainers.containers.ContainerLaunchException;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.wait.strategy.WaitStrategy;\n\nimport java.time.Duration;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.catchThrowable;\n\n/**\n * Common test methods for {@link WaitStrategy} implementations.\n */\npublic abstract class AbstractWaitStrategyTest<W extends WaitStrategy> {\n\n    static final long WAIT_TIMEOUT_MILLIS = 3000;\n\n    /**\n     * Indicates that the WaitStrategy has completed waiting successfully.\n     */\n    AtomicBoolean ready;\n\n    /**\n     * Subclasses should return an instance of {@link W} that sets {@code ready} to {@code true},\n     * if the wait was successful.\n     *\n     * @param ready the AtomicBoolean on which to indicate success\n     * @return WaitStrategy implementation\n     */\n    @NotNull\n    protected abstract W buildWaitStrategy(final AtomicBoolean ready);\n\n    @BeforeEach\n    public void setUp() {\n        ready = new AtomicBoolean(false);\n    }\n\n    /**\n     * Starts a GenericContainer with the Alpine image, passing the given {@code shellCommand} as\n     * a parameter to {@literal sh -c} (the container CMD).\n     *\n     * @param shellCommand the shell command to execute\n     * @return the (unstarted) container\n     */\n    private GenericContainer<?> startContainerWithCommand(String shellCommand) {\n        return startContainerWithCommand(shellCommand, buildWaitStrategy(ready));\n    }\n\n    /**\n     * Starts a GenericContainer with the Alpine image, passing the given {@code shellCommand} as\n     * a parameter to {@literal sh -c} (the container CMD) and apply a give wait strategy.\n     * Note that the timeout will be overwritten if any with {@link #WAIT_TIMEOUT_MILLIS}.\n     * @param shellCommand the shell command to execute\n     * @param waitStrategy The wait strategy to apply\n     * @return the (unstarted) container\n     */\n    protected GenericContainer<?> startContainerWithCommand(String shellCommand, WaitStrategy waitStrategy) {\n        return startContainerWithCommand(shellCommand, waitStrategy, 8080);\n    }\n\n    protected GenericContainer<?> startContainerWithCommand(\n        String shellCommand,\n        WaitStrategy waitStrategy,\n        Integer... ports\n    ) {\n        // apply WaitStrategy to container\n        return new GenericContainer<>(TestImages.ALPINE_IMAGE)\n            .withExposedPorts(ports)\n            .withCommand(\"sh\", \"-c\", shellCommand)\n            .waitingFor(waitStrategy.withStartupTimeout(Duration.ofMillis(WAIT_TIMEOUT_MILLIS)));\n    }\n\n    /**\n     * Expects that the WaitStrategy returns successfully after connection to a container with a listening port.\n     *\n     * @param shellCommand the shell command to execute\n     */\n    protected void waitUntilReadyAndSucceed(String shellCommand) {\n        try (GenericContainer<?> container = startContainerWithCommand(shellCommand)) {\n            waitUntilReadyAndSucceed(container);\n        }\n    }\n\n    /**\n     * Expects that the WaitStrategy throws a {@link RetryCountExceededException} after unsuccessful connection\n     * to a container with a listening port.\n     *\n     * @param shellCommand the shell command to execute\n     */\n    protected void waitUntilReadyAndTimeout(String shellCommand) {\n        try (GenericContainer<?> container = startContainerWithCommand(shellCommand)) {\n            waitUntilReadyAndTimeout(container);\n        }\n    }\n\n    /**\n     * Expects that the WaitStrategy throws a {@link RetryCountExceededException} after unsuccessful connection\n     * to a container with a listening port.\n     *\n     * @param container the container to start\n     */\n    protected void waitUntilReadyAndTimeout(GenericContainer<?> container) {\n        // start() blocks until successful or timeout\n        assertThat(catchThrowable(container::start))\n            .as(\"an exception is thrown when timeout occurs (\" + WAIT_TIMEOUT_MILLIS + \"ms)\")\n            .isInstanceOf(ContainerLaunchException.class);\n    }\n\n    /**\n     * Expects that the WaitStrategy returns successfully after connection to a container with a listening port.\n     *\n     * @param container the container to start\n     */\n    protected void waitUntilReadyAndSucceed(GenericContainer<?> container) {\n        // start() blocks until successful or timeout\n        container.start();\n\n        assertThat(ready)\n            .as(String.format(\"Expected container to be ready after timeout of %sms\", WAIT_TIMEOUT_MILLIS))\n            .isTrue();\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/wait/strategy/HostPortWaitStrategyTest.java",
    "content": "package org.testcontainers.junit.wait.strategy;\n\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.TestImages;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\n\nimport java.time.Duration;\n\n/**\n * Test wait strategy with overloaded waitingFor methods.\n * Other implementations of WaitStrategy are tested through backwards compatible wait strategy tests\n */\nclass HostPortWaitStrategyTest {\n\n    @Nested\n    class DefaultHostPortWaitStrategyTest {\n\n        public GenericContainer<?> container = new GenericContainer<>(TestImages.ALPINE_IMAGE)\n            .withExposedPorts()\n            .withCommand(\"sh\", \"-c\", \"while true; do nc -lp 8080; done\")\n            .withExposedPorts(8080)\n            .waitingFor(Wait.forListeningPort().withStartupTimeout(Duration.ofSeconds(10)));\n\n        @Test\n        void testWaiting() {\n            container.start();\n        }\n    }\n\n    @Nested\n    class ExplicitHostPortWaitStrategyTest {\n\n        public GenericContainer<?> container = new GenericContainer<>(TestImages.ALPINE_IMAGE)\n            .withExposedPorts()\n            .withCommand(\"sh\", \"-c\", \"while true; do nc -lp 8080; done\")\n            .withExposedPorts(8080)\n            .waitingFor(Wait.forListeningPorts(8080).withStartupTimeout(Duration.ofSeconds(10)));\n\n        @Test\n        void testWaiting() {\n            container.start();\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/wait/strategy/HttpWaitStrategyTest.java",
    "content": "package org.testcontainers.junit.wait.strategy;\n\nimport org.assertj.core.api.Assertions;\nimport org.jetbrains.annotations.NotNull;\nimport org.junit.jupiter.api.Test;\nimport org.rnorth.ducttape.RetryCountExceededException;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.wait.strategy.HttpWaitStrategy;\nimport org.testcontainers.images.builder.ImageFromDockerfile;\n\nimport java.time.Duration;\nimport java.util.HashMap;\nimport java.util.concurrent.atomic.AtomicBoolean;\nimport java.util.function.Predicate;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Tests for {@link HttpWaitStrategy}.\n */\nclass HttpWaitStrategyTest extends AbstractWaitStrategyTest<HttpWaitStrategy> {\n\n    /**\n     * newline sequence indicating end of the HTTP header.\n     */\n    private static final String NEWLINE = \"\\r\\n\";\n\n    private static final String GOOD_RESPONSE_BODY = \"Good Response Body\";\n\n    /**\n     * Expects that the WaitStrategy returns successfully after receiving an HTTP 200 response from the container.\n     */\n    @Test\n    void testWaitUntilReadyWithSuccess() {\n        waitUntilReadyAndSucceed(createShellCommand(\"200 OK\", GOOD_RESPONSE_BODY));\n    }\n\n    /**\n     * Ensures that HTTP requests made with the HttpWaitStrategy can be enriched with user defined headers,\n     * although the test web server does not depend on the header to response with a 200, by checking the\n     * logs we can ensure the HTTP request was correctly sent.\n     */\n    @Test\n    void testWaitUntilReadyWithSuccessWithCustomHeaders() {\n        HashMap<String, String> headers = new HashMap<>();\n        headers.put(\"baz\", \"boo\");\n        try (\n            GenericContainer<?> container = startContainerWithCommand(\n                createShellCommand(\"200 OK\", GOOD_RESPONSE_BODY),\n                createHttpWaitStrategy(ready).withHeader(\"foo\", \"bar\").withHeaders(headers)\n            )\n        ) {\n            waitUntilReadyAndSucceed(container);\n\n            String logs = container.getLogs();\n\n            assertThat(logs).contains(\"foo: bar\");\n            assertThat(logs).contains(\"baz: boo\");\n        }\n    }\n\n    /**\n     * Ensures that HTTPS requests made with the HttpWaitStrategy can skip the\n     * certificate validation chains (to support self-signed certificates for example).\n     */\n    @Test\n    void testWaitUntilReadyWithTlsAndAllowUnsecure() {\n        try (\n            GenericContainer<?> container = startContainerWithCommand(\n                createHttpsShellCommand(\"200 OK\", GOOD_RESPONSE_BODY, 8080),\n                createHttpWaitStrategy(ready).usingTls().allowInsecure()\n            )\n        ) {\n            waitUntilReadyAndSucceed(container);\n        }\n    }\n\n    /**\n     * Expects that the WaitStrategy returns successfully after receiving an HTTP 401 response from the container.\n     * This 401 response is checked with a lambda using {@link HttpWaitStrategy#forStatusCodeMatching(Predicate)}\n     */\n    @Test\n    void testWaitUntilReadyWithUnauthorizedWithLambda() {\n        try (\n            GenericContainer<?> container = startContainerWithCommand(\n                createShellCommand(\"401 UNAUTHORIZED\", GOOD_RESPONSE_BODY),\n                createHttpWaitStrategy(ready).forStatusCodeMatching(it -> it >= 200 && it < 300 || it == 401)\n            )\n        ) {\n            waitUntilReadyAndSucceed(container);\n        }\n    }\n\n    /**\n     * Expects that the WaitStrategy returns successfully after receiving an HTTP 401 response from the container.\n     * This 401 response is checked with many status codes using {@link HttpWaitStrategy#forStatusCode(int)}\n     */\n    @Test\n    void testWaitUntilReadyWithManyStatusCodes() {\n        try (\n            GenericContainer<?> container = startContainerWithCommand(\n                createShellCommand(\"401 UNAUTHORIZED\", GOOD_RESPONSE_BODY),\n                createHttpWaitStrategy(ready).forStatusCode(300).forStatusCode(401).forStatusCode(500)\n            )\n        ) {\n            waitUntilReadyAndSucceed(container);\n        }\n    }\n\n    /**\n     * Expects that the WaitStrategy returns successfully after receiving an HTTP 401 response from the container.\n     * This 401 response is checked with with many status codes using {@link HttpWaitStrategy#forStatusCode(int)}\n     * and a lambda using {@link HttpWaitStrategy#forStatusCodeMatching(Predicate)}\n     */\n    @Test\n    void testWaitUntilReadyWithManyStatusCodesAndLambda() {\n        try (\n            GenericContainer<?> container = startContainerWithCommand(\n                createShellCommand(\"401 UNAUTHORIZED\", GOOD_RESPONSE_BODY),\n                createHttpWaitStrategy(ready)\n                    .forStatusCode(300)\n                    .forStatusCode(500)\n                    .forStatusCodeMatching(it -> it == 401)\n            )\n        ) {\n            waitUntilReadyAndSucceed(container);\n        }\n    }\n\n    /**\n     * Expects that the WaitStrategy throws a {@link RetryCountExceededException} after not receiving any of the\n     * error code defined with {@link HttpWaitStrategy#forStatusCode(int)}\n     * and {@link HttpWaitStrategy#forStatusCodeMatching(Predicate)}\n     */\n    @Test\n    void testWaitUntilReadyWithTimeoutAndWithManyStatusCodesAndLambda() {\n        try (\n            GenericContainer<?> container = startContainerWithCommand(\n                createShellCommand(\"401 UNAUTHORIZED\", GOOD_RESPONSE_BODY),\n                createHttpWaitStrategy(ready).forStatusCode(300).forStatusCodeMatching(it -> it == 500)\n            )\n        ) {\n            waitUntilReadyAndTimeout(container);\n        }\n    }\n\n    /**\n     * Expects that the WaitStrategy throws a {@link RetryCountExceededException} after not receiving any of the\n     * error code defined with {@link HttpWaitStrategy#forStatusCode(int)}\n     * and {@link HttpWaitStrategy#forStatusCodeMatching(Predicate)}. Note that a 200 status code should not\n     * be considered as a successful return as not explicitly set.\n     * Test case for: https://github.com/testcontainers/testcontainers-java/issues/880\n     */\n    @Test\n    void testWaitUntilReadyWithTimeoutAndWithLambdaShouldNotMatchOk() {\n        try (\n            GenericContainer<?> container = startContainerWithCommand(\n                createShellCommand(\"200 OK\", GOOD_RESPONSE_BODY),\n                createHttpWaitStrategy(ready).forStatusCodeMatching(it -> it >= 300)\n            )\n        ) {\n            waitUntilReadyAndTimeout(container);\n        }\n    }\n\n    /**\n     * Expects that the WaitStrategy throws a {@link RetryCountExceededException} after not receiving an HTTP 200\n     * response from the container within the timeout period.\n     */\n    @Test\n    void testWaitUntilReadyWithTimeout() {\n        waitUntilReadyAndTimeout(createShellCommand(\"400 Bad Request\", GOOD_RESPONSE_BODY));\n    }\n\n    /**\n     * Expects that the WaitStrategy throws a {@link RetryCountExceededException} after not the expected response body\n     * from the container within the timeout period.\n     */\n    @Test\n    void testWaitUntilReadyWithTimeoutAndBadResponseBody() {\n        waitUntilReadyAndTimeout(createShellCommand(\"200 OK\", \"Bad Response\"));\n    }\n\n    /**\n     * Expects the WaitStrategy probing the right port.\n     */\n    @Test\n    void testWaitUntilReadyWithSpecificPort() {\n        try (\n            GenericContainer<?> container = startContainerWithCommand(\n                createShellCommand(\"200 OK\", GOOD_RESPONSE_BODY, 9090),\n                createHttpWaitStrategy(ready).forPort(9090),\n                7070,\n                8080,\n                9090\n            )\n        ) {\n            waitUntilReadyAndSucceed(container);\n        }\n    }\n\n    @Test\n    void testWaitUntilReadyWithTimeoutCausedByReadTimeout() {\n        try (\n            GenericContainer<?> container = startContainerWithCommand(\n                createShellCommand(\"0 Connection Refused\", GOOD_RESPONSE_BODY, 9090),\n                createHttpWaitStrategy(ready).forPort(9090).withReadTimeout(Duration.ofMillis(1)),\n                9090\n            )\n        ) {\n            waitUntilReadyAndTimeout(container);\n        }\n    }\n\n    /**\n     * Test to validate fix from GitHub Pull Request <a href=\"https://github.com/testcontainers/testcontainers-java/pull/5778\">#5778</a>, i.e. when the container startup fails (ContainerLaunchException) before timeout for some reason, we are able to see the root cause of the error in the stack trace, e.g. in this case, a TLS certificate validation error during the TLS handshake test, because we are using a NGINX docker image with self-signed certificate created with the image, that is obviously not trusted.\n     * The exceptions we should see in the stacktrace ('/' means 'caused by'): ContainerLaunchException / TimeoutException / RuntimeException / SSLHandshakeException / ValidatorException (in sun.* package so not accessible) / SunCertPathBuilderException (in sun.* package so not accessible).\n     */\n    @Test\n    void testWaitUntilReadyWithTimeoutCausedBySslHandshakeError() {\n        try (\n            GenericContainer<?> container = new GenericContainer<>(\n                new ImageFromDockerfile()\n                    .withFileFromClasspath(\"Dockerfile\", \"https-wait-strategy-dockerfile/Dockerfile\")\n                    .withFileFromClasspath(\"nginx-ssl.conf\", \"https-wait-strategy-dockerfile/nginx-ssl.conf\")\n            )\n                .withExposedPorts(8443)\n                .waitingFor(\n                    createHttpWaitStrategy(ready)\n                        .forPort(8443)\n                        .usingTls()\n                        .withStartupTimeout(Duration.ofMillis(WAIT_TIMEOUT_MILLIS))\n                )\n        ) {\n            Throwable throwable = Assertions.catchThrowable(container::start);\n            assertThat(throwable).hasStackTraceContaining(\"javax.net.ssl.SSLHandshakeException\");\n        }\n    }\n\n    /**\n     * @param ready the AtomicBoolean on which to indicate success\n     * @return the WaitStrategy under test\n     */\n    @NotNull\n    protected HttpWaitStrategy buildWaitStrategy(final AtomicBoolean ready) {\n        return createHttpWaitStrategy(ready).forResponsePredicate(s -> s.equals(GOOD_RESPONSE_BODY));\n    }\n\n    /**\n     * Create a HttpWaitStrategy instance with a waitUntilReady implementation\n     *\n     * @param ready Indicates that the WaitStrategy has completed waiting successfully.\n     * @return the HttpWaitStrategy instance\n     */\n    private HttpWaitStrategy createHttpWaitStrategy(final AtomicBoolean ready) {\n        return new HttpWaitStrategy() {\n            @Override\n            protected void waitUntilReady() {\n                // blocks until ready or timeout occurs\n                super.waitUntilReady();\n                ready.set(true);\n            }\n        };\n    }\n\n    private String createShellCommand(String header, String responseBody) {\n        return createShellCommand(header, responseBody, 8080);\n    }\n\n    private String createShellCommand(String header, String responseBody, int port) {\n        int length = responseBody.getBytes().length;\n        return (\n            \"while true; do { echo -e \\\"HTTP/1.1 \" +\n            header +\n            NEWLINE +\n            \"Content-Type: text/html\" +\n            NEWLINE +\n            \"Content-Length: \" +\n            length +\n            NEWLINE +\n            \"\\\";\" +\n            \" echo \\\"\" +\n            responseBody +\n            \"\\\";} | nc -lp \" +\n            port +\n            \"; done\"\n        );\n    }\n\n    private String createHttpsShellCommand(String header, String responseBody, int port) {\n        int length = responseBody.getBytes().length;\n        return (\n            \"apk add nmap-ncat; while true; do { echo -e \\\"HTTP/1.1 \" +\n            header +\n            NEWLINE +\n            \"Content-Type: text/html\" +\n            NEWLINE +\n            \"Content-Length: \" +\n            length +\n            NEWLINE +\n            \"\\\";\" +\n            \" echo \\\"\" +\n            responseBody +\n            \"\\\";} | ncat -lp \" +\n            port +\n            \" --ssl; done\"\n        );\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/wait/strategy/LogMessageWaitStrategyTest.java",
    "content": "package org.testcontainers.junit.wait.strategy;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy;\n\nimport java.util.concurrent.atomic.AtomicBoolean;\n\n/**\n * Tests for {@link LogMessageWaitStrategy}.\n */\n@ParameterizedClass(name = \"{0}\")\n@MethodSource(\"parameters\")\nclass LogMessageWaitStrategyTest extends AbstractWaitStrategyTest<LogMessageWaitStrategy> {\n\n    private final String pattern;\n\n    public static Object[] parameters() {\n        return new String[] {\n            \".*ready.*\\\\s\", // previous recommended style (explicit line ending)\n            \".*ready!\\\\s\", // explicit line ending without wildcard after expected text\n            \".*ready.*\", // new style (line ending matched by wildcard)\n        };\n    }\n\n    public LogMessageWaitStrategyTest(String pattern) {\n        this.pattern = pattern;\n    }\n\n    private static final String READY_MESSAGE = \"I'm ready!\";\n\n    @Test\n    void testWaitUntilReady_Success() {\n        waitUntilReadyAndSucceed(\n            \"echo -e \\\"\" +\n            READY_MESSAGE +\n            \"\\\";\" +\n            \"echo -e \\\"foobar\\\";\" +\n            \"echo -e \\\"\" +\n            READY_MESSAGE +\n            \"\\\";\" +\n            \"sleep 300\"\n        );\n    }\n\n    @Test\n    void testWaitUntilReady_Timeout() {\n        waitUntilReadyAndTimeout(\"echo -e \\\"\" + READY_MESSAGE + \"\\\";\" + \"echo -e \\\"foobar\\\";\" + \"sleep 300\");\n    }\n\n    @NotNull\n    @Override\n    protected LogMessageWaitStrategy buildWaitStrategy(AtomicBoolean ready) {\n        return new LogMessageWaitStrategy() {\n            @Override\n            protected void waitUntilReady() {\n                super.waitUntilReady();\n                ready.set(true);\n            }\n        }\n            .withRegEx(pattern)\n            .withTimes(2);\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/junit/wait/strategy/ShellStrategyTest.java",
    "content": "package org.testcontainers.junit.wait.strategy;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.wait.strategy.ShellStrategy;\n\nimport java.util.concurrent.atomic.AtomicBoolean;\n\n/**\n * Tests for {@link ShellStrategy}.\n */\nclass ShellStrategyTest extends AbstractWaitStrategyTest<ShellStrategy> {\n\n    private static final String LOCK_FILE = \"/tmp/ready.lock\";\n\n    @Test\n    void testWaitUntilReady_Success() {\n        waitUntilReadyAndSucceed(String.format(\"touch %s; sleep 300\", LOCK_FILE));\n    }\n\n    @Test\n    void testWaitUntilReady_Timeout() {\n        waitUntilReadyAndTimeout(\"sleep 300\");\n    }\n\n    @NotNull\n    @Override\n    protected ShellStrategy buildWaitStrategy(AtomicBoolean ready) {\n        return createShellStrategy(ready).withCommand(String.format(\"stat %s\", LOCK_FILE));\n    }\n\n    @NotNull\n    private static ShellStrategy createShellStrategy(AtomicBoolean ready) {\n        return new ShellStrategy() {\n            @Override\n            protected void waitUntilReady() {\n                super.waitUntilReady();\n                ready.set(true);\n            }\n        };\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/utility/AuthenticatedImagePullTest.java",
    "content": "package org.testcontainers.utility;\n\nimport com.github.dockerjava.api.model.AuthConfig;\nimport org.intellij.lang.annotations.Language;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.Mockito;\nimport org.testcontainers.DockerRegistryContainer;\nimport org.testcontainers.TestImages;\nimport org.testcontainers.containers.ContainerState;\nimport org.testcontainers.containers.DockerComposeContainer;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.images.builder.ImageFromDockerfile;\n\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.argThat;\nimport static org.mockito.Mockito.when;\n\n/**\n * This test checks the integration between Testcontainers and an authenticated registry, but uses\n * a mock instance of {@link RegistryAuthLocator} - the purpose of the test is solely to ensure that\n * the auth locator is utilised, and that the credentials it provides flow through to the registry.\n * <p>\n * {@link RegistryAuthLocatorTest} covers actual credential scenarios at a lower level, which are\n * impractical to test end-to-end.\n */\npublic class AuthenticatedImagePullTest {\n\n    /**\n     * Containerised docker image registry, with simple hardcoded credentials\n     */\n    public static DockerRegistryContainer authenticatedRegistry = new DockerRegistryContainer(\n        new ImageFromDockerfile()\n            .withDockerfileFromBuilder(builder -> {\n                builder\n                    .from(TestImages.DOCKER_REGISTRY_IMAGE.asCanonicalNameString())\n                    .run(\"htpasswd -Bbn testuser notasecret > /htpasswd\")\n                    .env(\"REGISTRY_AUTH\", \"htpasswd\")\n                    .env(\"REGISTRY_AUTH_HTPASSWD_PATH\", \"/htpasswd\")\n                    .env(\"REGISTRY_AUTH_HTPASSWD_REALM\", \"Test\");\n            })\n    );\n\n    private static RegistryAuthLocator originalAuthLocatorSingleton;\n\n    private final DockerImageName testImageName = authenticatedRegistry.createImage();\n\n    @BeforeAll\n    public static void beforeClass() throws Exception {\n        authenticatedRegistry.start();\n\n        originalAuthLocatorSingleton = RegistryAuthLocator.instance();\n\n        String testRegistryAddress = authenticatedRegistry.getEndpoint();\n\n        final AuthConfig authConfig = new AuthConfig()\n            .withUsername(\"testuser\")\n            .withPassword(\"notasecret\")\n            .withRegistryAddress(\"http://\" + testRegistryAddress);\n\n        // Replace the RegistryAuthLocator singleton with our mock, for the duration of this test\n        final RegistryAuthLocator mockAuthLocator = Mockito.mock(RegistryAuthLocator.class);\n        RegistryAuthLocator.setInstance(mockAuthLocator);\n        when(\n            mockAuthLocator.lookupAuthConfig(\n                argThat(argument -> testRegistryAddress.equals(argument.getRegistry())),\n                any()\n            )\n        )\n            .thenReturn(authConfig);\n    }\n\n    @AfterAll\n    public static void tearDown() {\n        RegistryAuthLocator.setInstance(originalAuthLocatorSingleton);\n    }\n\n    @Test\n    void testThatAuthLocatorIsUsedForContainerCreation() {\n        // actually start a container, which will require an authenticated pull\n        try (\n            final GenericContainer<?> container = new GenericContainer<>(testImageName)\n                .withCommand(\"/bin/sh\", \"-c\", \"sleep 10\")\n        ) {\n            container.start();\n\n            assertThat(container.isRunning()).as(\"container started following an authenticated pull\").isTrue();\n        }\n    }\n\n    @Test\n    void testThatAuthLocatorIsUsedForDockerfileBuild() throws IOException {\n        // Prepare a simple temporary Dockerfile which requires our custom private image\n        Path tempFile = getLocalTempFile(\".Dockerfile\");\n        String dockerFileContent = \"FROM \" + testImageName.asCanonicalNameString();\n        Files.write(tempFile, dockerFileContent.getBytes());\n\n        // Start a container built from a derived image, which will require an authenticated pull\n        try (\n            final GenericContainer<?> container = new GenericContainer<>(\n                new ImageFromDockerfile().withDockerfile(tempFile)\n            )\n                .withCommand(\"/bin/sh\", \"-c\", \"sleep 10\")\n        ) {\n            container.start();\n\n            assertThat(container.isRunning()).as(\"container started following an authenticated pull\").isTrue();\n        }\n    }\n\n    @Test\n    void testThatAuthLocatorIsUsedForDockerComposePull() throws IOException {\n        // Prepare a simple temporary Docker Compose manifest which requires our custom private image\n        Path tempFile = getLocalTempFile(\".docker-compose.yml\");\n        @Language(\"yaml\")\n        String composeFileContent =\n            \"version: '2.0'\\n\" +\n            \"services:\\n\" +\n            \"  privateservice:\\n\" +\n            \"      command: /bin/sh -c 'sleep 60'\\n\" +\n            \"      image: \" +\n            testImageName.asCanonicalNameString();\n        Files.write(tempFile, composeFileContent.getBytes());\n\n        // Start the docker compose project, which will require an authenticated pull\n        try (\n            final DockerComposeContainer<?> compose = new DockerComposeContainer<>(\n                DockerImageName.parse(\"docker/compose:1.29.2\"),\n                tempFile.toFile()\n            )\n        ) {\n            compose.start();\n\n            assertThat(\n                compose.getContainerByServiceName(\"privateservice_1\").map(ContainerState::isRunning).orElse(false)\n            )\n                .as(\"container started following an authenticated pull\")\n                .isTrue();\n        }\n    }\n\n    private Path getLocalTempFile(String s) throws IOException {\n        Path projectRoot = Paths.get(\".\");\n        Path tempDirectory = Files.createTempDirectory(projectRoot, this.getClass().getSimpleName() + \"-test-\");\n        Path relativeTempDirectory = projectRoot.relativize(tempDirectory);\n        Path tempFile = Files.createTempFile(relativeTempDirectory, \"test\", s);\n\n        tempDirectory.toFile().deleteOnExit();\n        tempFile.toFile().deleteOnExit();\n\n        return tempFile;\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/utility/ClasspathScannerTest.java",
    "content": "package org.testcontainers.utility;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\nimport java.net.URL;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nclass ClasspathScannerTest {\n\n    private static URL FILE_A;\n\n    private static URL FILE_B;\n\n    private static URL JAR_A;\n\n    private static URL JAR_B;\n\n    private static URL FILE_C;\n\n    @BeforeAll\n    public static void setUp() throws Exception {\n        FILE_A = new URL(\"file:///a/someName\");\n        FILE_B = new URL(\"file:///b/someName\");\n        FILE_C = new URL(\"file:///c/someName\");\n        JAR_A = new URL(\"jar:file:a!/someName\");\n        JAR_B = new URL(\"jar:file:b!/someName\");\n    }\n\n    @Test\n    void realClassLoaderLookupOccurs() {\n        // look for a resource that we know exists only once\n        final List<URL> foundURLs = ClasspathScanner.scanFor(\"expectedClasspathFile.txt\").collect(Collectors.toList());\n\n        assertThat(foundURLs).as(\"Exactly one resource was found\").hasSize(1);\n    }\n\n    @Test\n    void multipleResultsOnOneClassLoaderAreFound() throws IOException {\n        final ClassLoader firstMockClassLoader = mock(ClassLoader.class);\n        when(firstMockClassLoader.getResources(eq(\"someName\")))\n            .thenReturn(Collections.enumeration(Arrays.asList(FILE_A, FILE_B)));\n\n        final List<URL> foundURLs = ClasspathScanner\n            .scanFor(\"someName\", firstMockClassLoader)\n            .collect(Collectors.toList());\n        assertThat(foundURLs).as(\"The expected URLs are found\").containsExactly(FILE_A, FILE_B);\n    }\n\n    @Test\n    void orderIsAlphabeticalForDeterminism() throws IOException {\n        final ClassLoader firstMockClassLoader = mock(ClassLoader.class);\n        when(firstMockClassLoader.getResources(eq(\"someName\")))\n            .thenReturn(Collections.enumeration(Arrays.asList(FILE_B, JAR_A, JAR_B, FILE_A)));\n\n        final List<URL> foundURLs = ClasspathScanner\n            .scanFor(\"someName\", firstMockClassLoader)\n            .collect(Collectors.toList());\n        assertThat(foundURLs)\n            .as(\"The expected URLs are found in the expected order\")\n            .containsExactly(FILE_A, FILE_B, JAR_A, JAR_B);\n    }\n\n    @Test\n    void multipleClassLoadersAreQueried() throws IOException {\n        final ClassLoader firstMockClassLoader = mock(ClassLoader.class);\n        when(firstMockClassLoader.getResources(eq(\"someName\")))\n            .thenReturn(Collections.enumeration(Arrays.asList(FILE_A, FILE_B)));\n        final ClassLoader secondMockClassLoader = mock(ClassLoader.class);\n        when(secondMockClassLoader.getResources(eq(\"someName\")))\n            .thenReturn(\n                Collections.enumeration(\n                    Arrays.asList(\n                        FILE_B, // duplicate\n                        FILE_C\n                    )\n                )\n            );\n\n        final List<URL> foundURLs = ClasspathScanner\n            .scanFor(\"someName\", firstMockClassLoader, secondMockClassLoader)\n            .collect(Collectors.toList());\n\n        assertThat(foundURLs).as(\"The expected URLs are found\").containsExactly(FILE_A, FILE_B, FILE_C);\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/utility/ComparableVersionTest.java",
    "content": "package org.testcontainers.utility;\n\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport java.util.Arrays;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass ComparableVersionTest {\n\n    @ParameterizedTest(name = \"Parsed version: {0}={1}\")\n    @MethodSource(\"data\")\n    void shouldParseVersions(String given, int[] expected) {\n        assertThat(ComparableVersion.parseVersion(given)).containsExactly(expected);\n    }\n\n    public static Iterable<Object[]> data() {\n        return Arrays.asList(\n            new Object[][] {\n                { \"1.2.3\", new int[] { 1, 2, 3 } },\n                { \"\", new int[0] },\n                { \"1\", new int[] { 1 } },\n                { \"1.2.3.4.5.6.7\", new int[] { 1, 2, 3, 4, 5, 6, 7 } },\n                { \"1.2-dev\", new int[] { 1, 2 } },\n                { \"18.06.0-dev\", new int[] { 18, 6 } },\n            }\n        );\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/utility/DefaultImageNameSubstitutorTest.java",
    "content": "package org.testcontainers.utility;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.Mockito;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.eq;\n\n@ExtendWith(MockTestcontainersConfigurationExtension.class)\nclass DefaultImageNameSubstitutorTest {\n\n    public static final DockerImageName ORIGINAL_IMAGE = DockerImageName.parse(\"foo\");\n\n    public static final DockerImageName SUBSTITUTE_IMAGE = DockerImageName.parse(\"bar\");\n\n    private ConfigurationFileImageNameSubstitutor underTest;\n\n    @BeforeEach\n    public void setUp() {\n        underTest = new ConfigurationFileImageNameSubstitutor(TestcontainersConfiguration.getInstance());\n    }\n\n    @Test\n    void testConfigurationLookup() {\n        Mockito\n            .doReturn(SUBSTITUTE_IMAGE)\n            .when(TestcontainersConfiguration.getInstance())\n            .getConfiguredSubstituteImage(eq(ORIGINAL_IMAGE));\n\n        final DockerImageName substitute = underTest.apply(ORIGINAL_IMAGE);\n\n        assertThat(substitute).as(\"match is found\").isEqualTo(SUBSTITUTE_IMAGE);\n        assertThat(substitute.isCompatibleWith(ORIGINAL_IMAGE)).as(\"compatibility is automatically set\").isTrue();\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/utility/DirectoryTarResourceTest.java",
    "content": "package org.testcontainers.utility;\n\nimport org.assertj.core.api.Condition;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.startupcheck.OneShotStartupCheckStrategy;\nimport org.testcontainers.images.builder.ImageFromDockerfile;\n\nimport java.io.File;\nimport java.util.function.Predicate;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass DirectoryTarResourceTest {\n\n    @Test\n    void simpleRecursiveFileTest() {\n        // 'src' is expected to be the project base directory, so all source code/resources should be copied in\n        File directory = new File(\"src\");\n\n        GenericContainer container = new GenericContainer(\n            new ImageFromDockerfile()\n                .withDockerfileFromBuilder(builder -> {\n                    builder\n                        .from(\"alpine:3.17\")\n                        .copy(\"/tmp/foo\", \"/foo\")\n                        .cmd(\"cat /foo/test/resources/test-recursive-file.txt\")\n                        .build();\n                })\n                .withFileFromFile(\"/tmp/foo\", directory)\n        )\n            .withStartupCheckStrategy(new OneShotStartupCheckStrategy());\n\n        container.start();\n\n        final String results = container.getLogs();\n\n        assertThat(results)\n            .as(\"The container has a file that was copied in via a recursive copy\")\n            .contains(\"Used for DirectoryTarResourceTest\");\n    }\n\n    @Test\n    void simpleRecursiveFileWithPermissionTest() {\n        try (\n            GenericContainer container = new GenericContainer(\n                new ImageFromDockerfile()\n                    .withDockerfileFromBuilder(builder -> {\n                        builder\n                            .from(\"alpine:3.17\") //\n                            .copy(\"/tmp/foo\", \"/foo\")\n                            .cmd(\"ls\", \"-al\", \"/\")\n                            .build();\n                    })\n                    .withFileFromFile(\"/tmp/foo\", new File(\"/mappable-resource/test-resource.txt\"), 0754)\n            )\n                .withStartupCheckStrategy(new OneShotStartupCheckStrategy())\n        ) {\n            container.start();\n            String listing = container.getLogs();\n\n            Predicate<String> condition = s -> s.contains(\"-rwxr-xr--\") && s.contains(\"foo\");\n            assertThat(listing.split(\"\\\\n\"))\n                .as(\"Listing shows that file is copied with mode requested.\")\n                .haveAtLeastOne(new Condition<>(condition, \"File not found in listing\"));\n        }\n    }\n\n    @Test\n    void simpleRecursiveClasspathResourceTest() {\n        // This test combines the copying of classpath resources from JAR files with the recursive TAR approach, to allow JARed classpath resources to be copied in to an image\n\n        try (\n            GenericContainer container = new GenericContainer(\n                new ImageFromDockerfile()\n                    .withDockerfileFromBuilder(builder -> {\n                        builder\n                            .from(\"alpine:3.17\") //\n                            .copy(\"/tmp/foo\", \"/foo\")\n                            .cmd(\"ls -lRt /foo\")\n                            .build();\n                    })\n                    .withFileFromClasspath(\"/tmp/foo\", \"/recursive/dir\")\n            ) // here we use /org/junit as a directory that really should exist on the classpath\n                .withStartupCheckStrategy(new OneShotStartupCheckStrategy())\n        ) {\n            container.start();\n\n            final String results = container.getLogs();\n\n            // ExternalResource.class is known to exist in a subdirectory of /org/junit so should be successfully copied in\n            assertThat(results)\n                .as(\"The container has a file that was copied in via a recursive copy from a JAR resource\")\n                .contains(\"content.txt\");\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/utility/DockerImageNameCompatibilityTest.java",
    "content": "package org.testcontainers.utility;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\n\nclass DockerImageNameCompatibilityTest {\n\n    @Test\n    void testPlainImage() {\n        DockerImageName subject = DockerImageName.parse(\"foo\");\n\n        assertThat(subject.isCompatibleWith(DockerImageName.parse(\"bar\"))).as(\"image name foo != bar\").isFalse();\n    }\n\n    @Test\n    void testNoTagTreatedAsWildcard() {\n        final DockerImageName subject = DockerImageName.parse(\"foo:4.5.6\");\n        /*\n        foo:1.2.3 != foo:4.5.6\n        foo:1.2.3 ~= foo\n\n        The test is effectively making sure that 'no tag' is treated as a wildcard\n         */\n        assertThat(subject.isCompatibleWith(DockerImageName.parse(\"foo:1.2.3\"))).as(\"foo:4.5.6 != foo:1.2.3\").isFalse();\n        assertThat(subject.isCompatibleWith(DockerImageName.parse(\"foo\"))).as(\"foo:4.5.6 ~= foo\").isTrue();\n    }\n\n    @Test\n    void testImageWithAutomaticCompatibilityForFullPath() {\n        DockerImageName subject = DockerImageName.parse(\"repo/foo:1.2.3\");\n\n        assertThat(subject.isCompatibleWith(DockerImageName.parse(\"repo/foo\")))\n            .as(\"repo/foo:1.2.3 ~= repo/foo\")\n            .isTrue();\n    }\n\n    @Test\n    void testImageWithClaimedCompatibility() {\n        DockerImageName subject = DockerImageName.parse(\"foo\").asCompatibleSubstituteFor(\"bar\");\n\n        assertThat(subject.isCompatibleWith(DockerImageName.parse(\"bar\"))).as(\"foo(bar) ~= bar\").isTrue();\n        assertThat(subject.isCompatibleWith(DockerImageName.parse(\"fizz\"))).as(\"foo(bar) != fizz\").isFalse();\n    }\n\n    @Test\n    void testImageWithClaimedCompatibilityAndVersion() {\n        DockerImageName subject = DockerImageName.parse(\"foo:1.2.3\").asCompatibleSubstituteFor(\"bar\");\n\n        assertThat(subject.isCompatibleWith(DockerImageName.parse(\"bar\"))).as(\"foo:1.2.3(bar) ~= bar\").isTrue();\n    }\n\n    @Test\n    void testImageWithClaimedCompatibilityForFullPath() {\n        DockerImageName subject = DockerImageName.parse(\"foo\").asCompatibleSubstituteFor(\"registry/repo/bar\");\n\n        assertThat(subject.isCompatibleWith(DockerImageName.parse(\"registry/repo/bar\")))\n            .as(\"foo(registry/repo/bar) ~= registry/repo/bar\")\n            .isTrue();\n        assertThat(subject.isCompatibleWith(DockerImageName.parse(\"repo/bar\")))\n            .as(\"foo(registry/repo/bar) != repo/bar\")\n            .isFalse();\n        assertThat(subject.isCompatibleWith(DockerImageName.parse(\"bar\")))\n            .as(\"foo(registry/repo/bar) != bar\")\n            .isFalse();\n    }\n\n    @Test\n    void testImageWithClaimedCompatibilityForVersion() {\n        DockerImageName subject = DockerImageName.parse(\"foo\").asCompatibleSubstituteFor(\"bar:1.2.3\");\n\n        assertThat(subject.isCompatibleWith(DockerImageName.parse(\"bar\"))).as(\"foo(bar:1.2.3) ~= bar\").isTrue();\n        assertThat(subject.isCompatibleWith(DockerImageName.parse(\"bar:1.2.3\")))\n            .as(\"foo(bar:1.2.3) ~= bar:1.2.3\")\n            .isTrue();\n        assertThat(subject.isCompatibleWith(DockerImageName.parse(\"bar:0.0.1\")))\n            .as(\"foo(bar:1.2.3) != bar:0.0.1\")\n            .isFalse();\n        assertThat(subject.isCompatibleWith(DockerImageName.parse(\"bar:2.0.0\")))\n            .as(\"foo(bar:1.2.3) != bar:2.0.0\")\n            .isFalse();\n        assertThat(subject.isCompatibleWith(DockerImageName.parse(\"bar:1.2.4\")))\n            .as(\"foo(bar:1.2.3) != bar:1.2.4\")\n            .isFalse();\n    }\n\n    @Test\n    void testAssertMethodAcceptsCompatible() {\n        DockerImageName subject = DockerImageName.parse(\"foo\").asCompatibleSubstituteFor(\"bar\");\n        subject.assertCompatibleWith(DockerImageName.parse(\"bar\"));\n    }\n\n    @Test\n    void testAssertMethodAcceptsCompatibleLibraryPrefix() {\n        DockerImageName subject = DockerImageName.parse(\"library/foo\");\n        subject.assertCompatibleWith(DockerImageName.parse(\"foo\"));\n    }\n\n    @Test\n    void testAssertMethodRejectsIncompatible() {\n        DockerImageName subject = DockerImageName.parse(\"foo\");\n        assertThatThrownBy(() -> subject.assertCompatibleWith(DockerImageName.parse(\"bar\")))\n            .isInstanceOf(IllegalStateException.class)\n            .hasMessageContaining(\"Failed to verify that image 'foo' is a compatible substitute for 'bar'\");\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/utility/DockerImageNameTest.java",
    "content": "package org.testcontainers.utility;\n\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport java.util.stream.Stream;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\n\nclass DockerImageNameTest {\n\n    @Nested\n    class ValidNames {\n\n        public static String[] getNames() {\n            return new String[] {\n                \"myname:latest\",\n                \"repo/my-name:1.0\",\n                \"registry.foo.com:1234/my-name:1.0\",\n                \"registry.foo.com/my-name:1.0\",\n                \"registry.foo.com:1234/repo_here/my-name:1.0\",\n                \"registry.foo.com:1234/repo-here/my-name@sha256:1234abcd1234abcd1234abcd1234abcd\",\n                \"registry.foo.com:1234/my-name@sha256:1234abcd1234abcd1234abcd1234abcd\",\n                \"1.2.3.4/my-name:1.0\",\n                \"1.2.3.4:1234/my-name:1.0\",\n                \"1.2.3.4/repo-here/my-name:1.0\",\n                \"1.2.3.4:1234/repo-here/my-name:1.0\",\n            };\n        }\n\n        @ParameterizedTest\n        @MethodSource(\"getNames\")\n        void testValidNameAccepted(String imageName) {\n            DockerImageName.parse(imageName).assertValid();\n        }\n    }\n\n    @Nested\n    class InvalidNames {\n\n        public static String[] getNames() {\n            return new String[] {\n                \":latest\",\n                \"/myname:latest\",\n                \"/myname@sha256:latest\",\n                \"/myname@sha256:gggggggggggggggggggggggggggggggg\",\n                \"repo:notaport/myname:latest\",\n            };\n        }\n\n        @ParameterizedTest\n        @MethodSource(\"getNames\")\n        void testInvalidNameRejected(String imageName) {\n            assertThatThrownBy(() -> DockerImageName.parse(imageName).assertValid())\n                .isInstanceOf(IllegalArgumentException.class);\n        }\n    }\n\n    @Nested\n    class Parsing {\n\n        public static Stream<Arguments> getNames() {\n            return Stream.of(\n                Arguments.of(\"\", \"\", \"myname\", \":\", null),\n                Arguments.of(\"\", \"\", \"myname\", \":\", \"latest\"),\n                Arguments.of(\"\", \"\", \"repo/myname\", \":\", null),\n                Arguments.of(\"\", \"\", \"repo/myname\", \":\", \"latest\"),\n                Arguments.of(\"registry.foo.com:1234\", \"/\", \"my-name\", \":\", null),\n                Arguments.of(\"registry.foo.com:1234\", \"/\", \"my-name\", \":\", \"1.0\"),\n                Arguments.of(\"registry.foo.com\", \"/\", \"my-name\", \":\", \"1.0\"),\n                Arguments.of(\"registry.foo.com:1234\", \"/\", \"repo_here/my-name\", \":\", null),\n                Arguments.of(\"registry.foo.com:1234\", \"/\", \"repo_here/my-name\", \":\", \"1.0\"),\n                Arguments.of(\"1.2.3.4:1234\", \"/\", \"repo_here/my-name\", \":\", null),\n                Arguments.of(\"1.2.3.4:1234\", \"/\", \"repo_here/my-name\", \":\", \"1.0\"),\n                Arguments.of(\"1.2.3.4:1234\", \"/\", \"my-name\", \":\", null),\n                Arguments.of(\"1.2.3.4:1234\", \"/\", \"my-name\", \":\", \"1.0\"),\n                Arguments.of(\"\", \"\", \"myname\", \"@\", \"sha256:1234abcd1234abcd1234abcd1234abcd\"),\n                Arguments.of(\"\", \"\", \"repo/myname\", \"@\", \"sha256:1234abcd1234abcd1234abcd1234abcd\"),\n                Arguments.of(\n                    \"registry.foo.com:1234\",\n                    \"/\",\n                    \"repo-here/my-name\",\n                    \"@\",\n                    \"sha256:1234abcd1234abcd1234abcd1234abcd\"\n                ),\n                Arguments.of(\"registry.foo.com:1234\", \"/\", \"my-name\", \"@\", \"sha256:1234abcd1234abcd1234abcd1234abcd\"),\n                Arguments.of(\"1.2.3.4\", \"/\", \"my-name\", \"@\", \"sha256:1234abcd1234abcd1234abcd1234abcd\"),\n                Arguments.of(\"1.2.3.4:1234\", \"/\", \"my-name\", \"@\", \"sha256:1234abcd1234abcd1234abcd1234abcd\"),\n                Arguments.of(\"1.2.3.4\", \"/\", \"my-name\", \"@\", \"sha256:1234abcd1234abcd1234abcd1234abcd\"),\n                Arguments.of(\"1.2.3.4:1234\", \"/\", \"my-name\", \"@\", \"sha256:1234abcd1234abcd1234abcd1234abcd\")\n            );\n        }\n\n        @ParameterizedTest\n        @MethodSource(\"getNames\")\n        void testParsing(\n            String registry,\n            String registrySeparator,\n            String repo,\n            String versionSeparator,\n            String version\n        ) {\n            final String unversionedPart = registry + registrySeparator + repo;\n\n            String combined;\n            String canonicalName;\n            if (version != null) {\n                combined = unversionedPart + versionSeparator + version;\n                canonicalName = unversionedPart + versionSeparator + version;\n            } else {\n                combined = unversionedPart;\n                canonicalName = unversionedPart + \":latest\";\n            }\n\n            final DockerImageName imageName = DockerImageName.parse(combined);\n            assertThat(imageName.getRegistry()).as(combined + \" has registry address: \" + registry).isEqualTo(registry);\n            assertThat(imageName.getUnversionedPart())\n                .as(combined + \" has unversioned part: \" + unversionedPart)\n                .isEqualTo(unversionedPart);\n            if (version != null) {\n                assertThat(imageName.getVersionPart())\n                    .as(combined + \" has version part: \" + version)\n                    .isEqualTo(version);\n            } else {\n                assertThat(imageName.getVersionPart())\n                    .as(combined + \" has automatic 'latest' version specified\")\n                    .isEqualTo(\"latest\");\n            }\n            assertThat(imageName.asCanonicalNameString())\n                .as(combined + \" has canonical name: \" + canonicalName)\n                .isEqualTo(canonicalName);\n\n            if (version != null) {\n                final DockerImageName imageNameFromSecondaryConstructor = new DockerImageName(unversionedPart, version);\n                assertThat(imageNameFromSecondaryConstructor.getRegistry())\n                    .as(combined + \" has registry address: \" + registry)\n                    .isEqualTo(registry);\n                assertThat(imageNameFromSecondaryConstructor.getUnversionedPart())\n                    .as(combined + \" has unversioned part: \" + unversionedPart)\n                    .isEqualTo(unversionedPart);\n                assertThat(imageNameFromSecondaryConstructor.getVersionPart())\n                    .as(combined + \" has version part: \" + version)\n                    .isEqualTo(version);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/utility/DockerLoggerFactoryTest.java",
    "content": "package org.testcontainers.utility;\n\nimport ch.qos.logback.classic.Level;\nimport ch.qos.logback.classic.Logger;\nimport ch.qos.logback.classic.spi.ILoggingEvent;\nimport ch.qos.logback.core.read.ListAppender;\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass DockerLoggerFactoryTest {\n\n    private static final Logger LOGGER = (Logger) DockerLoggerFactory.getLogger(\"dockerImageName\");\n\n    @Test\n    void debugIsNotSwallowedForContainerLogs() {\n        ListAppender<ILoggingEvent> listAppender = new ListAppender<>();\n        listAppender.start();\n        LOGGER.addAppender(listAppender);\n\n        LOGGER.debug(\"some text\");\n\n        assertThat(listAppender.list).withFailMessage(\"Log message has been swallowed\").hasSize(1);\n\n        ILoggingEvent event = listAppender.list.get(0);\n\n        assertThat(event.getFormattedMessage()).isEqualTo(\"some text\");\n        assertThat(event.getLevel()).isEqualTo(Level.DEBUG);\n        assertThat(event.getLoggerName()).startsWith(\"tc\");\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/utility/DockerStatusTest.java",
    "content": "package org.testcontainers.utility;\n\nimport com.github.dockerjava.api.command.InspectContainerResponse;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.mockito.Mockito;\n\nimport java.time.Duration;\nimport java.time.Instant;\nimport java.time.ZoneId;\nimport java.time.format.DateTimeFormatter;\nimport java.util.stream.Stream;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.when;\n\n@ParameterizedClass\n@MethodSource(\"parameters\")\nclass DockerStatusTest {\n\n    private DateTimeFormatter dateTimeFormatter;\n\n    private static final Instant now = Instant.now();\n\n    private final InspectContainerResponse.ContainerState running;\n\n    private final InspectContainerResponse.ContainerState runningVariant;\n\n    private final InspectContainerResponse.ContainerState shortRunning;\n\n    private final InspectContainerResponse.ContainerState created;\n\n    // a container in the \"created\" state is not running, and has both startedAt and finishedAt empty.\n    private final InspectContainerResponse.ContainerState createdVariant;\n\n    private final InspectContainerResponse.ContainerState exited;\n\n    private final InspectContainerResponse.ContainerState paused;\n\n    private static final Duration minimumDuration = Duration.ofMillis(20);\n\n    public DockerStatusTest(DateTimeFormatter dateTimeFormatter) {\n        this.dateTimeFormatter = dateTimeFormatter;\n        running = buildState(true, false, buildTimestamp(now.minusMillis(30)), DockerStatus.DOCKER_TIMESTAMP_ZERO);\n        runningVariant = buildState(true, false, buildTimestamp(now.minusMillis(30)), \"\");\n        shortRunning = buildState(true, false, buildTimestamp(now.minusMillis(10)), DockerStatus.DOCKER_TIMESTAMP_ZERO);\n        created = buildState(false, false, DockerStatus.DOCKER_TIMESTAMP_ZERO, DockerStatus.DOCKER_TIMESTAMP_ZERO);\n        createdVariant = buildState(false, false, null, null);\n        exited = buildState(false, false, buildTimestamp(now.minusMillis(100)), buildTimestamp(now.minusMillis(90)));\n        paused = buildState(false, true, buildTimestamp(now.minusMillis(100)), DockerStatus.DOCKER_TIMESTAMP_ZERO);\n    }\n\n    public static Stream<DateTimeFormatter> parameters() {\n        return Stream.of(\n            DateTimeFormatter.ISO_INSTANT,\n            DateTimeFormatter.ISO_OFFSET_DATE_TIME.withZone(ZoneId.of(\"America/New_York\"))\n        );\n    }\n\n    @Test\n    void testRunning() {\n        assertThat(DockerStatus.isContainerRunning(running, minimumDuration, now)).isTrue();\n        assertThat(DockerStatus.isContainerRunning(runningVariant, minimumDuration, now)).isTrue();\n        assertThat(DockerStatus.isContainerRunning(shortRunning, minimumDuration, now)).isFalse();\n        assertThat(DockerStatus.isContainerRunning(created, minimumDuration, now)).isFalse();\n        assertThat(DockerStatus.isContainerRunning(createdVariant, minimumDuration, now)).isFalse();\n        assertThat(DockerStatus.isContainerRunning(exited, minimumDuration, now)).isFalse();\n        assertThat(DockerStatus.isContainerRunning(paused, minimumDuration, now)).isFalse();\n    }\n\n    @Test\n    void testStopped() {\n        assertThat(DockerStatus.isContainerStopped(running)).isFalse();\n        assertThat(DockerStatus.isContainerStopped(runningVariant)).isFalse();\n        assertThat(DockerStatus.isContainerStopped(shortRunning)).isFalse();\n        assertThat(DockerStatus.isContainerStopped(created)).isFalse();\n        assertThat(DockerStatus.isContainerStopped(createdVariant)).isFalse();\n        assertThat(DockerStatus.isContainerStopped(exited)).isTrue();\n        assertThat(DockerStatus.isContainerStopped(paused)).isFalse();\n    }\n\n    private String buildTimestamp(Instant instant) {\n        return dateTimeFormatter.format(instant);\n    }\n\n    // ContainerState is a non-static inner class, with private member variables, in a different package.\n    // It's simpler to mock it that to try to create one.\n    private static InspectContainerResponse.ContainerState buildState(\n        boolean running,\n        boolean paused,\n        String startedAt,\n        String finishedAt\n    ) {\n        InspectContainerResponse.ContainerState state = Mockito.mock(InspectContainerResponse.ContainerState.class);\n        when(state.getRunning()).thenReturn(running);\n        when(state.getPaused()).thenReturn(paused);\n        when(state.getStartedAt()).thenReturn(startedAt);\n        when(state.getFinishedAt()).thenReturn(finishedAt);\n        return state;\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/utility/FakeImagePullPolicy.java",
    "content": "package org.testcontainers.utility;\n\nimport org.testcontainers.images.AbstractImagePullPolicy;\nimport org.testcontainers.images.ImageData;\n\npublic class FakeImagePullPolicy extends AbstractImagePullPolicy {\n\n    @Override\n    protected boolean shouldPullCached(DockerImageName imageName, ImageData localImageData) {\n        return false;\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/utility/FakeImageSubstitutor.java",
    "content": "package org.testcontainers.utility;\n\npublic class FakeImageSubstitutor extends ImageNameSubstitutor {\n\n    @Override\n    public DockerImageName apply(final DockerImageName original) {\n        return DockerImageName.parse(\"transformed-\" + original.asCanonicalNameString());\n    }\n\n    @Override\n    protected String getDescription() {\n        return \"test implementation\";\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/utility/FilterRegistryTest.java",
    "content": "package org.testcontainers.utility;\n\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.utility.ResourceReaper.FilterRegistry;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.util.AbstractMap.SimpleEntry;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Map.Entry;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass FilterRegistryTest {\n\n    private static final List<Entry<String, String>> FILTERS = Arrays.asList(\n        new SimpleEntry<>(\"key1!\", \"value2?\"),\n        new SimpleEntry<>(\"key2#\", \"value2%\")\n    );\n\n    private static final String URL_ENCODED_FILTERS = \"key1%21=value2%3F&key2%23=value2%25\";\n\n    private static final byte[] ACKNOWLEDGEMENT = FilterRegistry.ACKNOWLEDGMENT.getBytes();\n\n    private static final byte[] NO_ACKNOWLEDGEMENT = \"\".getBytes();\n\n    private static final String NEW_LINE = \"\\n\";\n\n    @Test\n    void registerReturnsTrueIfAcknowledgementIsReadFromInputStream() throws IOException {\n        FilterRegistry registry = new FilterRegistry(inputStream(ACKNOWLEDGEMENT), anyOutputStream());\n\n        boolean successful = registry.register(FILTERS);\n\n        assertThat(successful).isTrue();\n    }\n\n    @Test\n    void registerReturnsFalseIfNoAcknowledgementIsReadFromInputStream() throws IOException {\n        FilterRegistry registry = new FilterRegistry(inputStream(NO_ACKNOWLEDGEMENT), anyOutputStream());\n\n        boolean successful = registry.register(FILTERS);\n\n        assertThat(successful).isFalse();\n    }\n\n    @Test\n    void registerWritesUrlEncodedFiltersAndNewlineToOutputStream() throws IOException {\n        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();\n        FilterRegistry registry = new FilterRegistry(anyInputStream(), outputStream);\n\n        registry.register(FILTERS);\n\n        assertThat(new String(outputStream.toByteArray())).isEqualTo(URL_ENCODED_FILTERS + NEW_LINE);\n    }\n\n    private static InputStream inputStream(byte[] bytes) {\n        return new ByteArrayInputStream(bytes);\n    }\n\n    private static InputStream anyInputStream() {\n        return inputStream(ACKNOWLEDGEMENT);\n    }\n\n    private static OutputStream anyOutputStream() {\n        return new ByteArrayOutputStream();\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/utility/ImageNameSubstitutorTest.java",
    "content": "package org.testcontainers.utility;\n\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.mockito.Mockito;\nimport org.testcontainers.containers.GenericContainer;\n\nimport java.io.FileWriter;\nimport java.io.IOException;\nimport java.net.URL;\nimport java.net.URLClassLoader;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\nimport static org.mockito.ArgumentMatchers.eq;\n\n@ExtendWith(MockTestcontainersConfigurationExtension.class)\nclass ImageNameSubstitutorTest {\n\n    @TempDir\n    public Path tempFolder;\n\n    private ImageNameSubstitutor originalInstance;\n\n    private ImageNameSubstitutor originalDefaultImplementation;\n\n    @BeforeEach\n    public void setUp() throws Exception {\n        originalInstance = ImageNameSubstitutor.instance;\n        originalDefaultImplementation = ImageNameSubstitutor.defaultImplementation;\n        ImageNameSubstitutor.instance = null;\n        ImageNameSubstitutor.defaultImplementation = Mockito.mock(ImageNameSubstitutor.class);\n\n        Mockito\n            .doReturn(DockerImageName.parse(\"substituted-image\"))\n            .when(ImageNameSubstitutor.defaultImplementation)\n            .apply(eq(DockerImageName.parse(\"original\")));\n        Mockito.doReturn(\"default implementation\").when(ImageNameSubstitutor.defaultImplementation).getDescription();\n    }\n\n    @AfterEach\n    public void tearDown() throws Exception {\n        ImageNameSubstitutor.instance = originalInstance;\n        ImageNameSubstitutor.defaultImplementation = originalDefaultImplementation;\n    }\n\n    @Test\n    void simpleConfigurationTest() {\n        Mockito\n            .doReturn(FakeImageSubstitutor.class.getCanonicalName())\n            .when(TestcontainersConfiguration.getInstance())\n            .getImageSubstitutorClassName();\n\n        final ImageNameSubstitutor imageNameSubstitutor = ImageNameSubstitutor.instance();\n\n        DockerImageName result = imageNameSubstitutor.apply(DockerImageName.parse(\"original\"));\n        assertThat(result.asCanonicalNameString())\n            .as(\"the image has been substituted by default then configured implementations\")\n            .isEqualTo(\"transformed-substituted-image:latest\");\n    }\n\n    @Test\n    void testWorksWithoutConfiguredImplementation() {\n        Mockito.doReturn(null).when(TestcontainersConfiguration.getInstance()).getImageSubstitutorClassName();\n\n        final ImageNameSubstitutor imageNameSubstitutor = ImageNameSubstitutor.instance();\n\n        DockerImageName result = imageNameSubstitutor.apply(DockerImageName.parse(\"original\"));\n        assertThat(result.asCanonicalNameString())\n            .as(\"the image has been substituted by default then configured implementations\")\n            .isEqualTo(\"substituted-image:latest\");\n    }\n\n    @Test\n    void testImageNameSubstitutorToString() {\n        Mockito\n            .doReturn(FakeImageSubstitutor.class.getCanonicalName())\n            .when(TestcontainersConfiguration.getInstance())\n            .getImageSubstitutorClassName();\n\n        try (GenericContainer<?> container = new GenericContainer<>(DockerImageName.parse(\"original\"))) {\n            assertThatThrownBy(container::start)\n                .hasMessageContaining(\n                    \"imageNameSubstitutor=Chained substitutor of 'default implementation' and then 'test implementation'\"\n                );\n        }\n    }\n\n    @Test\n    void testImageNameSubstitutorFromServiceLoader() throws IOException {\n        Path tempDir = this.tempFolder.resolve(\"image-name-substitutor-test\");\n        Path metaInfDir = Paths.get(tempDir.toString(), \"META-INF\", \"services\");\n        Files.createDirectories(metaInfDir);\n\n        createClassFile(tempDir, \"org/testcontainers/utility/ImageNameSubstitutor.class\", ImageNameSubstitutor.class);\n        createClassFile(tempDir, \"org/testcontainers/utility/FakeImageSubstitutor.class\", FakeImageSubstitutor.class);\n\n        // Create service provider configuration file\n        createServiceProviderFile(\n            metaInfDir,\n            \"org.testcontainers.utility.ImageNameSubstitutor\",\n            \"org.testcontainers.utility.FakeImageSubstitutor\"\n        );\n\n        URL[] urls = { tempDir.toUri().toURL() };\n        URLClassLoader classLoader = new URLClassLoader(urls, ImageNameSubstitutorTest.class.getClassLoader());\n\n        final ImageNameSubstitutor imageNameSubstitutor = ImageNameSubstitutor.instance(classLoader);\n\n        DockerImageName result = imageNameSubstitutor.apply(DockerImageName.parse(\"original\"));\n        assertThat(result.asCanonicalNameString())\n            .as(\"the image has been substituted by default then configured implementations\")\n            .isEqualTo(\"transformed-substituted-image:latest\");\n    }\n\n    private void createClassFile(Path tempDir, String classFilePath, Class<?> clazz) throws IOException {\n        Path classFile = Paths.get(tempDir.toString(), classFilePath);\n        Files.createDirectories(classFile.getParent());\n        Files.copy(clazz.getResourceAsStream(\"/\" + classFilePath), classFile);\n    }\n\n    private void createServiceProviderFile(Path metaInfDir, String serviceInterface, String... implementations)\n        throws IOException {\n        Path serviceFile = Paths.get(metaInfDir.toString(), serviceInterface);\n        try (FileWriter writer = new FileWriter(serviceFile.toFile())) {\n            for (String impl : implementations) {\n                writer.write(impl + \"\\n\");\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/utility/LazyFutureTest.java",
    "content": "package org.testcontainers.utility;\n\nimport com.google.common.util.concurrent.Futures;\nimport lombok.SneakyThrows;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Timeout;\n\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.ForkJoinPool;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.stream.Collectors;\nimport java.util.stream.IntStream;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.catchThrowable;\n\nclass LazyFutureTest {\n\n    @Test\n    void testLaziness() throws Exception {\n        AtomicInteger counter = new AtomicInteger();\n\n        Future<Integer> lazyFuture = new LazyFuture<Integer>() {\n            @Override\n            protected Integer resolve() {\n                return counter.incrementAndGet();\n            }\n        };\n\n        assertThat(counter).as(\"No resolve() invocations before get()\").hasValue(0);\n        assertThat(lazyFuture.get()).as(\"get() call returns proper result\").isEqualTo(1);\n        assertThat(counter).as(\"resolve() was called only once after single get() call\").hasValue(1);\n\n        counter.incrementAndGet();\n        assertThat(lazyFuture.get()).as(\"result of resolve() must be cached\").isEqualTo(1);\n    }\n\n    @Test\n    @Timeout(5)\n    void timeoutWorks() {\n        Future<Void> lazyFuture = new LazyFuture<Void>() {\n            @Override\n            @SneakyThrows(InterruptedException.class)\n            protected Void resolve() {\n                TimeUnit.MINUTES.sleep(1);\n                return null;\n            }\n        };\n\n        assertThat(catchThrowable(() -> lazyFuture.get(10, TimeUnit.MILLISECONDS)))\n            .as(\"Should timeout\")\n            .isInstanceOf(TimeoutException.class);\n    }\n\n    @Test\n    @Timeout(5)\n    void testThreadSafety() throws Exception {\n        final int numOfThreads = 3;\n        CountDownLatch latch = new CountDownLatch(numOfThreads);\n        AtomicInteger counter = new AtomicInteger();\n\n        Future<Integer> lazyFuture = new LazyFuture<Integer>() {\n            @Override\n            @SneakyThrows(InterruptedException.class)\n            protected Integer resolve() {\n                latch.await();\n                return counter.incrementAndGet();\n            }\n        };\n\n        Future<List<Integer>> task = new ForkJoinPool(numOfThreads)\n            .submit(() -> {\n                return IntStream\n                    .rangeClosed(1, numOfThreads)\n                    .parallel()\n                    .mapToObj(i -> Futures.getUnchecked(lazyFuture))\n                    .collect(Collectors.toList());\n            });\n\n        while (latch.getCount() > 0) {\n            latch.countDown();\n        }\n\n        assertThat(task.get())\n            .as(\"All threads receives the same result\")\n            .isEqualTo(Collections.nCopies(numOfThreads, 1));\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/utility/LicenseAcceptanceTest.java",
    "content": "package org.testcontainers.utility;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\n\nclass LicenseAcceptanceTest {\n\n    @Test\n    void testForExistingNames() {\n        LicenseAcceptance.assertLicenseAccepted(\"a\");\n        LicenseAcceptance.assertLicenseAccepted(\"b\");\n    }\n\n    @Test\n    void testForMissingNames() {\n        assertThatThrownBy(() -> LicenseAcceptance.assertLicenseAccepted(\"c\"))\n            .isInstanceOf(IllegalStateException.class);\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/utility/MockTestcontainersConfigurationExtension.java",
    "content": "package org.testcontainers.utility;\n\nimport org.junit.jupiter.api.extension.AfterEachCallback;\nimport org.junit.jupiter.api.extension.BeforeEachCallback;\nimport org.junit.jupiter.api.extension.ExtensionContext;\nimport org.mockito.Mockito;\n\nimport java.util.concurrent.atomic.AtomicReference;\n\n/**\n * This {@link org.junit.jupiter.api.extension.Extension} applies a spy on {@link TestcontainersConfiguration}\n * for testing features that depend on the global configuration.\n */\npublic class MockTestcontainersConfigurationExtension implements BeforeEachCallback, AfterEachCallback {\n\n    private static final ExtensionContext.Namespace NS = ExtensionContext.Namespace.create(\n        MockTestcontainersConfigurationExtension.class\n    );\n\n    static AtomicReference<TestcontainersConfiguration> REF = TestcontainersConfiguration.getInstanceField();\n\n    @Override\n    public void beforeEach(ExtensionContext context) throws Exception {\n        TestcontainersConfiguration previous = REF.get();\n        if (previous == null) {\n            previous = TestcontainersConfiguration.getInstance();\n        }\n        REF.set(Mockito.spy(previous));\n        context.getStore(NS).put(context.getUniqueId(), previous);\n    }\n\n    @Override\n    public void afterEach(ExtensionContext context) throws Exception {\n        TestcontainersConfiguration previous = context\n            .getStore(NS)\n            .remove(context.getUniqueId(), TestcontainersConfiguration.class);\n        REF.set(previous);\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/utility/MountableFileTest.java",
    "content": "package org.testcontainers.utility;\n\nimport lombok.Cleanup;\nimport org.apache.commons.compress.archivers.ArchiveEntry;\nimport org.apache.commons.compress.archivers.tar.TarArchiveInputStream;\nimport org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;\nimport org.jetbrains.annotations.NotNull;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.function.Consumer;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass MountableFileTest {\n\n    private static final int TEST_FILE_MODE = 0532;\n\n    private static final int BASE_FILE_MODE = 0100000;\n\n    private static final int BASE_DIR_MODE = 0040000;\n\n    @Test\n    void forClasspathResource() throws Exception {\n        final MountableFile mountableFile = MountableFile.forClasspathResource(\"mappable-resource/test-resource.txt\");\n\n        performChecks(mountableFile);\n    }\n\n    @Test\n    void forClasspathResourceWithAbsolutePath() throws Exception {\n        final MountableFile mountableFile = MountableFile.forClasspathResource(\"/mappable-resource/test-resource.txt\");\n\n        performChecks(mountableFile);\n    }\n\n    @Test\n    void forClasspathResourceFromJar() throws Exception {\n        final MountableFile mountableFile = MountableFile.forClasspathResource(\"META-INF/dummy_unique_name.txt\");\n\n        performChecks(mountableFile);\n    }\n\n    @Test\n    void forClasspathResourceFromJarWithAbsolutePath() throws Exception {\n        final MountableFile mountableFile = MountableFile.forClasspathResource(\"/META-INF/dummy_unique_name.txt\");\n\n        performChecks(mountableFile);\n    }\n\n    @Test\n    void forHostPath() throws Exception {\n        final Path file = createTempFile(\"somepath\");\n        final MountableFile mountableFile = MountableFile.forHostPath(file.toString());\n\n        performChecks(mountableFile);\n    }\n\n    @Test\n    void forHostPathWithSpaces() throws Exception {\n        final Path file = createTempFile(\"some path\");\n        final MountableFile mountableFile = MountableFile.forHostPath(file.toString());\n\n        performChecks(mountableFile);\n\n        assertThat(mountableFile.getResolvedPath()).as(\"The resolved path contains the original space\").contains(\" \");\n        assertThat(mountableFile.getResolvedPath())\n            .as(\"The resolved path does not contain an escaped space\")\n            .doesNotContain(\"\\\\ \");\n    }\n\n    @Test\n    void forHostPathWithPlus() throws Exception {\n        final Path file = createTempFile(\"some+path\");\n        final MountableFile mountableFile = MountableFile.forHostPath(file.toString());\n\n        performChecks(mountableFile);\n\n        assertThat(mountableFile.getResolvedPath()).as(\"The resolved path contains the original space\").contains(\"+\");\n        assertThat(mountableFile.getResolvedPath())\n            .as(\"The resolved path does not contain an escaped space\")\n            .doesNotContain(\" \");\n    }\n\n    @Test\n    void forClasspathResourceWithPermission() throws Exception {\n        final MountableFile mountableFile = MountableFile.forClasspathResource(\n            \"mappable-resource/test-resource.txt\",\n            TEST_FILE_MODE\n        );\n\n        performChecks(mountableFile);\n        assertThat(mountableFile.getFileMode()).as(\"Valid file mode.\").isEqualTo(BASE_FILE_MODE | TEST_FILE_MODE);\n    }\n\n    @Test\n    void forHostFilePathWithPermission() throws Exception {\n        final Path file = createTempFile(\"somepath\");\n        final MountableFile mountableFile = MountableFile.forHostPath(file.toString(), TEST_FILE_MODE);\n        performChecks(mountableFile);\n        assertThat(mountableFile.getFileMode()).as(\"Valid file mode.\").isEqualTo(BASE_FILE_MODE | TEST_FILE_MODE);\n    }\n\n    @Test\n    void forHostDirPathWithPermission() throws Exception {\n        final Path dir = createTempDir();\n        final MountableFile mountableFile = MountableFile.forHostPath(dir.toString(), TEST_FILE_MODE);\n        performChecks(mountableFile);\n        assertThat(mountableFile.getFileMode()).as(\"Valid dir mode.\").isEqualTo(BASE_DIR_MODE | TEST_FILE_MODE);\n    }\n\n    @Test\n    void noTrailingSlashesInTarEntryNames() throws Exception {\n        final MountableFile mountableFile = MountableFile.forClasspathResource(\"mappable-resource/test-resource.txt\");\n\n        @Cleanup\n        final TarArchiveInputStream tais = intoTarArchive(taos -> {\n            mountableFile.transferTo(taos, \"/some/path.txt\");\n            mountableFile.transferTo(taos, \"/path.txt\");\n            mountableFile.transferTo(taos, \"path.txt\");\n        });\n\n        ArchiveEntry entry;\n        while ((entry = tais.getNextEntry()) != null) {\n            assertThat(entry.getName()).as(\"no entries should have a trailing slash\").doesNotEndWith(\"/\");\n        }\n    }\n\n    private TarArchiveInputStream intoTarArchive(Consumer<TarArchiveOutputStream> consumer) throws IOException {\n        @Cleanup\n        final ByteArrayOutputStream baos = new ByteArrayOutputStream();\n        @Cleanup\n        final TarArchiveOutputStream taos = new TarArchiveOutputStream(baos);\n        consumer.accept(taos);\n        taos.close();\n\n        return new TarArchiveInputStream(new ByteArrayInputStream(baos.toByteArray()));\n    }\n\n    @SuppressWarnings(\"ResultOfMethodCallIgnored\")\n    @NotNull\n    private Path createTempFile(final String name) throws IOException {\n        final File tempParentDir = File.createTempFile(\"testcontainers\", \"\");\n        tempParentDir.delete();\n        tempParentDir.mkdirs();\n        final Path file = new File(tempParentDir, name).toPath();\n\n        Files.copy(MountableFileTest.class.getResourceAsStream(\"/mappable-resource/test-resource.txt\"), file);\n        return file;\n    }\n\n    @NotNull\n    private Path createTempDir() throws IOException {\n        return Files.createTempDirectory(\"testcontainers\");\n    }\n\n    private void performChecks(final MountableFile mountableFile) {\n        final String mountablePath = mountableFile.getResolvedPath();\n        assertThat(new File(mountablePath)).as(\"The filesystem path '\" + mountablePath + \"' can be found\").exists();\n        assertThat(mountablePath)\n            .as(\"The filesystem path '\" + mountablePath + \"' does not contain any URL escaping\")\n            .doesNotContain(\"%20\");\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/utility/PrefixingImageNameSubstitutorTest.java",
    "content": "package org.testcontainers.utility;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nclass PrefixingImageNameSubstitutorTest {\n\n    private TestcontainersConfiguration mockConfiguration;\n\n    private PrefixingImageNameSubstitutor underTest;\n\n    @BeforeEach\n    public void setUp() {\n        mockConfiguration = mock(TestcontainersConfiguration.class);\n        underTest = new PrefixingImageNameSubstitutor(mockConfiguration);\n    }\n\n    @Test\n    void testHappyPath() {\n        when(mockConfiguration.getEnvVarOrProperty(eq(PrefixingImageNameSubstitutor.PREFIX_PROPERTY_KEY), any()))\n            .thenReturn(\"someregistry.com/our-mirror/\");\n\n        final DockerImageName result = underTest.apply(DockerImageName.parse(\"some/image:tag\"));\n\n        assertThat(result.asCanonicalNameString())\n            .as(\"The prefix is applied\")\n            .isEqualTo(\"someregistry.com/our-mirror/some/image:tag\");\n    }\n\n    @Test\n    void hubIoRegistryIsNotChanged() {\n        when(mockConfiguration.getEnvVarOrProperty(eq(PrefixingImageNameSubstitutor.PREFIX_PROPERTY_KEY), any()))\n            .thenReturn(\"someregistry.com/our-mirror/\");\n\n        final DockerImageName result = underTest.apply(DockerImageName.parse(\"docker.io/some/image:tag\"));\n\n        assertThat(result.asCanonicalNameString()).as(\"The prefix is applied\").isEqualTo(\"docker.io/some/image:tag\");\n    }\n\n    @Test\n    void hubComRegistryIsNotChanged() {\n        when(mockConfiguration.getEnvVarOrProperty(eq(PrefixingImageNameSubstitutor.PREFIX_PROPERTY_KEY), any()))\n            .thenReturn(\"someregistry.com/our-mirror/\");\n\n        final DockerImageName result = underTest.apply(DockerImageName.parse(\"registry.hub.docker.com/some/image:tag\"));\n\n        assertThat(result.asCanonicalNameString())\n            .as(\"The prefix is applied\")\n            .isEqualTo(\"registry.hub.docker.com/some/image:tag\");\n    }\n\n    @Test\n    void thirdPartyRegistriesNotAffected() {\n        when(mockConfiguration.getEnvVarOrProperty(eq(PrefixingImageNameSubstitutor.PREFIX_PROPERTY_KEY), any()))\n            .thenReturn(\"someregistry.com/our-mirror/\");\n\n        final DockerImageName result = underTest.apply(DockerImageName.parse(\"gcr.io/something/image:tag\"));\n\n        assertThat(result.asCanonicalNameString())\n            .as(\"The prefix is not applied if a third party registry is used\")\n            .isEqualTo(\"gcr.io/something/image:tag\");\n    }\n\n    @Test\n    void testNoDoublePrefixing() {\n        when(mockConfiguration.getEnvVarOrProperty(eq(PrefixingImageNameSubstitutor.PREFIX_PROPERTY_KEY), any()))\n            .thenReturn(\"someregistry.com/our-mirror/\");\n\n        final DockerImageName result = underTest.apply(DockerImageName.parse(\"someregistry.com/some/image:tag\"));\n\n        assertThat(result.asCanonicalNameString())\n            .as(\"The prefix is not applied if already present\")\n            .isEqualTo(\"someregistry.com/some/image:tag\");\n    }\n\n    @Test\n    void testHandlesEmptyValue() {\n        when(mockConfiguration.getEnvVarOrProperty(eq(PrefixingImageNameSubstitutor.PREFIX_PROPERTY_KEY), any()))\n            .thenReturn(\"\");\n\n        final DockerImageName result = underTest.apply(DockerImageName.parse(\"some/image:tag\"));\n\n        assertThat(result.asCanonicalNameString())\n            .as(\"The prefix is not applied if the env var is not set\")\n            .isEqualTo(\"some/image:tag\");\n    }\n\n    @Test\n    void testHandlesRegistryOnlyWithTrailingSlash() {\n        when(mockConfiguration.getEnvVarOrProperty(eq(PrefixingImageNameSubstitutor.PREFIX_PROPERTY_KEY), any()))\n            .thenReturn(\"someregistry.com/\");\n\n        final DockerImageName result = underTest.apply(DockerImageName.parse(\"some/image:tag\"));\n\n        assertThat(result.asCanonicalNameString())\n            .as(\"The prefix is applied\")\n            .isEqualTo(\"someregistry.com/some/image:tag\");\n    }\n\n    @Test\n    void testCombinesLiterallyForRegistryOnlyWithoutTrailingSlash() {\n        when(mockConfiguration.getEnvVarOrProperty(eq(PrefixingImageNameSubstitutor.PREFIX_PROPERTY_KEY), any()))\n            .thenReturn(\"someregistry.com\");\n\n        final DockerImageName result = underTest.apply(DockerImageName.parse(\"some/image:tag\"));\n\n        assertThat(result.asCanonicalNameString())\n            .as(\"The prefix is applied\")\n            .isEqualTo(\"someregistry.comsome/image:tag\");\n    }\n\n    @Test\n    void testCombinesLiterallyForBothPartsWithoutTrailingSlash() {\n        when(mockConfiguration.getEnvVarOrProperty(eq(PrefixingImageNameSubstitutor.PREFIX_PROPERTY_KEY), any()))\n            .thenReturn(\"someregistry.com/our-mirror\");\n\n        final DockerImageName result = underTest.apply(DockerImageName.parse(\"some/image:tag\"));\n\n        assertThat(result.asCanonicalNameString())\n            .as(\"The prefix is applied\")\n            .isEqualTo(\"someregistry.com/our-mirrorsome/image:tag\");\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/utility/RegistryAuthLocatorTest.java",
    "content": "package org.testcontainers.utility;\n\nimport com.github.dockerjava.api.model.AuthConfig;\nimport com.google.common.io.Resources;\nimport org.apache.commons.io.FileUtils;\nimport org.apache.commons.lang3.SystemUtils;\nimport org.jetbrains.annotations.NotNull;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.nio.charset.StandardCharsets;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass RegistryAuthLocatorTest {\n\n    @Test\n    void lookupAuthConfigWithoutCredentials() throws URISyntaxException, IOException {\n        final RegistryAuthLocator authLocator = createTestAuthLocator(\"config-empty.json\");\n\n        final AuthConfig authConfig = authLocator.lookupAuthConfig(\n            DockerImageName.parse(\"unauthenticated.registry.org/org/repo\"),\n            new AuthConfig()\n        );\n\n        assertThat(authConfig.getRegistryAddress())\n            .as(\"Default docker registry URL is set on auth config\")\n            .isEqualTo(\"https://index.docker.io/v1/\");\n        assertThat(authConfig.getUsername()).as(\"No username is set\").isNull();\n        assertThat(authConfig.getPassword()).as(\"No password is set\").isNull();\n    }\n\n    @Test\n    void lookupAuthConfigWithBasicAuthCredentials() throws URISyntaxException, IOException {\n        final RegistryAuthLocator authLocator = createTestAuthLocator(\"config-basic-auth.json\");\n\n        final AuthConfig authConfig = authLocator.lookupAuthConfig(\n            DockerImageName.parse(\"registry.example.com/org/repo\"),\n            new AuthConfig()\n        );\n\n        assertThat(authConfig.getRegistryAddress())\n            .as(\"Default docker registry URL is set on auth config\")\n            .isEqualTo(\"https://registry.example.com\");\n        assertThat(authConfig.getUsername()).as(\"Username is set\").isEqualTo(\"user\");\n        assertThat(authConfig.getPassword()).as(\"Password is set\").isEqualTo(\"pass\");\n    }\n\n    @Test\n    void lookupAuthConfigWithJsonKeyCredentials() throws URISyntaxException, IOException {\n        final RegistryAuthLocator authLocator = createTestAuthLocator(\"config-with-json-key.json\");\n\n        final AuthConfig authConfig = authLocator.lookupAuthConfig(\n            DockerImageName.parse(\"registry.example.com/org/repo\"),\n            new AuthConfig()\n        );\n\n        assertThat(authConfig.getRegistryAddress())\n            .as(\"Default docker registry URL is set on auth config\")\n            .isEqualTo(\"https://registry.example.com\");\n        assertThat(authConfig.getUsername()).as(\"Username is set\").isEqualTo(\"_json_key\");\n        assertThat(authConfig.getPassword()).as(\"Password is set\").isNotNull();\n    }\n\n    @Test\n    void lookupAuthConfigWithJsonKeyCredentialsPartialMatchShouldGiveNoResult() throws URISyntaxException, IOException {\n        // contains entry for registry.example.com\n        final RegistryAuthLocator authLocator = createTestAuthLocator(\"config-with-json-key.json\");\n\n        final AuthConfig authConfig = authLocator.lookupAuthConfig(\n            DockerImageName.parse(\"registry.example.co/org/repo\"), // partial match of registry name\n            new AuthConfig()\n        );\n\n        assertThat(authConfig.getUsername()).as(\"auth config username\").isNull();\n        assertThat(authConfig.getPassword()).as(\"auth config password\").isNull();\n    }\n\n    @Test\n    void lookupAuthConfigUsingStore() throws URISyntaxException, IOException {\n        final RegistryAuthLocator authLocator = createTestAuthLocator(\"config-with-store.json\");\n\n        final AuthConfig authConfig = authLocator.lookupAuthConfig(\n            DockerImageName.parse(\"registry.example.com/org/repo\"),\n            new AuthConfig()\n        );\n\n        assertThat(authConfig.getRegistryAddress())\n            .as(\"Correct server URL is obtained from a credential store\")\n            .isEqualTo(\"url\");\n        assertThat(authConfig.getUsername())\n            .as(\"Correct username is obtained from a credential store\")\n            .isEqualTo(\"username\");\n        assertThat(authConfig.getPassword())\n            .as(\"Correct secret is obtained from a credential store\")\n            .isEqualTo(\"secret\");\n    }\n\n    @Test\n    void lookupAuthConfigUsingHelper() throws URISyntaxException, IOException {\n        final RegistryAuthLocator authLocator = createTestAuthLocator(\"config-with-helper.json\");\n\n        final AuthConfig authConfig = authLocator.lookupAuthConfig(\n            DockerImageName.parse(\"registry.example.com/org/repo\"),\n            new AuthConfig()\n        );\n\n        assertThat(authConfig.getRegistryAddress())\n            .as(\"Correct server URL is obtained from a credential store\")\n            .isEqualTo(\"url\");\n        assertThat(authConfig.getUsername())\n            .as(\"Correct username is obtained from a credential store\")\n            .isEqualTo(\"username\");\n        assertThat(authConfig.getPassword())\n            .as(\"Correct secret is obtained from a credential store\")\n            .isEqualTo(\"secret\");\n    }\n\n    @Test\n    void lookupAuthConfigUsingHelperWithToken() throws URISyntaxException, IOException {\n        final RegistryAuthLocator authLocator = createTestAuthLocator(\"config-with-helper-using-token.json\");\n\n        final AuthConfig authConfig = authLocator.lookupAuthConfig(\n            DockerImageName.parse(\"registrytoken.example.com/org/repo\"),\n            new AuthConfig()\n        );\n\n        assertThat(authConfig.getRegistryAddress())\n            .as(\"Correct server URL is obtained from a credential store\")\n            .isEqualTo(\"url\");\n        assertThat(authConfig.getIdentitytoken())\n            .as(\"Correct identitytoken is obtained from a credential store\")\n            .isEqualTo(\"secret\");\n    }\n\n    @Test\n    void lookupUsingHelperEmptyAuth() throws URISyntaxException, IOException {\n        final RegistryAuthLocator authLocator = createTestAuthLocator(\"config-empty-auth-with-helper.json\");\n\n        final AuthConfig authConfig = authLocator.lookupAuthConfig(\n            DockerImageName.parse(\"registry.example.com/org/repo\"),\n            new AuthConfig()\n        );\n\n        assertThat(authConfig.getRegistryAddress())\n            .as(\"Correct server URL is obtained from a credential store\")\n            .isEqualTo(\"url\");\n        assertThat(authConfig.getUsername())\n            .as(\"Correct username is obtained from a credential store\")\n            .isEqualTo(\"username\");\n        assertThat(authConfig.getPassword())\n            .as(\"Correct secret is obtained from a credential store\")\n            .isEqualTo(\"secret\");\n    }\n\n    @Test\n    void lookupNonEmptyAuthWithHelper() throws URISyntaxException, IOException {\n        final RegistryAuthLocator authLocator = createTestAuthLocator(\"config-existing-auth-with-helper.json\");\n\n        final AuthConfig authConfig = authLocator.lookupAuthConfig(\n            DockerImageName.parse(\"registry.example.com/org/repo\"),\n            new AuthConfig()\n        );\n\n        assertThat(authConfig.getRegistryAddress())\n            .as(\"Correct server URL is obtained from a credential helper\")\n            .isEqualTo(\"url\");\n        assertThat(authConfig.getUsername())\n            .as(\"Correct username is obtained from a credential helper\")\n            .isEqualTo(\"username\");\n        assertThat(authConfig.getPassword())\n            .as(\"Correct password is obtained from a credential helper\")\n            .isEqualTo(\"secret\");\n    }\n\n    @Test\n    void lookupAuthConfigUsingHelperNoServerUrl() throws URISyntaxException, IOException {\n        final RegistryAuthLocator authLocator = createTestAuthLocator(\"config-with-helper-no-server-url.json\");\n\n        final AuthConfig authConfig = authLocator.lookupAuthConfig(\n            DockerImageName.parse(\"registrynoserverurl.example.com/org/repo\"),\n            new AuthConfig()\n        );\n\n        assertThat(authConfig.getRegistryAddress())\n            .as(\"Fallback (registry) server URL is used\")\n            .isEqualTo(\"registrynoserverurl.example.com\");\n        assertThat(authConfig.getUsername())\n            .as(\"Correct username is obtained from a credential store\")\n            .isEqualTo(\"username\");\n        assertThat(authConfig.getPassword())\n            .as(\"Correct secret is obtained from a credential store\")\n            .isEqualTo(\"secret\");\n    }\n\n    @Test\n    void lookupAuthConfigUsingHelperNoServerUrlWithToken() throws URISyntaxException, IOException {\n        final RegistryAuthLocator authLocator = createTestAuthLocator(\n            \"config-with-helper-no-server-url-using-token.json\"\n        );\n\n        final AuthConfig authConfig = authLocator.lookupAuthConfig(\n            DockerImageName.parse(\"registrynoserverurltoken.example.com/org/repo\"),\n            new AuthConfig()\n        );\n\n        assertThat(authConfig.getRegistryAddress())\n            .as(\"Fallback (registry) server URL is used\")\n            .isEqualTo(\"registrynoserverurltoken.example.com\");\n        assertThat(authConfig.getIdentitytoken())\n            .as(\"Correct identitytoken is obtained from a credential store\")\n            .isEqualTo(\"secret\");\n    }\n\n    @Test\n    void lookupAuthConfigWithCredentialsNotFound() throws URISyntaxException, IOException {\n        Map<String, String> notFoundMessagesReference = new HashMap<>();\n        final RegistryAuthLocator authLocator = createTestAuthLocator(\n            \"config-with-store.json\",\n            notFoundMessagesReference\n        );\n\n        DockerImageName dockerImageName = DockerImageName.parse(\"registry2.example.com/org/repo\");\n        final AuthConfig authConfig = authLocator.lookupAuthConfig(dockerImageName, new AuthConfig());\n\n        assertThat(authConfig.getUsername())\n            .as(\"No username should have been obtained from a credential store\")\n            .isNull();\n        assertThat(authConfig.getPassword()).as(\"No secret should have been obtained from a credential store\").isNull();\n        assertThat(notFoundMessagesReference.size())\n            .as(\"Should have one 'credentials not found' message discovered\")\n            .isEqualTo(1);\n\n        String discoveredMessage = notFoundMessagesReference.values().iterator().next();\n\n        assertThat(discoveredMessage)\n            .as(\"Not correct message discovered\")\n            .isEqualTo(\"Fake credentials not found on credentials store 'registry2.example.com'\");\n    }\n\n    @Test\n    void lookupAuthConfigWithCredStoreEmpty() throws URISyntaxException, IOException {\n        final RegistryAuthLocator authLocator = createTestAuthLocator(\"config-with-store-empty.json\");\n\n        DockerImageName dockerImageName = DockerImageName.parse(\"registry2.example.com/org/repo\");\n        final AuthConfig authConfig = authLocator.lookupAuthConfig(dockerImageName, new AuthConfig());\n\n        assertThat(authConfig.getAuth()).as(\"CredStore field will be ignored, because value is blank\").isNull();\n    }\n\n    @Test\n    void lookupAuthConfigFromEnvVarWithCredStoreEmpty() throws URISyntaxException, IOException {\n        final RegistryAuthLocator authLocator = createTestAuthLocator(null, \"config-with-store-empty.json\");\n\n        DockerImageName dockerImageName = DockerImageName.parse(\"registry2.example.com/org/repo\");\n        final AuthConfig authConfig = authLocator.lookupAuthConfig(dockerImageName, new AuthConfig());\n\n        assertThat(authConfig.getAuth()).as(\"CredStore field will be ignored, because value is blank\").isNull();\n    }\n\n    @Test\n    void lookupAuthConfigWithoutConfigFile() throws URISyntaxException, IOException {\n        final RegistryAuthLocator authLocator = createTestAuthLocator(null);\n\n        final AuthConfig authConfig = authLocator.lookupAuthConfig(\n            DockerImageName.parse(\"unauthenticated.registry.org/org/repo\"),\n            new AuthConfig()\n        );\n\n        assertThat(authConfig.getRegistryAddress())\n            .as(\"Default docker registry URL is set on auth config\")\n            .isEqualTo(\"https://index.docker.io/v1/\");\n        assertThat(authConfig.getUsername()).as(\"No username is set\").isNull();\n        assertThat(authConfig.getPassword()).as(\"No password is set\").isNull();\n    }\n\n    @Test\n    void lookupAuthConfigRespectsCheckOrderPreference() throws URISyntaxException, IOException {\n        final RegistryAuthLocator authLocator = createTestAuthLocator(\"config-empty.json\", \"config-basic-auth.json\");\n\n        final AuthConfig authConfig = authLocator.lookupAuthConfig(\n            DockerImageName.parse(\"registry.example.com/org/repo\"),\n            new AuthConfig()\n        );\n\n        assertThat(authConfig.getRegistryAddress())\n            .as(\"Default docker registry URL is set on auth config\")\n            .isEqualTo(\"https://registry.example.com\");\n        assertThat(authConfig.getUsername()).as(\"Username is set\").isEqualTo(\"user\");\n        assertThat(authConfig.getPassword()).as(\"Password is set\").isEqualTo(\"pass\");\n    }\n\n    @Test\n    void lookupAuthConfigFromEnvironmentVariable() throws URISyntaxException, IOException {\n        final RegistryAuthLocator authLocator = createTestAuthLocator(null, \"config-basic-auth.json\");\n\n        final AuthConfig authConfig = authLocator.lookupAuthConfig(\n            DockerImageName.parse(\"registry.example.com/org/repo\"),\n            new AuthConfig()\n        );\n\n        assertThat(authConfig.getRegistryAddress())\n            .as(\"Default docker registry URL is set on auth config\")\n            .isEqualTo(\"https://registry.example.com\");\n        assertThat(authConfig.getUsername()).as(\"Username is set\").isEqualTo(\"user\");\n        assertThat(authConfig.getPassword()).as(\"Password is set\").isEqualTo(\"pass\");\n    }\n\n    @NotNull\n    private RegistryAuthLocator createTestAuthLocator(String configName, String envConfigName)\n        throws URISyntaxException, IOException {\n        return createTestAuthLocator(configName, envConfigName, new HashMap<>());\n    }\n\n    @NotNull\n    private RegistryAuthLocator createTestAuthLocator(String configName) throws URISyntaxException, IOException {\n        return createTestAuthLocator(configName, null, new HashMap<>());\n    }\n\n    @NotNull\n    private RegistryAuthLocator createTestAuthLocator(String configName, Map<String, String> notFoundMessagesReference)\n        throws URISyntaxException, IOException {\n        return createTestAuthLocator(configName, null, notFoundMessagesReference);\n    }\n\n    @NotNull\n    private RegistryAuthLocator createTestAuthLocator(\n        String configName,\n        String envConfigName,\n        Map<String, String> notFoundMessagesReference\n    ) throws URISyntaxException, IOException {\n        File configFile = null;\n        String commandPathPrefix = \"\";\n        String commandExtension = \"\";\n        String configEnv = null;\n\n        if (configName != null) {\n            configFile = new File(Resources.getResource(\"auth-config/\" + configName).toURI());\n\n            commandPathPrefix = configFile.getParentFile().getAbsolutePath() + \"/\";\n        } else {\n            configFile = new File(new URI(\"file:///not-exists.json\"));\n        }\n\n        if (envConfigName != null) {\n            final File envConfigFile = new File(Resources.getResource(\"auth-config/\" + envConfigName).toURI());\n            configEnv = FileUtils.readFileToString(envConfigFile, StandardCharsets.UTF_8);\n\n            commandPathPrefix = envConfigFile.getParentFile().getAbsolutePath() + \"/\";\n        }\n\n        if (SystemUtils.IS_OS_WINDOWS) {\n            commandPathPrefix += \"win/\";\n\n            // need to provide executable extension otherwise won't run it\n            // with real docker wincredential exe there is no problem\n            commandExtension = \".bat\";\n        }\n\n        return new RegistryAuthLocator(\n            configFile,\n            configEnv,\n            commandPathPrefix,\n            commandExtension,\n            notFoundMessagesReference\n        );\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/utility/ResourceReaperTest.java",
    "content": "package org.testcontainers.utility;\n\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.github.dockerjava.api.DockerClient;\nimport lombok.SneakyThrows;\nimport org.awaitility.Awaitility;\nimport org.awaitility.core.ConditionFactory;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.DockerClientFactory;\nimport org.testcontainers.containers.GenericContainer;\nimport org.zeroturnaround.exec.ProcessExecutor;\nimport org.zeroturnaround.exec.ProcessResult;\n\nimport java.io.File;\nimport java.time.Duration;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.function.Consumer;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass ResourceReaperTest {\n\n    @Test\n    void shouldCleanupWithRyuk() {\n        Map<String, String> labels = runProcess(processExecutor -> {});\n\n        assertCleanup(labels);\n    }\n\n    @Test\n    void shouldCleanupWithJVM() {\n        Map<String, String> labels = runProcess(processExecutor -> {\n            processExecutor.environment(\"TESTCONTAINERS_RYUK_DISABLED\", \"true\");\n        });\n\n        assertCleanup(labels);\n    }\n\n    private void assertCleanup(Map<String, String> labels) {\n        DockerClient client = DockerClientFactory.instance().client();\n        ConditionFactory awaitFactory = Awaitility\n            .await()\n            .atMost(Duration.ofMinutes(1))\n            .pollInterval(Duration.ofSeconds(1));\n\n        List<String> labelValues = labels\n            .entrySet()\n            .stream()\n            .map(it -> it.getKey() + \"=\" + it.getValue())\n            .collect(Collectors.toList());\n\n        awaitFactory.untilAsserted(() -> {\n            assertThat(client.listContainersCmd().withFilter(\"label\", labelValues).withShowAll(true).exec()).isEmpty();\n        });\n\n        awaitFactory.untilAsserted(() -> {\n            assertThat(client.listNetworksCmd().withFilter(\"label\", labelValues).exec()).isEmpty();\n        });\n\n        awaitFactory.untilAsserted(() -> {\n            assertThat(client.listVolumesCmd().withFilter(\"label\", labelValues).exec().getVolumes()).isEmpty();\n        });\n    }\n\n    @SneakyThrows\n    private Map<String, String> runProcess(Consumer<ProcessExecutor> processExecutorConsumer) {\n        ProcessExecutor processExecutor = new ProcessExecutor(\n            new File(System.getProperty(\"java.home\")).toPath().resolve(\"bin\").resolve(\"java\").toString(),\n            \"-ea\",\n            \"-classpath\",\n            System.getProperty(\"java.class.path\"),\n            SimpleUsage.class.getName()\n        );\n        processExecutor.readOutput(true);\n        processExecutor.redirectOutput(System.out);\n        processExecutor.redirectError(System.err);\n        processExecutorConsumer.accept(processExecutor);\n\n        ProcessResult result = processExecutor.execute();\n        assertThat(result.getExitValue()).isEqualTo(0);\n\n        String labelsJson = Stream\n            .of(result.outputUTF8().split(\"\\n\"))\n            .filter(it -> it.startsWith(SimpleUsage.LABELS_MARKER))\n            .map(it -> it.substring(SimpleUsage.LABELS_MARKER.length()))\n            .findFirst()\n            .get();\n\n        return new ObjectMapper().readValue(labelsJson, Map.class);\n    }\n\n    public static class SimpleUsage {\n\n        static final String LABELS_MARKER = \"LABELS:\";\n\n        @SneakyThrows\n        @SuppressWarnings(\"deprecation\")\n        public static void main(String[] args) {\n            System.out.println(\n                LABELS_MARKER + new ObjectMapper().writeValueAsString(ResourceReaper.instance().getLabels())\n            );\n\n            GenericContainer<?> container = new GenericContainer<>(\"testcontainers/helloworld:1.1.0\")\n                .withNetwork(org.testcontainers.containers.Network.newNetwork())\n                .withExposedPorts(8080);\n\n            container.start();\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/utility/TestEnvironmentTest.java",
    "content": "package org.testcontainers.utility;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Created by rnorth on 03/07/2016.\n */\nclass TestEnvironmentTest {\n\n    @Test\n    void testCompareVersionGreaterThanSameMajor() {\n        assertThat(new ComparableVersion(\"1.22\").compareTo(new ComparableVersion(\"1.20\")) > 0)\n            .as(\"1.22 > 1.20\")\n            .isTrue();\n    }\n\n    @Test\n    void testCompareVersionEqual() {\n        assertThat(new ComparableVersion(\"1.20\"))\n            .as(\"1.20 == 1.20\")\n            .isEqualByComparingTo(new ComparableVersion(\"1.20\"));\n    }\n\n    @Test\n    void testCompareVersionGreaterThan() {\n        assertThat(new ComparableVersion(\"2.10\").compareTo(new ComparableVersion(\"1.20\")) > 0)\n            .as(\"2.10 > 1.20\")\n            .isTrue();\n    }\n\n    @Test\n    void testCompareVersionIgnoresExcessLength() {\n        assertThat(new ComparableVersion(\"1.20\"))\n            .as(\"1.20 == 1.20.3\")\n            .isEqualByComparingTo(new ComparableVersion(\"1.20.3\"));\n    }\n}\n"
  },
  {
    "path": "core/src/test/java/org/testcontainers/utility/TestcontainersConfigurationTest.java",
    "content": "package org.testcontainers.utility;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Properties;\nimport java.util.UUID;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass TestcontainersConfigurationTest {\n\n    private Properties userProperties;\n\n    private Properties classpathProperties;\n\n    private Map<String, String> environment;\n\n    @BeforeEach\n    public void setUp() {\n        userProperties = new Properties();\n        classpathProperties = new Properties();\n        environment = new HashMap<>();\n    }\n\n    @Test\n    void shouldSubstituteImageNamesFromClasspathProperties() {\n        classpathProperties.setProperty(\"ryuk.container.image\", \"foo:version\");\n        assertThat(newConfig().getConfiguredSubstituteImage(DockerImageName.parse(\"testcontainers/ryuk:any\")))\n            .as(\"an image name can be pulled from classpath properties\")\n            .isEqualTo(DockerImageName.parse(\"foo:version\"));\n    }\n\n    @Test\n    void shouldSubstituteImageNamesFromUserProperties() {\n        userProperties.setProperty(\"ryuk.container.image\", \"foo:version\");\n        assertThat(newConfig().getConfiguredSubstituteImage(DockerImageName.parse(\"testcontainers/ryuk:any\")))\n            .as(\"an image name can be pulled from user properties\")\n            .isEqualTo(DockerImageName.parse(\"foo:version\"));\n    }\n\n    @Test\n    void shouldSubstituteImageNamesFromEnvironmentVariables() {\n        environment.put(\"TESTCONTAINERS_RYUK_CONTAINER_IMAGE\", \"foo:version\");\n        assertThat(newConfig().getConfiguredSubstituteImage(DockerImageName.parse(\"testcontainers/ryuk:any\")))\n            .as(\"an image name can be pulled from an environment variable\")\n            .isEqualTo(DockerImageName.parse(\"foo:version\"));\n    }\n\n    @Test\n    void shouldApplySettingsInOrder() {\n        assertThat(newConfig().getEnvVarOrProperty(\"key\", \"default\"))\n            .as(\"precedence order for multiple sources of the same value is correct\")\n            .isEqualTo(\"default\");\n\n        classpathProperties.setProperty(\"key\", \"foo\");\n\n        assertThat(newConfig().getEnvVarOrProperty(\"key\", \"default\"))\n            .as(\"precedence order for multiple sources of the same value is correct\")\n            .isEqualTo(\"foo\");\n\n        userProperties.setProperty(\"key\", \"bar\");\n\n        assertThat(newConfig().getEnvVarOrProperty(\"key\", \"default\"))\n            .as(\"precedence order for multiple sources of the same value is correct\")\n            .isEqualTo(\"bar\");\n\n        environment.put(\"TESTCONTAINERS_KEY\", \"baz\");\n\n        assertThat(newConfig().getEnvVarOrProperty(\"key\", \"default\"))\n            .as(\"precedence order for multiple sources of the same value is correct\")\n            .isEqualTo(\"baz\");\n    }\n\n    @Test\n    void shouldNotReadChecksFromClasspathProperties() {\n        assertThat(newConfig().isDisableChecks()).as(\"checks enabled by default\").isFalse();\n\n        classpathProperties.setProperty(\"checks.disable\", \"true\");\n        assertThat(newConfig().isDisableChecks()).as(\"checks are not affected by classpath properties\").isFalse();\n    }\n\n    @Test\n    void shouldReadChecksFromUserProperties() {\n        assertThat(newConfig().isDisableChecks()).as(\"checks enabled by default\").isFalse();\n\n        userProperties.setProperty(\"checks.disable\", \"true\");\n        assertThat(newConfig().isDisableChecks()).as(\"checks disabled via user properties\").isTrue();\n    }\n\n    @Test\n    void shouldReadChecksFromEnvironment() {\n        assertThat(newConfig().isDisableChecks()).as(\"checks enabled by default\").isFalse();\n\n        userProperties.remove(\"checks.disable\");\n        environment.put(\"TESTCONTAINERS_CHECKS_DISABLE\", \"true\");\n        assertThat(newConfig().isDisableChecks()).as(\"checks disabled via env var\").isTrue();\n    }\n\n    @Test\n    void shouldReadDockerSettingsFromEnvironmentWithoutTestcontainersPrefix() {\n        userProperties.remove(\"docker.foo\");\n        environment.put(\"DOCKER_FOO\", \"some value\");\n        assertThat(newConfig().getEnvVarOrUserProperty(\"docker.foo\", \"default\"))\n            .as(\"reads unprefixed env vars for docker. settings\")\n            .isEqualTo(\"some value\");\n    }\n\n    @Test\n    void shouldNotReadDockerSettingsFromEnvironmentWithTestcontainersPrefix() {\n        userProperties.remove(\"docker.foo\");\n        environment.put(\"TESTCONTAINERS_DOCKER_FOO\", \"some value\");\n        assertThat(newConfig().getEnvVarOrUserProperty(\"docker.foo\", \"default\"))\n            .as(\"reads unprefixed env vars for docker. settings\")\n            .isEqualTo(\"default\");\n    }\n\n    @Test\n    void shouldReadDockerSettingsFromUserProperties() {\n        environment.remove(\"DOCKER_FOO\");\n        userProperties.put(\"docker.foo\", \"some value\");\n        assertThat(newConfig().getEnvVarOrUserProperty(\"docker.foo\", \"default\"))\n            .as(\"reads unprefixed user properties for docker. settings\")\n            .isEqualTo(\"some value\");\n    }\n\n    @Test\n    void shouldNotReadSettingIfCorrespondingEnvironmentVarIsEmptyString() {\n        environment.put(\"DOCKER_FOO\", \"\");\n        assertThat(newConfig().getEnvVarOrUserProperty(\"docker.foo\", \"default\"))\n            .as(\"reads unprefixed env vars for docker. settings\")\n            .isEqualTo(\"default\");\n    }\n\n    @Test\n    void shouldNotReadDockerClientStrategyFromClasspathProperties() {\n        String currentValue = newConfig().getDockerClientStrategyClassName();\n\n        classpathProperties.setProperty(\"docker.client.strategy\", UUID.randomUUID().toString());\n        assertThat(newConfig().getDockerClientStrategyClassName())\n            .as(\"Docker client strategy is not affected by classpath properties\")\n            .isEqualTo(currentValue);\n    }\n\n    @Test\n    void shouldReadDockerClientStrategyFromUserProperties() {\n        userProperties.setProperty(\"docker.client.strategy\", \"foo\");\n        assertThat(newConfig().getDockerClientStrategyClassName())\n            .as(\"Docker client strategy is changed by user property\")\n            .isEqualTo(\"foo\");\n    }\n\n    @Test\n    void shouldReadDockerClientStrategyFromEnvironment() {\n        userProperties.remove(\"docker.client.strategy\");\n        environment.put(\"TESTCONTAINERS_DOCKER_CLIENT_STRATEGY\", \"foo\");\n        assertThat(newConfig().getDockerClientStrategyClassName())\n            .as(\"Docker client strategy is changed by env var\")\n            .isEqualTo(\"foo\");\n    }\n\n    @Test\n    void shouldNotUseImplicitDockerClientStrategyWhenDockerHostAndStrategyAreBothSet() {\n        userProperties.put(\"docker.client.strategy\", \"foo\");\n        userProperties.put(\"docker.host\", \"tcp://1.2.3.4:5678\");\n        assertThat(newConfig().getDockerClientStrategyClassName())\n            .as(\"Docker client strategy is can be explicitly set\")\n            .isEqualTo(\"foo\");\n\n        userProperties.remove(\"docker.client.strategy\");\n\n        environment.put(\"TESTCONTAINERS_DOCKER_CLIENT_STRATEGY\", \"bar\");\n        userProperties.put(\"docker.client.strategy\", \"foo\");\n        assertThat(newConfig().getDockerClientStrategyClassName())\n            .as(\"Docker client strategy is can be explicitly set\")\n            .isEqualTo(\"bar\");\n\n        environment.put(\"TESTCONTAINERS_DOCKER_CLIENT_STRATEGY\", \"bar\");\n        userProperties.remove(\"docker.client.strategy\");\n        assertThat(newConfig().getDockerClientStrategyClassName())\n            .as(\"Docker client strategy is can be explicitly set\")\n            .isEqualTo(\"bar\");\n\n        environment.remove(\"TESTCONTAINERS_DOCKER_CLIENT_STRATEGY\");\n        userProperties.put(\"docker.client.strategy\", \"foo\");\n        assertThat(newConfig().getDockerClientStrategyClassName())\n            .as(\"Docker client strategy is can be explicitly set\")\n            .isEqualTo(\"foo\");\n    }\n\n    @Test\n    void shouldNotReadReuseFromClasspathProperties() {\n        assertThat(newConfig().environmentSupportsReuse()).as(\"no reuse by default\").isFalse();\n\n        classpathProperties.setProperty(\"testcontainers.reuse.enable\", \"true\");\n        assertThat(newConfig().environmentSupportsReuse())\n            .as(\"reuse is not affected by classpath properties\")\n            .isFalse();\n    }\n\n    @Test\n    void shouldReadReuseFromUserProperties() {\n        assertThat(newConfig().environmentSupportsReuse()).as(\"no reuse by default\").isFalse();\n\n        userProperties.setProperty(\"testcontainers.reuse.enable\", \"true\");\n        assertThat(newConfig().environmentSupportsReuse()).as(\"reuse enabled via user property\").isTrue();\n    }\n\n    @Test\n    void shouldReadReuseFromEnvironment() {\n        assertThat(newConfig().environmentSupportsReuse()).as(\"no reuse by default\").isFalse();\n\n        userProperties.remove(\"testcontainers.reuse.enable\");\n        environment.put(\"TESTCONTAINERS_REUSE_ENABLE\", \"true\");\n        assertThat(newConfig().environmentSupportsReuse()).as(\"reuse enabled via env var\").isTrue();\n    }\n\n    @Test\n    void shouldTrimImageNames() {\n        userProperties.setProperty(\"ryuk.container.image\", \" testcontainers/ryuk:0.3.2 \");\n        assertThat(newConfig().getRyukImage())\n            .as(\"trailing whitespace was not removed from image name property\")\n            .isEqualTo(\"testcontainers/ryuk:0.3.2\");\n    }\n\n    private TestcontainersConfiguration newConfig() {\n        return new TestcontainersConfiguration(userProperties, classpathProperties, environment);\n    }\n}\n"
  },
  {
    "path": "core/src/test/resources/Dockerfile",
    "content": "FROM postgres\n"
  },
  {
    "path": "core/src/test/resources/Dockerfile-multistage",
    "content": "FROM alpine:3.14 AS builder\n\nWORKDIR /my-files\n\nRUN echo 'Hello World' > hello.txt\n\nFROM alpine:3.14\n\nCOPY --from=builder /my-files/hello.txt hello.txt\n"
  },
  {
    "path": "core/src/test/resources/META-INF/services/org.testcontainers.core.CreateContainerCmdModifier",
    "content": "org.testcontainers.custom.TestCreateContainerCmdModifier\n"
  },
  {
    "path": "core/src/test/resources/auth-config/config-basic-auth.json",
    "content": "{\n    \"auths\": {\n        \"https://registry.example.com\": {\n            \"email\": \"user@example.com\",\n            \"auth\": \"dXNlcjpwYXNz\"\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/resources/auth-config/config-empty-auth-with-helper.json",
    "content": "{\n    \"auths\": {\n    },\n    \"HttpHeaders\": {\n        \"User-Agent\": \"Docker-Client/18.03.0-ce (darwin)\"\n    },\n    \"credHelpers\": {\n        \"registry.example.com\": \"fake\"\n    }\n}\n"
  },
  {
    "path": "core/src/test/resources/auth-config/config-empty.json",
    "content": "{\n    \"HttpHeaders\": {\n        \"User-Agent\": \"Docker-Client/18.03.0-ce (darwin)\"\n    }\n}\n"
  },
  {
    "path": "core/src/test/resources/auth-config/config-existing-auth-with-helper.json",
    "content": "{\n    \"auths\": {\n        \"https://registry.example.com\": {\n            \"email\": \"not@val.id\",\n            \"auth\": \"dXNlcjpwYXNz\"\n        }\n    },\n    \"HttpHeaders\": {\n        \"User-Agent\": \"Docker-Client/18.03.0-ce (darwin)\"\n    },\n    \"credHelpers\": {\n        \"registry.example.com\": \"fake\"\n    }\n}\n"
  },
  {
    "path": "core/src/test/resources/auth-config/config-with-helper-and-store.json",
    "content": "{\n    \"auths\": {\n        \"registry.example.com\": {}\n    },\n    \"HttpHeaders\": {\n        \"User-Agent\": \"Docker-Client/18.03.0-ce (darwin)\"\n    },\n    \"credsStore\": \"fake\",\n    \"credHelpers\": {\n        \"registry.example.com\": \"fake\"\n    }\n}\n"
  },
  {
    "path": "core/src/test/resources/auth-config/config-with-helper-no-server-url-using-token.json",
    "content": "{\n    \"auths\": {\n        \"registrynoserverurltoken.example.com\": {}\n    },\n    \"HttpHeaders\": {\n        \"User-Agent\": \"Docker-Client/18.03.0-ce (darwin)\"\n    },\n    \"credHelpers\": {\n        \"registrynoserverurltoken.example.com\": \"fake\"\n    }\n}\n"
  },
  {
    "path": "core/src/test/resources/auth-config/config-with-helper-no-server-url.json",
    "content": "{\n    \"auths\": {\n        \"registrynoserverurl.example.com\": {}\n    },\n    \"HttpHeaders\": {\n        \"User-Agent\": \"Docker-Client/18.03.0-ce (darwin)\"\n    },\n    \"credHelpers\": {\n        \"registrynoserverurl.example.com\": \"fake\"\n    }\n}\n"
  },
  {
    "path": "core/src/test/resources/auth-config/config-with-helper-using-token.json",
    "content": "{\n    \"auths\": {\n        \"registrytoken.example.com\": {}\n    },\n    \"HttpHeaders\": {\n        \"User-Agent\": \"Docker-Client/18.03.0-ce (darwin)\"\n    },\n    \"credHelpers\": {\n        \"registrytoken.example.com\": \"fake\"\n    }\n}\n"
  },
  {
    "path": "core/src/test/resources/auth-config/config-with-helper.json",
    "content": "{\n    \"auths\": {\n        \"registry.example.com\": {}\n    },\n    \"HttpHeaders\": {\n        \"User-Agent\": \"Docker-Client/18.03.0-ce (darwin)\"\n    },\n    \"credHelpers\": {\n        \"registry.example.com\": \"fake\"\n    }\n}\n"
  },
  {
    "path": "core/src/test/resources/auth-config/config-with-json-key.json",
    "content": "{\n    \"auths\": {\n        \"https://registry.example.com\": {\n            \"auth\": \"X2pzb25fa2V5OnsKInR5cGUiOiAic2VydmljZV9hY2NvdW50IiwKInByb2plY3RfaWQiOiAiZXhhbXBsZS1wcm9qZWN0IiwKInByaXZhdGVfa2V5X2lkIjogImV4YW1wbGUta2V5LWlkIiwKInByaXZhdGVfa2V5IjogImV4YW1wbGUtcHJpdmF0ZS1rZXkiLAoiY2xpZW50X2VtYWlsIjogInVzZXJAZXhhbXBsZS5jb20iLAoiY2xpZW50X2lkIjogInVzZXJAZXhhbXBsZS5jb20iLAoiYXV0aF91cmkiOiAiaHR0cHM6Ly9yZWdpc3RyeS5leGFtcGxlLmNvbS9vL29hdXRoMi9hdXRoIiwKInRva2VuX3VyaSI6ICJodHRwczovL3JlZ2lzdHJ5LmV4YW1wbGUuY29tL3Rva2VuIiwKImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6ICJodHRwczovL3JlZ2lzdHJ5LmV4YW1wbGUuY29tIiwKImNsaWVudF94NTA5X2NlcnRfdXJsIjogImh0dHBzOi8vcmVnaXN0cnkuZXhhbXBsZS5jb20iCn0=\"\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/resources/auth-config/config-with-store-empty.json",
    "content": "{\n    \"auths\": {\n        \"registry.example.com\": {}\n    },\n    \"HttpHeaders\": {\n        \"User-Agent\": \"Docker-Client/18.03.0-ce (darwin)\"\n    },\n    \"credsStore\": \"\"\n}\n"
  },
  {
    "path": "core/src/test/resources/auth-config/config-with-store.json",
    "content": "{\n    \"auths\": {\n        \"registry.example.com\": {}\n    },\n    \"HttpHeaders\": {\n        \"User-Agent\": \"Docker-Client/18.03.0-ce (darwin)\"\n    },\n    \"credsStore\": \"fake\"\n}\n"
  },
  {
    "path": "core/src/test/resources/auth-config/docker-credential-fake",
    "content": "#!/bin/sh\n\nif [ $1 != \"get\" ]; then\n    exit 1\nfi\n\nread inputLine\n\nif [ \"$inputLine\" = \"registry2.example.com\" ]; then\n    echo Fake credentials not found on credentials store \\'$inputLine\\' 0>&2\n    exit 1\nfi\n\nif [ \"$inputLine\" = \"registry.example.com\" ]; then\n    echo '{' \\\n        '  \"ServerURL\": \"url\",' \\\n        '  \"Username\": \"username\",' \\\n        '  \"Secret\": \"secret\"' \\\n        '}'\n    exit 0\nfi\nif [ \"$inputLine\" = \"registrytoken.example.com\" ]; then\n    echo '{' \\\n        '  \"ServerURL\": \"url\",' \\\n        '  \"Username\": \"<token>\",' \\\n        '  \"Secret\": \"secret\"' \\\n        '}'\n    exit 0\nfi\nif [ \"$inputLine\" = \"registrynoserverurl.example.com\" ]; then\n    echo '{' \\\n        '  \"Username\": \"username\",' \\\n        '  \"Secret\": \"secret\"' \\\n        '}'\n    exit 0\nfi\nif [ \"$inputLine\" = \"registrynoserverurltoken.example.com\" ]; then\n    echo '{' \\\n        '  \"Username\": \"<token>\",' \\\n        '  \"Secret\": \"secret\"' \\\n        '}'\n    exit 0\nfi\n\nexit 1\n"
  },
  {
    "path": "core/src/test/resources/auth-config/win/docker-credential-fake.bat",
    "content": "@echo off\nif not \"%1\" == \"get\" (\n    exit 1\n)\n\nset /p inputLine=\"\"\n\nif \"%inputLine%\" == \"registry2.example.com\" (\n     echo Fake credentials not found on credentials store '%inputLine%' 0>&2\n     exit 1\n)\n\nif \"%inputLine%\" == \"registry.example.com\" (\n     echo {\n     echo   \"ServerURL\": \"url\",\n     echo   \"Username\": \"username\",\n     echo   \"Secret\": \"secret\"\n     echo }\n     exit 0\n)\nif \"%inputLine%\" == \"registrytoken.example.com\" (\n     echo {\n     echo   \"ServerURL\": \"url\",\n     echo   \"Username\": \"<token>\",\n     echo   \"Secret\": \"secret\"\n     echo }\n     exit 0\n)\nif \"%inputLine%\" == \"registrynoserverurl.example.com\" (\n     echo {\n     echo   \"Username\": \"username\",\n     echo   \"Secret\": \"secret\"\n     echo }\n     exit 0\n)\nif \"%inputLine%\" == \"registrynoserverurltoken.example.com\" (\n     echo {\n     echo   \"Username\": \"<token>\",\n     echo   \"Secret\": \"secret\"\n     echo }\n     exit 0\n)\n\nexit 1\n"
  },
  {
    "path": "core/src/test/resources/compose-build-test/Dockerfile",
    "content": "FROM redis:6.0-alpine\n\nCMD [\"redis-server\"]\n"
  },
  {
    "path": "core/src/test/resources/compose-build-test/docker-compose.yml",
    "content": "version: '2.0'\nservices:\n  customredis:\n      build: .\n  normalredis:\n      image: redis:6.0-alpine\n"
  },
  {
    "path": "core/src/test/resources/compose-dockerfile/Dockerfile",
    "content": "FROM alpine:3.17\n\nADD passthrough.sh /passthrough.sh\n\nCMD /passthrough.sh\n"
  },
  {
    "path": "core/src/test/resources/compose-dockerfile/passthrough.sh",
    "content": "#!/usr/bin/env sh\n\nwhile true; do\n    echo \"Exposing env on port 3000\"\n    env | nc -l -p 3000\ndone"
  },
  {
    "path": "core/src/test/resources/compose-file-copy-inclusions/Dockerfile",
    "content": "FROM jbangdev/jbang-action\n\nWORKDIR /app\nCOPY EnvVariableRestEndpoint.java .\n\nRUN jbang export portable --force EnvVariableRestEndpoint.java\n\nEXPOSE 8080\nCMD [\"./EnvVariableRestEndpoint.java\"]\n"
  },
  {
    "path": "core/src/test/resources/compose-file-copy-inclusions/EnvVariableRestEndpoint.java",
    "content": "///usr/bin/env jbang \"$0\" \"$@\" ; exit $?\n\nimport com.sun.net.httpserver.HttpServer;\nimport com.sun.net.httpserver.HttpHandler;\nimport com.sun.net.httpserver.HttpExchange;\n\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.net.InetSocketAddress;\n\npublic class EnvVariableRestEndpoint {\n    private static final String ENV_VARIABLE_NAME = \"MY_ENV_VARIABLE\";\n    private static final int PORT = 8080;\n\n    public static void main(String[] args) throws IOException {\n        HttpServer server = HttpServer.create(new InetSocketAddress(PORT), 0);\n        server.createContext(\"/env\", new EnvVariableHandler());\n        server.setExecutor(null);\n        server.start();\n        System.out.println(\"Server started on port \" + PORT);\n    }\n\n    static class EnvVariableHandler implements HttpHandler {\n        @Override\n        public void handle(HttpExchange exchange) throws IOException {\n            if (\"GET\".equals(exchange.getRequestMethod())) {\n                String envValue = System.getenv(ENV_VARIABLE_NAME);\n                String response = envValue != null\n                    ? ENV_VARIABLE_NAME + \": \" + envValue\n                    : \"Environment variable \" + ENV_VARIABLE_NAME + \" not found\";\n\n                exchange.sendResponseHeaders(200, response.length());\n                try (OutputStream os = exchange.getResponseBody()) {\n                    os.write(response.getBytes());\n                }\n            } else {\n                String response = \"Method not allowed\";\n                exchange.sendResponseHeaders(405, response.length());\n                try (OutputStream os = exchange.getResponseBody()) {\n                    os.write(response.getBytes());\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "core/src/test/resources/compose-file-copy-inclusions/compose-root-only.yml",
    "content": "services:\n  app:\n    build: .\n    ports:\n      - \"8080\"\n    env_file:\n      - '.env'\n"
  },
  {
    "path": "core/src/test/resources/compose-file-copy-inclusions/compose-test-only.yml",
    "content": "services:\n  app:\n    build: .\n    ports:\n      - \"8080\"\n    env_file:\n      - './test/.env'\n"
  },
  {
    "path": "core/src/test/resources/compose-file-copy-inclusions/compose.yml",
    "content": "services:\n  app:\n    build: .\n    ports:\n      - \"8080\"\n    env_file:\n      - '.env'\n      - './test/.env'\n"
  },
  {
    "path": "core/src/test/resources/compose-options-test/with-deploy-block.yml",
    "content": "version: '3.7'\nservices:\n  redis:\n      image: redis:6-alpine\n      deploy:\n        resources:\n          limits:\n            memory: 150M\n"
  },
  {
    "path": "core/src/test/resources/compose-override/compose-override.yml",
    "content": "services:\n  redis:\n    image: redis:6-alpine\n    ports:\n      - 6379\n    environment:\n      foo: !reset null\n"
  },
  {
    "path": "core/src/test/resources/compose-override/compose.yml",
    "content": "services:\n  redis:\n    image: redis:6-alpine\n    ports:\n      - 6379\n    environment:\n      foo: bar\n"
  },
  {
    "path": "core/src/test/resources/compose-profile-option/compose-test.yml",
    "content": "services:\n  redis:\n    image: redis\n    profiles:\n      - cache\n  db:\n    image: mysql:8.0.36\n    environment:\n      MYSQL_RANDOM_ROOT_PASSWORD: \"true\"\n    profiles:\n      - db\n"
  },
  {
    "path": "core/src/test/resources/compose-scaling-multiple-containers.yml",
    "content": "version: '2.4'\nservices:\n  redis:\n    image: redis\n  other:\n    image: alpine:3.17\n    command: sleep 10000\n"
  },
  {
    "path": "core/src/test/resources/compose-test.yml",
    "content": "redis:\n  image: redis\ndb:\n  image: mysql:8.0.36\n  environment:\n    MYSQL_RANDOM_ROOT_PASSWORD: \"true\"\n"
  },
  {
    "path": "core/src/test/resources/compose-v2-build-test/Dockerfile",
    "content": "FROM redis:7.0-alpine\n\nCMD [\"redis-server\"]\n"
  },
  {
    "path": "core/src/test/resources/compose-v2-build-test/docker-compose.yml",
    "content": "version: '2.0'\nservices:\n  customredis:\n      build: .\n  normalredis:\n      image: redis:7.0-alpine\n"
  },
  {
    "path": "core/src/test/resources/compose-with-inline-scale-test.yml",
    "content": "version: '2.4'\nservices:\n  redis:\n    image: redis\n    scale: 3      # legacy mechanism to specify scale\n"
  },
  {
    "path": "core/src/test/resources/composev2/compose-test.yml",
    "content": "services:\n  redis:\n    image: redis\n  db:\n    image: mysql:8.0.36\n    environment:\n      MYSQL_RANDOM_ROOT_PASSWORD: \"true\"\n"
  },
  {
    "path": "core/src/test/resources/composev2/scaled-compose-test.yml",
    "content": "services:\n  redis:\n    image: redis\n"
  },
  {
    "path": "core/src/test/resources/container-license-acceptance.txt",
    "content": "a\nb\n\n"
  },
  {
    "path": "core/src/test/resources/docker-compose-base.yml",
    "content": "version: '2.1'\nservices:\n  alpine:\n    build: compose-dockerfile\n    environment:\n      bar: base\n"
  },
  {
    "path": "core/src/test/resources/docker-compose-container-name-v1.yml",
    "content": "redis:\n    image: redis\n    container_name: redis\n"
  },
  {
    "path": "core/src/test/resources/docker-compose-deserialization.yml",
    "content": "redis:\n    image: redis\n    key: !!org.testcontainers.containers.ParsedDockerComposeFileBean '{foo: bar}'\n"
  },
  {
    "path": "core/src/test/resources/docker-compose-healthcheck.yml",
    "content": "version: \"3.9\"\nservices:\n  redis:\n    image: redis:alpine\n    healthcheck:\n      test: [ \"CMD\", \"redis-cli\", \"--raw\", \"incr\", \"ping\" ]\n"
  },
  {
    "path": "core/src/test/resources/docker-compose-imagename-overriding-a.yml",
    "content": "version: \"2.1\"\nservices:\n    aservice:\n      image: aservice\n    redis:\n        image: redis:a\n    mysql:\n        image: mysql:a\n    custom:\n        build: .\nnetworks:\n    custom_network: {}\n"
  },
  {
    "path": "core/src/test/resources/docker-compose-imagename-overriding-b.yml",
    "content": "version: \"2.1\"\nservices:\n    redis:\n        image: redis:b\n    mysql:\n        image: mysql:b\n    custom:\n        build: compose-dockerfile\nnetworks:\n    custom_network: {}\n"
  },
  {
    "path": "core/src/test/resources/docker-compose-imagename-parsing-dockerfile-with-context.yml",
    "content": "version: \"2.1\"\nservices:\n    redis:\n        image: redis\n    mysql:\n        image: mysql\n    custom:\n        build:\n            context: compose-dockerfile\n            dockerfile: Dockerfile\nnetworks:\n    custom_network: {}\n"
  },
  {
    "path": "core/src/test/resources/docker-compose-imagename-parsing-dockerfile.yml",
    "content": "version: \"2.1\"\nservices:\n    redis:\n        image: redis\n    mysql:\n        image: mysql\n    custom:\n        build: compose-dockerfile\nnetworks:\n    custom_network: {}\n"
  },
  {
    "path": "core/src/test/resources/docker-compose-imagename-parsing-v1.yml",
    "content": "redis:\n    image: redis\nmysql:\n    image: mysql\ncustom:\n    build: .\n"
  },
  {
    "path": "core/src/test/resources/docker-compose-imagename-parsing-v2-no-version.yml",
    "content": "services:\n    redis:\n        image: redis\n    mysql:\n        image: mysql\n    custom:\n        build: .\nnetworks:\n    custom_network: {}\n"
  },
  {
    "path": "core/src/test/resources/docker-compose-imagename-parsing-v2.yml",
    "content": "version: \"2.1\"\nservices:\n    redis:\n        image: redis\n    mysql:\n        image: mysql\n    custom:\n        build: .\nnetworks:\n    custom_network: {}\n"
  },
  {
    "path": "core/src/test/resources/docker-compose-non-default-override.yml",
    "content": "version: '2.1'\nservices:\n  alpine:\n    environment:\n      bar: overwritten\n"
  },
  {
    "path": "core/src/test/resources/dockerfile-build-invalid/.dockerignore",
    "content": "# This is an invalid dockerignore file\n*~\n[a-b-c]"
  },
  {
    "path": "core/src/test/resources/dockerfile-build-invalid/Dockerfile",
    "content": "FROM alpine:3.16\n"
  },
  {
    "path": "core/src/test/resources/dockerfile-build-test/.dockerignore",
    "content": "should_be_ignored.txt\n"
  },
  {
    "path": "core/src/test/resources/dockerfile-build-test/Dockerfile",
    "content": "FROM alpine:3.16\nCOPY localfile.txt /test.txt\n"
  },
  {
    "path": "core/src/test/resources/dockerfile-build-test/Dockerfile-alt",
    "content": "FROM alpine:3.16\nRUN echo \"test4567\" > /test.txt\n"
  },
  {
    "path": "core/src/test/resources/dockerfile-build-test/Dockerfile-buildarg",
    "content": "FROM alpine:3.16\nARG CUSTOM_ARG\nRUN echo \"${CUSTOM_ARG}\" > /test.txt\n"
  },
  {
    "path": "core/src/test/resources/dockerfile-build-test/Dockerfile-currentdir",
    "content": "FROM alpine:3.16\nCOPY . /\n"
  },
  {
    "path": "core/src/test/resources/dockerfile-build-test/Dockerfile-from-buildarg",
    "content": "ARG BUILD_IMAGE\nARG BASE_IMAGE\nARG BASE_IMAGE_TAG\n\nFROM ${BUILD_IMAGE} AS build\nCOPY localfile.txt /test-build.txt\n\nFROM $BASE_IMAGE:${BASE_IMAGE_TAG} AS base\nCOPY --from=build /test-build.txt /test.txt\n"
  },
  {
    "path": "core/src/test/resources/dockerfile-build-test/localfile.txt",
    "content": "test1234\n"
  },
  {
    "path": "core/src/test/resources/dockerfile-build-test/should_be_ignored.txt",
    "content": "this file should be ignored by a .dockerignore file"
  },
  {
    "path": "core/src/test/resources/dockerfile-build-test/should_not_be_ignored.txt",
    "content": "this file should not be ignored by a .dockerignore file"
  },
  {
    "path": "core/src/test/resources/expectedClasspathFile.txt",
    "content": "This file exists for org.testcontainers.utility.ClasspathScannerTest\n"
  },
  {
    "path": "core/src/test/resources/fixtures/statements/KeyValuesStatementTest/keyWithNewLinesTest",
    "content": "\"key\\nwith\\nnewlines\"=\"1\""
  },
  {
    "path": "core/src/test/resources/fixtures/statements/KeyValuesStatementTest/keyWithSpacesTest",
    "content": "\"key with spaces\"=\"1\""
  },
  {
    "path": "core/src/test/resources/fixtures/statements/KeyValuesStatementTest/keyWithTabsTest",
    "content": "\"key\\twith\\ttab\"=\"1\"\n"
  },
  {
    "path": "core/src/test/resources/fixtures/statements/KeyValuesStatementTest/multilineTest",
    "content": "\"line1\"=\"1\" \\\n\t\"line2\"=\"2\" \\\n\t\"line3\"=\"3\"\n"
  },
  {
    "path": "core/src/test/resources/fixtures/statements/KeyValuesStatementTest/valueIsEscapedTest",
    "content": "\"1\"=\"value with spaces\" \\\n\t\"2\"=\"value\\nwith\\nnewlines\" \\\n\t\"3\"=\"value\\twith\\ttab\"\n"
  },
  {
    "path": "core/src/test/resources/fixtures/statements/MultiArgsStatementTest/multilineTest",
    "content": "[\"some\\nmultiline\\nargument\"]"
  },
  {
    "path": "core/src/test/resources/fixtures/statements/MultiArgsStatementTest/simpleTest",
    "content": "[\"a\",\"b\",\"c\"]"
  },
  {
    "path": "core/src/test/resources/fixtures/statements/RawStatementTest/simpleTest",
    "content": "value\nas\t\\\nis"
  },
  {
    "path": "core/src/test/resources/fixtures/statements/SingleArgumentStatementTest/multilineTest",
    "content": "hello\\\nworld"
  },
  {
    "path": "core/src/test/resources/fixtures/statements/SingleArgumentStatementTest/simpleTest",
    "content": "hello"
  },
  {
    "path": "core/src/test/resources/health-wait-strategy-dockerfile/Dockerfile",
    "content": "FROM alpine:3.16\n\nHEALTHCHECK --interval=1s CMD test -e /testfile\n\nCOPY write_file_and_loop.sh write_file_and_loop.sh\nRUN chmod +x write_file_and_loop.sh\n\nCMD [\"/write_file_and_loop.sh\"]\n"
  },
  {
    "path": "core/src/test/resources/health-wait-strategy-dockerfile/write_file_and_loop.sh",
    "content": "#!/bin/ash\n\nset -ex\n\nsleep 2\ntouch /testfile\n\nwhile true; do sleep 1; done\n"
  },
  {
    "path": "core/src/test/resources/https-wait-strategy-dockerfile/Dockerfile",
    "content": "FROM nginx:1.17-alpine\n\n# Create keypair and self-signed certificate for https test\nRUN apk update && apk add bash openssl && openssl req -batch -x509 -nodes -days 365 -newkey rsa:2048 -subj \"/CN=localhost\" -keyout /etc/ssl/private/nginx-selfsigned.key -out /etc/ssl/certs/nginx-selfsigned.crt\n\nADD nginx-ssl.conf /etc/nginx/conf.d/default.conf\n"
  },
  {
    "path": "core/src/test/resources/https-wait-strategy-dockerfile/nginx-ssl.conf",
    "content": "# This configuration makes Nginx listen on port port 8443\n# In order to use this config, add this line to the Dockerfile to create the keypair and self-signed certificate:\n# RUN apk update && apk add openssl && openssl req -batch -x509 -nodes -days 365 -newkey rsa:2048 -subj \"/CN=localhost\" -keyout /etc/ssl/private/nginx-selfsigned.key -out /etc/ssl/certs/nginx-selfsigned.crt\n\nserver {\n    listen              8443 ssl;\n    server_name         localhost;\n    ssl_certificate     /etc/ssl/certs/nginx-selfsigned.crt;\n    ssl_certificate_key /etc/ssl/private/nginx-selfsigned.key;\n}\n"
  },
  {
    "path": "core/src/test/resources/internal-port-check-dockerfile/Dockerfile-bash",
    "content": "FROM nginx:1.17-alpine\n\nRUN apk add bash\n\n# Make sure the /proc/net/tcp* check fails in this container\nRUN rm /usr/bin/awk\n\n# Make sure the nc check fails in this container\nRUN rm /usr/bin/nc\n\nADD nginx.conf /etc/nginx/conf.d/default.conf\n"
  },
  {
    "path": "core/src/test/resources/internal-port-check-dockerfile/Dockerfile-nc",
    "content": "FROM nginx:1.17-alpine\n\n# If this fails, you ended up using a base image with bash installed. Consider removing /bin/bash in this case\nRUN if bash -c true &> /dev/null; then exit 1; fi\n\n# Make sure the /proc/net/tcp* check fails in this container\nRUN rm /usr/bin/awk\n\nADD nginx.conf /etc/nginx/conf.d/default.conf\n"
  },
  {
    "path": "core/src/test/resources/internal-port-check-dockerfile/Dockerfile-tcp",
    "content": "FROM nginx:1.17-alpine\n\n# If this fails, you ended up using a base image with bash installed. Consider removing /bin/bash in this case\nRUN if bash -c true &> /dev/null; then exit 1; fi\n\n# Make sure the nc check fails in this container\nRUN rm /usr/bin/nc\n\nADD nginx.conf /etc/nginx/conf.d/default.conf\n"
  },
  {
    "path": "core/src/test/resources/internal-port-check-dockerfile/nginx.conf",
    "content": "# This configuration makes Nginx listen on port port 8080\n\nserver {\n    # Port 8080 is necessary to prove that the command formatting in the /proc/net/tcp* check uses the correct casing for hexadecimal numbers (i.e. 1F90 and not 1f90)\n    listen       8080;\n    # Port 100 is necessary to ensure that the /proc/net/tcp* check also succeeds with trailing zeros in the hexadecimal port\n    listen       100;\n}\n"
  },
  {
    "path": "core/src/test/resources/invalid-compose.yml",
    "content": "this is not a valid docker-compose file"
  },
  {
    "path": "core/src/test/resources/local-compose-test.yml",
    "content": "version: '2.1'\nservices:\n  redis:\n    image: redis-local:latest\n    ports:\n      - 6379\n"
  },
  {
    "path": "core/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n    <logger name=\"tc.dockerImageName\" level=\"DEBUG\"/>\n\n    <logger name=\"com.github.dockerjava\" level=\"WARN\"/>\n    <logger name=\"org.zeroturnaround.exec\" level=\"WARN\"/>\n    <logger name=\"org.testcontainers.shaded\" level=\"WARN\"/>\n\n</configuration>\n"
  },
  {
    "path": "core/src/test/resources/mappable-dockerfile/Dockerfile",
    "content": "FROM alpine:3.16\nADD folder/someFile.txt /someFile.txt\nRUN cat /someFile.txt\nADD test.txt /test.txt\nRUN cat /test.txt\nCMD ping -c 5 www.google.com\n"
  },
  {
    "path": "core/src/test/resources/mappable-resource/test-resource.txt",
    "content": "FOOBAR"
  },
  {
    "path": "core/src/test/resources/redis.conf",
    "content": ""
  },
  {
    "path": "core/src/test/resources/scaled-compose-test.yml",
    "content": "redis:\n  image: redis"
  },
  {
    "path": "core/src/test/resources/test-recursive-file.txt",
    "content": "Used for DirectoryTarResourceTest"
  },
  {
    "path": "core/src/test/resources/test_copy_to_container.txt",
    "content": "Some Test\nMessage"
  },
  {
    "path": "core/src/test/resources/v2-compose-test-passthrough.yml",
    "content": "version: '2.1'\nservices:\n  alpine:\n    build: compose-dockerfile\n    environment:\n      bar: ${foo}\n"
  },
  {
    "path": "core/src/test/resources/v2-compose-test-port-via-env.yml",
    "content": "services:\n  redis:\n    image: redis\n    ports:\n      - ${REDIS_PORT}\n"
  },
  {
    "path": "core/src/test/resources/v2-compose-test-with-network.yml",
    "content": "version: '2.1'\nservices:\n  redis:\n    image: redis\n    networks:\n      - redis-net\n\nnetworks:\n  redis-net:\n"
  },
  {
    "path": "core/src/test/resources/v2-compose-test.yml",
    "content": "version: '2.1'\nservices:\n  redis:\n    image: redis\n    ports:\n      - 6379\n"
  },
  {
    "path": "core/testlib/META-INF/dummy_unique_name.txt",
    "content": "This is a file inside a JAR archive"
  },
  {
    "path": "core/testlib/README.md",
    "content": "This directory contains a synthetic JAR (`fakejar.jar`) that is needed for `org.testcontainers.utility.MountableFileTest`.\n\nThe `create_fakejar.sh` script may be used to recreate it."
  },
  {
    "path": "core/testlib/create_fakejar.sh",
    "content": "#!/usr/bin/env bash\n\nrm fakejar.jar\nzip -r fakejar.jar META-INF/ recursive/\nmv fakejar.jar repo/fakejar/fakejar/0/fakejar-0.jar"
  },
  {
    "path": "core/testlib/recursive/dir/content.txt",
    "content": "This is example content to show recursive mounting of files from inside a JAR archive."
  },
  {
    "path": "core/testlib/repo/fakejar/fakejar/0/fakejar-0.pom",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\" xmlns=\"http://maven.apache.org/POM/4.0.0\"\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n  <modelVersion>4.0.0</modelVersion>\n  <groupId>fakejar</groupId>\n  <artifactId>fakejar</artifactId>\n  <version>0</version>\n  <description>POM was created from install:install-file</description>\n</project>\n"
  },
  {
    "path": "core/testlib/repo/fakejar/fakejar/maven-metadata-local.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<metadata>\n  <groupId>fakejar</groupId>\n  <artifactId>fakejar</artifactId>\n  <versioning>\n    <release>0</release>\n    <versions>\n      <version>0</version>\n    </versions>\n    <lastUpdated>20170414082010</lastUpdated>\n  </versioning>\n</metadata>\n"
  },
  {
    "path": "docker-compose.yml",
    "content": "version: \"3.7\"\n\nservices:\n  docs:\n    image: python:3.8\n    command: sh -c \"pip install -r requirements.txt && mkdocs serve -a 0.0.0.0:8000\"\n    working_dir: /docs\n    volumes:\n      - ./:/docs\n    ports:\n      - 8000:8000\n"
  },
  {
    "path": "docs/_headers",
    "content": "/search/search_index.json\n  Access-Control-Allow-Origin: *\n"
  },
  {
    "path": "docs/_redirects",
    "content": "# Each redirect rule must be listed on a separate line, with the original path followed by the new path or URL.\n\n/usage/docker_compose.html              /modules/docker_compose/\n/usage/generic_containers.html          /features/creating_container/\n/usage/windows_support.html             /supported_docker_environment/windows/\n/usage/dockerfile.html                  /features/creating_images/\n/usage/inside_docker.html               /supported_docker_environment/continuous_integration/dind_patterns/\n/usage/webdriver_containers.html        /modules/webdriver_containers/\n/usage/properties.html                  /features/configuration/\n/usage/kafka_containers.html            /modules/kafka/\n/usage/elasticsearch_container.html     /modules/elasticsearch/\n/usage/database_containers.html         /modules/databases/\n/usage/neo4j_container.html             /modules/databases/neo4j/\n/compatibility.html                     /supported_docker_environment/\n/on_failure.html                        /error_missing_container_runtime_environment\n\n# No great 1:1 mapping exists for the following, so redirect to somewhere where at least a sensible sidebar will be shown\n\n/usage/options.html                     /features/creating_container/\n/ci/ci.html                             /supported_docker_environment/\n/usage.html                             /\n\n"
  },
  {
    "path": "docs/bounty.md",
    "content": "# Testcontainers issue bounty policy\n\n## General\n\nWe want to use issue bounties to encourage contributions in areas that are important to our sponsors, or tricky to solve.\nThis includes bug fixes and new features.\nWe hope that this will provide incentives to tackle issues, and gives sponsors a way to influence where development time is expended.\nWe also want to reward our contributors, some of whom make huge efforts to improve Testcontainers and help their fellow developers!\n\n!!! note\n    It's early days for our use of sponsorship, so we expect to evolve this policy over time, possibly without notice. In the event of any ambiguity or dispute, the [Testcontainers org core maintainers](#organisation-core-maintainers) have the final say.\n\n    If you'd like to suggest an improvement to this policy, we'd be grateful for your input - please raise a pull request!\n\n## For Sponsors\n\nSponsors will be able to create a number of 'bounties' per month, varying according to sponsorship tier.\n\nAs a sponsor, the process for creating a bounty is as follows:\n\n* Raise an issue, or find an existing issue that describes the bug or feature.\n* Start a discussion with the [Testcontainers org core maintainers](#organisation-core-maintainers) to agree that the issue is suitable for a bounty, and how much the reward amount should be.\n* Once agreed, we will assign a label to the issue so that interested developers can find it.\n\nSponsors can create up to 1 or 3 bounties (according to tier) _per calendar month_ - i.e. the counter resets on the 1st of each month. \nIf a sponsor does not use their full quota of bounty credits in a calendar month, they cannot be rolled over to the next month.\n\nBounties will expire 90 days after creation - after this time, if they have not been resolved we will close them.\n\n## For Contributors\n\nAs a contributor, the process for working on an issue with a bounty attached is:\n\n* Find an issue with a bounty attached to it and no assignee, clarify the requirements if necessary, and consider how you would approach working on it.\n* Start a discussion with the [Testcontainers org core maintainers](#organisation-core-maintainers) and the bounty owner. To avoid unpleasant surprises at review time, we'll try to confirm that we're happy with your proposed solution.\n* If we're happy with your proposed solution, we will assign the ticket to you.\n* Once work is complete, we will go through the PR process as usual and merge the work when finished.\n* To receive the bounty reward, [raise an invoice](https://opencollective.com/testcontainers/expenses/new) on Open Collective, following the expenses policy on that page.\n\nNote that a 20% cut of the bounty amount will normally be assigned to project maintainers for PR review work.\nWe believe this reflects that PR review can often be a significant amount of work for some issues - and also gives maintainers an incentive to complete the review and unlock the bounty reward!\nSome pull requests are so well done that very little review is necessary. If that happens, the maintainers may choose not to take a cut of the bounty, and instead release the full amount to the contributor.\n\n## Organisation core maintainers\n\nThe organisation core maintainers are:\n\n* Richard North (@rnorth)\n* Sergei Egorov (@bsideup)\n* Kevin Wittek (@kiview)\n\n"
  },
  {
    "path": "docs/contributing.md",
    "content": "# Contributing\n\n* Star the project on [GitHub](https://github.com/testcontainers/testcontainers-java) and help spread the word :)\n* Join our [Slack workspace](http://slack.testcontainers.org)\n* [Start a discussion](https://github.com/testcontainers/testcontainers-java/discussions) if you have an idea, find a possible bug or have a general question.\n* Contribute improvements or fixes using a [Pull Request](https://github.com/testcontainers/testcontainers-java/pulls). If you're going to contribute, thank you! Please just be sure to:\n    * discuss with the authors prior to doing anything big.\n    * follow the style, naming and structure conventions of the rest of the project.\n    * make commits atomic and easy to merge.\n    * when updating documentation, please see [our guidance for documentation contributions](contributing_docs.md).\n    * apply format running `./gradlew spotlessApply` (this requires [Node.js](https://nodejs.org/) to be installed on your machine, one of the [package managers](https://nodejs.org/en/download/package-manager/) might be handy)\n    * verify all tests are passing. Build the project with `./gradlew check` to do this.\n    **N.B.** Gradle's Build Cache is enabled by default, but you can add `--no-build-cache` flag to disable it.\n\n## Contributing new modules\n\nWe often receive proposals (or fully formed PRs) for new modules.\nWe're very happy to have contributions, but new modules require specific extra care. We want to balance:\n\n* Usefulness of the module.\n* Our ability to support the module in the future, potentially after contributors have moved on.\n* Contributors time, so that nobody puts in wasted effort.\n\n### Does it need to be a module?\n\n*N.B. this is not a perfect list - please always reach out to us before starting on a module contribution!*\n\n* Does the module enable use of Testcontainers with a popular or rapidly growing technology?\n* Does the module 'add value' beyond a `GenericContainer` code snippet/example? e.g.\n    * does it neatly encapsulate a difficult problem of running the program in a container?\n    * does it add technology-specific [wait strategies](features/startup_and_waits.md)?\n    * does it enable straightforward usage of client libraries?\n\nIf the answers to the above are all yes, then a new module may be a good approach.\n\nOtherwise, it is entirely possible for you to:\n\n* publish a code snippet\n* contribute an example to the Testcontainers repo\n* publish your own third party library\n\nIn any case, please contact us to help validate your proposal!\n\n### Checklist\n\n*Suggestion: copy and paste this list into PRs for new modules.*\n\nEvery item on this list will require judgement by the Testcontainers core maintainers. Exceptions will sometimes be possible; items with `should` are more likely to be negotiable than those items with `must`.\n\n#### Default docker image\n\n- [ ] Should be a Docker Hub official image, or published by a reputable source (ideally the company or organisation that officially supports the technology)\n- [ ] Should have a verifiable open source Dockerfile and a way to view the history of changes\n- [ ] MUST show general good practices regarding container image tagging - e.g. we do not use `latest` tags, and we do not use tags that may be mutated in the future\n- [ ] MUST be legal for Testcontainers developers and Testcontainers users to pull and use. Mechanisms exist to allow EULA acceptance to be signalled, but images that can be used without a licence are greatly preferred.\n\n#### Module dependencies\n\n- [ ] The module should use as few dependencies as possible,\n- [ ] Regarding libraries, either:\n    - they should be `compileOnly` if they are likely to already be on the classpath for users' tests (e.g. client libraries or drivers)\n    - they can be `implementation` (and thus transitive dependencies) if they are very unlikely to conflict with users' dependencies.\n- [ ] If client libraries are used to test or use the module, these MUST be legal for Testcontainers developers and Testcontainers users to download and use.\n\n#### API (e.g. `MyModuleContainer` class)\n\n- [ ] Favour doing the right thing, and least surprising thing, by default\n- [ ] Ensure that method and parameter names are easy to understand. Many users will ignore documentation, so IDE-based substitutes (autocompletion and Javadocs) should be intuitive.\n- [ ] The module's public API should only handle standard JDK data types and MUST not expose data types that come from `compileOnly` dependencies. This is to reduce the risk of compatibility problems with future versions of third party libraries.\n#### Documentation\n\n- [ ] Every module MUST have a dedicated documentation page containing:\n    - [ ] A high level overview\n    - [ ] A usage example\n    - [ ] If appropriate, basic API documentation or further usage guidelines\n    - [ ] Dependency information\n    - [ ] Acknowledgements, if appropriate\n- [ ] Consider that many users will not read the documentation pages - even if the first person to add it to a project does, people reading/updating the code in the future may not. Try and avoid the need for critical knowledge that is only present in documentation.\n\n\n\n### Incubating modules\n\nWe have a policy of marking new modules as 'incubating' so that we can evaluate its maintainability and usability traits over a longer period of time.\nWe currently believe 3 months is a fair period of time, but may change this.\n\nNew modules should have the following warning at the top of their documentation pages:\n\n!!! note\n    This module is INCUBATING. While it is ready for use and operational in the current version of Testcontainers, it is possible that it may receive breaking changes in the future. See [our contributing guidelines](/contributing/#incubating-modules) for more information on our incubating modules policy.\n\nWe will evaluate incubating modules periodically, and remove the label when appropriate.\n\n\n## Combining Dependabot PRs\n\nSince we generally get a lot of Dependabot PRs, we regularly combine them into single commits.\nFor this, we are using the [gh-combine-prs](https://github.com/rnorth/gh-combine-prs) extension for [GitHub CLI](https://cli.github.com/).\n\nThe whole process is as follows:\n\n1. Check that all open Dependabot PRs did succeed their build. If they did not succeed, trigger a rerun if the cause were external factors or else document the reason if obvious.\n2. Run the extension from an up-to-date local `main` branch: `gh combine-prs --query \"author:app/dependabot\"`\n3. Merge conflicts might appear. Just ignore them, we will get those PRs in a future run.\n4. Once the build of the combined PR did succeed, temporarily enable merge commits and merge the PR using a merge commit through the GitHub UI.\n5. After the merge, disable merge commits again.\n"
  },
  {
    "path": "docs/contributing_docs.md",
    "content": "# Contributing to documentation\n\nThe Testcontainers for Java documentation is a static site built with [MkDocs](https://www.mkdocs.org/).\nWe use the [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/) theme, which offers a number of useful extensions to MkDocs.\n\nIn addition we use a [custom plugin](https://github.com/rnorth/mkdocs-codeinclude-plugin) for inclusion of code snippets.\n\nWe publish our documentation using Netlify. \n\n## Previewing rendered content\n\n### Using Docker locally\n\nThe root of the project contains a `docker-compose.yml` file. Simply run `docker-compose up` and then access the docs at [http://localhost:8000](http://localhost:8000).\n\n### Using Python locally\n\n* Ensure that you have Python 3.8.0 or higher.\n* Set up a virtualenv and run `pip install -r requirements.txt` in the `testcontainers-java` root directory.\n* Once Python dependencies have been installed, run `mkdocs serve` to start a local auto-updating MkDocs server.\n\n### PR Preview deployments\n\nNote that documentation for pull requests will automatically be published by Netlify as 'deploy previews'.\nThese deployment previews can be accessed via the `deploy/netlify` check that appears for each pull request.\n\n## Codeincludes\n\nThe Gradle project under `docs/examples` is intended to hold compilable, runnable example code that can be included as\nsnippets into the documentation at build-time.\n\nAs a result, we can have more confidence that code samples shown in the documentation is valid.\n\nWe use a custom plugin for MkDocs to include snippets into our docs.\n\nA codeinclude block will resemble a regular markdown link surrounded by a pair of XML comments, e.g.:\n\n<!-- \nTo prevent this from being rendered as a codeinclude when rendering this page, we use HTML tags.\nSee this in its rendered form to understand its actual appearance, or look at other pages in the\ndocs.\n-->\n\n<pre><code>&lt;!--codeinclude--&gt;\n[Human readable title for snippet](./relative_path_to_example_code.java) targeting_expression\n&lt;!--/codeinclude--&gt;\n</code></pre>\n\nWhere `targeting_expression` could be:\n\n* `block:someString` or\n* `inside_block:someString`\n\nIf these are provided, the macro will seek out any line containing the token `someString` and grab the next curly brace\ndelimited block that it finds. `block` will grab the starting line and closing brace, whereas `inside_block` will omit \nthese.\n\ne.g., given:\n```java\n\npublic class FooService {\n\n    public void doFoo() {\n        foo.doSomething();\n    }\n    \n    ...\n\n```\n\nIf we use `block:doFoo` as our targeting expression, we will have the following content included into our page:\n\n```java\npublic void doFoo() {\n    foo.doSomething();\n}\n```\n\nWhereas using `inside_block:doFoo` we would just have the inner content of the method included:\n\n```java\nfoo.doSomething();\n```\n\nNote that:\n\n* Any code included will have its indentation reduced\n* Every line in the source file will be searched for an instance of the token (e.g. `doFoo`). If more than one line\n  includes that token, then potentially more than one block could be targeted for inclusion. It is advisable to use a\n  specific, unique token to avoid unexpected behaviour.\n  \nWhen we wish to include a section of code that does not naturally appear within braces, we can simply insert our token,\nwith matching braces, in a comment. \nWhile a little ugly, this has the benefit of working in any context and is easy to understand. \nFor example:\n\n```java\npublic class FooService {\n\n    public void boringMethod() {\n        doSomethingBoring();\n        \n        // doFoo {\n        doTheThingThatWeActuallyWantToShow();\n        // }\n    }\n\n\n``` \n"
  },
  {
    "path": "docs/css/extra.css",
    "content": "h1, h2, h3, h4, h5, h6 {\n    font-family: 'Rubik', sans-serif;\n}\n\n[data-md-color-scheme=\"testcontainers\"] {\n    --md-primary-fg-color:       #00bac2;\n    --md-accent-fg-color:        #361E5B;\n    --md-typeset-a-color:        #0C94AA;\n    --md-primary-fg-color--dark: #291A3F;\n    --md-default-fg-color--lightest: #F2F4FE;\n    --md-footer-fg-color: #361E5B;\n    --md-footer-fg-color--light: #746C8F;\n    --md-footer-fg-color--lighter: #C3BEDE;\n    --md-footer-bg-color: #F7F9FD;\n    --md-footer-bg-color--dark: #F7F9FD;\n}\n\n.card-grid {\n    display: grid;\n    gap: 10px;\n}\n\n.tc-version {\n    font-size: 1.1em;\n    text-align: center;\n    margin: 0;\n}\n\n@media (min-width: 680px) {\n    .card-grid {\n        grid-template-columns: repeat(3, 1fr);\n    }\n}\n\nbody .card-grid-item {\n    display: flex;\n    align-items: center;\n    gap: 20px;\n    border: 1px solid #C3BEDE;\n    border-radius: 6px;\n    padding: 16px;\n    font-weight: 600;\n    color: #9991B5;\n    background: #F2F4FE;\n}\n\nbody .card-grid-item:hover,\nbody .card-grid-item:focus {\n    color: #9991B5;\n}\n\n.card-grid-item[href] {\n    color: var(--md-primary-fg-color--dark);\n    background: transparent;\n}\n\n.card-grid-item[href]:hover,\n.card-grid-item[href]:focus {\n    background: #F2F4FE;\n    color: var(--md-primary-fg-color--dark);\n}\n\n.community-callout-wrapper {\n    padding: 30px 10px 0 10px;\n}\n\n.community-callout {\n    color: #F2F4FE;\n    background: linear-gradient(10.88deg, rgba(102, 56, 242, 0.4) 9.56%, #6638F2 100%), #291A3F;\n    box-shadow: 0px 20px 45px rgba(#9991B5, 0.75);\n    border-radius: 10px;\n    padding: 20px;\n}\n\n.community-callout h2 {\n    font-size: 1.15em;\n    margin: 0 0 20px 0;\n    color: #F2F4FE;\n    text-align: center;\n}\n\n.community-callout ul {\n    list-style: none;\n    padding: 0;\n    display: flex;\n    justify-content: space-between;\n    gap: 10px;\n    margin-top: 20px;\n    margin-bottom: 0;\n}\n\n.community-callout a {\n    transition: opacity 0.2s ease;\n}\n\n.community-callout a:hover {\n    opacity: 0.5;\n}\n\n.community-callout a img {\n    height: 1.75em;\n    width: auto;\n    aspect-ratio: 1;\n}\n\n@media (min-width: 1220px) {\n    .community-callout-wrapper {\n       padding: 40px 0 0;\n    }\n\n    .community-callout h2 {\n        font-size: 1.25em;\n    }\n\n    .community-callout a img {\n        height: 2em;\n    }\n}\n\n@media (min-width: 1600px) {\n    .community-callout h2 {\n        font-size: 1.15em;\n    }\n\n    .community-callout a img {\n        height: 1.75em;\n    }\n}"
  },
  {
    "path": "docs/css/tc-header.css",
    "content": "\n:root {\n    --color-catskill: #F2F4FE;\n    --color-catskill-45: rgba(242, 244, 254, 0.45);\n    --color-mist: #E7EAFB;\n    --color-fog: #C3C7E6;\n    --color-smoke: #9991B5;\n    --color-smoke-75: rgba(153, 145, 181, 0.75);\n    --color-storm: #746C8F;\n    --color-topaz: #00BAC2;\n    --color-pacific: #17A6B2;\n    --color-teal: #027F9E;\n    --color-eggplant: #291A3F;\n    --color-plum: #361E5B;\n\n}\n\n#site-header {\n    color: var(--color-storm);\n    background: #fff;\n    font-family: 'Rubik', Arial, Helvetica, sans-serif;\n    font-size: 12px;\n    line-height: 1.5;\n    position: relative;\n    width: 100%;\n    z-index: 4;\n    display: flex;\n    align-items: center;\n    justify-content: space-between;\n    gap: 20px;\n    padding: 20px;\n}\n\nbody.tc-header-active #site-header {\n    z-index: 5;\n}\n\n#site-header .brand {\n    display: flex;\n    justify-content: space-between;\n    gap: 20px;\n    width: 100%;\n}\n\n#site-header .logo {\n    display: flex;\n}\n\n#site-header .logo img,\n#site-header .logo svg {\n    height: 30px;\n    width: auto;\n    max-width: 100%;\n}\n\n#site-header #mobile-menu-toggle {\n    background: none;\n    border: none;\n    display: flex;\n    align-items: center;\n    gap: 10px;\n    cursor: pointer;\n    color: var(--color-eggplant);\n    padding: 0;\n    margin: 0;\n    font-weight: 500;\n}\n\nbody.mobile-menu #site-header #mobile-menu-toggle {\n    color: var(--color-topaz);\n}\n\n#site-header ul {\n    list-style: none;\n    padding: 0;\n    margin: 0;\n}\n\n#site-header nav {\n    display: none;\n}\n\n#site-header .menu-item {\n    display: flex;\n}\n\n#site-header .menu-item button,\n#site-header .menu-item a {\n    min-height: 30px;\n    display: flex;\n    gap: 6px;\n    align-items: center;\n    border: none;\n    background: none;\n    cursor: pointer;\n    padding: 0;\n    font-weight: 500;\n    color: var(--color-eggplant);\n    text-decoration: none;\n    font-size: 14px;\n    transition: color 0.2s ease;\n    white-space: nowrap;\n}\n\n#site-header .menu-item .badge {\n    color: white;\n    font-size: 10px;\n    padding: 2px 6px;\n    background-color: #0FD5C6; // somehow $topaz is too dark for me.\ntext-align: center;\n    text-decoration: none;\n    display: inline-block;\n    border-radius: 6px;\n    &:hover {\n\n    }\n}\n\n#site-header .menu-item button:hover,\n#site-header .menu-item a:hover {\n    color: var(--color-topaz);\n}\n\n#site-header .menu-item button .icon-external,\n#site-header .menu-item a .icon-externa {\n    margin-left: auto;\n    opacity: .3;\n    flex-shrink: 0;\n}\n\n#site-header .menu-item button .icon-caret,\n#site-header .menu-item a .icon-caret {\n    opacity: .3;\n    height: 8px;\n}\n\n#site-header .menu-item button .icon-slack,\n#site-header .menu-item a .icon-slack,\n#site-header .menu-item button .icon-github,\n#site-header .menu-item a .icon-github {\n    height: 18px;\n}\n\n#site-header .menu-item .menu-dropdown {\n    flex-direction: column;\n}\n\nbody #site-header .menu-item .menu-dropdown {\n    display: none;\n}\n\n#site-header .menu-item.has-children.active .menu-dropdown {\n    display: flex;\n    z-index: 10;\n}\n\n#site-header .menu-dropdown-item + .menu-dropdown-item {\n    border-top: 1px solid var(--color-mist);\n}\n\n#site-header .menu-dropdown-item a {\n    display: flex;\n    gap: 10px;\n    align-items: center;\n    padding: 10px 20px;\n    font-weight: 500;\n    color: var(--color-eggplant);\n    text-decoration: none;\n    transition:\n        color 0.2s ease,\n        background 0.2s ease;\n}\n\n#site-header .menu-dropdown-item a .icon-external {\n    margin-left: auto;\n    color: var(--color-fog);\n    flex-shrink: 0;\n    opacity: 1;\n}\n\n#site-header .menu-dropdown-item a:hover {\n    background-color: var(--color-catskill-45);\n}\n\n#site-header .menu-dropdown-item a:hover .icon-external {\n    color: var(--color-topaz);\n}\n\n#site-header .menu-dropdown-item a img {\n    height: 24px;\n}\n\n.md-header {\n    background-color: var(--color-catskill);\n    color: var(--color-eggplant);\n}\n\n.md-header.md-header--shadow {\n    box-shadow: none;\n}\n\n.md-header__inner.md-grid {\n    max-width: 100%;\n    padding: 1.5px 20px;\n}\n\n[dir=ltr] .md-header__title {\n    margin: 0;\n}\n\n.md-header__topic:first-child {\n    font-size: 16px;\n    font-weight: 500;\n    font-family: 'Rubik', Arial, Helvetica, sans-serif;\n}\n\n.md-header__title.md-header__title--active .md-header__topic,\n.md-header__title[data-md-state=active] .md-header__topic {\n    opacity: 1;\n    pointer-events: all;\n    transform: translateX(0);\n    transition: none;\n    z-index: 0;\n}\n\n.md-header__topic a {\n    max-width: 100%;\n    overflow: hidden;\n    text-overflow: ellipsis;\n    transition: color .2s ease;\n}\n\n.md-header__topic a:hover {\n    color: var(--color-topaz);\n}\n\ndiv.md-header__source {\n    width: auto;\n}\n\ndiv.md-source__repository {\n    max-width: 100%;\n}\n\n.md-main {\n    padding: 0 12px;\n}\n\n@media screen and (min-width: 60em) {\n    form.md-search__form {\n        background-color: #FBFBFF;\n        color: var(--color-storm);\n    }\n\n    form.md-search__form:hover {\n        background-color: #fff;\n    }\n\n    .md-search__input + .md-search__icon {\n        color: var(--color-plum);\n    }\n\n    .md-search__input::placeholder {\n        color: var(--color-smoke);\n    }\n}\n\n@media (min-width: 500px) {\n    #site-header {\n        font-size: 16px;\n        padding: 20px 40px;\n    }\n    #site-header .logo img,\n    #site-header .logo svg {\n        height: 48px;\n    }\n\n    #site-header .menu-item button .icon-caret,\n    #site-header .menu-item a .icon-caret {\n        height: 10px;\n    }\n\n    #site-header .menu-item button .icon-slack,\n    #site-header .menu-item a .icon-slack,\n    #site-header .menu-item button .icon-github,\n    #site-header .menu-item a .icon-github {\n        height: 24px;\n    }\n\n    .md-header__inner.md-grid {\n        padding: 5px 40px;\n    }\n\n    .md-main {\n        padding: 0 32px;\n    }\n}\n\n@media (min-width: 1024px) {\n    #site-header #mobile-menu-toggle {\n        display: none;\n    }\n\n    #site-header nav {\n        display: block;\n    }\n\n    #site-header .menu {\n        display: flex;\n        justify-content: center;\n        gap: 30px;\n    }\n\n    #site-header .menu-item {\n        align-items: center;\n        position: relative;\n    }\n\n    #site-header .menu-item button,\n    #site-header .menu-item a {\n        min-height: 48px;\n        gap: 8px;\n        font-size: 16px;\n    }\n\n    #site-header .menu-item .menu-dropdown {\n        position: absolute;\n        top: 100%;\n        right: -8px;\n        border: 1px solid var(--color-mist);\n        border-radius: 6px;\n        background: #fff;\n        box-shadow: 0px 30px 35px var(--color-smoke-75);\n        min-width: 200px;\n    }\n}\n\n\n@media (max-width: 1023px) {\n    #site-header {\n        flex-direction: column;\n    }\n\n    body.mobile-tc-header-active #site-header {\n        z-index: 5;\n    }\n\n    body.mobile-menu #site-header nav {\n        display: flex;\n    }\n\n    #site-header nav {\n        position: absolute;\n        top: calc(100% - 5px);\n        width: calc(100% - 80px);\n        flex-direction: column;\n        border: 1px solid var(--color-mist);\n        border-radius: 6px;\n        background: #fff;\n        box-shadow: 0px 30px 35px var(--color-smoke-75);\n        min-width: 200px;\n    }\n\n    #site-header .menu-item {\n        flex-direction: column;\n    }\n    #site-header .menu-item + .menu-item {\n        border-top: 1px solid var(--color-mist);\n    }\n\n    #site-header .menu-item button,\n    #site-header .menu-item a {\n        padding: 10px 20px;\n    }\n\n    #site-header .menu-item.has-children.active .menu-dropdown {\n        border-top: 1px solid var(--color-mist);\n    }\n\n    #site-header .menu-dropdown-item a {\n        padding: 10px 20px 10px 30px;\n    }\n}\n\n@media (max-width: 499px) {\n    #site-header nav {\n        width: calc(100% - 40px);\n    }\n}\n"
  },
  {
    "path": "docs/error_missing_container_runtime_environment.md",
    "content": "# Fixing Issues with Discovering A Supported Container Runtime Environment\n\n\nIf you ended up on this page, \nit seems that either Testcontainers was not able to find a supported container runtime in your environment,\nor you found this page while searching for information to deal with errors regarding the environment discovery mechanism of Testcontainers.\n\nTestcontainers requires a supported container runtime environment to be present in order to manage and run containers.\nHere is a list of supported container runtime environments:\n\n* [Docker Desktop](https://www.docker.com/products/docker-desktop/)\n* [Docker Engine on Linux](https://docs.docker.com/engine/install/)\n* [Testcontainers Cloud](https://www.testcontainers.cloud?utm_medium=direct&utm_source=testcontainers.com&utm_content=docs&utm_term=on-failure)\n\nFor more extensive information on supported container runtime environments, as well as known limitations of alternative container runtime environments,\nplease refer to [this page](https://java.testcontainers.org/supported_docker_environment/) in our documentation.\n"
  },
  {
    "path": "docs/examples/junit4/generic/build.gradle",
    "content": "description = \"Examples for docs\"\n\ndependencies {\n    testImplementation \"junit:junit:4.13.2\"\n    testImplementation project(\":testcontainers\")\n    testImplementation project(\":testcontainers-selenium\")\n    testImplementation project(\":testcontainers-mysql\")\n\n    testRuntimeOnly 'com.mysql:mysql-connector-j:8.2.0'\n    testImplementation \"org.seleniumhq.selenium:selenium-api:4.35.0\"\n    testImplementation 'org.assertj:assertj-core:3.27.4'\n}\n"
  },
  {
    "path": "docs/examples/junit4/generic/src/test/java/generic/CmdModifierTest.java",
    "content": "package generic;\n\nimport com.github.dockerjava.api.DockerClient;\nimport com.github.dockerjava.api.model.Info;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.testcontainers.DockerClientFactory;\nimport org.testcontainers.containers.Container;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.IOException;\nimport java.util.Objects;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\npublic class CmdModifierTest {\n\n    // hostname {\n    @Rule\n    public GenericContainer theCache = new GenericContainer<>(DockerImageName.parse(\"redis:6-alpine\"))\n        .withCreateContainerCmdModifier(cmd -> cmd.withHostName(\"the-cache\"));\n\n    // }\n\n    // spotless:off\n    // memory {\n    private long memoryInBytes = 32l * 1024l * 1024l;\n\n    private long memorySwapInBytes = 64l * 1024l * 1024l;\n\n    @Rule\n    public GenericContainer memoryLimitedRedis = new GenericContainer<>(DockerImageName.parse(\"redis:6-alpine\"))\n        .withCreateContainerCmdModifier(cmd -> {\n            cmd.getHostConfig()\n                .withMemory(memoryInBytes)\n                .withMemorySwap(memorySwapInBytes);\n        });\n\n    // }\n    // spotless:on\n\n    @Test\n    public void testHostnameModified() throws IOException, InterruptedException {\n        final Container.ExecResult execResult = theCache.execInContainer(\"hostname\");\n        assertThat(execResult.getStdout().trim()).isEqualTo(\"the-cache\");\n    }\n\n    @Test\n    public void testMemoryLimitModified() throws IOException, InterruptedException {\n        final Container.ExecResult execResult = memoryLimitedRedis.execInContainer(\"cat\", getMemoryLimitFilePath());\n        assertThat(execResult.getStdout().trim()).isEqualTo(String.valueOf(memoryInBytes));\n    }\n\n    private String getMemoryLimitFilePath() {\n        DockerClient dockerClient = DockerClientFactory.instance().client();\n        Info info = dockerClient.infoCmd().exec();\n        Object cgroupVersion = info.getRawValues().get(\"CgroupVersion\");\n        boolean cgroup2 = Objects.equals(\"2\", cgroupVersion);\n        if (cgroup2) {\n            return \"/sys/fs/cgroup/memory.max\";\n        }\n        return \"/sys/fs/cgroup/memory/memory.limit_in_bytes\";\n    }\n}\n"
  },
  {
    "path": "docs/examples/junit4/generic/src/test/java/generic/CommandsTest.java",
    "content": "package generic;\n\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.utility.DockerImageName;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\npublic class CommandsTest {\n\n    @Rule\n    // startupCommand {\n    public GenericContainer redisWithCustomPort = new GenericContainer(DockerImageName.parse(\"redis:6-alpine\"))\n        .withCommand(\"redis-server --port 7777\")\n        // }\n        .withExposedPorts(7777);\n\n    @Test\n    public void testStartupCommandOverrideApplied() {\n        assertThat(redisWithCustomPort.isRunning()).isTrue(); // good enough to check that the container started listening\n    }\n}\n"
  },
  {
    "path": "docs/examples/junit4/generic/src/test/java/generic/ContainerCreationTest.java",
    "content": "package generic;\n\nimport org.junit.ClassRule;\nimport org.junit.Test;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.utility.DockerImageName;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\npublic class ContainerCreationTest {\n\n    // spotless:off\n    // simple {\n    public static final DockerImageName REDIS_IMAGE = DockerImageName.parse(\"redis:6-alpine\");\n\n    @ClassRule\n    public static GenericContainer<?> redis = new GenericContainer<>(REDIS_IMAGE)\n        .withExposedPorts(6379);\n\n    // }\n    // spotless:on\n\n    public static final DockerImageName ALPINE_IMAGE = DockerImageName.parse(\"alpine:3.17\");\n\n    // spotless:off\n    // withOptions {\n    // Set up a plain OS container and customize environment,\n    //   command and exposed ports. This just listens on port 80\n    //   and always returns '42'\n    @ClassRule\n    public static GenericContainer<?> alpine = new GenericContainer<>(ALPINE_IMAGE)\n        .withExposedPorts(80)\n        .withEnv(\"MAGIC_NUMBER\", \"42\")\n        .withCommand(\"/bin/sh\", \"-c\",\n            \"while true; do echo \\\"$MAGIC_NUMBER\\\" | nc -l -p 80; done\");\n\n    // }\n    // spotless:on\n\n    @Test\n    public void testStartup() {\n        assertThat(redis.isRunning()).isTrue(); // good enough to check that the container started listening\n        assertThat(alpine.isRunning()).isTrue(); // good enough to check that the container started listening\n    }\n}\n"
  },
  {
    "path": "docs/examples/junit4/generic/src/test/java/generic/ContainerLabelTest.java",
    "content": "package generic;\n\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic class ContainerLabelTest {\n\n    // single_label {\n    public GenericContainer containerWithLabel = new GenericContainer(DockerImageName.parse(\"alpine:3.17\"))\n        .withLabel(\"key\", \"value\");\n    // }\n\n    // multiple_labels {\n    private Map<String, String> mapOfLabels = new HashMap<>();\n    // populate map, e.g. mapOfLabels.put(\"key1\", \"value1\");\n\n    public GenericContainer containerWithMultipleLabels = new GenericContainer(DockerImageName.parse(\"alpine:3.17\"))\n        .withLabels(mapOfLabels);\n    // }\n}\n"
  },
  {
    "path": "docs/examples/junit4/generic/src/test/java/generic/DependsOnTest.java",
    "content": "package generic;\n\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.testcontainers.containers.GenericContainer;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\npublic class DependsOnTest {\n\n    @Rule\n    // dependsOn {\n    public GenericContainer<?> redis = new GenericContainer<>(\"redis:6-alpine\").withExposedPorts(6379);\n\n    @Rule\n    public GenericContainer<?> nginx = new GenericContainer<>(\"nginx:1.27.0-alpine3.19-slim\")\n        .dependsOn(redis)\n        .withExposedPorts(80);\n\n    // }\n\n    @Test\n    public void testContainersAllStarted() {\n        assertThat(redis.isRunning()).isTrue();\n        assertThat(nginx.isRunning()).isTrue();\n    }\n}\n"
  },
  {
    "path": "docs/examples/junit4/generic/src/test/java/generic/ExampleImageNameSubstitutor.java",
    "content": "package generic;\n\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.ImageNameSubstitutor;\n\npublic class ExampleImageNameSubstitutor extends ImageNameSubstitutor {\n\n    @Override\n    public DockerImageName apply(DockerImageName original) {\n        // convert the original name to something appropriate for\n        // our build environment\n        return DockerImageName.parse(\n            // your code goes here - silly example of capitalising\n            // the original name is shown\n            original.asCanonicalNameString().toUpperCase()\n        );\n    }\n\n    @Override\n    protected String getDescription() {\n        // used in logs\n        return \"example image name substitutor\";\n    }\n}\n"
  },
  {
    "path": "docs/examples/junit4/generic/src/test/java/generic/ExecTest.java",
    "content": "package generic;\n\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.testcontainers.containers.Container;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.IOException;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\npublic class ExecTest {\n\n    @Rule\n    public GenericContainer<?> container = new GenericContainer<>(DockerImageName.parse(\"alpine:3.17\"))\n        .withCommand(\"top\");\n\n    @Test\n    public void testSimpleExec() throws IOException, InterruptedException {\n        // standaloneExec {\n        container.execInContainer(\"touch\", \"/somefile.txt\");\n        // }\n\n        // execReadingStdout {\n        Container.ExecResult lsResult = container.execInContainer(\"ls\", \"-al\", \"/\");\n        String stdout = lsResult.getStdout();\n        int exitCode = lsResult.getExitCode();\n        assertThat(stdout).contains(\"somefile.txt\");\n        assertThat(exitCode).isZero();\n        // }\n    }\n}\n"
  },
  {
    "path": "docs/examples/junit4/generic/src/test/java/generic/HostPortExposedTest.java",
    "content": "package generic;\n\nimport com.sun.net.httpserver.HttpServer;\nimport org.junit.AfterClass;\nimport org.junit.BeforeClass;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.openqa.selenium.chrome.ChromeOptions;\nimport org.openqa.selenium.remote.RemoteWebDriver;\nimport org.testcontainers.Testcontainers;\nimport org.testcontainers.containers.BrowserWebDriverContainer;\n\nimport java.io.OutputStream;\nimport java.net.InetSocketAddress;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\npublic class HostPortExposedTest {\n\n    private static HttpServer server;\n\n    private static int localServerPort;\n\n    @BeforeClass\n    public static void setUp() throws Exception {\n        server = HttpServer.create(new InetSocketAddress(0), 0);\n        server.createContext(\n            \"/\",\n            exchange -> {\n                byte[] content = \"Hello World!\".getBytes();\n                exchange.sendResponseHeaders(200, content.length);\n                try (OutputStream responseBody = exchange.getResponseBody()) {\n                    responseBody.write(content);\n                    responseBody.flush();\n                }\n            }\n        );\n\n        server.start();\n        localServerPort = server.getAddress().getPort();\n\n        // exposePort {\n        Testcontainers.exposeHostPorts(localServerPort);\n        // }\n    }\n\n    @AfterClass\n    public static void tearDown() throws Exception {\n        server.stop(0);\n    }\n\n    @Rule\n    public BrowserWebDriverContainer<?> browser = new BrowserWebDriverContainer<>()\n        .withCapabilities(new ChromeOptions());\n\n    @Test\n    public void testContainerRunningAgainstExposedHostPort() {\n        // useHostExposedPort {\n        final String rootUrl = String.format(\"http://host.testcontainers.internal:%d/\", localServerPort);\n\n        final RemoteWebDriver webDriver = new RemoteWebDriver(this.browser.getSeleniumAddress(), new ChromeOptions());\n        webDriver.get(rootUrl);\n        // }\n\n        final String pageSource = webDriver.getPageSource();\n        assertThat(pageSource).contains(\"Hello World!\");\n    }\n}\n"
  },
  {
    "path": "docs/examples/junit4/generic/src/test/java/generic/ImageNameSubstitutionTest.java",
    "content": "package generic;\n\nimport generic.support.TestSpecificImageNameSubstitutor;\nimport org.junit.Test;\nimport org.testcontainers.containers.MySQLContainer;\nimport org.testcontainers.utility.DockerImageName;\n\npublic class ImageNameSubstitutionTest {\n\n    @Test\n    public void simpleExample() {\n        try (\n            // spotless:off\n            // directDockerHubReference {\n            // Referring directly to an image on Docker Hub (mysql:8.0.36)\n            final MySQLContainer<?> mysql = new MySQLContainer<>(\n                DockerImageName.parse(\"mysql:8.0.36\")\n            )\n            // start the container and use it for testing\n            // }\n            // spotless:on\n        ) {\n            mysql.start();\n        }\n    }\n\n    /**\n     * Note that this test uses a fake image name, which will only work because\n     * {@link TestSpecificImageNameSubstitutor} steps in to override the substitution for this exact\n     * image name.\n     */\n    @Test\n    public void substitutedExample() {\n        try (\n            // spotless:off\n            // hardcodedMirror {\n            // Referring directly to an image on a private registry - image name will vary\n            final MySQLContainer<?> mysql = new MySQLContainer<>(\n                DockerImageName.parse(\"registry.mycompany.com/mirror/mysql:8.0.36\")\n                    .asCompatibleSubstituteFor(\"mysql\")\n            )\n            // start the container and use it for testing\n            // }\n            // spotless:on\n        ) {\n            mysql.start();\n        }\n    }\n}\n"
  },
  {
    "path": "docs/examples/junit4/generic/src/test/java/generic/MultiplePortsExposedTest.java",
    "content": "package generic;\n\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.output.Slf4jLogConsumer;\nimport org.testcontainers.utility.DockerImageName;\n\npublic class MultiplePortsExposedTest {\n\n    private static final Logger log = LoggerFactory.getLogger(MultiplePortsExposedTest.class);\n\n    @Rule\n    // rule {\n    public GenericContainer<?> container = new GenericContainer<>(\n        DockerImageName.parse(\"testcontainers/helloworld:1.1.0\")\n    )\n        .withExposedPorts(8080, 8081)\n        .withLogConsumer(new Slf4jLogConsumer(log));\n\n    // }\n\n    @Test\n    public void fetchPortsByNumber() {\n        Integer firstMappedPort = container.getMappedPort(8080);\n        Integer secondMappedPort = container.getMappedPort(8081);\n    }\n\n    @Test\n    public void fetchFirstMappedPort() {\n        Integer firstMappedPort = container.getFirstMappedPort();\n    }\n\n    @Test\n    public void getHostOnly() {\n        String ipAddress = container.getHost();\n    }\n\n    @Test\n    public void getHostAndMappedPort() {\n        String address = container.getHost() + \":\" + container.getMappedPort(8080);\n    }\n}\n"
  },
  {
    "path": "docs/examples/junit4/generic/src/test/java/generic/WaitStrategiesTest.java",
    "content": "package generic;\n\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.wait.strategy.HttpWaitStrategy;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.containers.wait.strategy.WaitStrategy;\nimport org.testcontainers.utility.DockerImageName;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\npublic class WaitStrategiesTest {\n\n    @Rule\n    // waitForNetworkListening {\n    public GenericContainer nginx = new GenericContainer(DockerImageName.parse(\"nginx:1.27.0-alpine3.19-slim\")) //\n        .withExposedPorts(80);\n\n    // }\n\n    @Rule\n    // waitForSimpleHttp {\n    public GenericContainer nginxWithHttpWait = new GenericContainer(\n        DockerImageName.parse(\"nginx:1.27.0-alpine3.19-slim\")\n    )\n        .withExposedPorts(80)\n        .waitingFor(Wait.forHttp(\"/\"));\n\n    // }\n\n    @Rule\n    // logMessageWait {\n    public GenericContainer containerWithLogWait = new GenericContainer(DockerImageName.parse(\"redis:6-alpine\"))\n        .withExposedPorts(6379)\n        .waitingFor(Wait.forLogMessage(\".*Ready to accept connections.*\\\\n\", 1));\n\n    // }\n\n    private static final HttpWaitStrategy MULTI_CODE_HTTP_WAIT =\n        // spotless:off\n        // waitForHttpWithMultipleStatusCodes {\n        Wait.forHttp(\"/\")\n            .forStatusCode(200)\n            .forStatusCode(301);\n        // }\n        // spotless:on\n\n    private static final HttpWaitStrategy PREDICATE_HTTP_WAIT =\n        // spotless:off\n        // waitForHttpWithStatusCodePredicate {\n        Wait.forHttp(\"/all\")\n            .forStatusCodeMatching(it -> it >= 200 && it < 300 || it == 401);\n        // }\n        // spotless:on\n\n    private static final HttpWaitStrategy TLS_HTTP_WAIT =\n        // spotless:off\n        // waitForHttpWithTls {\n        Wait.forHttp(\"/all\")\n            .usingTls();\n        // }\n        // spotless:on\n\n    private static final WaitStrategy HEALTHCHECK_WAIT =\n        // spotless:off\n        // healthcheckWait {\n        Wait.forHealthcheck();\n        // }\n        // spotless:on\n\n    @Test\n    public void testContainersAllStarted() {\n        assertThat(nginx.isRunning()).isTrue();\n        assertThat(nginxWithHttpWait.isRunning()).isTrue();\n        assertThat(containerWithLogWait.isRunning()).isTrue();\n    }\n}\n"
  },
  {
    "path": "docs/examples/junit4/generic/src/test/java/generic/support/TestSpecificImageNameSubstitutor.java",
    "content": "package generic.support;\n\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.ImageNameSubstitutor;\n\n/**\n * An {@link ImageNameSubstitutor} which makes it possible to use fake image names in\n * {@link generic.ImageNameSubstitutionTest}. This implementation simply reverses a fake image name when presented, and\n * is hardcoded to act upon the specific fake name in that test.\n */\npublic class TestSpecificImageNameSubstitutor extends ImageNameSubstitutor {\n\n    @Override\n    public DockerImageName apply(final DockerImageName original) {\n        if (original.equals(DockerImageName.parse(\"registry.mycompany.com/mirror/mysql:8.0.36\"))) {\n            return DockerImageName.parse(\"mysql:8.0.36\");\n        } else {\n            return original;\n        }\n    }\n\n    @Override\n    protected String getDescription() {\n        return TestSpecificImageNameSubstitutor.class.getSimpleName();\n    }\n}\n"
  },
  {
    "path": "docs/examples/junit4/generic/src/test/java/org/testcontainers/containers/startupcheck/StartupCheckStrategyTest.java",
    "content": "package org.testcontainers.containers.startupcheck;\n\nimport lombok.SneakyThrows;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.Suite;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.output.OutputFrame;\nimport org.testcontainers.containers.output.WaitingConsumer;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.time.Duration;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n@RunWith(Suite.class)\n@Suite.SuiteClasses(\n    {\n        StartupCheckStrategyTest.OneShotStrategyTest.class,\n        StartupCheckStrategyTest.IndefiniteOneShotStrategyTest.class,\n        StartupCheckStrategyTest.MinimumDurationStrategyTest.class,\n    }\n)\npublic class StartupCheckStrategyTest {\n\n    private static final String HELLO_TESTCONTAINERS = \"Hello Testcontainers!\";\n\n    private static void waitForHello(GenericContainer container) throws TimeoutException {\n        WaitingConsumer consumer = new WaitingConsumer();\n        container.followOutput(consumer, OutputFrame.OutputType.STDOUT);\n\n        consumer.waitUntil(frame -> frame.getUtf8String().contains(HELLO_TESTCONTAINERS), 30, TimeUnit.SECONDS);\n    }\n\n    public static class OneShotStrategyTest {\n\n        @Rule\n        // spotless:off\n        // withOneShotStrategy {\n        public GenericContainer<?> bboxWithOneShot = new GenericContainer<>(DockerImageName.parse(\"busybox:1.31.1\"))\n            .withCommand(String.format(\"echo %s\", HELLO_TESTCONTAINERS))\n            .withStartupCheckStrategy(\n                new OneShotStartupCheckStrategy().withTimeout(Duration.ofSeconds(3))\n            );\n\n        // }\n        // spotless:on\n\n        @SneakyThrows\n        @Test\n        public void testCommandIsExecuted() {\n            waitForHello(bboxWithOneShot);\n\n            assertThat(bboxWithOneShot.isRunning()).isFalse();\n        }\n    }\n\n    public static class IndefiniteOneShotStrategyTest {\n\n        @Rule\n        // spotless:off\n        // withIndefiniteOneShotStrategy {\n        public GenericContainer<?> bboxWithIndefiniteOneShot = new GenericContainer<>(\n            DockerImageName.parse(\"busybox:1.31.1\")\n        )\n            .withCommand(\"sh\", \"-c\", String.format(\"sleep 5 && echo \\\"%s\\\"\", HELLO_TESTCONTAINERS))\n            .withStartupCheckStrategy(\n                new IndefiniteWaitOneShotStartupCheckStrategy()\n            );\n\n        // }\n        // spotless:on\n\n        @SneakyThrows\n        @Test\n        public void testCommandIsExecuted() {\n            waitForHello(bboxWithIndefiniteOneShot);\n\n            assertThat(bboxWithIndefiniteOneShot.isRunning()).isFalse();\n        }\n    }\n\n    public static class MinimumDurationStrategyTest {\n\n        @Rule\n        // spotless:off\n        // withMinimumDurationStrategy {\n        public GenericContainer<?> bboxWithMinimumDuration = new GenericContainer<>(\n            DockerImageName.parse(\"busybox:1.31.1\")\n        )\n            .withCommand(\"sh\", \"-c\", String.format(\"sleep 5 && echo \\\"%s\\\"\", HELLO_TESTCONTAINERS))\n            .withStartupCheckStrategy(\n                new MinimumDurationRunningStartupCheckStrategy(Duration.ofSeconds(1))\n            );\n\n        // }\n        // spotless:on\n\n        @SneakyThrows\n        @Test\n        public void testCommandIsExecuted() {\n            assertThat(bboxWithMinimumDuration.isRunning()).isTrue();\n\n            waitForHello(bboxWithMinimumDuration);\n        }\n    }\n}\n"
  },
  {
    "path": "docs/examples/junit4/generic/src/test/resources/logback-test.xml",
    "content": "<configuration>\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"info\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n    <logger name=\"com.github.dockerjava\" level=\"WARN\"/>\n</configuration>\n"
  },
  {
    "path": "docs/examples/junit4/generic/src/test/resources/testcontainers.properties",
    "content": "image.substitutor=generic.support.TestSpecificImageNameSubstitutor\n"
  },
  {
    "path": "docs/examples/junit4/redis/build.gradle",
    "content": "description = \"Examples for docs\"\n\ndependencies {\n    api \"io.lettuce:lettuce-core:6.8.0.RELEASE\"\n\n    testImplementation \"junit:junit:4.13.2\"\n    testImplementation project(\":testcontainers\")\n    testImplementation 'org.assertj:assertj-core:3.27.4'\n}\n"
  },
  {
    "path": "docs/examples/junit4/redis/src/main/java/quickstart/RedisBackedCache.java",
    "content": "package quickstart;\n\nimport io.lettuce.core.RedisClient;\nimport io.lettuce.core.api.StatefulRedisConnection;\n\npublic class RedisBackedCache {\n\n    private final StatefulRedisConnection<String, String> connection;\n\n    public RedisBackedCache(String hostname, Integer port) {\n        RedisClient client = RedisClient.create(String.format(\"redis://%s:%d/0\", hostname, port));\n        connection = client.connect();\n    }\n\n    public String get(String key) {\n        return connection.sync().get(key);\n    }\n\n    public void put(String key, String value) {\n        connection.sync().set(key, value);\n    }\n}\n"
  },
  {
    "path": "docs/examples/junit4/redis/src/test/java/quickstart/RedisBackedCacheIntTest.java",
    "content": "package quickstart;\n\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.utility.DockerImageName;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\npublic class RedisBackedCacheIntTest {\n\n    private RedisBackedCache underTest;\n\n    // rule {\n    @Rule\n    public GenericContainer redis = new GenericContainer(DockerImageName.parse(\"redis:6-alpine\"))\n        .withExposedPorts(6379);\n\n    // }\n\n    @Before\n    public void setUp() {\n        String address = redis.getHost();\n        Integer port = redis.getFirstMappedPort();\n\n        // Now we have an address and port for Redis, no matter where it is running\n        underTest = new RedisBackedCache(address, port);\n    }\n\n    @Test\n    public void testSimplePutAndGet() {\n        underTest.put(\"test\", \"example\");\n\n        String retrieved = underTest.get(\"test\");\n        assertThat(retrieved).isEqualTo(\"example\");\n    }\n}\n"
  },
  {
    "path": "docs/examples/junit4/redis/src/test/java/quickstart/RedisBackedCacheIntTestStep0.java",
    "content": "package quickstart;\n\nimport org.junit.Before;\nimport org.junit.Ignore;\nimport org.junit.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n@Ignore(\"This test class is deliberately invalid, as it relies on a non-existent local Redis\")\npublic class RedisBackedCacheIntTestStep0 {\n\n    private RedisBackedCache underTest;\n\n    @Before\n    public void setUp() {\n        // Assume that we have Redis running locally?\n        underTest = new RedisBackedCache(\"localhost\", 6379);\n    }\n\n    @Test\n    public void testSimplePutAndGet() {\n        underTest.put(\"test\", \"example\");\n\n        String retrieved = underTest.get(\"test\");\n        assertThat(retrieved).isEqualTo(\"example\");\n    }\n}\n"
  },
  {
    "path": "docs/examples/junit4/redis/src/test/resources/logback-test.xml",
    "content": "<configuration>\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n</configuration>\n"
  },
  {
    "path": "docs/examples/junit5/redis/build.gradle",
    "content": "description = \"Examples for docs\"\n\ndependencies {\n    api \"io.lettuce:lettuce-core:6.8.0.RELEASE\"\n\n    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.13.4'\n    testImplementation 'org.junit.jupiter:junit-jupiter-params:5.13.4'\n    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.13.4'\n    testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.10.3'\n    testImplementation project(\":testcontainers\")\n    testImplementation project(\":testcontainers-junit-jupiter\")\n    testImplementation 'org.assertj:assertj-core:3.27.4'\n}\n\ntest {\n    useJUnitPlatform()\n}\n"
  },
  {
    "path": "docs/examples/junit5/redis/src/main/java/quickstart/RedisBackedCache.java",
    "content": "package quickstart;\n\nimport io.lettuce.core.RedisClient;\nimport io.lettuce.core.api.StatefulRedisConnection;\n\npublic class RedisBackedCache {\n\n    private final StatefulRedisConnection<String, String> connection;\n\n    public RedisBackedCache(String hostname, Integer port) {\n        RedisClient client = RedisClient.create(String.format(\"redis://%s:%d/0\", hostname, port));\n        connection = client.connect();\n    }\n\n    public String get(String key) {\n        return connection.sync().get(key);\n    }\n\n    public void put(String key, String value) {\n        connection.sync().set(key, value);\n    }\n}\n"
  },
  {
    "path": "docs/examples/junit5/redis/src/test/java/quickstart/RedisBackedCacheIntTest.java",
    "content": "package quickstart;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.junit.jupiter.Container;\nimport org.testcontainers.junit.jupiter.Testcontainers;\nimport org.testcontainers.utility.DockerImageName;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n// class {\n@Testcontainers\npublic class RedisBackedCacheIntTest {\n\n    private RedisBackedCache underTest;\n\n    // container {\n    @Container\n    public GenericContainer redis = new GenericContainer(DockerImageName.parse(\"redis:6-alpine\"))\n        .withExposedPorts(6379);\n\n    // }\n\n    @BeforeEach\n    public void setUp() {\n        String address = redis.getHost();\n        Integer port = redis.getFirstMappedPort();\n\n        // Now we have an address and port for Redis, no matter where it is running\n        underTest = new RedisBackedCache(address, port);\n    }\n\n    @Test\n    public void testSimplePutAndGet() {\n        underTest.put(\"test\", \"example\");\n\n        String retrieved = underTest.get(\"test\");\n        assertThat(retrieved).isEqualTo(\"example\");\n    }\n}\n// }\n"
  },
  {
    "path": "docs/examples/junit5/redis/src/test/java/quickstart/RedisBackedCacheIntTestStep0.java",
    "content": "package quickstart;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n@Disabled(\"This test class is deliberately invalid, as it relies on a non-existent local Redis\")\npublic class RedisBackedCacheIntTestStep0 {\n\n    private RedisBackedCache underTest;\n\n    @BeforeEach\n    public void setUp() {\n        // Assume that we have Redis running locally?\n        underTest = new RedisBackedCache(\"localhost\", 6379);\n    }\n\n    @Test\n    public void testSimplePutAndGet() {\n        underTest.put(\"test\", \"example\");\n\n        String retrieved = underTest.get(\"test\");\n        assertThat(retrieved).isEqualTo(\"example\");\n    }\n}\n"
  },
  {
    "path": "docs/examples/junit5/redis/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n\n    <logger name=\"org.apache.http\" level=\"WARN\"/>\n    <logger name=\"com.github.dockerjava\" level=\"WARN\"/>\n    <logger name=\"org.zeroturnaround.exec\" level=\"WARN\"/>\n    <logger name=\"io.netty\" level=\"WARN\" />\n    <logger name=\"org.testcontainers.shaded\" level=\"WARN\"/>\n\n    <turboFilter class=\"ch.qos.logback.classic.turbo.MarkerFilter\">\n        <Marker>PROFILER</Marker>\n        <OnMatch>DENY</OnMatch>\n    </turboFilter>\n</configuration>\n"
  },
  {
    "path": "docs/examples/spock/redis/build.gradle",
    "content": "plugins {\n    id 'java'\n    id 'groovy'\n}\n\ndependencies {\n    api \"io.lettuce:lettuce-core:6.8.0.RELEASE\"\n    testImplementation 'org.spockframework:spock-core:2.3-groovy-4.0'\n    testImplementation project(\":testcontainers-spock\")\n    testImplementation 'ch.qos.logback:logback-classic:1.3.15'\n}\n\ntest {\n    useJUnitPlatform()\n}\n"
  },
  {
    "path": "docs/examples/spock/redis/src/main/java/quickstart/RedisBackedCache.java",
    "content": "package quickstart;\n\nimport io.lettuce.core.RedisClient;\nimport io.lettuce.core.api.StatefulRedisConnection;\n\npublic class RedisBackedCache {\n\n    private final StatefulRedisConnection<String, String> connection;\n\n    public RedisBackedCache(String hostname, Integer port) {\n        RedisClient client = RedisClient.create(String.format(\"redis://%s:%d/0\", hostname, port));\n        connection = client.connect();\n    }\n\n    public String get(String key) {\n        return connection.sync().get(key);\n    }\n\n    public void put(String key, String value) {\n        connection.sync().set(key, value);\n    }\n}\n"
  },
  {
    "path": "docs/examples/spock/redis/src/test/groovy/quickstart/RedisBackedCacheIntTest.groovy",
    "content": "package quickstart\n\nimport org.testcontainers.containers.GenericContainer\nimport spock.lang.Specification\n\n// complete {\n@org.testcontainers.spock.Testcontainers\nclass RedisBackedCacheIntTest extends Specification {\n\n\tprivate RedisBackedCache underTest\n\n\t// init {\n\tGenericContainer redis = new GenericContainer<>(\"redis:6-alpine\")\n\t.withExposedPorts(6379)\n\t// }\n\n\tvoid setup() {\n\t\tString address = redis.host\n\t\tInteger port = redis.firstMappedPort\n\n\t\t// Now we have an address and port for Redis, no matter where it is running\n\t\tunderTest = new RedisBackedCache(address, port)\n\t}\n\n\tvoid testSimplePutAndGet() {\n\t\tsetup:\n\t\tunderTest.put(\"test\", \"example\")\n\n\t\twhen:\n\t\tString retrieved = underTest.get(\"test\")\n\n\t\tthen:\n\t\tretrieved == \"example\"\n\t}\n}\n// }\n"
  },
  {
    "path": "docs/examples/spock/redis/src/test/groovy/quickstart/RedisBackedCacheIntTestStep0.groovy",
    "content": "package quickstart\n\nimport spock.lang.Ignore\nimport spock.lang.Specification\n\n@Ignore(\"This test class is deliberately invalid, as it relies on a non-existent local Redis\")\nclass RedisBackedCacheIntTestStep0 extends Specification {\n\tprivate RedisBackedCache underTest\n\n\tvoid setup() {\n\t\t// Assume that we have Redis running locally?\n\t\tunderTest = new RedisBackedCache(\"localhost\", 6379)\n\t}\n\n\tvoid testSimplePutAndGet() {\n\t\tsetup:\n\t\tunderTest.put(\"test\", \"example\")\n\n\t\twhen:\n\t\tString retrieved = underTest.get(\"test\")\n\n\t\tthen:\n\t\tretrieved == \"example\"\n\t}\n}\n"
  },
  {
    "path": "docs/examples/spock/redis/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"DEBUG\"/>\n</configuration>\n"
  },
  {
    "path": "docs/examples.md",
    "content": "# Examples\n\nExamples of different use cases provided by Testcontainers can be found below:\n\n- [Hazelcast](https://github.com/testcontainers/testcontainers-java/tree/main/examples/hazelcast)\n- [Kafka Cluster with multiple brokers](https://github.com/testcontainers/testcontainers-java/tree/main/examples/kafka-cluster)\n- [Neo4j](https://github.com/testcontainers/testcontainers-java/tree/main/examples/neo4j-container)\n- [Redis](https://github.com/testcontainers/testcontainers-java/tree/main/examples/redis-backed-cache)\n- [Selenium](https://github.com/testcontainers/testcontainers-java/tree/main/examples/selenium-container)\n- [Selenium Module with Cucumber](https://github.com/testcontainers/testcontainers-java/tree/main/examples/cucumber)\n- [Singleton Container Pattern](https://github.com/testcontainers/testcontainers-java/tree/main/examples/singleton-container)\n- [Solr](https://github.com/testcontainers/testcontainers-java/tree/main/examples/solr-container)\n- [Spring Boot](https://github.com/testcontainers/testcontainers-java/tree/main/examples/spring-boot)\n- [Spring Boot with Kotlin](https://github.com/testcontainers/testcontainers-java/tree/main/examples/spring-boot-kotlin-redis)\n- [TestNG](https://github.com/testcontainers/testcontainers-java/tree/main/examples/redis-backed-cache-testng)\n- [ImmuDb](https://github.com/testcontainers/testcontainers-java/tree/main/examples/immudb)\n- [Zookeeper](https://github.com/testcontainers/testcontainers-java/tree/main/examples/zookeeper)\n- [NATS](https://github.com/testcontainers/testcontainers-java/tree/main/examples/nats)\n- [SFTP](https://github.com/testcontainers/testcontainers-java/tree/main/examples/sftp)\n"
  },
  {
    "path": "docs/features/advanced_options.md",
    "content": "# Advanced options\n\n## Container labels\n\nTo add a custom label to the container, use `withLabel`:\n\n<!--codeinclude-->\n[Adding a single label](../examples/junit4/generic/src/test/java/generic/ContainerLabelTest.java) inside_block:single_label\n<!--/codeinclude-->\n\nAdditionally, multiple labels may be applied together from a map:\n\n<!--codeinclude-->\n[Adding multiple labels](../examples/junit4/generic/src/test/java/generic/ContainerLabelTest.java) inside_block:multiple_labels\n<!--/codeinclude-->\n\n## Image Pull Policy\n\nBy default, the container image is retrieved from the local Docker images cache.\nThis works well when running against a specific version, but for images with a static tag (i.e. 'latest') this may lead to a newer version not being pulled.\n\nIt is possible to specify an Image Pull Policy to determine at runtime whether an image should be pulled or not:\n\n<!--codeinclude-->\n[Setting image pull policy](../../core/src/test/java/org/testcontainers/images/ImagePullPolicyTest.java) inside_block:built_in_image_pull_policy\n<!--/codeinclude-->\n\n... or providing a function:\n\n<!--codeinclude-->\n[Custom image pull policy](../../core/src/test/java/org/testcontainers/images/ImagePullPolicyTest.java) inside_block:custom_image_pull_policy\n<!--/codeinclude-->\n\nYou can also configure Testcontainers to use your custom implementation by using `pull.policy`\n\n=== \"`src/test/resources/testcontainers.properties`\"\n    ```text\n    pull.policy=com.mycompany.testcontainers.ExampleImagePullPolicy\n    ```\n\nYou can also use the provided implementation to always pull images\n\n=== \"`src/test/resources/testcontainers.properties`\"\n    ```text\n    pull.policy=org.testcontainers.images.AlwaysPullPolicy\n    ```\n\n\nPlease see [the documentation on configuration mechanisms](./configuration.md) for more information.\n\n## Customizing the container\n\n### Using docker-java\n\nIt is possible to use the [`docker-java`](https://github.com/docker-java/docker-java) API directly to customize containers before creation. This is useful if there is a need to use advanced Docker features that are not exposed by the Testcontainers API. Any customizations you make using `withCreateContainerCmdModifier` will be applied _on top_ of the container definition that Testcontainers creates, but before it is created.\n\nFor example, this can be used to change the container hostname:\n\n<!--codeinclude-->\n[Using modifier to change hostname](../examples/junit4/generic/src/test/java/generic/CmdModifierTest.java) inside_block:hostname\n<!--/codeinclude-->\n\n... or modify container memory (see [this](https://fabiokung.com/2014/03/13/memory-inside-linux-containers/) if it does not appear to work):\n\n<!--codeinclude-->\n[Using modifier to change memory limits](../examples/junit4/generic/src/test/java/generic/CmdModifierTest.java) inside_block:memory\n<!--/codeinclude-->\n\n!!! note\n    It is recommended to use this sparingly, and follow changes to the `docker-java` API if you choose to use this. \n    It is typically quite stable, though.\n\nFor what is possible, consult the [`docker-java CreateContainerCmd` source code](https://github.com/docker-java/docker-java/blob/3.2.1/docker-java-api/src/main/java/com/github/dockerjava/api/command/CreateContainerCmd.java).\n\n### Using CreateContainerCmdModifier\n\nTestcontainers provides a `CreateContainerCmdModifier` to customize [`docker-java CreateContainerCmd`](https://github.com/docker-java/docker-java/blob/3.2.1/docker-java-api/src/main/java/com/github/dockerjava/api/command/CreateContainerCmd.java)\nvia Service Provider Interface (SPI) mechanism.\n\n<!--codeinclude-->\n[CreateContainerCmd example implementation](../../core/src/test/java/org/testcontainers/custom/TestCreateContainerCmdModifier.java)\n<!--/codeinclude-->\n\nThe previous implementation should be registered in `META-INF/services/org.testcontainers.core.CreateContainerCmdModifier` file.\n\n!!! warning\n    `CreateContainerCmdModifier` implementation will apply to all containers created by Testcontainers.\n\n## Parallel Container Startup\n\nUsually, containers are started sequentially when more than one container is used.\nUsing `Startables.deepStart(container1, container2, ...).join()` will start all containers in parallel. \nThis can be advantageous to reduce the impact of the container startup overhead.\n"
  },
  {
    "path": "docs/features/commands.md",
    "content": "# Executing commands\n\n## Container startup command\n\nBy default the container will execute whatever command is specified in the image's Dockerfile. To override this, and specify a different command, use `withCommand`. For example:\n\n<!--codeinclude-->\n[Specifying a startup command](../examples/junit4/generic/src/test/java/generic/CommandsTest.java) inside_block:startupCommand\n<!--/codeinclude-->\n\n## Executing a command\n\nYour test can execute a command inside a running container, similar to a `docker exec` call:\n\n<!--codeinclude-->\n[Executing a command inside a running container](../examples/junit4/generic/src/test/java/generic/ExecTest.java) inside_block:standaloneExec\n<!--/codeinclude-->\n\nThis can be useful for software that has a command line administration tool. You can also get the output (stdout/stderr) and exit code from the command - for example:\n\n<!--codeinclude-->\n[Executing a command inside a running container and reading the result](../examples/junit4/generic/src/test/java/generic/ExecTest.java) inside_block:execReadingStdout\n<!--/codeinclude-->\n\n## Environment variables\n\nTo add environment variables to the container, use `withEnv`:\n```java\nnew GenericContainer(...)\n\t\t.withEnv(\"API_TOKEN\", \"foo\")\n```\n"
  },
  {
    "path": "docs/features/configuration.md",
    "content": "# Custom configuration\n\nYou can override some default properties if your environment requires that.\n\n## Configuration locations\nThe configuration will be loaded from multiple locations. Properties are considered in the following order:\n\n1. Environment variables\n2. `.testcontainers.properties` in user's home folder. Example locations:  \n**Linux:** `/home/myuser/.testcontainers.properties`  \n**Windows:** `C:/Users/myuser/.testcontainers.properties`  \n**macOS:** `/Users/myuser/.testcontainers.properties`\n3. `testcontainers.properties` on the classpath.\n\nNote that when using environment variables, configuration property names should be set in upper \ncase with underscore separators, preceded by `TESTCONTAINERS_` - e.g. `checks.disable` becomes \n`TESTCONTAINERS_CHECKS_DISABLE`.\n\nThe classpath `testcontainers.properties` file may exist within the local codebase (e.g. within the `src/test/resources` directory) or within library dependencies that you may have. \nAny such configuration files will have their contents merged.\nIf any keys conflict, the value will be taken on the basis of the first value found in:\n\n* 'local' classpath (i.e. where the URL of the file on the classpath begins with `file:`), then\n* other classpath locations (i.e. JAR files) - considered in _alphabetical order of path_  to provide deterministic ordering.\n\n## Disabling the startup checks\n> **checks.disable = [true|false]**\n\nBefore running any containers Testcontainers will perform a set of startup checks to ensure that your environment is configured correctly. Usually they look like this:\n```\n        ℹ︎ Checking the system...\n        ✔ Docker version should be at least 1.6.0\n        ✔ File should be mountable\n        ✔ A port exposed by a docker container should be accessible\n```\nIt takes a couple of seconds, but if you want to speed up your tests, you can disable the checks once you have everything configured. Add `checks.disable=true` to your `$HOME/.testcontainers.properties` to completely disable them.\n\n## Customizing images\n\n!!! note\n    This approach is discouraged and deprecated, but is documented for completeness.\n    Overriding individual image names via configuration may be removed in 2021.\n    See [Image Name Substitution](./image_name_substitution.md) for other strategies for substituting image names to pull from other registries.\n\n\nTestcontainers uses public Docker images to perform different actions like startup checks, VNC recording and others. \nSome companies disallow the usage of Docker Hub, but you can override `*.image` properties with your own images from your private registry to workaround that.\n\n> **ryuk.container.image = testcontainers/ryuk:0.3.3**\n> Performs fail-safe cleanup of containers, and always required (unless [Ryuk is disabled](#disabling-ryuk))\n\n> **tinyimage.container.image = alpine:3.17**  \n> Used to check whether images can be pulled at startup, and always required (unless [startup checks are disabled](#disabling-the-startup-checks))\n\n> **sshd.container.image = testcontainers/sshd:1.1.0**  \n> Required if [exposing host ports to containers](./networking.md#exposing-host-ports-to-the-container)\n\n> **vncrecorder.container.image = testcontainers/vnc-recorder:1.3.0**\n> Used by VNC recorder in Testcontainers' Selenium integration\n\n> **socat.container.image = alpine/socat**  \n> **compose.container.image = docker/compose:1.8.0**  \n> Required if using [Docker Compose](../modules/docker_compose.md)\n\n> **kafka.container.image = confluentinc/cp-kafka**  \n> Used by KafkaContainer \n\n> **localstack.container.image = localstack/localstack**  \n> Used by LocalStack\n\n> **pulsar.container.image = apachepulsar/pulsar:2.2.0**  \n> Used by Apache Pulsar\n\n## Customizing Ryuk resource reaper\n\n> **ryuk.container.image = testcontainers/ryuk:0.3.3**\n> The resource reaper is responsible for container removal and automatic cleanup of dead containers at JVM shutdown\n\n> **ryuk.container.privileged = true**\n> In some environments ryuk must be started in privileged mode to work properly (--privileged flag)\n\n### Disabling Ryuk\nRyuk must be started as a privileged container.  \nIf your environment already implements automatic cleanup of containers after the execution,\nbut does not allow starting privileged containers, you can turn off the Ryuk container by setting\n`TESTCONTAINERS_RYUK_DISABLED` **environment variable** to `true`.\n\n!!!tip\n    Note that Testcontainers will continue doing the cleanup at JVM's shutdown, unless you `kill -9` your JVM process.\n\n## Customizing image pull behaviour\n\n> **pull.timeout = 120**\n> By default Testcontainers will timeout if pull takes more than this duration (in seconds)\n\n> **pull.pause.timeout = 30**\n> By default Testcontainers will abort the pull of an image if the pull appears stalled (no data transferred) for longer than this duration (in seconds).\n\n## Customizing client ping behaviour\n\n> **client.ping.timeout = 10**\n> Specifies for how long Testcontainers will try to connect to the Docker client to obtain valid info about the client before giving up and trying next strategy, if applicable (in seconds).\n\n## Customizing Docker host detection\n\nTestcontainers will attempt to detect the Docker environment and configure everything to work automatically.\n\nHowever, sometimes customization is required. Testcontainers will respect the following **environment variables**:\n\n> **DOCKER_HOST** = unix:///var/run/docker.sock  \n> See [Docker environment variables](https://docs.docker.com/engine/reference/commandline/cli/#environment-variables)\n>\n> **TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE**  \n> Path to Docker's socket. Used by Ryuk, Docker Compose, and a few other containers that need to perform Docker actions.  \n> Example: `/var/run/docker-alt.sock`\n> \n> **TESTCONTAINERS_HOST_OVERRIDE**  \n> Docker's host on which ports are exposed.  \n> Example: `docker.svc.local`\n\nFor advanced users, the Docker host connection can be configured **via configuration** in `~/.testcontainers.properties`.\nNote that these settings require use of the `EnvironmentAndSystemPropertyClientProviderStrategy`. The example below \nillustrates usage:\n\n```properties\ndocker.client.strategy=org.testcontainers.dockerclient.EnvironmentAndSystemPropertyClientProviderStrategy\ndocker.host=tcp\\://my.docker.host\\:1234     # Equivalent to the DOCKER_HOST environment variable. Colons should be escaped.\ndocker.tls.verify=1                         # Equivalent to the DOCKER_TLS_VERIFY environment variable\ndocker.cert.path=/some/path                 # Equivalent to the DOCKER_CERT_PATH environment variable\n```\nIn addition, you can deactivate this behaviour by specifying:\n```properties\ndockerconfig.source=autoIgnoringUserProperties # 'auto' by default\n```\n"
  },
  {
    "path": "docs/features/container_logs.md",
    "content": "# Accessing container logs\n\nIt is possible to capture container output using:\n \n * the `getLogs()` method, which simply returns a `String` snapshot of a container's entire log output\n * the `followOutput()` method. This method accepts a Consumer and (optionally)\na varargs list stating which of STDOUT, STDERR, or both, should be followed. If not specified, both will be followed.\n\nAt present, container output will always begin from the time of container creation.\n\n## Reading all logs (from startup time to present)\n\n`getLogs()` is the simplest mechanism for accessing container logs, and can be used as follows:\n\n<!--codeinclude--> \n[Accessing all output (stdout and stderr)](../../core/src/test/java/org/testcontainers/containers/output/ContainerLogsTest.java) inside_block:docsGetAllLogs\n<!--/codeinclude-->\n\n<!--codeinclude--> \n[Accessing just stdout](../../core/src/test/java/org/testcontainers/containers/output/ContainerLogsTest.java) inside_block:docsGetStdOut\n<!--/codeinclude-->\n\n<!--codeinclude--> \n[Accessing just stderr](../../core/src/test/java/org/testcontainers/containers/output/ContainerLogsTest.java) inside_block:docsGetStdErr\n<!--/codeinclude-->\n\n## Streaming logs\n\nTestcontainers includes some out-of-the-box Consumer implementations that can be used with the streaming `followOutput()` model; examples follow.\n\n### Streaming container output to an SLF4J logger\n\nGiven an existing SLF4J logger instance named LOGGER:\n```java\nSlf4jLogConsumer logConsumer = new Slf4jLogConsumer(LOGGER);\ncontainer.followOutput(logConsumer);\n```\n\nBy default both standard out and standard error will both be emitted at INFO level. \nStandard error may be emitted at ERROR level, if desired:\n\n```java\nSlf4jLogConsumer logConsumer = new Slf4jLogConsumer(LOGGER).withSeparateOutputStreams();\n```\n\nThe [Mapped Diagnostic Context (MDC)](http://logback.qos.ch/manual/mdc.html) for emitted messages may be configured using the `withMdc(...)` option:\n\n```java\nSlf4jLogConsumer logConsumer = new Slf4jLogConsumer(LOGGER).withMdc(\"key\", \"value\");\n```\n\nor using an existing map of key-value pairs:\n\n```java\nSlf4jLogConsumer logConsumer = new Slf4jLogConsumer(LOGGER).withMdc(map);\n```\n\n### Capturing container output as a String\n\nTo stream logs live or customize the decoding, `ToStringConsumer` may be used:\n\n```java\nToStringConsumer toStringConsumer = new ToStringConsumer();\ncontainer.followOutput(toStringConsumer, OutputType.STDOUT);\n\nString utf8String = toStringConsumer.toUtf8String();\n\n// Or if the container output is not UTF-8\nString otherString = toStringConsumer.toString(CharSet.forName(\"ISO-8859-1\"));\n```\n\n### Waiting for container output to contain expected content\n\n`WaitingConsumer` will block until a frame of container output (usually a line) matches a provided predicate.\n\nA timeout may be specified, as shown in this example.\n```java\nWaitingConsumer consumer = new WaitingConsumer();\n\ncontainer.followOutput(consumer, STDOUT);\n\nconsumer.waitUntil(frame -> \n    frame.getUtf8String().contains(\"STARTED\"), 30, TimeUnit.SECONDS);\n```\n\nAdditionally, as the Java 8 Consumer functional interface is used, Consumers may be composed together. This is\nuseful, for example, to capture all the container output but only when a matching string has been found. e.g.:\n```java\nWaitingConsumer waitingConsumer = new WaitingConsumer();\nToStringConsumer toStringConsumer = new ToStringConsumer();\n\nConsumer<OutputFrame> composedConsumer = toStringConsumer.andThen(waitingConsumer);\ncontainer.followOutput(composedConsumer);\n\nwaitingConsumer.waitUntil(frame -> \n    frame.getUtf8String().contains(\"STARTED\"), 30, TimeUnit.SECONDS);\n\nString utf8String = toStringConsumer.toUtf8String();\n```\n"
  },
  {
    "path": "docs/features/creating_container.md",
    "content": "# Creating a container\n\n## Creating a generic container based on an image\n\nTestcontainers' generic container support offers the most flexibility, and makes it easy to use virtually any container\nimages as temporary test dependencies. For example, if you might use it to test interactions with:\n\n* NoSQL databases or other data stores (e.g. redis, elasticsearch, mongo)\n* Web servers/proxies (e.g. nginx, apache)\n* Log services (e.g. logstash, kibana)\n* Other services developed by your team/organization which are already dockerized\n\nWith a generic container, you set the container image using a parameter to the rule constructor, e.g.:\n```java\nnew GenericContainer(DockerImageName.parse(\"jboss/wildfly:9.0.1.Final\"))\n```\n\n### Specifying an image\n\nMany Container classes in Testcontainers have historically supported: \n\n* a no-args constructor - for example `new GenericContainer()` and `new ElasticsearchContainer()`. With these constructors, Testcontainers has traditionally used a default image name (including a fixed image tag/version). This has caused a conflict between the need to keep the defaults sane (i.e. up to date) and the need to avoid silently upgrading these dependencies along with new versions of Testcontainers. \n* a single string-argument constructor, which has taken either a version or an image name as a String. This has caused some ambiguity and confusion.\n\nSince v1.15.0, both of these constructor types have been deprecated, for the reasons given above.\n\nInstead, it is highly recommended that _all containers_ be constructed using a constructor that accepts a `DockerImageName` object.\nThe `DockerImageName` class is an unambiguous reference to a docker image.\n\nIt is suggested that developers treat `DockerImageName`s as you would any other potentially-constant value - consider defining a constant in your test codebase that matches the production version of the dependency you are using.\n\n### Examples\n\nA generic container rule can be used with any public docker image; for example:\n\n<!--codeinclude--> \n[Creating a Redis container (JUnit 4)](../examples/junit4/generic/src/test/java/generic/ContainerCreationTest.java) inside_block:simple\n<!--/codeinclude-->\n\nFurther options may be specified:\n\n<!--codeinclude--> \n[Creating a container with more options (JUnit 4)](../examples/junit4/generic/src/test/java/generic/ContainerCreationTest.java) inside_block:withOptions\n<!--/codeinclude-->\n\nThese containers, as `@ClassRule`s, will be started before any tests in the class run, and will be destroyed after all\ntests have run.\n"
  },
  {
    "path": "docs/features/creating_images.md",
    "content": "# Creating images on-the-fly\n\n## Overview\n\nIn situations where there is no pre-existing Docker image, Testcontainers can create a new temporary image on-the-fly\nfrom a Dockerfile. For example, when the component under test is the Docker image itself, or when an existing base\nimage needs to be customized for specific test(s).\n\nSimply pass a configured instance of `ImageFromDockerfile` as a constructor parameter to `GenericContainer`.\nTestcontainers will `docker build` a temporary container image, and will use it when creating the container.\n\n## Dockerfile from String, file or classpath resource\n\n`ImageFromDockerfile` accepts arbitrary files, strings or classpath resources to be used as files in the build context.\nAt least one of these needs to be a `Dockerfile`.\n```java\n@Rule\npublic GenericContainer dslContainer = new GenericContainer(\n    new ImageFromDockerfile()\n            .withFileFromString(\"folder/someFile.txt\", \"hello\")\n            .withFileFromClasspath(\"test.txt\", \"mappable-resource/test-resource.txt\")\n            .withFileFromClasspath(\"Dockerfile\", \"mappable-dockerfile/Dockerfile\"))\n```\n\nThe following methods may be used to provide the `Dockerfile` and any other required build context files:\n\n* `withFileFromString(buildContextPath, content)`\n* `withFileFromClasspath(buildContextPath, classpathPath)`\n* `withFileFromPath(buildContextPath, filesystemPath)`\n* `withFileFromFile(buildContextPath, filesystemFile)`\n\n!!! info\n    In older versions of Testcontainers (before 1.3.0) it was necessary to explicitly declare each file that needed to \n    be present in the Docker build context.\n    This can be replaced with the following syntax:\n    <!--codeinclude--> \n    [Passing an entire directory of files to the Dockerfile build context](../../core/src/test/java/org/testcontainers/images/builder/DockerfileBuildTest.java) inside_block:docsShowRecursiveFileInclusion\n    <!--/codeinclude-->\n    \n    Where `RESOURCE_PATH` is the path to a directory containing a `Dockerfile` and any files that it needs to refer to.\n    Doing this is equivalent to `docker build RESOURCE_PATH` on the command line.\n    \n    To mimic `docker build .`, `RESOURCE_PATH` would simply be set to `.` as well.\n\n## Dockerfile DSL\n\nIf a static Dockerfile is not sufficient (e.g. your test needs to cover many variations that are best generated\nprogrammatically), there is a DSL available to allow Dockerfiles to be defined in code. e.g.:\n```java\nnew GenericContainer(\n        new ImageFromDockerfile()\n                .withDockerfileFromBuilder(builder ->\n                        builder\n                                .from(\"alpine:3.17\")\n                                .run(\"apk add --update nginx\")\n                                .cmd(\"nginx\", \"-g\", \"daemon off;\")\n                                .build()))\n                .withExposedPorts(80);\n```\n\nSee `ParameterizedDockerfileContainerTest` for a very basic example of using this in conjunction with JUnit\nparameterized testing.\n\n## Automatic deletion\n\nTemporary container images will be automatically removed when the test JVM shuts down. If this is not desired and\nthe image should be retained between tests, pass a stable image name and `false` flag to the `ImageFromDockerfile`\nconstructor.\n\nRetaining the image between tests will use Docker's image cache to accelerate subsequent test runs.\n\nBy default the no-args constructor will use an image name of the form `testcontainers/` + random string:\n\n* `public ImageFromDockerfile()`\n* `public ImageFromDockerfile(String dockerImageName)`\n* `public ImageFromDockerfile(String dockerImageName, boolean deleteOnExit)`\n\n## Alternative Dockerfiles\n\nNormally Docker will automatically build an image from any `/Dockerfile` that it finds in the root of the build context.\nTo override this behaviour, use `.withDockerfilePath(\"./Name-Of-Other-Dockerfile\")`.\n\n## Build Args\n\n[Build Args](https://docs.docker.com/engine/reference/builder/#arg) may be used to allow lightweight parameterization.\n\nTo specify build args, use `.withBuildArg(\"varname\", \"value\")` or provide a `Map` of args using `.withBuildArgs(map)`.\n"
  },
  {
    "path": "docs/features/files.md",
    "content": "# Files and volumes\n\n## Copying files\n\nFiles can be copied into the container before startup, or can be copied from the container after the container has started.\n\n!!! note\n    This is the recommended approach for portability cross-docker environments.\n\n### Copying to a container before startup\n\n<!--codeinclude-->\n[Copying files using MountableFile](../../core/src/test/java/org/testcontainers/junit/CopyFileToContainerTest.java) inside_block:copyToContainer\n<!--/codeinclude-->\n\nUsing `Transferable`, file content will be placed in the specified location.\n\n<!--codeinclude-->\n[Copying files using Transferable](../../core/src/test/java/org/testcontainers/containers/GenericContainerTest.java) inside_block:transferableFile\n<!--/codeinclude-->\n\nSetting file mode is also possible. \n\n<!--codeinclude-->\n[Copying files using Transferable with file mode](../../core/src/test/java/org/testcontainers/containers/GenericContainerTest.java) inside_block:transferableWithFileMode\n<!--/codeinclude-->\n\n### Copying a file from a running container\n\n<!--codeinclude-->\n[Copying files from a container](../../core/src/test/java/org/testcontainers/junit/CopyFileToContainerTest.java) inside_block:copyFileFromContainer\n<!--/codeinclude-->\n"
  },
  {
    "path": "docs/features/image_name_substitution.md",
    "content": "# Image name substitution\n\nTestcontainers supports automatic substitution of Docker image names.\n\nThis allows replacement of an image name specified in test code with an alternative name - for example, to replace the \nname of a Docker Hub image dependency with an alternative hosted on a private image registry.\n\nThis is advisable to avoid [Docker Hub rate limiting](../supported_docker_environment/image_registry_rate_limiting.md), and some companies will prefer this for policy reasons.\n\nThis page describes four approaches for image name substitution:\n\n* [Manual substitution](#manual-substitution) - not relying upon an automated approach\n* Using an Image Name Substitutor:\n    * [Developing a custom function for transforming image names on the fly](#developing-a-custom-function-for-transforming-image-names-on-the-fly)\n    * [Overriding image names individually in configuration](#overriding-image-names-individually-in-configuration)\n\nIt is assumed that you have already set up a private registry hosting [all the Docker images your build requires](../supported_docker_environment/image_registry_rate_limiting.md#which-images-are-used-by-testcontainers).\n\n\n\n\n## Manual substitution\n\nConsider this if:\n\n* You use only a few images and updating code is not a chore\n* All developers and CI machines in your organisation have access to a common registry server\n* You also use one of the automated mechanisms to substitute [the images that Testcontainers itself requires](../supported_docker_environment/image_registry_rate_limiting.md#which-images-are-used-by-testcontainers)\n\nThis approach simply entails modifying test code manually, e.g. changing:\n\nFor example, you may have a test that uses the `mysql` container image from Docker Hub:\n\n<!--codeinclude--> \n[Direct Docker Hub image name](../examples/junit4/generic/src/test/java/generic/ImageNameSubstitutionTest.java) inside_block:directDockerHubReference\n<!--/codeinclude-->\n\nto:\n\n<!--codeinclude--> \n[Private registry image name](../examples/junit4/generic/src/test/java/generic/ImageNameSubstitutionTest.java) inside_block:hardcodedMirror\n<!--/codeinclude-->\n\n\n\n\n\n## Automatically modifying Docker Hub image names\n\nTestcontainers can be configured to modify Docker Hub image names on the fly to apply a prefix string.\n\nConsider this if:\n\n* Developers and CI machines need to use different image names. For example, developers are able to pull images from Docker Hub, but CI machines need to pull from a private registry\n* Your private registry has copies of images from Docker Hub where the names are predictable, and just adding a prefix is enough. \n  For example, `registry.mycompany.com/mirror/mysql:8.0.36` can be derived from the original Docker Hub image name (`mysql:8.0.36`) with a consistent prefix string: `registry.mycompany.com/mirror/`\n\nIn this case, image name references in code are **unchanged**.\ni.e. you would leave as-is:\n\n<!--codeinclude--> \n[Unchanged direct Docker Hub image name](../examples/junit4/generic/src/test/java/generic/ImageNameSubstitutionTest.java) inside_block:directDockerHubReference\n<!--/codeinclude-->\n\nYou can then configure Testcontainers to apply the prefix `registry.mycompany.com/mirror/` to every image that it tries to pull from Docker Hub.\nThis can be done in one of two ways:\n\n* Setting environment variables `TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX=registry.mycompany.com/mirror/`\n* Via config file, setting `hub.image.name.prefix` in either:\n    * the `~/.testcontainers.properties` file in your user home directory, or\n    * a file named `testcontainers.properties` on the classpath\n    \nTestcontainers will automatically apply the prefix to every image that it pulls from Docker Hub - please verify that all [the required images](../supported_docker_environment/image_registry_rate_limiting.md#which-images-are-used-by-testcontainers) exist in your registry.\n\nTestcontainers will not apply the prefix to:\n\n* non-Hub image names (e.g. where another registry is set)\n* Docker Hub image names where the hub registry is explicitly part of the name (i.e. anything with a `docker.io` or `registry.hub.docker.com` host part)\n\n\n\n## Developing a custom function for transforming image names on the fly\n\nConsider this if:\n\n* You have complex rules about which private registry images should be used as substitutes, e.g.:\n    * non-deterministic mapping of names meaning that a [name prefix](#automatically-modifying-docker-hub-image-names) cannot be used\n    * rules depending upon developer identity or location\n* or you wish to add audit logging of images used in the build\n* or you wish to prevent accidental usage of images that are not on an approved list\n\nIn this case, image name references in code are **unchanged**.\ni.e. you would leave as-is:\n\n<!--codeinclude--> \n[Unchanged direct Docker Hub image name](../examples/junit4/generic/src/test/java/generic/ImageNameSubstitutionTest.java) inside_block:directDockerHubReference\n<!--/codeinclude-->\n\nYou can implement a custom image name substitutor by:\n\n* subclassing `org.testcontainers.utility.ImageNameSubstitutor`\n* configuring Testcontainers to use your custom implementation\n\nThe following is an example image substitutor implementation:\n\n<!--codeinclude--> \n[Example Image Substitutor](../examples/junit4/generic/src/test/java/generic/ExampleImageNameSubstitutor.java) block:ExampleImageNameSubstitutor\n<!--/codeinclude-->\n\nTestcontainers can be configured to find it at runtime via configuration.\nTo do this, create or modify a file on the classpath named `testcontainers.properties`.\n\nFor example:\n\n=== \"`src/test/resources/testcontainers.properties`\"\n    ```text\n    image.substitutor=com.mycompany.testcontainers.ExampleImageNameSubstitutor\n    ``` \n\nNote that it is also possible to provide this same configuration property:\n\n* in a `testcontainers.properties` file at the root of a library JAR file (useful if you wish to distribute a drop-in image substitutor JAR within an organization) \n* in a properties file in the user's home directory (`~/.testcontainers.properties`; note the leading `.`)\n* or as an environment variable (e.g. `TESTCONTAINERS_IMAGE_SUBSTITUTOR=com.mycompany.testcontainers.ExampleImageNameSubstitutor`).\n\nPlease see [the documentation on configuration mechanisms](./configuration.md) for more information.\n\nAlso, you can use the `ServiceLoader` mechanism to provide the fully qualified class name of the `ImageNameSubstitutor` implementation:\n\n=== \"`src/test/resources/META-INF/services/org.testcontainers.utility.ImageNameSubstitutor`\"\n    ```text\n    com.mycompany.testcontainers.ExampleImageNameSubstitutor\n    ```\n\n\n## Overriding image names individually in configuration\n\n!!! note\n    This approach is discouraged and deprecated, but is documented for completeness.\n    Please consider one of the other approaches outlined in this page instead.\n    Overriding individual image names via configuration may be removed in the future. \n\nConsider this if:\n\n* You have many references to image names in code and changing them is impractical, and\n* None of the other options are practical for you\n\nIn this case, image name references in code are left **unchanged**.\ni.e. you would leave as-is:\n\n<!--codeinclude--> \n[Unchanged direct Docker Hub image name](../examples/junit4/generic/src/test/java/generic/ImageNameSubstitutionTest.java) inside_block:directDockerHubReference\n<!--/codeinclude-->\n\nYou can force Testcontainers to substitute in a different image [using a configuration file](./configuration.md), which allows some (but not all) container names to be substituted. \n"
  },
  {
    "path": "docs/features/jib.md",
    "content": "# Using Jib\n\n[Jib](https://github.com/GoogleContainerTools/jib/tree/master/jib-core) is a library for building Docker images.\nYou can use it as an alternative to Testcontainers default `DockerfileBuilder`.\n\n<!--codeinclude-->\n[GenericContainer with JibImage](../../core/src/test/java/org/testcontainers/containers/JibTest.java) inside_block:jibContainerUsage\n<!--/codeinclude-->\n\n!!! hint\nThe Testcontainers library JAR will not automatically add a `jib-core` JAR to your project. Minimum version required is `com.google.cloud.tools:jib-core:0.22.0`.\n"
  },
  {
    "path": "docs/features/networking.md",
    "content": "# Networking and communicating with containers\n\n## Exposing container ports to the host\n\nIt is common to want to connect to a container from your test process, running on the test 'host' machine.\nFor example, you may be testing a class that needs to connect to a backend or data store container.\n\nGenerally, each required port needs to be explicitly exposed. For example, we can specify one or more ports as follows:\n\n<!--codeinclude-->\n[Exposing ports](../examples/junit4/generic/src/test/java/generic/MultiplePortsExposedTest.java) inside_block:rule\n<!--/codeinclude-->\n\nNote that this exposed port number is from the *perspective of the container*. \n\n*From the host's perspective* Testcontainers actually exposes this on a random free port.\nThis is by design, to avoid port collisions that may arise with locally running software or in between parallel test runs.\n\nBecause there is this layer of indirection, it is necessary to ask Testcontainers for the actual mapped port at runtime.\nThis can be done using the `getMappedPort` method, which takes the original (container) port as an argument:\n\n<!--codeinclude-->\n[Retrieving actual ports at runtime](../examples/junit4/generic/src/test/java/generic/MultiplePortsExposedTest.java) inside_block:fetchPortsByNumber\n<!--/codeinclude-->\n\n!!! warning\n    Because the randomised port mapping happens during container startup, the container must be running at the time `getMappedPort` is called. \n    You may need to ensure that the startup order of components in your tests caters for this.\n\nThere is also a `getFirstMappedPort` method for convenience, for the fairly common scenario of a container that only exposes one port:\n\n<!--codeinclude-->\n[Retrieving the first mapped port](../examples/junit4/generic/src/test/java/generic/MultiplePortsExposedTest.java) inside_block:fetchFirstMappedPort\n<!--/codeinclude-->\n\n## Getting the container host\n\nWhen running with a local Docker daemon, exposed ports will usually be reachable on `localhost`.\nHowever, in some CI environments they may instead be reachable on a different host.\n\nAs such, Testcontainers provides a convenience method to obtain an address on which the container should be reachable from the host machine.\n\n<!--codeinclude-->\n[Getting the container host](../examples/junit4/generic/src/test/java/generic/MultiplePortsExposedTest.java) inside_block:getHostOnly\n<!--/codeinclude-->\n\nIt is normally advisable to use `getHost` and `getMappedPort` together when constructing addresses - for example:\n\n<!--codeinclude-->\n[Getting the container host and mapped port](../examples/junit4/generic/src/test/java/generic/MultiplePortsExposedTest.java) inside_block:getHostAndMappedPort\n<!--/codeinclude-->\n\n!!! tip\n    `getHost()` is a replacement for `getContainerIpAddress()` and returns the same result.\n    `getContainerIpAddress()` is believed to be confusingly named, and will eventually be deprecated.\n\n## Exposing host ports to the container\n\nIn some cases it is necessary to make a network connection from a container to a socket that is listening on the host machine.\nNatively, Docker has limited support for this model across platforms.\nTestcontainers, however, makes this possible.\n\nIn this example, assume that `localServerPort` is a port on our test host machine where a server (e.g. a web application) is running.\n\nWe need to tell Testcontainers to prepare to expose this port to containers:\n\n<!--codeinclude-->\n[Exposing the host port](../examples/junit4/generic/src/test/java/generic/HostPortExposedTest.java) inside_block:exposePort\n<!--/codeinclude-->\n\n!!! warning\n    Note that the above command should be invoked _before_ containers are started, but _after_ the server on the host was started.  \n    Alternatively, use `container.withAccessToHost(true)` to force the host access mechanism (you still need to call `exposeHostPorts` to make the port available).\n    \nHaving done so, we can now access this port from any containers that are launched.\nFrom a container's perspective, the hostname will be `host.testcontainers.internal` and the port will be the same value as `localServerPort`.\n\nFor example, here we construct an HTTP URL for our local web application and tell a Selenium container to get a page from it:\n\n<!--codeinclude-->\n[Accessing the exposed host port from a container](../examples/junit4/generic/src/test/java/generic/HostPortExposedTest.java) inside_block:useHostExposedPort\n<!--/codeinclude-->\n\n\n## Advanced networking\n\nDocker provides the ability for you to create custom networks and place containers on one or more networks. Then, communication can occur between networked containers without the need of exposing ports through the host. With Testcontainers, you can do this as well. \n\n!!! warning\n    Note that Testcontainers currently only allows a container to be on a single network.\n\n<!--codeinclude-->\n[Creating custom networks](../../core/src/test/java/org/testcontainers/containers/NetworkTest.java) inside_block:useCustomNetwork\n<!--/codeinclude-->\n"
  },
  {
    "path": "docs/features/reuse.md",
    "content": "# Reusable Containers (Experimental)\n\n!!! warning \n    Reusable Containers is still an experimental feature and the behavior can change.\n    Those containers won't stop after all tests are finished.\n\nThe *Reusable* feature keeps the containers running and next executions with the same container configuration\nwill reuse it. To use it, start the container manually by calling `start()` method, do not call `stop()` method\ndirectly or indirectly via `try-with-resources` or `JUnit integration`, and enable it manually through an\nopt-in mechanism per environment. To reuse a container, the container configuration **must be the same**.\n\n!!! note\n    Reusable containers are not suited for CI usage and as an experimental feature\n    not all Testcontainers features are fully working (e.g., resource cleanup\n    or networking).\n\n## How to use it\n\n* Enable `Reusable Containers` \n    * through environment variable `TESTCONTAINERS_REUSE_ENABLE=true` \n    * through user property file `~/.testcontainers.properties`, by adding `testcontainers.reuse.enable=true` \n    * **not** through classpath properties file [see this comment](https://github.com/testcontainers/testcontainers-java/issues/5364#issuecomment-1125907734)\n* Define a container and subscribe to reuse the container using `withReuse(true)`\n\n```java\nGenericContainer container = new GenericContainer(\"redis:6-alpine\")\n    .withExposedPorts(6379)\n    .withReuse(true)\n```\n\n* Start the container manually by using `container.start()`\n\n### Reusable Container with Testcontainers JDBC URL\n\nIf using the [Testcontainers JDBC URL support](../../modules/databases/jdbc#database-containers-launched-via-jdbc-url-scheme)\nthe URL **must** follow the pattern of `jdbc:tc:mysql:8.0.36:///databasename?TC_REUSABLE=true`.\n`TC_REUSABLE=true` is set as a parameter of the JDBC URL.\n"
  },
  {
    "path": "docs/features/startup_and_waits.md",
    "content": "# Waiting for containers to start or be ready\n\n!!! info \"Wait strategies vs Startup strategies\"\n\n    **Wait strategy:** is the container in a state that is useful for testing. This is generally approximated as 'can we talk to this container over the network'. However, there are quite a few variations and nuances.\n    \n    **Startup strategy:** did a container reach the desired running state. *Almost always* this just means 'wait until the container is running' - for a daemon process in a container this is the goal. Sometimes we need to wait until the container reaches a running state and then exits - this is the 'one shot startup' strategy, only used for cases where we need to run a one off command in a container but not a daemon.\n\n\n## Wait Strategies\n\nOrdinarily Testcontainers will wait for up to 60 seconds for the container's first mapped network port to start listening.\n\nThis simple measure provides a basic check whether a container is ready for use.\n\n<!--codeinclude--> \n[Waiting for the first exposed port to start listening](../examples/junit4/generic/src/test/java/generic/WaitStrategiesTest.java) inside_block:waitForNetworkListening\n<!--/codeinclude-->\n\nIf the default 60s timeout is not sufficient, it can be altered with the `withStartupTimeout()` method.\n\nIf waiting for a listening TCP port is not sufficient to establish whether the container is ready, you can use the\n`waitingFor()` method with other [`WaitStrategy`](http://static.javadoc.io/org.testcontainers/testcontainers/{{ latest_version }}/org/testcontainers/containers/wait/strategy/WaitStrategy.html) implementations as shown below.\n\n### HTTP Wait strategy examples\n\nYou can choose to wait for an HTTP(S) endpoint to return a particular status code.\n\n#### Waiting for 200 OK\n<!--codeinclude--> \n[](../examples/junit4/generic/src/test/java/generic/WaitStrategiesTest.java) inside_block:waitForSimpleHttp\n<!--/codeinclude-->\n\nVariations on the HTTP wait strategy are supported, including:\n\n#### Waiting for multiple possible status codes\n<!--codeinclude--> \n[](../examples/junit4/generic/src/test/java/generic/WaitStrategiesTest.java) inside_block:waitForHttpWithMultipleStatusCodes\n<!--/codeinclude-->\n\n#### Waiting for a status code that matches a predicate\n<!--codeinclude--> \n[Waiting for a status code that matches a predicate](../examples/junit4/generic/src/test/java/generic/WaitStrategiesTest.java) inside_block:waitForHttpWithStatusCodePredicate\n<!--/codeinclude-->\n\n#### Using TLS\n<!--codeinclude--> \n[](../examples/junit4/generic/src/test/java/generic/WaitStrategiesTest.java) inside_block:waitForHttpWithTls\n<!--/codeinclude-->\n\n### Healthcheck Wait strategy examples\n\nIf the used image supports Docker's [Healthcheck](https://docs.docker.com/engine/reference/builder/#healthcheck) feature, you can directly leverage the `healthy` state of the container as your wait condition:\n\n<!--codeinclude-->\n[](../examples/junit4/generic/src/test/java/generic/WaitStrategiesTest.java) inside_block:healthcheckWait\n<!--/codeinclude-->\n\n### Log output Wait Strategy\n\nIn some situations a container's log output is a simple way to determine if it is ready or not.\nFor example, we can wait for a `Ready' message in the container's logs as follows:\n\n<!--codeinclude-->\n[](../examples/junit4/generic/src/test/java/generic/WaitStrategiesTest.java) inside_block:logMessageWait\n<!--/codeinclude-->\n\n### Other Wait Strategies\n\nFor further options, check out the [`Wait`](http://static.javadoc.io/org.testcontainers/testcontainers/{{ latest_version }}/org/testcontainers/containers/wait/strategy/Wait.html) convenience class, or the various subclasses of [`WaitStrategy`](http://static.javadoc.io/org.testcontainers/testcontainers/{{ latest_version }}/org/testcontainers/containers/wait/strategy/WaitStrategy.html). \n\nIf none of these options meet your requirements, you can create your own subclass of \n[`AbstractWaitStrategy`](http://static.javadoc.io/org.testcontainers/testcontainers/{{ latest_version }}/org/testcontainers/containers/wait/strategy/AbstractWaitStrategy.html) with an \nappropriate wait mechanism in `waitUntilReady()`. \nThe `GenericContainer.waitingFor()` method accepts any valid [`WaitStrategy`](http://static.javadoc.io/org.testcontainers/testcontainers/{{ latest_version }}/org/testcontainers/containers/wait/strategy/WaitStrategy.html).\n\n\n## Startup check Strategies\n\nOrdinarily Testcontainers will check that the container has reached the running state and has not exited.\nIn order to do that inspect is executed against the container and state parameter is extracted.\n\nAll logic is implemented in [`StartupCheckStrategy`](http://static.javadoc.io/org.testcontainers/testcontainers/{{ latest_version }}/org/testcontainers/containers/startupcheck/StartupCheckStrategy.html) child classes.\n\n### Running startup strategy example\n\nThis is the strategy used by default. Testcontainers just checks if container is running.\n\nImplemented in [`IsRunningStartupCheckStrategy`](http://static.javadoc.io/org.testcontainers/testcontainers/{{ latest_version }}/org/testcontainers/containers/startupcheck/IsRunningStartupCheckStrategy.html) class.\n\n### One shot startup strategy example\n\nThis strategy is intended for use with containers that only run briefly and exit of their own accord. As such, success is deemed to be when\nthe container has stopped with exit code 0.\n \n<!--codeinclude--> \n[Using one shot startup strategy](../examples/junit4/generic/src/test/java/org/testcontainers/containers/startupcheck/StartupCheckStrategyTest.java) inside_block:withOneShotStrategy\n<!--/codeinclude-->\n\n### Indefinite one shot startup strategy example\n\nVariant of one shot strategy that does not impose a timeout. Intended for situation such as when a long running task forms part of\n container startup.\n\nIt has to be assumed that the container will stop of its own accord, either with a success or failure exit code.\n\n<!--codeinclude--> \n[Using indefinite one shot startup strategy](../examples/junit4/generic/src/test/java/org/testcontainers/containers/startupcheck/StartupCheckStrategyTest.java) inside_block:withIndefiniteOneShotStrategy\n<!--/codeinclude-->\n\n### Minimum duration startup strategy example\n\nChecks that the container is running and has been running for a defined minimum period of time.\n\n<!--codeinclude--> \n[Using minimum duration strategy](../examples/junit4/generic/src/test/java/org/testcontainers/containers/startupcheck/StartupCheckStrategyTest.java) inside_block:withMinimumDurationStrategy\n<!--/codeinclude-->\n\n### Other startup strategies\n\nIf none of these options meet your requirements, you can create your own subclass of \n[`StartupCheckStrategy`](http://static.javadoc.io/org.testcontainers/testcontainers/{{ latest_version }}/org/testcontainers/containers\n/startupcheck/StartupCheckStrategy.html) with an appropriate startup check mechanism in `waitUntilStartupSuccessful()`.\nOr you can leave it as is and just implement the `checkStartupState(DockerClient dockerClient, String containerId)` if you still want to check state\n periodically.\n\n## Depending on another container\n\nSometimes, a container relies on another container to be ready before it should start itself. An example of this might be a database that needs to be started before your application container can link to it. You can tell a container that it depends on another container by using the `dependsOn` method:\n\n<!--codeinclude--> \n[Depending on another container](../examples/junit4/generic/src/test/java/generic/DependsOnTest.java) inside_block:dependsOn\n<!--/codeinclude-->\n"
  },
  {
    "path": "docs/getting_help.md",
    "content": "# Getting help\n\nWe hope that you find Testcontainers intuitive to use and reliable.\nHowever, sometimes things don't go the way we'd expect, and we'd like to try and help out if we can.\n\nTo contact the Testcontainers team and other users you can:\n\n* Join our [Slack team](https://slack.testcontainers.org)\n* [Search our issues tracker](https://github.com/testcontainers/testcontainers-java/issues), or raise a new issue if you find any bugs or have suggested improvements\n* [Search Stack Overflow](https://stackoverflow.com/questions/tagged/testcontainers), especially among posts tagged with `testcontainers`\n"
  },
  {
    "path": "docs/index.md",
    "content": "# Testcontainers for Java\n\n<p align=center><strong>Not using Java? Here are other supported languages!</strong></p>\n<div class=\"card-grid\">\n    <a class=\"card-grid-item\"><img src=\"language-logos/java.svg\"/>Java</a>\n    <a href=\"https://golang.testcontainers.org/\" class=\"card-grid-item\"><img src=\"language-logos/go.svg\"/>Go</a>\n    <a href=\"https://dotnet.testcontainers.org/\" class=\"card-grid-item\"><img src=\"language-logos/dotnet.svg\"/>.NET</a>\n    <a href=\"https://node.testcontainers.org/\" class=\"card-grid-item\"><img src=\"language-logos/nodejs.svg\"/>Node.js</a>\n    <a href=\"https://testcontainers-python.readthedocs.io/en/latest/\" class=\"card-grid-item\"><img src=\"language-logos/python.svg\"/>Python</a>\n    <a href=\"https://docs.rs/testcontainers/latest/testcontainers/\" class=\"card-grid-item\"><img src=\"language-logos/rust.svg\"/>Rust</a>\n    <a href=\"https://github.com/testcontainers/testcontainers-hs/\" class=\"card-grid-item\" ><img src=\"language-logos/haskell.svg\"/>Haskell</a>\n    <a href=\"https://github.com/testcontainers/testcontainers-ruby/\" class=\"card-grid-item\" ><img src=\"language-logos/ruby.svg\"/>Ruby</a>\n</div>\n\n## About Testcontainers for Java\n\n*Testcontainers for Java* is a Java library that supports JUnit tests, providing lightweight, throwaway instances of common databases, Selenium web browsers, or anything else that can run in a Docker container.\n\nTestcontainers make the following kinds of tests easier:\n\n* **Data access layer integration tests**: use a containerized instance of a MySQL, PostgreSQL or Oracle database to test your data access layer code for complete compatibility, but without requiring complex setup on developers' machines and safe in the knowledge that your tests will always start with a known DB state. Any other database type that can be containerized can also be used.\n* **Application integration tests**: for running your application in a short-lived test mode with dependencies, such as databases, message queues or web servers.\n* **UI/Acceptance tests**: use [containerized web browsers](modules/webdriver_containers.md), compatible with Selenium, for conducting automated UI tests. Each test can get a fresh instance of the browser, with no browser state, plugin variations or automated browser upgrades to worry about. And you get a video recording of each test session, or just each session where tests failed.\n* **Much more!** Check out the various contributed modules or create your own custom container classes using [`GenericContainer`](features/creating_container.md) as a base.\n\n## Prerequisites\n\n* Docker - please see [General Docker requirements](supported_docker_environment/index.md)\n* A supported JVM testing framework:\n    * [JUnit 4](test_framework_integration/junit_4.md) - See the [JUnit 4 Quickstart Guide](quickstart/junit_4_quickstart.md)\n    * [Jupiter/JUnit 5](test_framework_integration/junit_5.md)\n    * [Spock](test_framework_integration/spock.md)\n    * *Or* manually add code to control the container/test lifecycle (See [hints for this approach](test_framework_integration/junit_4.md#manually-controlling-container-lifecycle))\n\n## Maven dependencies\n\nTestcontainers is distributed as separate JARs with a common version number:\n\n* A core JAR file for core functionality, generic containers and docker-compose support\n* A separate JAR file for each of the specialised modules. Each module's documentation describes the Maven/Gradle dependency to add to your project's build.\n\nFor the core library, the latest Maven/Gradle dependency is as follows: \n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers:{{latest_version}}\"\n    ```\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n\nYou can also [check the latest version available on Maven Central](https://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.testcontainers%22).\n\n### Managing versions for multiple Testcontainers dependencies\n\nTo avoid specifying the version of each dependency, you can use a `BOM` or `Bill Of Materials`.\n\nUsing Maven you can add the following to `dependencyManagement` section in your `pom.xml`:\n=== \"Maven\"\n```xml\n<dependencyManagement>\n    <dependencies>\n        <dependency>\n            <groupId>org.testcontainers</groupId>\n            <artifactId>testcontainers-bom</artifactId>\n            <version>{{latest_version}}</version>\n            <type>pom</type>\n            <scope>import</scope>\n        </dependency>\n    </dependencies>\n</dependencyManagement>\n```\n\nand then use dependencies without specifying a version:\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-mysql</artifactId>\n        <scope>test</scope>\n    </dependency>\n    ```\n\nUsing Gradle 5.0 or higher, you can add the following to the `dependencies` section in your `build.gradle`:\n\n=== \"Gradle\"\n    ```groovy\n    implementation platform('org.testcontainers:testcontainers-bom:{{latest_version}}') //import bom\n    testImplementation('org.testcontainers:testcontainers-mysql') //no version specified\n    ```\n\n\n[JitPack](jitpack_dependencies.md) builds are available for pre-release versions.\n\n!!! warning \"Shaded dependencies\"\n    Testcontainers depends on other libraries (like docker-java) for it to work.  \n    Some of them (JUnit, docker-java-{api,transport} and its transitive dependencies, JNA, visible-assertions and others) are part of our public API.  \n    But there are also \"private\", implementation detail dependencies (e.g., docker-java-core, Guava, OkHttp, etc.) that are not exposed to public API but prone to conflicts with test code/application under test code. \n    As such, **these libraries are 'shaded' into the core Testcontainers JAR** and relocated under `org.testcontainers.shaded` to prevent class conflicts.\n\n## Sponsors\n\nA huge thank you to our sponsors:\n\n### Bronze sponsors\n\n<div style=\"text-align:center; max-width: 128px; display: inline-block; margin: 5px;\">\n    <a href=\"https://cirrus-ci.org/\">\n        <img src=\"sponsor_logos/cirrus_labs.jpg\" style=\"width: 100%\"/>\n        <p>Cirrus CI</p>\n        <!-- via fkorotkov's sponsorship -->\n    </a>\n</div>\n\n<div style=\"text-align:center; max-width: 128px; display: inline-block; margin: 5px;\">\n    <a href=\"https://vivy.com\">\n        <img src=\"sponsor_logos/vivy.png\" style=\"width: 100%\"/>\n        <p>Vivy</p>\n    </a>\n</div>\n\n<div style=\"text-align:center; max-width: 128px; display: inline-block; margin: 5px;\">\n    <a href=\"https://www.jooq.org/\">\n        <img src=\"sponsor_logos/jooq.jpg\" style=\"width: 100%\"/>\n        <p>jOOQ</p>\n    </a>\n</div>\n\n<div style=\"text-align:center; max-width: 128px; display: inline-block; margin: 5px;\">\n    <a href=\"https://www.backbase.com/\">\n        <img src=\"sponsor_logos/backbase.png\" style=\"width: 100%\"/>\n        <p>Backbase</p>\n    </a>\n</div>\n\n<div style=\"text-align:center; max-width: 128px; display: inline-block; margin: 5px;\">\n    <a href=\"https://www.elastic.co/\">\n        <img src=\"sponsor_logos/elastic.png\" style=\"width: 100%\"/>\n        <p>Elastic</p>\n    </a>\n</div>\n\n### Donors\n\n<div style=\"text-align:center; max-width: 128px; display: inline-block; margin: 5px;\">\n    <a href=\"https://www.redhat.com\">\n        <img src=\"sponsor_logos/red_hat.png\" style=\"width: 100%\"/>\n        <p>Red Hat</p>\n    </a>\n</div>\n\n<div style=\"text-align:center; max-width: 128px; display: inline-block; margin: 5px;\">\n    <a href=\"https://www.spotify.com\">\n        <img src=\"sponsor_logos/spotify.png\" style=\"width: 100%\"/>\n        <p>Spotify</p>\n    </a>\n</div>\n\n### Backers\n\n* [Philip Riecks (@rieckpil)](https://github.com/rieckpil)\n* [Karl Heinz Marbaise (@khmarbaise)](https://github.com/khmarbaise)\n* [Sascha Frinken (@sascha-frinken)](https://github.com/sascha-frinken)\n* [Christoph Dreis (@dreis2211)](https://github.com/dreis2211)\n* [Nikita Zhevnitskiy (@zhenik)](https://github.com/zhenik)\n* [Bas Stoker (@bastoker)](https://github.com/bastoker)\n* [Oleg Nenashev (@oleg-nenashev)](https://github.com/oleg-nenashev)\n* [Rik Glover (@rikglover)](https://github.com/rikglover)\n* [Amitosh Swain Mahapatra (@recrsn)](https://github.com/recrsn)\n* [Paris Apostolopoulos](https://opencollective.com/paris-apostolopoulos)\n\n## Who is using Testcontainers?\n\n* [ZeroTurnaround](https://zeroturnaround.com) - Testing of the Java Agents, micro-services, Selenium browser automation\n* [Zipkin](https://zipkin.io) - MySQL and Cassandra testing\n* [Apache Gora](https://gora.apache.org) - CouchDB testing\n* [Apache James](https://james.apache.org) - LDAP and Cassandra integration testing\n* [StreamSets](https://github.com/streamsets/datacollector) - LDAP, MySQL Vault, MongoDB, Redis integration testing\n* [Playtika](https://github.com/Playtika/testcontainers-spring-boot) - Kafka, Couchbase, MariaDB, Redis, Neo4j, Aerospike, MemSQL\n* [JetBrains](https://www.jetbrains.com/) - Testing of the TeamCity plugin for HashiCorp Vault\n* [Plumbr](https://plumbr.io) - Integration testing of data processing pipeline micro-services\n* [Streamlio](https://streaml.io/) - Integration and Chaos Testing of our fast data platform based on Apache Pulsar, Apache BookKeeper and Apache Heron.\n* [Spring Session](https://projects.spring.io/spring-session/) - Redis, PostgreSQL, MySQL and MariaDB integration testing\n* [Apache Camel](https://camel.apache.org) - Testing Camel against native services such as Consul, Etcd and so on\n* [Infinispan](https://infinispan.org) - Testing the Infinispan Server as well as integration tests with databases, LDAP and KeyCloak\n* [Instana](https://www.instana.com) - Testing agents and stream processing backends\n* [eBay Marketing](https://www.ebay.com) - Testing for MySQL, Cassandra, Redis, Couchbase, Kafka, etc.\n* [Skyscanner](https://www.skyscanner.net/) - Integration testing against HTTP service mocks and various data stores\n* [Neo4j-OGM](https://neo4j.com/developer/neo4j-ogm/) - Testing with Neo4j\n* [Spring Data Neo4j](https://github.com/spring-projects/spring-data-neo4j/) - Testing imperative and reactive implementations with Neo4j\n* [Lightbend](https://www.lightbend.com/) - Testing [Alpakka Kafka](https://doc.akka.io/docs/alpakka-kafka/current/) and support in [Alpakka Kafka Testkit](https://doc.akka.io/docs/alpakka-kafka/current/testing.html#testing-with-kafka-in-docker)\n* [Zalando SE](https://corporate.zalando.com/en) - Testing core business services\n* [Europace AG](https://tech.europace.de/) - Integration testing for databases and micro services\n* [Micronaut Data](https://github.com/micronaut-projects/micronaut-data/) - Testing of Micronaut Data JDBC, a database access toolkit\n* [Vert.x SQL Client](https://github.com/eclipse-vertx/vertx-sql-client) - Testing with PostgreSQL, MySQL, MariaDB, SQL Server, etc.\n* [JHipster](https://www.jhipster.tech/) - Couchbase and Cassandra integration testing\n* [wescale](https://www.wescale.com) - Integration testing against HTTP service mocks and various data stores\n* [Marquez](https://marquezproject.github.io/marquez) - PostgreSQL integration testing\n* [Wise (formerly TransferWise)](https://wise.com) - Integration testing for different RDBMS, kafka and micro services\n* [XWiki](https://xwiki.org) - [Testing XWiki](https://dev.xwiki.org/xwiki/bin/view/Community/Testing/DockerTesting/) under all [supported configurations](https://dev.xwiki.org/xwiki/bin/view/Community/SupportStrategy/)\n* [Apache SkyWalking](http://github.com/apache/skywalking) - End-to-end testing of the Apache SkyWalking, and plugin tests of its subproject, [Apache SkyWalking Python](http://github.com/apache/skywalking-python), and of its eco-system built by the community, like [SkyAPM NodeJS Agent](http://github.com/SkyAPM/nodejs)\n* [jOOQ](https://www.jooq.org) - Integration testing all of jOOQ with a variety of RDBMS\n* [Trino (formerly Presto SQL)](https://trino.io) - Integration testing all Trino core & connectors, including tests of multi-node deployments and security configurations.\n* Google - Various open source projects: [OpenTelemetry](https://github.com/GoogleCloudPlatform/opentelemetry-operations-java), [Universal Application Tool](https://github.com/seattle-uat/universal-application-tool), [CloudBowl](https://github.com/GoogleCloudPlatform/cloudbowl-microservice-game)\n* [Backbase](https://www.backbase.com/) - Unit, Integration and Acceptance testing for different the databases supported (Oracle, SQL Server, MySQL), the different messaging systems supported (Kafka, Rabbit, AMQ) and other microservices and HTTP mocks.\n* [CloudBees](https://www.cloudbees.com/) - Integration testing of products, including but not limited to database and AWS/Localstack integration testing.\n* [Jenkins](https://www.jenkins.io/) - Integration testing of multiple plugins and the Trilead SSH2 fork maintained by the Jenkins community\n  ([query](https://github.com/search?l=Maven+POM&q=org%3Ajenkinsci+testcontainers&type=Code)).\n* [Elastic](https://www.elastic.co) - Integration testing of the Java APM agent\n* [Alkira](https://www.alkira.com/) - Testing of multiple micro-services using Kafka, PostgreSQL, Apache Zookeeper, Etcd and so on.\n* [Togglz](https://www.togglz.org/) - Feature Flags for the Java platform\n* [Byzer](https://www.byzer.org/home) - Integration tests for Data and AI platforms are based on multiple versions of Byzer, Ray and Apache Spark.\n* [Apache SeaTunnel](https://github.com/apache/incubator-seatunnel) - Integration testing with different datasource.\n* [Bucket4j](https://github.com/bucket4j/bucket4j) - Java rate-limiting library based on the token-bucket algorithm.\n* [Spark ClickHouse Connector](https://github.com/housepower/spark-clickhouse-connector) - Integration tests for Apache Spark with both single node ClickHouse instance and multi-node ClickHouse cluster.\n* [Quarkus](https://github.com/quarkusio/quarkus) - Testcontainers is used extensively for Quarkus' [DevServices](https://quarkus.io/guides/dev-services) feature.\n* [Apache Kyuubi](https://kyuubi.apache.org) - Integration testing with Trino as data source engine, Kafka, etc.\n* [Dash0](https://www.dash0.com) - Integration testing for OpenTelemetry Observability product.\n\n\n## License\n\nSee [LICENSE](https://raw.githubusercontent.com/testcontainers/testcontainers-java/main/LICENSE).\n\n## Attributions\n\nThis project includes a modified class (ScriptUtils) taken from the Spring JDBC project, adapted under the terms of the Apache license. Copyright for that class remains with the original authors.\n\nThis project was initially inspired by a [gist](https://gist.github.com/mosheeshel/c427b43c36b256731a0b) by [Moshe Eshel](https://github.com/mosheeshel).\n\n## Copyright\n\nCopyright (c) 2015-2021 Richard North and other authors.\n\nSee [AUTHORS](https://raw.githubusercontent.com/testcontainers/testcontainers-java/main/AUTHORS) for contributors.\n"
  },
  {
    "path": "docs/jitpack_dependencies.md",
    "content": "# JitPack (unreleased versions)\n\nIf you like to live on the bleeding edge, [jitpack.io](https://jitpack.io) can be used to obtain SNAPSHOT versions.\nUse the following dependency description instead:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"com.github.testcontainers.testcontainers-java:--artifact name--:main-SNAPSHOT\"\n    ```\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>com.github.testcontainers.testcontainers-java</groupId>\n        <artifactId>--artifact name--</artifactId>\n        <version>main-SNAPSHOT</version>\n    </dependency>\n    ```\n\nA specific git revision (such as `02782d9`) can be used as a fixed version instead: \n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"com.github.testcontainers.testcontainers-java:--artifact name--:02782d9\"\n    ```\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>com.github.testcontainers.testcontainers-java</groupId>\n        <artifactId>--artifact name--</artifactId>\n        <version>02782d9</version>\n    </dependency>\n    ```\n\n\nThe JitPack maven repository must also be declared, e.g.:\n\n=== \"Gradle\"\n    ```groovy\n    repositories {\n        maven {\n            url \"https://jitpack.io\"\n        }\n    }\n    ```\n=== \"Maven\"\n    ```xml\n    <repositories>\n        <repository>\n            <id>jitpack.io</id>\n            <url>https://jitpack.io</url>\n        </repository>\n    </repositories>\n    ```\n"
  },
  {
    "path": "docs/js/tc-header.js",
    "content": "const mobileToggle = document.getElementById(\"mobile-menu-toggle\");\nconst mobileSubToggle = document.getElementById(\"mobile-submenu-toggle\");\nfunction toggleMobileMenu() {\n    document.body.classList.toggle('mobile-menu');\n    document.body.classList.toggle(\"mobile-tc-header-active\");\n}\nfunction toggleMobileSubmenu() {\n    document.body.classList.toggle('mobile-submenu');\n}\nif (mobileToggle)\n    mobileToggle.addEventListener(\"click\", toggleMobileMenu);\nif (mobileSubToggle)\n    mobileSubToggle.addEventListener(\"click\", toggleMobileSubmenu);\n\nconst allParentMenuItems = document.querySelectorAll(\"#site-header .menu-item.has-children\");\nfunction clearActiveMenuItem() {\n    document.body.classList.remove(\"tc-header-active\");\n    allParentMenuItems.forEach((item) => {\n        item.classList.remove(\"active\");\n    });\n}\nfunction setActiveMenuItem(e) {\n    clearActiveMenuItem();\n    e.currentTarget.closest(\".menu-item\").classList.add(\"active\");\n    document.body.classList.add(\"tc-header-active\");\n}\nallParentMenuItems.forEach((item) => {\n    const trigger = item.querySelector(\":scope > a, :scope > button\");\n\n    trigger.addEventListener(\"click\", (e) => {\n        if (e.currentTarget.closest(\".menu-item\").classList.contains(\"active\")) {\n            clearActiveMenuItem();\n        }  else {\n            setActiveMenuItem(e);\n        }\n    });\n\n    trigger.addEventListener(\"mouseenter\", (e) => {\n        setActiveMenuItem(e);\n    });\n\n    item.addEventListener(\"mouseleave\", (e) => {\n        clearActiveMenuItem();\n    });\n});"
  },
  {
    "path": "docs/modules/activemq.md",
    "content": "# ActiveMQ\n\nTestcontainers module for [ActiveMQ](https://hub.docker.com/r/apache/activemq-classic) and\n[Artemis](https://hub.docker.com/r/apache/activemq-artemis).\n\n## ActiveMQContainer's usage examples\n\nYou can start an ActiveMQ Classic container instance from any Java application by using:\n\n<!--codeinclude-->\n[Default ActiveMQ container](../../modules/activemq/src/test/java/org/testcontainers/activemq/ActiveMQContainerTest.java) inside_block:container\n<!--/codeinclude-->\n\nWith custom credentials:\n\n<!--codeinclude-->\n[Setting custom credentials](../../modules/activemq/src/test/java/org/testcontainers/activemq/ActiveMQContainerTest.java) inside_block:settingCredentials\n<!--/codeinclude-->\n\n## ArtemisContainer's usage examples\n\nYou can start an ActiveMQ Artemis container instance from any Java application by using:\n\n<!--codeinclude-->\n[Default Artemis container](../../modules/activemq/src/test/java/org/testcontainers/activemq/ArtemisContainerTest.java) inside_block:container\n<!--/codeinclude-->\n\nWith custom credentials:\n\n<!--codeinclude-->\n[Setting custom credentials](../../modules/activemq/src/test/java/org/testcontainers/activemq/ArtemisContainerTest.java) inside_block:settingCredentials\n<!--/codeinclude-->\n\nWith anonymous login:\n\n<!--codeinclude-->\n[Allow anonymous login](../../modules/activemq/src/test/java/org/testcontainers/activemq/ArtemisContainerTest.java) inside_block:enableAnonymousLogin\n<!--/codeinclude-->\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-activemq:{{latest_version}}\"\n    ```\n\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-activemq</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n"
  },
  {
    "path": "docs/modules/azure.md",
    "content": "# Azure Module\n\n!!! note\nThis module is INCUBATING. While it is ready for use and operational in the current version of Testcontainers, it is possible that it may receive breaking changes in the future. See [our contributing guidelines](/contributing/#incubating-modules) for more information on our incubating modules policy.\n\nTestcontainers module for the Microsoft Azure's [SDK](https://github.com/Azure/azure-sdk-for-java).\n\nCurrently, the module supports `Azurite`, `Azure Event Hubs`, `Azure Service Bus` and `CosmosDB` emulators. In order to use them, you should use the following classes:\n\nClass | Container Image\n-|-\nAzuriteContainer | [mcr.microsoft.com/azure-storage/azurite](https://github.com/microsoft/containerregistry)\nEventHubsEmulatorContainer | [mcr.microsoft.com/azure-messaging/eventhubs-emulator](https://github.com/microsoft/containerregistry)\nServiceBusEmulatorContainer | [mcr.microsoft.com/azure-messaging/servicebus-emulator](https://github.com/microsoft/containerregistry)\nCosmosDBEmulatorContainer | [mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator](https://github.com/microsoft/containerregistry)\n\n## Usage example\n\n### Azurite Storage Emulator\n\nStart Azurite Emulator during a test:\n\n<!--codeinclude-->\n[Starting an Azurite container](../../modules/azure/src/test/java/org/testcontainers/azure/AzuriteContainerTest.java) inside_block:emulatorContainer\n<!--/codeinclude-->\n\n!!! note\n    SSL configuration is possible using the `withSsl(MountableFile, String)` and  `withSsl(MountableFile, MountableFile)` methods.\n\nIf the tested application needs to use more than one set of credentials, the container can be configured to use custom credentials.\nPlease see some examples below.\n\n<!--codeinclude-->\n[Starting an Azurite Blob container with one account and two keys](../../modules/azure/src/test/java/org/testcontainers/azure/AzuriteContainerTest.java) inside_block:withTwoAccountKeys\n<!--/codeinclude-->\n\n<!--codeinclude-->\n[Starting an Azurite Blob container with more accounts and keys](../../modules/azure/src/test/java/org/testcontainers/azure/AzuriteContainerTest.java) inside_block:withMoreAccounts\n<!--/codeinclude-->\n\n#### Using with Blob\n\nBuild Azure Blob client:\n\n<!--codeinclude-->\n[Build Azure Blob Service client](../../modules/azure/src/test/java/org/testcontainers/azure/AzuriteContainerTest.java) inside_block:createBlobClient\n<!--/codeinclude-->\n\nIn case the application needs to use custom credentials, we can obtain them with a different method:\n\n<!--codeinclude-->\n[Obtain connection string with non-default credentials](../../modules/azure/src/test/java/org/testcontainers/azure/AzuriteContainerTest.java) inside_block:useNonDefaultCredentials\n<!--/codeinclude-->\n\n#### Using with Queue\n\nBuild Azure Queue client:\n\n<!--codeinclude-->\n[Build Azure Queue Service client](../../modules/azure/src/test/java/org/testcontainers/azure/AzuriteContainerTest.java) inside_block:createQueueClient\n<!--/codeinclude-->\n\n!!! note\n    We can use custom credentials the same way as defined in the Blob section.\n\n#### Using with Table\n\nBuild Azure Table client:\n\n<!--codeinclude-->\n[Build Azure Table Service client](../../modules/azure/src/test/java/org/testcontainers/azure/AzuriteContainerTest.java) inside_block:createTableClient\n<!--/codeinclude-->\n\n!!! note\n    We can use custom credentials the same way as defined in the Blob section.\n\n### Azure Event Hubs Emulator\n\n<!--codeinclude-->\n[Configuring the Azure Event Hubs Emulator container](../../modules/azure/src/test/resources/eventhubs_config.json)\n<!--/codeinclude-->\n\nStart Azure Event Hubs Emulator during a test:\n\n<!--codeinclude-->\n[Setting up a network](../../modules/azure/src/test/java/org/testcontainers/azure/EventHubsEmulatorContainerTest.java) inside_block:network\n<!--/codeinclude-->\n\n<!--codeinclude-->\n[Starting an Azurite container as dependency](../../modules/azure/src/test/java/org/testcontainers/azure/EventHubsEmulatorContainerTest.java) inside_block:azuriteContainer\n<!--/codeinclude-->\n\n<!--codeinclude-->\n[Starting an Azure Event Hubs Emulator container](../../modules/azure/src/test/java/org/testcontainers/azure/EventHubsEmulatorContainerTest.java) inside_block:emulatorContainer\n<!--/codeinclude-->\n\n#### Using Azure Event Hubs clients\n\nConfigure the consumer and the producer clients:\n\n<!--codeinclude-->\n[Configuring the clients](../../modules/azure/src/test/java/org/testcontainers/azure/EventHubsEmulatorContainerTest.java) inside_block:createProducerAndConsumer\n<!--/codeinclude-->\n\n### Azure Service Bus Emulator\n\n<!--codeinclude-->\n[Configuring the Azure Service Bus Emulator container](../../modules/azure/src/test/resources/service-bus-config.json)\n<!--/codeinclude-->\n\nStart Azure Service Bus Emulator during a test:\n\n<!--codeinclude-->\n[Setting up a network](../../modules/azure/src/test/java/org/testcontainers/azure/ServiceBusEmulatorContainerTest.java) inside_block:network\n<!--/codeinclude-->\n\n<!--codeinclude-->\n[Starting a SQL Server container as dependency](../../modules/azure/src/test/java/org/testcontainers/azure/ServiceBusEmulatorContainerTest.java) inside_block:sqlContainer\n<!--/codeinclude-->\n\n<!--codeinclude-->\n[Starting a Service Bus Emulator container](../../modules/azure/src/test/java/org/testcontainers/azure/ServiceBusEmulatorContainerTest.java) inside_block:emulatorContainer\n<!--/codeinclude-->\n\n#### Using Azure Service Bus clients\n\nConfigure the sender and the processor clients:\n\n<!--codeinclude-->\n[Configuring the sender client](../../modules/azure/src/test/java/org/testcontainers/azure/ServiceBusEmulatorContainerTest.java) inside_block:senderClient\n<!--/codeinclude-->\n\n<!--codeinclude-->\n[Configuring the processor client](../../modules/azure/src/test/java/org/testcontainers/azure/ServiceBusEmulatorContainerTest.java) inside_block:processorClient\n<!--/codeinclude-->\n\n### CosmosDB\n\nStart Azure CosmosDB Emulator during a test:\n\n<!--codeinclude-->\n[Starting an Azure CosmosDB Emulator container](../../modules/azure/src/test/java/org/testcontainers/containers/CosmosDBEmulatorContainerTest.java) inside_block:emulatorContainer\n<!--/codeinclude-->\n\nPrepare KeyStore to use for SSL.\n\n<!--codeinclude-->\n[Building KeyStore from certificate within container](../../modules/azure/src/test/java/org/testcontainers/containers/CosmosDBEmulatorContainerTest.java) inside_block:buildAndSaveNewKeyStore\n<!--/codeinclude-->\n\nSet system trust-store parameters to use already built KeyStore:\n\n<!--codeinclude-->\n[Set system trust-store parameters](../../modules/azure/src/test/java/org/testcontainers/containers/CosmosDBEmulatorContainerTest.java) inside_block:setSystemTrustStoreParameters\n<!--/codeinclude-->\n\nBuild Azure CosmosDB client:\n\n<!--codeinclude-->\n[Build Azure CosmosDB client](../../modules/azure/src/test/java/org/testcontainers/containers/CosmosDBEmulatorContainerTest.java) inside_block:buildClient\n<!--/codeinclude-->\n\nTest against the Emulator:\n\n<!--codeinclude-->\n[Testing against Azure CosmosDB Emulator container](../../modules/azure/src/test/java/org/testcontainers/containers/CosmosDBEmulatorContainerTest.java) inside_block:testWithClientAgainstEmulatorContainer\n<!--/codeinclude-->\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-azure:{{latest_version}}\"\n    ```\n\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-azure</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n\n"
  },
  {
    "path": "docs/modules/chromadb.md",
    "content": "# ChromaDB\n\nTestcontainers module for [ChromaDB](https://registry.hub.docker.com/r/chromadb/chroma)\n\n## ChromaDB's usage examples\n\nYou can start a ChromaDB container instance from any Java application by using:\n\n<!--codeinclude-->\n[Default ChromaDB container](../../modules/chromadb/src/test/java/org/testcontainers/chromadb/ChromaDBContainerTest.java) inside_block:container\n<!--/codeinclude-->\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n```groovy\ntestImplementation \"org.testcontainers:testcontainers-chromadb:{{latest_version}}\"\n```\n\n=== \"Maven\"\n```xml\n<dependency>\n<groupId>org.testcontainers</groupId>\n<artifactId>testcontainers-chromadb</artifactId>\n<version>{{latest_version}}</version>\n<scope>test</scope>\n</dependency>\n```\n"
  },
  {
    "path": "docs/modules/consul.md",
    "content": "# Hashicorp Consul Module\n\nTestcontainers module for [Consul](https://github.com/hashicorp/consul). Consul is a tool for managing key value properties. More information on Consul [here](https://www.consul.io/).\n\n## Usage example\n\n<!--codeinclude-->\n[Running Consul in your Junit tests](../../modules/consul/src/test/java/org/testcontainers/consul/ConsulContainerTest.java)\n<!--/codeinclude-->\n\n## Why Consul in Junit tests?\n\nWith the increasing popularity of Consul and config externalization, applications are now needing to source properties from Consul.\nThis can prove challenging in the development phase without a running Consul instance readily on hand. This library \naims to solve your apps integration testing with Consul. You can also use it to\ntest how your application behaves with Consul by writing different test scenarios in Junit.\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-consul:{{latest_version}}\"\n    ```\n\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-consul</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n"
  },
  {
    "path": "docs/modules/databases/cassandra.md",
    "content": "# Cassandra Module\n\n## Usage example\n\nThis example connects to the Cassandra cluster:\n\n1. Define a container:\n    <!--codeinclude-->\n    [Container definition](../../../modules/cassandra/src/test/java/org/testcontainers/cassandra/CassandraContainerTest.java) inside_block:container-definition\n    <!--/codeinclude-->\n\n2. Build a `CqlSession`:\n    <!--codeinclude-->\n    [Building CqlSession](../../../modules/cassandra/src/test/java/org/testcontainers/cassandra/CassandraContainerTest.java) inside_block:cql-session\n    <!--/codeinclude-->\n\n3. Define a container with custom `cassandra.yaml` located in a directory `cassandra-auth-required-configuration`:\n    \n    <!--codeinclude-->\n    [Running init script with required authentication](../../../modules/cassandra/src/test/java/org/testcontainers/cassandra/CassandraContainerTest.java) inside_block:init-with-auth\n    <!--/codeinclude-->\n\n## Using secure connection (TLS)\n\nIf you override the default `cassandra.yaml` with a version setting the property `client_encryption_options.optional` \nto `false`, you have to provide a valid client certificate and key (PEM format) when you initialize your container:\n\n<!--codeinclude-->\n[SSL setup](../../../modules/cassandra/src/test/java/org/testcontainers/cassandra/CassandraContainerTest.java) inside_block:with-ssl-config\n<!--/codeinclude-->\n\n!!! hint\n    To generate the client certificate and key, please refer to\n    [this documentation](https://docs.datastax.com/en/cassandra-oss/3.x/cassandra/configuration/secureSSLCertificates.html). \n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-cassandra:{{latest_version}}\"\n    ```\n\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-cassandra</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n"
  },
  {
    "path": "docs/modules/databases/clickhouse.md",
    "content": "# Clickhouse Module\n\nTestcontainers module for [ClickHouse](https://hub.docker.com/r/clickhouse/clickhouse-server)\n\n## Usage example\n\nYou can start a ClickHouse container instance from any Java application by using:\n\n<!--codeinclude-->\n[Container definition](../../../modules/clickhouse/src/test/java/org/testcontainers/clickhouse/ClickHouseContainerTest.java) inside_block:container\n<!--/codeinclude-->\n\n### Testcontainers JDBC URL\n\n`jdbc:tc:clickhouse:18.10.3:///databasename`\n\nSee [JDBC](./jdbc.md) for documentation.\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-clickhouse:{{latest_version}}\"\n    ```\n\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-clickhouse</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n\n!!! hint\n    Adding this Testcontainers library JAR will not automatically add a database driver JAR to your project. You should ensure that your project also has a suitable database driver as a dependency.\n\n"
  },
  {
    "path": "docs/modules/databases/cockroachdb.md",
    "content": "# CockroachDB Module\n\nTestcontainers module for [CockroachDB](https://hub.docker.com/r/cockroachdb/cockroach)\n\n## Usage example\n\nYou can start a CockroachDB container instance from any Java application by using:\n\n<!--codeinclude-->\n[Container definition](../../../modules/cockroachdb/src/test/java/org/testcontainers/cockroachdb/CockroachContainerTest.java) inside_block:container\n<!--/codeinclude-->\n\nSee [Database containers](./index.md) for documentation and usage that is common to all relational database container types.\n\n### Testcontainers JDBC URL\n\n`jdbc:tc:cockroach:v21.2.3:///databasename`\n\nSee [JDBC](./jdbc.md) for documentation.\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-cockroachdb:{{latest_version}}\"\n    ```\n\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-cockroachdb</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n\n!!! hint\n    Adding this Testcontainers library JAR will not automatically add a database driver JAR to your project. You should ensure that your project also has a suitable database driver as a dependency.\n"
  },
  {
    "path": "docs/modules/databases/couchbase.md",
    "content": "# Couchbase Module\n\n<img src=\"https://cdn.worldvectorlogo.com/logos/couchbase.svg\" width=\"300\" />\n\nTestcontainers module for Couchbase. [Couchbase](https://www.couchbase.com/) is a document oriented NoSQL database.\n\n## Usage example\n\nRunning Couchbase as a stand-in in a test:\n\n1. Define a bucket:\n    <!--codeinclude-->\n    [Bucket Definition](../../../modules/couchbase/src/test/java/org/testcontainers/couchbase/CouchbaseContainerTest.java) inside_block:bucket_definition\n    <!--/codeinclude-->\n\n2. define a container:\n    <!--codeinclude-->\n    [Container definition](../../../modules/couchbase/src/test/java/org/testcontainers/couchbase/CouchbaseContainerTest.java) inside_block:container_definition\n    <!--/codeinclude-->\n\n3. create an cluster:\n    <!--codeinclude-->\n    [Cluster creation](../../../modules/couchbase/src/test/java/org/testcontainers/couchbase/CouchbaseContainerTest.java) inside_block:cluster_creation\n    <!--/codeinclude-->\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-couchbase:{{latest_version}}\"\n    ```\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-couchbase</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n"
  },
  {
    "path": "docs/modules/databases/cratedb.md",
    "content": "# CrateDB Module\n\nTestcontainers module for [CrateDB](https://hub.docker.com/_/crate)\n\n## Usage example\n\nYou can start a CrateDB container instance from any Java application by using:\n\n<!--codeinclude-->\n[Container definition](../../../modules/cratedb/src/test/java/org/testcontainers/junit/cratedb/SimpleCrateDBTest.java) inside_block:container\n<!--/codeinclude-->\n\nSee [Database containers](./index.md) for documentation and usage that is common to all relational database container types.\n\n### Testcontainers JDBC URL\n\n`jdbc:tc:cratedb:5.2.3:///databasename`\n\nSee [JDBC](./jdbc.md) for documentation.\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-cratedb:{{latest_version}}\"\n    ```\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-cratedb</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n\n!!! hint\n    Adding this Testcontainers library JAR will not automatically add a database driver JAR to your project. You should ensure that your project also has a suitable database driver as a dependency.\n\n"
  },
  {
    "path": "docs/modules/databases/databend.md",
    "content": "# Databend Module\n\nTestcontainers module for [Databend](https://hub.docker.com/r/datafuselabs/databend)\n\n## Usage example\n\nYou can start a Databend container instance from any Java application by using:\n\n<!--codeinclude-->\n[Container definition](../../../modules/databend/src/test/java/org/testcontainers/databend/DatabendContainerTest.java) inside_block:container\n<!--/codeinclude-->\n\n### Testcontainers JDBC URL\n\n`jdbc:tc:databend:v1.2.615:///databasename`\n\nSee [JDBC](./jdbc.md) for documentation.\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-databend:{{latest_version}}\"\n    ```\n\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-databend</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n\n!!! hint\nAdding this Testcontainers library JAR will not automatically add a database driver JAR to your project. You should ensure that your project also has a suitable database driver as a dependency.\n\n"
  },
  {
    "path": "docs/modules/databases/db2.md",
    "content": "# DB2 Module\n\nTestcontainers module for [DB2](https://www.ibm.com/docs/en/db2/11.5.x?topic=deployments-db2-community-edition-docker)\n\n## Usage example\n\nYou can start a DB2 container instance from any Java application by using:\n\n<!--codeinclude-->\n[Container definition](../../../modules/db2/src/test/java/org/testcontainers/db2/Db2ContainerTest.java) inside_block:container\n<!--/codeinclude-->\n\n!!! warning \"EULA Acceptance\"\n    Due to licencing restrictions you are required to accept an EULA for this container image. To indicate that you accept the DB2 image EULA, call the `acceptLicense()` method, or place a file at the root of the classpath named `container-license-acceptance.txt`, e.g. at `src/test/resources/container-license-acceptance.txt`. This file should contain the line: `ibmcom/db2:11.5.0.0a` (or, if you are overriding the docker image name/tag, update accordingly).\n\nSee [Database containers](./index.md) for documentation and usage that is common to all relational database container types.\n\n### Testcontainers JDBC URL\n\n`jdbc:tc:db2:11.5.0.0a:///databasename`\n\nSee [JDBC](./jdbc.md) for documentation.\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-db2:{{latest_version}}\"\n    ```\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-db2</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n\n!!! hint\n    Adding this Testcontainers library JAR will not automatically add a database driver JAR to your project. You should ensure that your project also has a suitable database driver as a dependency.\n"
  },
  {
    "path": "docs/modules/databases/index.md",
    "content": "# Database containers\n\n## Overview\n\nYou might want to use Testcontainers' database support:\n\n * **Instead of H2 database for DAO unit tests that depend on database features that H2 doesn't emulate.** Testcontainers is not as performant as H2, but does give you the benefit of 100% database compatibility (since it runs a real DB inside of a container).\n * **Instead of a database running on the local machine or in a VM** for DAO unit tests or end-to-end integration tests that need a database to be present. In this context, the benefit of Testcontainers is that the database always starts in a known state, without any contamination between test runs or on developers' local machines.\n\n!!! note\n    Of course, it's still important to have as few tests that hit the database as possible, and make good use of mocks for components higher up the stack.\n\nSee [JDBC](./jdbc.md) and [R2DBC](./r2dbc.md) for information on how to use Testcontainers with SQL-like databases."
  },
  {
    "path": "docs/modules/databases/influxdb.md",
    "content": "# InfluxDB Module\n\nTestcontainers module for InfluxData [InfluxDB](https://www.influxdata.com/products/influxdb/).\n\n## Important note\n\nThere are breaking changes in InfluxDB 2.x.\nFor more information refer to the main [documentation](https://docs.influxdata.com/influxdb/v2.0/upgrade/v1-to-v2/).\nYou can find more information about the official InfluxDB image on [Docker Hub](https://hub.docker.com/_/influxdb).\n\n## InfluxDB 2.x usage example\n\nRunning a `InfluxDBContainer` as a stand-in for InfluxDB in a test:\n\n<!--codeinclude-->\n[Create an InfluxDB container](../../../modules/influxdb/src/test/java/org/testcontainers/containers/InfluxDBContainerTest.java) inside_block:constructorWithDefaultVariables\n<!--/codeinclude-->\n\n\nThe InfluxDB instance will be setup with the following data:<br/>\n\n| Property     | Default Value | \n|--------------|:-------------:|\n| username     |   test-user   | \n| password     | test-password | \n| organization |   test-org    |\n| bucket       |  test-bucket  |  \n| retention    | 0 (infinite)  |\n| adminToken   |       -       |\n\nFor more details about the InfluxDB setup, please visit the official [InfluxDB documentation](https://docs.influxdata.com/influxdb/v2.0/upgrade/v1-to-v2/docker/#influxdb-2x-initialization-credentials).\n\nIt is possible to overwrite the default property values. Create a container with InfluxDB admin token:\n<!--codeinclude-->\n[Create an InfluxDB container with admin token](../../../modules/influxdb/src/test/java/org/testcontainers/containers/InfluxDBContainerTest.java) inside_block:constructorWithAdminToken\n<!--/codeinclude-->\n\nOr create a container with custom username, password, bucket, organization, and retention time:\n<!--codeinclude-->\n[Create an InfluxDB container with custom settings](../../../modules/influxdb/src/test/java/org/testcontainers/containers/InfluxDBContainerTest.java) inside_block:constructorWithCustomVariables\n<!--/codeinclude-->\n\nThe following code snippet shows how you can create an InfluxDB Java client:\n\n<!--codeinclude-->\n[Create an InfluxDB Java client](../../../modules/influxdb/src/test/java/org/testcontainers/containers/InfluxDBContainerTest.java) inside_block:createInfluxDBClient\n<!--/codeinclude-->\n\n!!! hint\n    You can find the latest documentation about the InfluxDB 2.x Java client [here](https://github.com/influxdata/influxdb-client-java).\n\n## InfluxDB 1.x usage example\n\nRunning a `InfluxDBContainer` as a stand-in for InfluxDB in a test with default env variables:\n\n<!--codeinclude-->\n[Create an InfluxDB container](../../../modules/influxdb/src/test/java/org/testcontainers/containers/InfluxDBContainerV1Test.java) inside_block:constructorWithDefaultVariables\n<!--/codeinclude-->\n\nThe InfluxDB instance will be setup with the following data:<br/>\n\n| Property      | Default Value | \n|---------------|:-------------:|\n| username      |   test-user   | \n| password      | test-password | \n| authEnabled   |     true      |  \n| admin         |     admin     |\n| adminPassword |   password    |\n| database      |       -       |\n\nIt is possible to overwrite the default values. \nFor instance, creating an InfluxDB container with a custom username, password, and database name:\n<!--codeinclude-->\n[Create an InfluxDB container with custom settings](../../../modules/influxdb/src/test/java/org/testcontainers/containers/InfluxDBContainerV1Test.java) inside_block:constructorWithUserPassword\n<!--/codeinclude-->\n\nIn the following example you will find a snippet to create an InfluxDB client using the official Java client:\n\n<!--codeinclude-->\n[Create an InfluxDB Java client](../../../modules/influxdb/src/test/java/org/testcontainers/containers/InfluxDBContainerV1Test.java) inside_block:createInfluxDBClient\n<!--/codeinclude-->\n\n!!! hint\n    You can find the latest documentation about the InfluxDB 1.x Java client [here](https://github.com/influxdata/influxdb-java).\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n\n```groovy\ntestImplementation \"org.testcontainers:testcontainers-influxdb:{{latest_version}}\"\n```\n\n=== \"Maven\"\n\n```xml\n\n<dependency>\n    <groupId>org.testcontainers</groupId>\n    <artifactId>testcontainers-influxdb</artifactId>\n    <version>{{latest_version}}</version>\n    <scope>test</scope>\n</dependency>\n```\n\n!!! hint\n    Adding this Testcontainers library JAR will not automatically add a database driver JAR to your project. You should ensure that your project also has a suitable database driver as a dependency.\n"
  },
  {
    "path": "docs/modules/databases/jdbc.md",
    "content": "# JDBC support\n\nYou can obtain a temporary database in one of two ways:\n\n * **Using a specially modified JDBC URL**: after making a very simple modification to your system's JDBC URL string, Testcontainers will provide a disposable stand-in database that can be used without requiring modification to your application code.\n * **JUnit @Rule/@ClassRule**: this mode starts a database inside a container before your tests and tears it down afterwards.\n\n## Database containers launched via JDBC URL scheme\n\nAs long as you have Testcontainers and the appropriate JDBC driver on your classpath, you can simply modify regular JDBC connection URLs to get a fresh containerized instance of the database each time your application starts up.\n\n_N.B:_\n\n* _TC needs to be on your application's classpath at runtime for this to work_\n* _For Spring Boot (Before version `2.3.0`) you need to specify the driver manually `spring.datasource.driver-class-name=org.testcontainers.jdbc.ContainerDatabaseDriver`_\n\n**Original URL**: `jdbc:mysql://localhost:3306/databasename`\n\nInsert `tc:` after `jdbc:` as follows. Note that the hostname, port and database name will be ignored; you can leave these as-is or set them to any value.\n\n!!! note\n    We will use `///` (host-less URIs) from now on to emphasis the unimportance of the `host:port` pair.  \n    From Testcontainers' perspective, `jdbc:mysql:8.0.36://localhost:3306/databasename` and `jdbc:mysql:8.0.36:///databasename` is the same URI.\n\n!!! warning\n    If you're using the JDBC URL support, there is no need to instantiate an instance of the container - Testcontainers will do it automagically.\n\n### JDBC URL examples\n\n#### Using ClickHouse\n\n`jdbc:tc:clickhouse:18.10.3:///databasename`\n\n#### Using CockroachDB\n\n`jdbc:tc:cockroach:v21.2.3:///databasename`\n\n#### Using CrateDB\n\n`jdbc:tc:cratedb:5.2.3:///databasename`\n\n#### Using DB2\n\n`jdbc:tc:db2:11.5.0.0a:///databasename`\n\n#### Using MariaDB\n\n`jdbc:tc:mariadb:10.3.39:///databasename`\n\n#### Using MySQL\n\n`jdbc:tc:mysql:8.0.36:///databasename`\n\n#### Using MSSQL Server\n\n`jdbc:tc:sqlserver:2017-CU12:///databasename`\n\n#### Using OceanBase\n\n`jdbc:tc:oceanbasece:4.2.1-lts:///databasename`\n\n#### Using Oracle\n\n`jdbc:tc:oracle:21-slim-faststart:///databasename`\n\n#### Using PostGIS\n\n`jdbc:tc:postgis:9.6-2.5:///databasename`\n\n#### Using PostgreSQL\n\n`jdbc:tc:postgresql:9.6.8:///databasename`\n\n#### Using QuestDB\n\n`jdbc:tc:questdb:6.5.3:///databasename`\n\n#### Using TimescaleDB\n\n`jdbc:tc:timescaledb:2.1.0-pg13:///databasename`\n\n#### Using PGVector\n\n`jdbc:tc:pgvector:pg16:///databasename`\n\n#### Using TiDB\n\n`jdbc:tc:tidb:v6.1.0:///databasename`\n\n#### Using Timeplus\n\n`jdbc:tc:timeplus:2.3.21:///databasename`\n\n#### Using Trino\n\n`jdbc:tc:trino:352://localhost/memory/default`\n\n#### Using YugabyteDB\n\n`jdbc:tc:yugabyte:2.14.4.0-b26:///databasename`\n\n\n### Using a classpath init script\n\nTestcontainers can run an init script after the database container is started, but before your code is given a connection to it. The script must be on the classpath, and is referenced as follows:\n\n`jdbc:tc:mysql:8.0.36:///databasename?TC_INITSCRIPT=somepath/init_mysql.sql`\n\nThis is useful if you have a fixed script for setting up database schema, etc.\n\n### Using an init script from a file\n\nIf the init script path is prefixed `file:`, it will be loaded from a file (relative to the working directory, which will usually be the project root).\n\n`jdbc:tc:mysql:8.0.36:///databasename?TC_INITSCRIPT=file:src/main/resources/init_mysql.sql`\n\n### Using an init function\n\nInstead of running a fixed script for DB setup, it may be useful to call a Java function that you define. This is intended to allow you to trigger database schema migration tools. To do this, add TC_INITFUNCTION to the URL as follows, passing a full path to the class name and method:\n\n `jdbc:tc:mysql:8.0.36:///databasename?TC_INITFUNCTION=org.testcontainers.jdbc.JDBCDriverTest::sampleInitFunction`\n\nThe init function must be a public static method which takes a `java.sql.Connection` as its only parameter, e.g.\n```java\npublic class JDBCDriverTest {\n    public static void sampleInitFunction(Connection connection) throws SQLException {\n        // e.g. run schema setup or Flyway/liquibase/etc DB migrations here...\n    }\n    ...\n```\n\n### Running container in daemon mode\n\nBy default database container is being stopped as soon as last connection is closed. There are cases when you might need to start container and keep it running till you stop it explicitly or JVM is shutdown. To do this, add `TC_DAEMON` parameter to the URL as follows:\n\n `jdbc:tc:mysql:8.0.36:///databasename?TC_DAEMON=true`\n\nWith this parameter database container will keep running even when there's no open connections.\n\n\n### Running container with tmpfs options\n\nContainer can have `tmpfs` mounts for storing data in host memory. This is useful if you want to speed up your database tests. Be aware that the data will be lost when the container stops.\n\nTo pass this option to the container, add `TC_TMPFS` parameter to the URL as follows:\n\n  `jdbc:tc:postgresql:9.6.8:///databasename?TC_TMPFS=/testtmpfs:rw`\n\nIf you need more than one option, separate them by comma (e.g. `TC_TMPFS=key:value,key1:value1&other_parameters=foo`).\n\nFor more information about `tmpfs` mount, see [the official Docker documentation](https://docs.docker.com/storage/tmpfs/).\n\n## Database container objects\n\nIn case you can't use the URL support, or need to fine-tune the container, you can instantiate it yourself.\n\nAdd a @Rule or @ClassRule to your test class, e.g.:\n\n```java\npublic class SimpleMySQLTest {\n    @Rule\n    public MySQLContainer mysql = new MySQLContainer();\n```\n\nNow, in your test code (or a suitable setup method), you can obtain details necessary to connect to this database:\n\n * `mysql.getJdbcUrl()` provides a JDBC URL your code can connect to\n * `mysql.getUsername()` provides the username your code should pass to the driver\n * `mysql.getPassword()` provides the password your code should pass to the driver\n\nNote that if you use `@Rule`, you will be given an isolated container for each test method. If you use `@ClassRule`, you will get on isolated container for all the methods in the test class.\n\nExamples/Tests:\n\n * [MySQL](https://github.com/testcontainers/testcontainers-java/blob/main/modules/mysql/src/test/java/org/testcontainers/junit/mysql/SimpleMySQLTest.java)\n * [PostgreSQL](https://github.com/testcontainers/testcontainers-java/blob/main/modules/postgresql/src/test/java/org/testcontainers/junit/postgresql/SimplePostgreSQLTest.java)\n"
  },
  {
    "path": "docs/modules/databases/mariadb.md",
    "content": "# MariaDB Module\n\nTestcontainers module for [MariaDB](https://hub.docker.com/_/mariadb)\n\n## Usage example\n\nYou can start a MySQL container instance from any Java application by using:\n\n<!--codeinclude-->\n[Container definition](../../../modules/mariadb/src/test/java/org/testcontainers/mariadb/MariaDBContainerTest.java) inside_block:container\n<!--/codeinclude-->\n\nSee [Database containers](./index.md) for documentation and usage that is common to all relational database container types.\n\n### Testcontainers JDBC URL\n\n`jdbc:tc:mariadb:10.3.39:///databasename`\n\nSee [JDBC](./jdbc.md) for documentation.\n\n## MariaDB `root` user password\n\nIf no custom password is specified, the container will use the default user password `test` for the `root` user as well.\nWhen you specify a custom password for the database user,\nthis will also act as the password of the MariaDB `root` user automatically.\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-mariadb:{{latest_version}}\"\n    ```\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-mariadb</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n\n!!! hint\n    Adding this Testcontainers library JAR will not automatically add a database driver JAR to your project. You should ensure that your project also has a suitable database driver as a dependency.\n\n"
  },
  {
    "path": "docs/modules/databases/mongodb.md",
    "content": "# MongoDB Module\n\nThe MongoDB module provides two Testcontainers for MongoDB unit testing:\n\n* [MongoDBContainer](#mongodbcontainer) - the core MongoDB database\n* [MongoDBAtlasLocalContainer](#mongodbatlaslocalcontainer) - the core MongoDB database combined with MongoDB Atlas Search + Atlas Vector Search\n\n## MongoDBContainer\n\n### Usage example\n\nThe following example shows how to create a MongoDBContainer:\n\n<!--codeinclude-->\n[Creating a MongoDB container](../../../modules/mongodb/src/test/java/org/testcontainers/mongodb/MongoDBContainerTest.java) inside_block:creatingMongoDBContainer\n<!--/codeinclude-->\n\nAnd how to start it:\n\n<!--codeinclude-->\n[Starting a MongoDB container](../../../modules/mongodb/src/test/java/org/testcontainers/mongodb/MongoDBContainerTest.java) inside_block:startingMongoDBContainer\n<!--/codeinclude-->\n\n!!! note\n    To construct a multi-node MongoDB cluster, consider the [mongodb-replica-set project](https://github.com/silaev/mongodb-replica-set/)     \n\n#### Motivation\nImplement a reusable, cross-platform, simple to install solution that doesn't depend on \nfixed ports to test MongoDB transactions.  \n  \n#### General info\nMongoDB starting from version 4 supports multi-document transactions only for a replica set.\nFor instance, to initialize a single node replica set on fixed ports via Docker, one has to do the following:\n\n* Run a MongoDB container of version 4 and up specifying --replSet command\n* Initialize a single replica set via executing a proper command\n* Wait for the initialization to complete\n* Provide a special url for a user to employ with a MongoDB driver without specifying replicaSet\n\nAs we can see, there is a lot of operations to execute and we even haven't touched a non-fixed port approach.\nThat's where the MongoDBContainer might come in handy. \n\n## MongoDBAtlasLocalContainer\n\n### Usage example\n\nThe following example shows how to create a MongoDBAtlasLocalContainer:\n\n<!--codeinclude-->\n[Creating a MongoDB Atlas Local Container](../../../modules/mongodb/src/test/java/org/testcontainers/mongodb/MongoDBAtlasLocalContainerTest.java) inside_block:creatingAtlasLocalContainer\n<!--/codeinclude-->\n\nAnd how to start it:\n\n<!--codeinclude-->\n[Start the Container](../../../modules/mongodb/src/test/java/org/testcontainers/mongodb/MongoDBAtlasLocalContainerTest.java) inside_block:startingAtlasLocalContainer\n<!--/codeinclude-->\n\nThe connection string provided by the MongoDBAtlasLocalContainer's getConnectionString() method includes the dynamically allocated port:\n\n<!--codeinclude-->\n[Get the Connection String](../../../modules/mongodb/src/test/java/org/testcontainers/mongodb/MongoDBAtlasLocalContainerTest.java) inside_block:getConnectionStringAtlasLocalContainer\n<!--/codeinclude-->\n\ne.g. `mongodb://localhost:12345/?directConnection=true`\n\n### References\nMongoDB Atlas Local combines the MongoDB database engine with MongoT, a sidecar process for advanced searching capabilities built by MongoDB and powered by [Apache Lucene](https://lucene.apache.org/). \n\nThe container (mongodb/mongodb-atlas-local) documentation can be found [here](https://www.mongodb.com/docs/atlas/cli/current/atlas-cli-deploy-docker/).\n\nGeneral information about Atlas Search can be found [here](https://www.mongodb.com/docs/atlas/atlas-search/).\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-mongodb:{{latest_version}}\"\n    ```\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-mongodb</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n\n!!! hint\n    Adding this Testcontainers library JAR will not automatically add a database driver JAR to your project. You should ensure that your project also has a suitable database driver as a dependency\n    \n#### Copyright\nCopyright (c) 2019 Konstantin Silaev <silaev256@gmail.com>\n"
  },
  {
    "path": "docs/modules/databases/mssqlserver.md",
    "content": "# MS SQL Server Module\n\nTestcontainers module for [MS SQL Server](https://mcr.microsoft.com/en-us/artifact/mar/mssql/server/)\n\n## Usage example\n\nYou can start a MS SQL Server container instance from any Java application by using:\n\n<!--codeinclude-->\n[Container definition](../../../modules/mssqlserver/src/test/java/org/testcontainers/mssqlserver/MSSQLServerContainerTest.java) inside_block:container\n<!--/codeinclude-->\n\n!!! warning \"EULA Acceptance\"\n    Due to licencing restrictions you are required to accept an EULA for this container image. To indicate that you accept the MS SQL Server image EULA, call the `acceptLicense()` method, or place a file at the root of the classpath named `container-license-acceptance.txt`, e.g. at `src/test/resources/container-license-acceptance.txt`. This file should contain the line: `mcr.microsoft.com/mssql/server:2017-CU12` (or, if you are overriding the docker image name/tag, update accordingly).\n    \n    Please see the [`microsoft-mssql-server` image documentation](https://hub.docker.com/_/microsoft-mssql-server#environment-variables) for a link to the EULA document.\n\nSee [Database containers](./index.md) for documentation and usage that is common to all relational database container types.\n\n### Testcontainers JDBC URL\n\n`jdbc:tc:sqlserver:2017-CU12:///databasename`\n\nSee [JDBC](./jdbc.md) for documentation.\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-mssqlserver:{{latest_version}}\"\n    ```\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-mssqlserver</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n\n\n!!! hint\n    Adding this Testcontainers library JAR will not automatically add a database driver JAR to your project. You should ensure that your project also has a suitable database driver as a dependency.\n\n## License\n\nSee [LICENSE](https://raw.githubusercontent.com/testcontainers/testcontainers-java/main/modules/mssqlserver/LICENSE).\n\n## Copyright\n\nCopyright (c) 2017 - 2019 G DATA Software AG and other authors.\n\nSee [AUTHORS](https://raw.githubusercontent.com/testcontainers/testcontainers-java/main/modules/mssqlserver/AUTHORS) for contributors.\n"
  },
  {
    "path": "docs/modules/databases/mysql.md",
    "content": "# MySQL Module\n\nTestcontainers module for [MySQL](https://hub.docker.com/_/mysql)\n\n## Usage example\n\nYou can start a MySQL container instance from any Java application by using:\n\n<!--codeinclude-->\n[Container definition](../../../modules/mysql/src/test/java/org/testcontainers/mysql/MySQLContainerTest.java) inside_block:container\n<!--/codeinclude-->\n\nSee [Database containers](./index.md) for documentation and usage that is common to all relational database container types.\n\n### Testcontainers JDBC URL\n\n`jdbc:tc:mysql:8.0.36:///databasename`\n\nSee [JDBC](./jdbc.md) for documentation.\n\n## Overriding MySQL my.cnf settings\n\nFor MySQL databases, it is possible to override configuration settings using resources on the classpath. Assuming `somepath/mysql_conf_override`\nis a directory on the classpath containing .cnf files, the following URL can be used:\n\n  `jdbc:tc:mysql:8.0.36://hostname/databasename?TC_MY_CNF=somepath/mysql_conf_override`\n\nAny .cnf files in this classpath directory will be mapped into the database container's /etc/mysql/conf.d directory,\nand will be able to override server settings when the container starts.\n\n## MySQL `root` user password\n\nIf no custom password is specified, the container will use the default user password `test` for the `root` user as well.\nWhen you specify a custom password for the database user,\nthis will also act as the password of the MySQL `root` user automatically. \n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-mysql:{{latest_version}}\"\n    ```\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-mysql</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n\n!!! hint\n    Adding this Testcontainers library JAR will not automatically add a database driver JAR to your project. You should ensure that your project also has a suitable database driver as a dependency.\n"
  },
  {
    "path": "docs/modules/databases/neo4j.md",
    "content": "# Neo4j Module\n\nThis module helps to run [Neo4j](https://neo4j.com/download/) using Testcontainers.\n\nNote that it's based on the [official Docker image](https://hub.docker.com/_/neo4j/) provided by Neo4j, Inc.\n\nEven though the latest LTS version of Neo4j 4.4 is used in the examples of this documentation,\nthe Testcontainers integration supports also newer 5.x images of Neo4j.\n\n## Usage example\n\nDeclare your Testcontainers as a `@ClassRule` or `@Rule` in a JUnit 4 test or as static or member attribute of a JUnit 5 test annotated with `@Container` as you would with other Testcontainers.\nYou can either use call `getBoltUrl()` or `getHttpUrl()` on the Neo4j container.\n`getBoltUrl()` is meant to be used with one of the [official Bolt drivers](https://neo4j.com/developer/language-guides/) while `getHttpUrl()` gives you the HTTP-address of the transactional HTTP endpoint.\nOn the JVM you would most likely use the [Java driver](https://github.com/neo4j/neo4j-java-driver).\n\nThe following example uses the JUnit 5 extension `@Testcontainers` and demonstrates both the usage of the Java Driver and the REST endpoint:\n\n<!--codeinclude-->\n[JUnit 5 example](../../../examples/neo4j-container/src/test/java/org/testcontainers/containers/Neo4jExampleTest.java) inside_block:junitExample\n<!--/codeinclude-->\n\nYou are not limited to Unit tests, and you can use an instance of the Neo4j Testcontainers in vanilla Java code as well.\n\n## Additional features\n\n### Custom password\n\nA custom password can be provided:\n\n<!--codeinclude-->\n[Custom password](../../../modules/neo4j/src/test/java/org/testcontainers/neo4j/Neo4jContainerTest.java) inside_block:withAdminPassword\n<!--/codeinclude-->\n\n### Disable authentication\n\nAuthentication can be disabled:\n\n<!--codeinclude-->\n[Disable authentication](../../../modules/neo4j/src/test/java/org/testcontainers/neo4j/Neo4jContainerTest.java) inside_block:withoutAuthentication\n<!--/codeinclude-->\n\n### Random password\n\nA random (`UUID`-random based) password can be set:\n\n<!--codeinclude-->\n[Random password](../../../modules/neo4j/src/test/java/org/testcontainers/neo4j/Neo4jContainerTest.java) inside_block:withRandomPassword\n<!--/codeinclude-->\n\n### Neo4j-Configuration\n\nNeo4j's Docker image needs Neo4j configuration options in a dedicated format.\nThe container takes care of that, and you can configure the database with standard options like the following:\n\n<!--codeinclude-->\n[Neo4j configuration](../../../modules/neo4j/src/test/java/org/testcontainers/neo4j/Neo4jContainerTest.java) inside_block:neo4jConfiguration\n<!--/codeinclude-->\n\n### Add custom plugins\n\nCustom plugins, like APOC, can be copied over to the container from any classpath or host resource like this:\n\n<!--codeinclude-->\n[Plugin jar](../../../modules/neo4j/src/test/java/org/testcontainers/neo4j/Neo4jContainerTest.java) inside_block:registerPluginsJar\n<!--/codeinclude-->\n\nWhole directories work as well:\n\n<!--codeinclude-->\n[Plugin folder](../../../modules/neo4j/src/test/java/org/testcontainers/neo4j/Neo4jContainerTest.java) inside_block:registerPluginsPath\n<!--/codeinclude-->\n\n### Add Neo4j Docker Labs plugins\n\nAdd any Neo4j Labs plugin from the [Neo4j 4.4 Docker Labs plugin list](https://neo4j.com/docs/operations-manual/4.4/docker/operations/#docker-neo4jlabs-plugins)\nor [Neo4j 5 plugin list](https://neo4j.com/docs/operations-manual/5/configuration/plugins/).\n\n!!! note\n    The methods `withLabsPlugins(Neo4jLabsPlugin...)` and `withLabsPlugins(String... plugins)` are deprecated.\n    Please the method `withPlugins(String... plugins)`.\n\n<!--codeinclude-->\n[Configure Neo4j Labs Plugins](../../../modules/neo4j/src/test/java/org/testcontainers/neo4j/Neo4jContainerTest.java) inside_block:configureLabsPlugins\n<!--/codeinclude-->\n\n\n### Start the container with a predefined database\n\nIf you have an existing database (`graph.db`) you want to work with, copy it over to the container like this:\n\n<!--codeinclude-->\n[Copy database](../../../modules/neo4j/src/test/java/org/testcontainers/neo4j/Neo4jContainerTest.java) inside_block:copyDatabase\n<!--/codeinclude-->\n\n!!! note\n    The `withDatabase` method will only work with Neo4j 3.5 and throw an exception if used in combination with a newer version.\n\n## Choose your Neo4j license\n\nIf you need the Neo4j enterprise license, you can declare your Neo4j container like this:\n\n<!--codeinclude-->\n[Enterprise edition](../../../modules/neo4j/src/test/java/org/testcontainers/neo4j/Neo4jContainerTest.java) inside_block:enterpriseEdition\n<!--/codeinclude-->\n\nThis creates a Testcontainers based on the Docker image build with the Enterprise version of Neo4j 4.4. \nThe call to `withEnterpriseEdition` adds the required environment variable that you accepted the terms and condition of the enterprise version.\nYou accept those by adding a file named `container-license-acceptance.txt` to the root of your classpath containing the text `neo4j:4.4-enterprise` in one line.\n\nIf you are planning to run a newer Neo4j 5.x enterprise edition image, you have to manually define the proper enterprise image (e.g. `neo4j:5-enterprise`)\nand set the environment variable `NEO4J_ACCEPT_LICENSE_AGREEMENT` by adding `.withEnv(\"NEO4J_ACCEPT_LICENSE_AGREEMENT\", \"yes\")` to your container definition.\n\nYou'll find more information about licensing Neo4j here: [About Neo4j Licenses](https://neo4j.com/licensing/).\n\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-neo4j:{{latest_version}}\"\n    ```\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-neo4j</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n\n!!! hint\n    Add the Neo4j Java driver if you plan to access the Testcontainers via Bolt:\n    \n    === \"Gradle\"\n        ```groovy\n        compile \"org.neo4j.driver:neo4j-java-driver:4.4.13\"\n        ```\n    \n    === \"Maven\"\n        ```xml\n        <dependency>\n            <groupId>org.neo4j.driver</groupId>\n            <artifactId>neo4j-java-driver</artifactId>\n            <version>4.4.13</version>\n        </dependency>\n        ```\n"
  },
  {
    "path": "docs/modules/databases/oceanbase.md",
    "content": "# OceanBase Module\n\nTestcontainers module for [OceanBase](https://hub.docker.com/r/oceanbase/oceanbase-ce)\n\n## Usage example\n\nYou can start an OceanBase container instance from any Java application by using:\n\n<!--codeinclude-->\n[Container definition](../../../modules/oceanbase/src/test/java/org/testcontainers/oceanbase/SimpleOceanBaseCETest.java) inside_block:container\n<!--/codeinclude-->\n\nSee [Database containers](./index.md) for documentation and usage that is common to all relational database container types.\n\n### Testcontainers JDBC URL\n\n`jdbc:tc:oceanbasece:4.2.1-lts:///databasename`\n\nSee [JDBC](./jdbc.md) for documentation.\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-oceanbase:{{latest_version}}\"\n    ```\n\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-oceanbase</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n\n!!! hint\nAdding this Testcontainers library JAR will not automatically add a database driver JAR to your project. You should ensure that your project also has a suitable database driver as a dependency.\n"
  },
  {
    "path": "docs/modules/databases/oraclefree.md",
    "content": "# Oracle Database Free Module\n\nTestcontainers module for [Oracle Free](https://hub.docker.com/r/gvenzl/oracle-free)\n\n## Usage example\n\nYou can start an Oracle-Free container instance from any Java application by using:\n\n<!--codeinclude-->\n[Container creation](../../../modules/oracle-free/src/test/java/org/testcontainers/junit/oracle/SimpleOracleTest.java) inside_block:container\n<!--/codeinclude-->\n\nSee [Database containers](./index.md) for documentation and usage that is common to all relational database container types.\n\n### Testcontainers JDBC URL\n\n`jdbc:tc:oracle:21-slim-faststart:///databasename`\n\nSee [JDBC](./jdbc.md) for documentation.\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-oracle-free:{{latest_version}}\"\n    ```\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-oracle-free</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n\n!!! hint\n    Adding this Testcontainers library JAR will not automatically add a database driver JAR to your project. You should ensure that your project also has a suitable database driver as a dependency.\n\n\n"
  },
  {
    "path": "docs/modules/databases/oraclexe.md",
    "content": "# Oracle-XE Module\n\nTestcontainers module for [Oracle XE](https://hub.docker.com/r/gvenzl/oracle-xe)\n\n## Usage example\n\nYou can start an Oracle-XE container instance from any Java application by using:\n\n<!--codeinclude-->\n[Container creation](../../../modules/oracle-xe/src/test/java/org/testcontainers/junit/oracle/SimpleOracleTest.java) inside_block:container\n<!--/codeinclude-->\n\nSee [Database containers](./index.md) for documentation and usage that is common to all relational database container types.\n\n### Testcontainers JDBC URL\n\n`jdbc:tc:oracle:21-slim-faststart:///databasename`\n\nSee [JDBC](./jdbc.md) for documentation.\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-oracle-xe:{{latest_version}}\"\n    ```\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-oracle-xe</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n\n!!! hint\n    Adding this Testcontainers library JAR will not automatically add a database driver JAR to your project. You should ensure that your project also has a suitable database driver as a dependency.\n\n\n"
  },
  {
    "path": "docs/modules/databases/orientdb.md",
    "content": "# OrientDB Module\n\nTestcontainers module for [OrientDB](https://hub.docker.com/_/orientdb/)\n\n## Usage example\n\nYou can start an OrientDB container instance from any Java application by using:\n\n<!--codeinclude-->\n[Container creation](../../../modules/orientdb/src/test/java/org/testcontainers/orientdb/OrientDBContainerTest.java) inside_block:container\n<!--/codeinclude-->\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-orientdb:{{latest_version}}\"\n    ```\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-orientdb</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n\n!!! hint\n    Add the OrientDB Java client if you plan to access the Testcontainer:\n    \n    === \"Gradle\"\n        ```groovy\n        compile \"com.orientechnologies:orientdb-client:3.0.24\"\n        ```\n    \n    === \"Maven\"\n        ```xml\n        <dependency>\n            <groupId>com.orientechnologies</groupId>\n            <artifactId>orientdb-client</artifactId>\n            <version>3.0.24</version>\n        </dependency>\n        ```\n    \n\n\n\n"
  },
  {
    "path": "docs/modules/databases/postgres.md",
    "content": "# Postgres Module\n\nTestcontainers module for [PostgresSQL](https://hub.docker.com/_/postgres)\n\n## Usage example\n\nYou can start a PostgreSQL container instance from any Java application by using:\n\n<!--codeinclude-->\n[Container creation](../../../modules/postgresql/src/test/java/org/testcontainers/postgresql/PostgreSQLContainerTest.java) inside_block:container\n<!--/codeinclude-->\n\nSee [Database containers](./index.md) for documentation and usage that is common to all relational database container types.\n\n### Testcontainers JDBC URL\n\n* PostgreSQL: `jdbc:tc:postgresql:9.6.8:///databasename`\n* PostGIS: `jdbc:tc:postgis:9.6-2.5:///databasename`\n* TimescaleDB: `jdbc:tc:timescaledb:2.1.0-pg13:///databasename`\n* PGvector: `jdbc:tc:pgvector:pg16:///databasename`\n\nSee [JDBC](./jdbc.md) for documentation.\n\n## Compatible images\n\n`PostgreSQLContainer` can also be used with the following images:\n\n* [pgvector/pgvector](https://hub.docker.com/r/pgvector/pgvector)\n\n<!--codeinclude-->\n[Using pgvector](../../../modules/postgresql/src/test/java/org/testcontainers/postgresql/CompatibleImageTest.java) inside_block:pgvectorContainer\n<!--/codeinclude-->\n\n* [postgis/postgis](https://registry.hub.docker.com/r/postgis/postgis)\n\n<!--codeinclude-->\n[Using PostGIS](../../../modules/postgresql/src/test/java/org/testcontainers/postgresql/CompatibleImageTest.java) inside_block:postgisContainer\n<!--/codeinclude-->\n\n* [timescale/timescaledb](https://hub.docker.com/r/timescale/timescaledb)\n\n<!--codeinclude-->\n[Using TimescaleDB](../../../modules/postgresql/src/test/java/org/testcontainers/postgresql/CompatibleImageTest.java) inside_block:timescaledbContainer\n<!--/codeinclude-->\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-postgresql:{{latest_version}}\"\n    ```\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-postgresql</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n\n!!! hint\n    Adding this Testcontainers library JAR will not automatically add a database driver JAR to your project. You should ensure that your project also has a suitable database driver as a dependency.\n\n\n"
  },
  {
    "path": "docs/modules/databases/presto.md",
    "content": "# Presto Module\n\n!!! note\n    This module is deprecated, use Trino module.\n\nSee [Database containers](./index.md) for documentation and usage that is common to all database container types.\n\n## Usage example\n\nRunning Presto as a stand-in for in a test:\n\n```java\npublic class SomeTest {\n\n    @Rule\n    public PrestoContainer presto = new PrestoContainer();\n    \n    @Test\n    public void someTestMethod() {\n        String url = presto.getJdbcUrl();\n\n        ... create a connection and run test as normal\n```\n\nPresto comes with several catalogs preconfigured. Most useful ones for testing are\n\n* `tpch` catalog using the [Presto TPCH Connector](https://prestosql.io/docs/current/connector/tpch.html).\n  This is a read-only catalog that defines standard TPCH schema, so is available for querying without a need\n  to create any tables.\n* `memory` catalog using the [Presto Memory Connector](https://prestosql.io/docs/current/connector/memory.html).\n  This catalog can be used for creating schemas and tables and does not require any storage, as everything\n  is stored fully in-memory.\n\nExample test using the `tpch` and `memory` catalogs:\n\n```java\npublic class SomeTest {\n    @Rule\n    public PrestoContainer prestoSql = new PrestoContainer();\n\n    @Test\n    public void queryMemoryAndTpchConnectors() throws SQLException {\n        try (Connection connection = prestoSql.createConnection();\n             Statement statement = connection.createStatement()) {\n            // Prepare data\n            statement.execute(\"CREATE TABLE memory.default.table_with_array AS SELECT 1 id, ARRAY[1, 42, 2, 42, 4, 42] my_array\");\n\n            // Query Presto using newly created table and a builtin connector\n            try (ResultSet resultSet = statement.executeQuery(\"\" +\n                \"SELECT nationkey, element \" +\n                \"FROM tpch.tiny.nation \" +\n                \"JOIN memory.default.table_with_array twa ON nationkey = twa.id \" +\n                \"LEFT JOIN UNNEST(my_array) a(element) ON true \" +\n                \"ORDER BY element OFFSET 1 FETCH NEXT 3 ROWS WITH TIES \")) {\n                List<Integer> actualElements = new ArrayList<>();\n                while (resultSet.next()) {\n                    actualElements.add(resultSet.getInt(\"element\"));\n                }\n                Assert.assertEquals(Arrays.asList(2, 4, 42, 42, 42), actualElements);\n            }\n        }\n    }\n}\n```\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-presto:{{latest_version}}\"\n    ```\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-presto</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n\n!!! hint\n    Adding this Testcontainers library JAR will not automatically add the Presto JDBC driver JAR to your project.\n    You should ensure that your project has the Presto JDBC driver as a dependency, if you plan on using it.\n    Refer to [Presto project download page](https://prestosql.io/download.html) for instructions.\n\n\n"
  },
  {
    "path": "docs/modules/databases/questdb.md",
    "content": "# QuestDB Module\n\nTestcontainers module for [QuestDB](https://hub.docker.com/r/questdb/questdb)\n\n## Usage example\n\nYou can start a QuestDB container instance from any Java application by using:\n\n<!--codeinclude-->\n[Container creation](../../../modules/questdb/src/test/java/org/testcontainers/junit/questdb/SimpleQuestDBTest.java) inside_block:container\n<!--/codeinclude-->\n\nSee [Database containers](./index.md) for documentation and usage that is common to all relational database container\ntypes.\n\n### Testcontainers JDBC URL\n\n`jdbc:tc:questdb:6.5.3:///databasename`\n\nSee [JDBC](./jdbc.md) for documentation.\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n\n```groovy\ntestImplementation \"org.testcontainers:testcontainers-questdb:{{latest_version}}\"\n```\n\n=== \"Maven\"\n\n```xml\n\n<dependency>\n    <groupId>org.testcontainers</groupId>\n    <artifactId>testcontainers-questdb</artifactId>\n    <version>{{latest_version}}</version>\n    <scope>test</scope>\n</dependency>\n```\n"
  },
  {
    "path": "docs/modules/databases/r2dbc.md",
    "content": "# R2DBC support\n\nYou can obtain a temporary database in one of two ways:\n\n * **Using a specially modified R2DBC URL**: after making a very simple modification to your system's R2DBC URL string, Testcontainers will provide a disposable stand-in database that can be used without requiring modification to your application code.\n * **JUnit @Rule/@ClassRule**: this mode starts a database inside a container before your tests and tears it down afterwards.\n\n## Database containers launched via R2DBC URL scheme\n\nAs long as you have Testcontainers and the appropriate R2DBC driver on your classpath, you can simply modify regular R2DBC connection URLs to get a fresh containerized instance of the database each time your application starts up.\n\nThe started container will be terminated when the `ConnectionFactory` is closed.\n\n!!! warning\n    Both the database module (e.g. `org.testcontainers:testcontainers-mysql`) **and** `org.testcontainers:testcontainers-r2dbc` need to be on your application's classpath at runtime.\n\n**Original URL**: `r2dbc:mysql://localhost:3306/databasename`\n\n1. Insert `tc:` after `r2dbc:` as follows. Note that the hostname, port and database name will be ignored; you can leave these as-is or set them to any value.\n1. Specify the mandatory Docker tag of the database's official image that you want using a `TC_IMAGE_TAG` query parameter.\n\n**Note that, unlike Testcontainers' JDBC URL support, it is not possible to specify an image tag in the 'scheme' part of the URL, and it is always necessary to specify a tag using `TC_IMAGE_TAG`.**\n\nSo that the URL becomes:  \n`r2dbc:tc:mysql:///databasename?TC_IMAGE_TAG=8.0.36`\n\n!!! note\n    We will use `///` (host-less URIs) from now on to emphasis the unimportance of the `host:port` pair.  \n    From Testcontainers' perspective, `r2dbc:mysql://localhost:3306/databasename` and `r2dbc:mysql:///databasename` is the same URI.\n\n!!! warning\n    If you're using the R2DBC URL support, there is no need to instantiate an instance of the container - Testcontainers will do it automagically.\n\n### R2DBC URL examples\n\n#### Using ClickHouse\n\n`r2dbc:tc:clickhouse:///databasename?TC_IMAGE_TAG=21.11.11-alpine`\n\n#### Using MySQL\n\n`r2dbc:tc:mysql:///databasename?TC_IMAGE_TAG=8.0.36`\n\n#### Using MariaDB\n\n`r2dbc:tc:mariadb:///databasename?TC_IMAGE_TAG=10.3.39`\n\n#### Using PostgreSQL\n\n`r2dbc:tc:postgresql:///databasename?TC_IMAGE_TAG=9.6.8`\n\n#### Using MSSQL:\n\n`r2dbc:tc:sqlserver:///?TC_IMAGE_TAG=2017-CU12`\n\n#### Using Oracle:\n\n`r2dbc:tc:oracle:///?TC_IMAGE_TAG=21-slim-faststart`\n\n## Obtaining `ConnectionFactoryOptions` from database container objects\n\nIf you already have an instance of the database container, you can get an instance of `ConnectionFactoryOptions` from it:\n<!--codeinclude--> \n[Creating `ConnectionFactoryOptions` from an instance)](../../../modules/postgresql/src/test/java/org/testcontainers/containers/PostgreSQLR2DBCDatabaseContainerTest.java) inside_block:get_options\n<!--/codeinclude-->\n"
  },
  {
    "path": "docs/modules/databases/scylladb.md",
    "content": "# ScyllaDB\n\nTestcontainers module for [ScyllaDB](https://hub.docker.com/r/scylladb/scylla)\n\n## ScyllaDB's usage examples\n\nYou can start a ScyllaDB container instance from any Java application by using:\n\n<!--codeinclude-->\n[Create container](../../../modules/scylladb/src/test/java/org/testcontainers/scylladb/ScyllaDBContainerTest.java) inside_block:container\n<!--/codeinclude-->\n\n<!--codeinclude-->\n[Custom config file](../../../modules/scylladb/src/test/java/org/testcontainers/scylladb/ScyllaDBContainerTest.java) inside_block:customConfiguration\n<!--/codeinclude-->\n\n### Building CqlSession\n\n<!--codeinclude-->\n[Using CQL port](../../../modules/scylladb/src/test/java/org/testcontainers/scylladb/ScyllaDBContainerTest.java) inside_block:session\n<!--/codeinclude-->\n\n<!--codeinclude-->\n[Using SSL](../../../modules/scylladb/src/test/java/org/testcontainers/scylladb/ScyllaDBContainerTest.java) inside_block:sslContext\n<!--/codeinclude-->\n\n<!--codeinclude-->\n[Using Shard Awareness port](../../../modules/scylladb/src/test/java/org/testcontainers/scylladb/ScyllaDBContainerTest.java) inside_block:shardAwarenessSession\n<!--/codeinclude-->\n\n### Alternator\n\n<!--codeinclude-->\n[Enabling Alternator](../../../modules/scylladb/src/test/java/org/testcontainers/scylladb/ScyllaDBContainerTest.java) inside_block:alternator\n<!--/codeinclude-->\n\n<!--codeinclude-->\n[DynamoDbClient with Alternator](../../../modules/scylladb/src/test/java/org/testcontainers/scylladb/ScyllaDBContainerTest.java) inside_block:dynamodDbClient\n<!--/codeinclude-->\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-scylladb:{{latest_version}}\"\n    ```\n\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-scylladb</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n"
  },
  {
    "path": "docs/modules/databases/tidb.md",
    "content": "# TiDB Module\n\nTestcontainers module for [TiDB](https://hub.docker.com/r/pingcap/tidb)\n\n## Usage example\n\nYou can start a TiDB container instance from any Java application by using:\n\n<!--codeinclude-->\n[Container creation](../../../modules/tidb/src/test/java/org/testcontainers/tidb/TiDBContainerTest.java) inside_block:container\n<!--/codeinclude-->\n\nSee [Database containers](./index.md) for documentation and usage that is common to all relational database container types.\n\n### Testcontainers JDBC URL\n\n`jdbc:tc:tidb:v6.1.0:///databasename`\n\nSee [JDBC](./jdbc.md) for documentation.\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-tidb:{{latest_version}}\"\n    ```\n\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-tidb</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n\n!!! hint\n    Adding this Testcontainers library JAR will not automatically add a database driver JAR to your project. You should ensure that your project also has a suitable database driver as a dependency.\n"
  },
  {
    "path": "docs/modules/databases/timeplus.md",
    "content": "# Timeplus Module\n\nTestcontainers module for [Timeplus](https://hub.docker.com/r/timeplus/timeplusd)\n\n## Usage example\n\nYou can start a Timeplus container instance from any Java application by using:\n\n<!--codeinclude-->\n[Container creation](../../../modules/timeplus/src/test/java/org/testcontainers/timeplus/TimeplusContainerTest.java) inside_block:container\n<!--/codeinclude-->\n\n### Testcontainers JDBC URL\n\n`jdbc:tc:timeplus:2.3.21:///databasename`\n\nSee [JDBC](./jdbc.md) for documentation.\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-timeplus:{{latest_version}}\"\n    ```\n\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-timeplus</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n\n!!! hint\n    Adding this Testcontainers library JAR will not automatically add a database driver JAR to your project. You should ensure that your project also has a suitable database driver as a dependency.\n\n"
  },
  {
    "path": "docs/modules/databases/trino.md",
    "content": "# Trino Module\n\nTestcontainers module for [Trino](https://hub.docker.com/r/trinodb/trino)\n\n## Usage example\n\nYou can start a Trino container instance from any Java application by using:\n\n<!--codeinclude-->\n[Container creation](../../../modules/trino/src/test/java/org/testcontainers/trino/TrinoContainerTest.java) inside_block:container\n<!--/codeinclude-->\n\nSee [Database containers](./index.md) for documentation and usage that is common to all database container types.\n\n### Testcontainers JDBC URL\n\n`jdbc:tc:trino:352:///defaultname`\n\nSee [JDBC](./jdbc.md) for documentation.\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-trino:{{latest_version}}\"\n    ```\n\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-trino</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n\n!!! hint\n    Adding this Testcontainers library JAR will not automatically add the Trino JDBC driver JAR to your project.\n    You should ensure that your project has the Trino JDBC driver as a dependency, if you plan on using it.\n    Refer to [Trino project download page](https://trino.io/download.html) for instructions.\n\n\n"
  },
  {
    "path": "docs/modules/databases/yugabytedb.md",
    "content": "# YugabyteDB Module\n\nTestcontainers module for [YugabyteDB](https://hub.docker.com/r/yugabytedb/yugabyte)\n\nSee [Database containers](./index.md) for documentation and usage that is common to all database container types.\n\nYugabyteDB supports two APIs.\n\n- Yugabyte Structured Query Language [YSQL](https://docs.yugabyte.com/latest/api/ysql/) is a fully-relational API that is built by the PostgreSQL code\n- Yugabyte Cloud Query Language [YCQL](https://docs.yugabyte.com/latest/api/ycql/) is a semi-relational SQL API that has its roots in the Cassandra Query Language\n\n## Usage example\n\n### YSQL API \n\n<!--codeinclude-->\n[Creating a YSQL container](../../../modules/yugabytedb/src/test/java/org/testcontainers/junit/yugabytedb/YugabyteDBYSQLTest.java) inside_block:creatingYSQLContainer\n<!--/codeinclude-->\n\n### Testcontainers JDBC URL\n\n`jdbc:tc:yugabyte:2.14.4.0-b26:///databasename`\n\nSee [JDBC](./jdbc.md) for documentation.\n\n### YCQL API\n\n<!--codeinclude-->\n[Creating a YCQL container](../../../modules/yugabytedb/src/test/java/org/testcontainers/junit/yugabytedb/YugabyteDBYCQLTest.java) inside_block:creatingYCQLContainer\n<!--/codeinclude-->\n\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-yugabytedb:{{latest_version}}\"\n    ```\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-yugabytedb</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n\n!!! hint\n    Adding this Testcontainers library JAR will not automatically add the Yugabytedb driver JAR to your project.\n    You should ensure that your project has the Yugabytedb driver as a dependency, if you plan on using it.\n    Refer to the driver page [YSQL](https://docs.yugabyte.com/latest/integrations/jdbc-driver/) and [YCQL](https://docs.yugabyte.com/latest/reference/drivers/ycql-client-drivers/) for instructions.\n"
  },
  {
    "path": "docs/modules/docker_compose.md",
    "content": "# Docker Compose Module\n\n## Benefits\n\nSimilar to generic container support, it's also possible to run a bespoke set of services specified in a \n`docker-compose.yml` file. \n\nThis is especially useful for projects where Docker Compose is already used in development \nor other environments to define services that an application may be dependent upon.\n\nThe `ComposeContainer` leverages [Compose V2](https://www.docker.com/blog/announcing-compose-v2-general-availability/),\nmaking it easy to use the same dependencies from the development environment within tests.\n\n## Example\n\nA single class `ComposeContainer`, defined based on a `docker-compose.yml` file, \nshould be sufficient to launch any number of services required by our tests:\n\n<!--codeinclude-->\n[Create a ComposeContainer](../../core/src/test/java/org/testcontainers/junit/ComposeContainerTest.java) inside_block:composeContainerConstructor\n<!--/codeinclude-->\n\n!!! note\n    Make sure the service names use a `-` rather than `_` as separator.\n\nIn this example, Docker Compose file should have content such as:\n```yaml\nservices:\n  redis:\n    image: redis\n  db:\n    image: mysql:8.0.36\n```\n\nNote that it is not necessary to define ports to be exposed in the YAML file, \nas this would inhibit the reuse/inclusion of the file in other contexts.\n\nInstead, Testcontainers will spin up a small `ambassador` container, \nwhich will proxy between the Compose-managed containers and ports that are accessible to our tests. \n\n## ComposeContainer vs DockerComposeContainer \n\nSo far, we discussed `ComposeContainer`, which supports docker compose [version 2](https://www.docker.com/blog/announcing-compose-v2-general-availability/). \n\nOn the other hand, `DockerComposeContainer` utilizes Compose V1, which has been marked deprecated by Docker.\n\nThe two APIs are quite similar, and most examples provided on this page can be applied to both of them.\n\n## Accessing a Container\n\n`ComposeContainer` provides methods for discovering how your tests can interact with the containers:\n\n* `getServiceHost(serviceName, servicePort)` returns the IP address where the container is listening (via an ambassador\n    container)\n* `getServicePort(serviceName, servicePort)` returns the Docker mapped port for a port that has been exposed (via an\n    ambassador container)\n\nLet's use this API to create the URL that will enable our tests to access the Redis service:\n<!--codeinclude-->\n[Access a Service's host and port](../../core/src/test/java/org/testcontainers/junit/ComposeContainerTest.java) inside_block:getServiceHostAndPort\n<!--/codeinclude-->\n\n## Wait Strategies and Startup Timeouts\nOrdinarily Testcontainers will wait for up to 60 seconds for each exposed container's first mapped network port to start listening.\nThis simple measure provides a basic check whether a container is ready for use.\n\nThere are overloaded `withExposedService` methods that take a `WaitStrategy` \nwhere we can specify a timeout strategy per container. \n\nWe can either use the fluent API to crate a [custom strategy](../features/startup_and_waits.md) or use one of the already existing ones, \naccessible via the static factory methods from of the `Wait` class.\n\nFor instance, we can wait for exposed port and set a custom timeout:\n<!--codeinclude-->\n[Wait for the exposed port and use a custom timeout](../../core/src/test/java/org/testcontainers/junit/ComposeContainerWithWaitStrategies.java) inside_block:composeContainerWaitForPortWithTimeout\n<!--/codeinclude-->\n\nNeedless to say, we can define different strategies for each service in our Docker Compose setup. \n\nFor example, our Redis container can wait for a successful redis-cli command, \nwhile our db service waits for a specific log message:\n\n<!--codeinclude-->\n[Wait for a custom command and a log message](../../core/src/test/java/org/testcontainers/junit/ComposeContainerWithWaitStrategies.java) inside_block:composeContainerWithCombinedWaitStrategies\n<!--/codeinclude-->\n\n\n\n## The 'Local Compose' Mode\n\nWe can override Testcontainers' default behaviour and make it use a `docker-compose` binary installed on the local machine. \n\nThis will generally yield an experience that is closer to running _docker compose_ locally, \nwith the caveat that Docker Compose needs to be present on dev and CI machines.\n\n<!--codeinclude-->\n[Use ComposeContainer in 'Local Compose' mode](../../core/src/test/java/org/testcontainers/containers/ComposeProfilesOptionTest.java) inside_block:composeContainerWithLocalCompose\n<!--/codeinclude-->\n\n## Build Working Directory\n\nWe can select what files should be copied only via `withCopyFilesInContainer`:\n\n<!--codeinclude-->\n[Use ComposeContainer in 'Local Compose' mode](../../core/src/test/java/org/testcontainers/junit/ComposeContainerWithCopyFilesTest.java) inside_block:composeContainerWithCopyFiles\n<!--/codeinclude-->\n\nIn this example, only docker compose and env files are copied over into the container that will run the Docker Compose file.  \nBy default, all files in the same directory as the compose file are copied over.\n\nWe can use file and directory references. \nThey are always resolved relative to the directory where the compose file resides.\n\n!!! note\n    This can be used with `DockerComposeContainer` and `ComposeContainer`, but **only in the containerized Compose (not with `Local Compose` mode)**.\n\n## Using private repositories in Docker compose\nWhen Docker Compose is used in container mode (not local), it needs to be made aware of Docker\nsettings for private repositories. \nBy default, those setting are located in `$HOME/.docker/config.json`. \n\nThere are 3 ways to specify location of the `config.json` for Docker Compose:\n\n* Use `DOCKER_CONFIG_FILE` environment variable. \n\n    `export DOCKER_CONFIG_FILE=/some/location/config.json`\n\n* Use `dockerConfigFile` java property\n    \n    `java -DdockerConfigFile=/some/location/config.json`\n\n* Don't specify anything. In this case default location `$HOME/.docker/config.json`, if present, will be used.\n\n!!! note \"Docker Compose and Credential Store / Credential Helpers\"\n    Modern Docker tends to store credentials using the credential store/helper mechanism rather than storing credentials in Docker's configuration file. So, your `config.json` may look something like:\n    \n    ```json\n    {\n      \"auths\" : {\n        \"https://index.docker.io/v1/\" : {\n        }\n      },\n      \"credsStore\" : \"osxkeychain\"\n    }\n    ```\n    \n    When run inside a container, Docker Compose cannot access the Keychain, thus making the configuration useless. \n    To work around this problem, there are two options:\n    \n    ##### Putting auths in a config file\n    Create a `config.json` in separate location with real authentication keys, like:\n    \n    ```json\n    {\n      \"auths\" : {\n        \"https://index.docker.io/v1/\" : {\n         \"auth\": \"QWEADSZXC...\"\n        }\n      },\n      \"credsStore\" : \"osxkeychain\"\n    }\n    ```\n    and specify the location to Testcontainers using any of the two first methods from above.\n    \n    ##### Using 'local compose' mode\n    \n    [Local Compose mode](#local-compose-mode), mentioned above, will allow compose to directly access the Docker auth system (to the same extent that running the `docker-compose` CLI manually works).\n    \n\n## Adding this module to your project dependencies\n\n*Docker Compose support is part of the core Testcontainers library.*\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers:{{latest_version}}\"\n    ```\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n\n"
  },
  {
    "path": "docs/modules/docker_mcp_gateway.md",
    "content": "# Docker MCP Gateway\n\nTestcontainers module for [Docker MCP Gateway](https://hub.docker.com/r/docker/mcp-gateway).\n\n## DockerMcpGatewayContainer's usage examples\n\nYou can start a Docker MCP Gateway container instance from any Java application by using:\n\n<!--codeinclude-->\n[Create a DockerMcpGatewayContainer](../../core/src/test/java/org/testcontainers/containers/DockerMcpGatewayContainerTest.java) inside_block:container\n<!--/codeinclude-->\n\n## Adding this module to your project dependencies\n\n*Docker MCP Gateway support is part of the core Testcontainers library.*\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers:{{latest_version}}\"\n    ```\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n\n"
  },
  {
    "path": "docs/modules/docker_model_runner.md",
    "content": "# Docker Model Runner\n\nThis module helps connect to [Docker Model Runner](https://docs.docker.com/desktop/features/model-runner/)\nprovided by Docker Desktop 4.40.0.\n\n## DockerModelRunner's usage examples\n\nYou can start a Docker Model Runner proxy container instance from any Java application by using:\n\n<!--codeinclude-->\n[Create a DockerModelRunnerContainer](../../core/src/test/java/org/testcontainers/containers/DockerModelRunnerContainerTest.java) inside_block:container\n<!--/codeinclude-->\n\n### Pulling the model\n\nPulling the model is as simple as:\n\n<!--codeinclude-->\n[Pull model](../../core/src/test/java/org/testcontainers/containers/DockerModelRunnerContainerTest.java) inside_block:pullModel\n<!--/codeinclude-->\n\n## Adding this module to your project dependencies\n\n*Docker Model Runner support is part of the core Testcontainers library.*\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers:{{latest_version}}\"\n    ```\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n\n"
  },
  {
    "path": "docs/modules/elasticsearch.md",
    "content": "# Elasticsearch container\n\nThis module helps running [elasticsearch](https://www.elastic.co/products/elasticsearch) using\nTestcontainers.\n\nNote that it's based on the [official Docker image](https://www.elastic.co/guide/en/elasticsearch/reference/current/docker.html) provided by elastic.\n\n## Usage example\n\nYou can start an elasticsearch container instance from any Java application by using:\n\n<!--codeinclude-->\n[HttpClient](../../modules/elasticsearch/src/test/java/org/testcontainers/elasticsearch/ElasticsearchContainerTest.java) inside_block:httpClientContainer7\n[HttpClient with Elasticsearch 8](../../modules/elasticsearch/src/test/java/org/testcontainers/elasticsearch/ElasticsearchContainerTest.java) inside_block:httpClientContainer8\n[HttpClient with Elasticsearch 8 and SSL disabled](../../modules/elasticsearch/src/test/java/org/testcontainers/elasticsearch/ElasticsearchContainerTest.java) inside_block:httpClientContainerNoSSL8\n[TransportClient](../../modules/elasticsearch/src/test/java/org/testcontainers/elasticsearch/ElasticsearchContainerTest.java) inside_block:transportClientContainer\n<!--/codeinclude-->\n\n\nNote that if you are still using the [TransportClient](https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/transport-client.html)\n(not recommended as it is deprecated), the default cluster name is set to `docker-cluster` so you need to change `cluster.name` setting\nor set `client.transport.ignore_cluster_name` to `true`.\n\n## Secure your Elasticsearch cluster\n\nThe default distribution of Elasticsearch comes with the basic license which contains security feature.\nYou can turn on security by providing a password:\n\n<!--codeinclude-->\n[HttpClient](../../modules/elasticsearch/src/test/java/org/testcontainers/elasticsearch/ElasticsearchContainerTest.java) inside_block:httpClientSecuredContainer\n<!--/codeinclude-->\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-elasticsearch:{{latest_version}}\"\n    ```\n\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-elasticsearch</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n"
  },
  {
    "path": "docs/modules/gcloud.md",
    "content": "# GCloud Module\n\n!!! note\n    This module is INCUBATING. While it is ready for use and operational in the current version of Testcontainers, it is possible that it may receive breaking changes in the future. See [our contributing guidelines](/contributing/#incubating-modules) for more information on our incubating modules policy.\n\nTestcontainers module for the Google Cloud Platform's [Cloud SDK](https://cloud.google.com/sdk/).\n\nCurrently, the module supports `BigQuery`, `Bigtable`, `Datastore`, `Firestore`, `Spanner`, and `Pub/Sub` emulators. In order to use it, you should use the following classes:\n\nClass | Container Image\n-|-\nBigQueryEmulatorContainer | [ghcr.io/goccy/bigquery-emulator](https://ghcr.io/goccy/bigquery-emulator)\nBigtableEmulatorContainer | [gcr.io/google.com/cloudsdktool/google-cloud-cli:emulators](https://gcr.io/google.com/cloudsdktool/google-cloud-cli)\nDatastoreEmulatorContainer | [gcr.io/google.com/cloudsdktool/google-cloud-cli:emulators](https://gcr.io/google.com/cloudsdktool/google-cloud-cli)\nFirestoreEmulatorContainer | [gcr.io/google.com/cloudsdktool/google-cloud-cli:emulators](https://gcr.io/google.com/cloudsdktool/google-cloud-cli)\nSpannerEmulatorContainer | [gcr.io/cloud-spanner-emulator/emulator](https://gcr.io/cloud-spanner-emulator/emulator)\nPubSubEmulatorContainer | [gcr.io/google.com/cloudsdktool/google-cloud-cli:emulators](https://gcr.io/google.com/cloudsdktool/google-cloud-cli)\n\n## Usage example\n\n### BigQuery\n\nStart BigQuery Emulator during a test:\n\n<!--codeinclude-->\n[Starting a BigQuery Emulator container](../../modules/gcloud/src/test/java/org/testcontainers/gcloud/BigQueryEmulatorContainerTest.java) inside_block:emulatorContainer\n<!--/codeinclude-->\n\n<!--codeinclude-->\n[Creating BigQuery Client](../../modules/gcloud/src/test/java/org/testcontainers/gcloud/BigQueryEmulatorContainerTest.java) inside_block:bigQueryClient\n<!--/codeinclude-->\n\n### Bigtable\n\nStart Bigtable Emulator during a test:\n\n<!--codeinclude-->\n[Starting a Bigtable Emulator container](../../modules/gcloud/src/test/java/org/testcontainers/gcloud/BigtableEmulatorContainerTest.java) inside_block:emulatorContainer\n<!--/codeinclude-->\n\nCreate a test Bigtable table in the Emulator:\n\n<!--codeinclude-->\n[Create a test table](../../modules/gcloud/src/test/java/org/testcontainers/gcloud/BigtableEmulatorContainerTest.java) inside_block:createTable\n<!--/codeinclude-->\n\nTest against the Emulator:\n\n<!--codeinclude-->\n[Testing with a Bigtable Emulator container](../../modules/gcloud/src/test/java/org/testcontainers/gcloud/BigtableEmulatorContainerTest.java) inside_block:testWithEmulatorContainer\n<!--/codeinclude-->\n\n### Datastore\n\nStart Datastore Emulator during a test:\n\n<!--codeinclude-->\n[Starting a Datastore Emulator container](../../modules/gcloud/src/test/java/org/testcontainers/gcloud/DatastoreEmulatorContainerTest.java) inside_block:creatingDatastoreEmulatorContainer\n<!--/codeinclude-->\n\nAnd test against the Emulator:\n\n<!--codeinclude-->\n[Testing with a Datastore Emulator container](../../modules/gcloud/src/test/java/org/testcontainers/gcloud/DatastoreEmulatorContainerTest.java) inside_block:startingDatastoreEmulatorContainer\n<!--/codeinclude-->\n\nSee more examples:\n\n * [Full sample code](https://github.com/testcontainers/testcontainers-java/tree/main/modules/gcloud/src/test/java/org/testcontainers/gcloud/DatastoreEmulatorContainerTest.java)\n * [With Spring Boot](https://github.com/saturnism/testcontainers-gcloud-examples/tree/main/springboot/datastore-example/src/test/java/com/example/springboot/datastore) \n\n### Firestore \n\nStart Firestore Emulator during a test:\n\n<!--codeinclude-->\n[Starting a Firestore Emulator container](../../modules/gcloud/src/test/java/org/testcontainers/gcloud/FirestoreEmulatorContainerTest.java) inside_block:emulatorContainer\n<!--/codeinclude-->\n\nAnd test against the Emulator:\n\n<!--codeinclude-->\n[Testing with a Firestore Emulator container](../../modules/gcloud/src/test/java/org/testcontainers/gcloud/FirestoreEmulatorContainerTest.java) inside_block:testWithEmulatorContainer\n<!--/codeinclude-->\n\nSee more examples:\n\n * [Full sample code](https://github.com/testcontainers/testcontainers-java/tree/main/modules/gcloud/src/test/java/org/testcontainers/gcloud/FirestoreEmulatorContainerTest.java)\n * [With Spring Boot](https://github.com/saturnism/testcontainers-gcloud-examples/tree/main/springboot/firestore-example/src/test/java/com/example/springboot/firestore/FirestoreIntegrationTests.java)\n\n### Spanner \n\nStart Spanner Emulator during a test:\n\n<!--codeinclude-->\n[Starting a Spanner Emulator container](../../modules/gcloud/src/test/java/org/testcontainers/gcloud/SpannerEmulatorContainerTest.java) inside_block:emulatorContainer\n<!--/codeinclude-->\n\nCreate a test Spanner Instance in the Emulator:\n\n<!--codeinclude-->\n[Create a test Spanner instance](../../modules/gcloud/src/test/java/org/testcontainers/gcloud/SpannerEmulatorContainerTest.java) inside_block:createInstance\n<!--/codeinclude-->\n\nCreate a test Database in the Emulator:\n\n<!--codeinclude-->\n[Creating a test Spanner database](../../modules/gcloud/src/test/java/org/testcontainers/gcloud/SpannerEmulatorContainerTest.java) inside_block:createDatabase\n<!--/codeinclude-->\n\nAnd test against the Emulator:\n\n<!--codeinclude-->\n[Testing with a Spanner Emulator container](../../modules/gcloud/src/test/java/org/testcontainers/gcloud/SpannerEmulatorContainerTest.java) inside_block:testWithEmulatorContainer\n<!--/codeinclude-->\n\nSee more examples:\n\n * [Full sample code](https://github.com/testcontainers/testcontainers-java/tree/main/modules/gcloud/src/test/java/org/testcontainers/gcloud/SpannerEmulatorContainerTest.java)\n * [With Spring Boot](https://github.com/saturnism/testcontainers-gcloud-examples/tree/main/springboot/spanner-example/src/test/java/com/example/springboot/spanner/SpannerIntegrationTests.java)\n\n### Pub/Sub \n\nStart Pub/Sub Emulator during a test:\n\n<!--codeinclude-->\n[Starting a Pub/Sub Emulator container](../../modules/gcloud/src/test/java/org/testcontainers/gcloud/PubSubEmulatorContainerTest.java) inside_block:emulatorContainer\n<!--/codeinclude-->\n\nCreate a test Pub/Sub topic in the Emulator:\n\n<!--codeinclude-->\n[Create a test topic](../../modules/gcloud/src/test/java/org/testcontainers/gcloud/PubSubEmulatorContainerTest.java) inside_block:createTopic\n<!--/codeinclude-->\n\nCreate a test Pub/Sub subscription in the Emulator:\n\n<!--codeinclude-->\n[Create a test subscription](../../modules/gcloud/src/test/java/org/testcontainers/gcloud/PubSubEmulatorContainerTest.java) inside_block:createSubscription\n<!--/codeinclude-->\n\nAnd test against the Emulator:\n\n<!--codeinclude-->\n[Testing with a Pub/Sub Emulator container](../../modules/gcloud/src/test/java/org/testcontainers/gcloud/PubSubEmulatorContainerTest.java) inside_block:testWithEmulatorContainer\n<!--/codeinclude-->\n\nSee more examples:\n\n * [Full sample code](https://github.com/testcontainers/testcontainers-java/tree/main/modules/gcloud/src/test/java/org/testcontainers/gcloud/PubSubEmulatorContainerTest.java)\n * [With Spring Boot](https://github.com/saturnism/testcontainers-gcloud-examples/tree/main/springboot/pubsub-example/src/test/java/com/example/springboot/pubsub/PubSubIntegrationTests.java)\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-gcloud:{{latest_version}}\"\n    ```\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-gcloud</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n"
  },
  {
    "path": "docs/modules/grafana.md",
    "content": "# Grafana\n\nTestcontainers module for [Grafana OTel LGTM](https://hub.docker.com/r/grafana/otel-lgtm).\n\n## LGTM's usage examples\n\nYou can start a Grafana OTel LGTM container instance from any Java application by using:\n\n<!--codeinclude-->\n[Grafana Otel LGTM container](../../modules/grafana/src/test/java/org/testcontainers/grafana/LgtmStackContainerTest.java) inside_block:container\n<!--/codeinclude-->\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-grafana:{{latest_version}}\"\n    ```\n\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-grafana</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n"
  },
  {
    "path": "docs/modules/hivemq.md",
    "content": "# HiveMQ Module\n\n![hivemq logo](../modules_logos/hivemq-module.png)\n\nAutomatic starting HiveMQ docker containers for JUnit4 and JUnit5 tests.\nThis enables testing MQTT client applications and integration testing of custom HiveMQ extensions.\n\n- Community forum: https://community.hivemq.com/\n- HiveMQ website: https://www.hivemq.com/\n- MQTT resources:\n    - [MQTT Essentials](https://www.hivemq.com/mqtt-essentials/)\n    - [MQTT 5 Essentials](https://www.hivemq.com/mqtt-5/)\n    \nPlease make sure to check out the hivemq-docs for the [Community Edition](https://github.com/hivemq/hivemq-community-edition/wiki/) \nand the [Enterprise Edition](https://www.hivemq.com/docs/hivemq/4.7/user-guide/).\n\n## Using HiveMQ CE/EE\n\nHiveMQ provides different editions of on [Docker Hub](https://hub.docker.com/u/hivemq):\n\n- the open source [Community Edition](https://github.com/hivemq/hivemq-community-edition) which \nis published as *hivemq/hivemq-ce*.\n- the [Enterprise Edition](https://www.hivemq.com/docs/hivemq/4.7/user-guide/) which is published as *hivemq/hivemq4*.\n\nBoth editions can be used directly:\n\nUsing the Community Edition:\n<!--codeinclude-->\n[Community Edition HiveMQ image](../../modules/hivemq/src/test/java/org/testcontainers/hivemq/docs/DemoHiveMQContainerIT.java) inside_block:ceVersion\n<!--/codeinclude-->\n\nUsing the Enterprise Edition:\n<!--codeinclude-->\n[Enterprise Edition HiveMQ image](../../modules/hivemq/src/test/java/org/testcontainers/hivemq/docs/DemoHiveMQContainerIT.java) inside_block:hiveEEVersion\n<!--/codeinclude-->\n\nUsing a specific version is possible by using the tag:\n<!--codeinclude-->\n[Specific HiveMQ Version](../../modules/hivemq/src/test/java/org/testcontainers/hivemq/docs/DemoHiveMQContainerIT.java) inside_block:specificVersion\n<!--/codeinclude-->\n\n## Test your MQTT 3 and MQTT 5 client application\n\nUsing an Mqtt-client (e.g. the [HiveMQ-Mqtt-Client](https://github.com/hivemq/hivemq-mqtt-client)) you can start \ntesting directly. \n\n<!--codeinclude-->\n[MQTT5 Client](../../modules/hivemq/src/test/java/org/testcontainers/hivemq/docs/DemoHiveMQContainerIT.java) inside_block:mqtt5client\n<!--/codeinclude-->\n\n## Settings\n\nThere are several things that can be adjusted before container setup.\nThe following example shows how to enable the Control Center (this is an enterprise feature), set the log level to DEBUG \nand load a HiveMQ-config-file from the classpath.\n\n<!--codeinclude-->\n[Config Examples](../../modules/hivemq/src/test/java/org/testcontainers/hivemq/docs/DemoHiveMQContainerIT.java) inside_block:eeVersionWithControlCenter\n<!--/codeinclude-->\n\n---\n**Note:**\nThe Control Center of HiveMQ can be accessed via the URL presented in the output of the starting container:\n\n```\n2021-09-10 10:35:53,511 INFO  - The HiveMQ Control Center is reachable under: http://localhost:55032\n```\n\nPlease be aware that the Control Center is a feature of the enterprise edition of HiveMQ and thus only available with \nthe enterprise image. \n\n---\n\n## Testing HiveMQ extensions\n\nUsing the [Extension SDK](https://github.com/hivemq/hivemq-extension-sdk) the functionality of all editions of HiveMQ\ncan be extended. \nThe HiveMQ module also supports testing your own custom extensions.\n\n### Wait Strategy\n\nThe raw HiveMQ module is built to wait for certain startup log messages to signal readiness.\nSince extensions are loaded dynamically they can be available a short while after the main container has started.\nWe therefore provide custom wait conditions for HiveMQ Extensions:\n\nThe following will specify an extension to be loaded from **src/test/resources/modifier-extension** into the container and \nwait for an  extension named **'My Extension Name'** to be started: \n\n<!--codeinclude-->\n[Custom Wait Strategy](../../modules/hivemq/src/test/java/org/testcontainers/hivemq/docs/DemoExtensionTestsIT.java) inside_block:waitStrategy\n<!--/codeinclude-->\n\nNext up we have an example for using an extension directly from the classpath and waiting directly on the extension:\n\n<!--codeinclude-->\n[Extension from Classpath](../../modules/hivemq/src/test/java/org/testcontainers/hivemq/docs/DemoExtensionTestsIT.java) inside_block:extensionClasspath\n<!--/codeinclude-->\n\n---\n**Note** Debugging extensions\n\nBoth examples contain ```.withDebugging()``` which enables remote debugging on the container.\nWith debugging enabled you can start putting breakpoints right into your extensions.\n\n--- \n\n### Testing extensions using Gradle\n\nIn a Gradle based HiveMQ Extension project, testing is supported using the dedicated [HiveMQ Extension Gradle Plugin](https://github.com/hivemq/hivemq-extension-gradle-plugin/README.md).\n\nThe plugin adds an `integrationTest` task which executes tests from the `integrationTest` source set.\n- Integration test source files are defined in `src/integrationTest`.\n- Integration test dependencies are defined via the `integrationTestImplementation`, `integrationTestRuntimeOnly`, etc. configurations.\n\nThe `integrationTest` task builds the extension and unzips it to the `build/hivemq-extension-test` directory.\nThe tests can then load the built extension into the HiveMQ Testcontainer.\n\n<!--codeinclude-->\n[Extension from filesystem](../../modules/hivemq/src/test/java/org/testcontainers/hivemq/docs/DemoDisableExtensionsIT.java) inside_block:startFromFilesystem\n<!--/codeinclude-->\n\n### Enable/Disable an extension\n\nIt is possible to enable and disable HiveMQ extensions during runtime. Extensions can also be disabled on startup.\n\n---\n**Note**: that disabling or enabling of extension during runtime is only supported in HiveMQ 4 Enterprise Edition Containers.\n\n---\n\nThe following example shows how to start a HiveMQ container with the extension called **my-extension** being disabled.\n\n\n<!--codeinclude-->\n[Disable Extension at startup](../../modules/hivemq/src/test/java/org/testcontainers/hivemq/docs/DemoDisableExtensionsIT.java) inside_block:startDisabled\n<!--/codeinclude-->\n\nThe following test then proceeds to enable and then disable the extension:\n\n<!--codeinclude-->\n[Enable/Disable extension at runtime](../../modules/hivemq/src/test/java/org/testcontainers/hivemq/docs/DemoDisableExtensionsIT.java) inside_block:hiveRuntimeEnable\n<!--/codeinclude-->\n\n## Enable/Disable an extension loaded from a folder\n\nExtensions loaded from an extension folder during runtime can also be enabled/disabled on the fly.\nIf the extension folder contains a DISABLED file, the extension will be disabled during startup.\n\n---\n**Note**: that disabling or enabling of extension during runtime is only supported in HiveMQ 4 Enterprise Edition Containers.\n\n---\n\nWe first load the extension from the filesystem:\n<!--codeinclude-->\n[Extension from filesystem](../../modules/hivemq/src/test/java/org/testcontainers/hivemq/docs/DemoDisableExtensionsIT.java) inside_block:startFromFilesystem\n<!--/codeinclude-->\n\nNow we can enable/disable the extension using its name:\n\n<!--codeinclude-->\n[Enable/Disable extension at runtime](../../modules/hivemq/src/test/java/org/testcontainers/hivemq/docs/DemoDisableExtensionsIT.java) inside_block:runtimeEnableFilesystem\n<!--/codeinclude-->\n\n### Remove prepackaged HiveMQ Extensions\n\nSince HiveMQ's 4.4 release, HiveMQ Docker images come with the HiveMQ Extension for Kafka, the HiveMQ Enterprise Bridge Extension\nand the HiveMQ Enterprise Security Extension.\nThese Extensions are disabled by default, but sometimes you my need to remove them before the container starts.\n\nRemoving all extension is as simple as:\n\n<!--codeinclude-->\n[Remove all extensions](../../modules/hivemq/src/test/java/org/testcontainers/hivemq/docs/DemoDisableExtensionsIT.java) inside_block:noExtensions\n<!--/codeinclude-->\n\nA single extension (e.g. Kafka) can be removed as easily:\n<!--codeinclude-->\n[Remove a specific extension](../../modules/hivemq/src/test/java/org/testcontainers/hivemq/docs/DemoDisableExtensionsIT.java) inside_block:noKafkaExtension\n<!--/codeinclude-->\n\n## Put files into the container\n\n### Put a file into HiveMQ home\n\n<!--codeinclude-->\n[Put file into HiveMQ home](../../modules/hivemq/src/test/java/org/testcontainers/hivemq/docs/DemoFilesIT.java) inside_block:hivemqHome\n<!--/codeinclude-->\n\n### Put files into extension home\n\n<!--codeinclude-->\n[Put file into HiveMQ-Extension home](../../modules/hivemq/src/test/java/org/testcontainers/hivemq/docs/DemoFilesIT.java) inside_block:extensionHome\n<!--/codeinclude-->\n\n### Put license files into the container\n\n<!--codeinclude-->\n[Put license file into the HiveMQ container](../../modules/hivemq/src/test/java/org/testcontainers/hivemq/docs/DemoFilesIT.java) inside_block:withLicenses\n<!--/codeinclude-->\n\n\n### Customize the Container further\n\nSince the `HiveMQContainer` extends from [Testcontainer's](https://github.com/testcontainers) `GenericContainer` the container\ncan be customized as desired.\n\n## Add to your project\n\n### Gradle\n\nAdd to `build.gradle`:\n\n````groovy\ntestImplementation 'org.testcontainers:testcontainers-hivemq:{{latest_version}}'\n````\n\nAdd to `build.gradle.kts`:\n\n````kotlin\ntestImplementation(\"org.testcontainers:testcontainers-hivemq:{{latest_version}}\")\n````\n\n### Maven\n\nAdd to `pom.xml`:\n\n```xml\n<dependency>\n    <groupId>org.testcontainers</groupId>\n    <artifactId>testcontainers-hivemq</artifactId>\n    <version>{{latest_version}}</version>\n    <scope>test</scope>\n</dependency>\n```\n"
  },
  {
    "path": "docs/modules/k3s.md",
    "content": "# K3s Module\n\n!!! note\n    This module is INCUBATING. While it is ready for use and operational in the current version of Testcontainers, it is possible that it may receive breaking changes in the future. See [our contributing guidelines](/contributing/#incubating-modules) for more information on our incubating modules policy.\n\nTestcontainers module for Rancher's [K3s](https://rancher.com/products/k3s/) lightweight Kubernetes.\nThis module is intended to be used for testing components that interact with Kubernetes APIs - for example, operators.\n\n## Usage example\n\nStart a K3s server as follows:\n\n<!--codeinclude-->\n[Starting a K3S server](../../modules/k3s/src/test/java/org/testcontainers/k3s/Fabric8K3sContainerTest.java) inside_block:starting_k3s\n<!--/codeinclude-->\n\n### Connecting to the server\n\n`K3sContainer` exposes a working Kubernetes client configuration, as a YAML String, via the `getKubeConfigYaml()` method.\n\nThis may be used with Kubernetes clients - e.g. for the [official Java client](connecting_with_k8sio) and \n[the Fabric8 Kubernetes client](https://github.com/fabric8io/kubernetes-client):\n\n<!--codeinclude-->\n[Official Java client](../../modules/k3s/src/test/java/org/testcontainers/k3s/OfficialClientK3sContainerTest.java) inside_block:connecting_with_k8sio\n[Fabric8 Kubernetes client](../../modules/k3s/src/test/java/org/testcontainers/k3s/Fabric8K3sContainerTest.java) inside_block:connecting_with_fabric8\n<!--/codeinclude-->\n\n## Known limitations\n\n!!! warning\n    * K3sContainer runs as a privileged container and needs to be able to spawn its own containers. For these reasons,\n    K3sContainer will not work in certain rootless Docker, Docker-in-Docker, or other environments where privileged\n    containers are disallowed.\n\n    * k3s containers may be unable to run on host machines where `/var/lib/docker` is on a BTRFS filesystem. See [k3s-io/k3s#4863](https://github.com/k3s-io/k3s/issues/4863) for an example.\n\n    * You may experience PKIX exceptions when trying to use a configured Fabric8 client. This is down to newer distributions of k3s issuing elliptic curve keys.\n    This can be fixed by adding [BouncyCastle PKI library](https://mvnrepository.com/artifact/org.bouncycastle/bcpkix-jdk15on) to your classpath.\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-k3s:{{latest_version}}\"\n    ```\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-k3s</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n"
  },
  {
    "path": "docs/modules/k6.md",
    "content": "# k6 Module\n\n!!! note \n    This module is INCUBATING. \n    While it is ready for use and operational in the current version of Testcontainers, it is possible that it may receive breaking changes in the future. \n    See [our contributing guidelines](/contributing/#incubating-modules) for more information on our incubating modules policy.\n\nTestcontainers module for [k6](https://registry.hub.docker.com/r/grafana/k6).\n\n[k6](https://k6.io/) is an extensible reliability testing tool built for developer happiness.\n\n## Basic script execution\n\nYou can start a K6 container instance from any Java application by using:\n\n<!--codeinclude-->\n[Setup the container](../../modules/k6/src/test/java/org/testcontainers/k6/K6ContainerTests.java) inside_block:standard_k6\n<!--/codeinclude-->\n\nThe test above uses a simple k6 script, `test.js`, with command line options and an injected script variable.\n\nOnce the container is started, you can wait for the test results to be collected:\n\n<!--codeinclude-->\n[Wait for test results](../../modules/k6/src/test/java/org/testcontainers/k6/K6ContainerTests.java) inside_block:wait\n<!--/codeinclude-->\n\nCreate a simple k6 test script to be executed as part of your tests:\n\n<!--codeinclude-->\n[Content of `scripts/test.js`](../../modules/k6/src/test/resources/scripts/test.js) inside_block:access_script_vars\n<!--/codeinclude-->\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-k6:{{latest_version}}\"\n    ```\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-k6</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n"
  },
  {
    "path": "docs/modules/kafka.md",
    "content": "# Kafka Module\n\nTestcontainers can be used to automatically instantiate and manage [Apache Kafka](https://kafka.apache.org) containers.\n\nCurrently, two different Kafka images are supported:\n\n* `org.testcontainers.kafka.ConfluentKafkaContainer` supports \n[confluentinc/cp-kafka](https://hub.docker.com/r/confluentinc/cp-kafka/)\n* `org.testcontainers.kafka.KafkaContainer` supports [apache/kafka](https://hub.docker.com/r/apache/kafka/) and [apache/kafka-native](https://hub.docker.com/r/apache/kafka-native/)\n\n!!! note\n    `org.testcontainers.containers.KafkaContainer` is deprecated.\n    Please use `org.testcontainers.kafka.ConfluentKafkaContainer` or `org.testcontainers.kafka.KafkaContainer` instead, depending on the used image.\n\n## Benefits\n\n* Running a single node Kafka installation with just one line of code\n* No need to manage external Zookeeper installation, required by Kafka. \n\n## Example\n\n### Using org.testcontainers.kafka.KafkaContainer\n\nCreate a `KafkaContainer` to use it in your tests:\n\n<!--codeinclude-->\n[Creating a KafkaContainer](../../modules/kafka/src/test/java/org/testcontainers/kafka/KafkaContainerTest.java) inside_block:constructorWithVersion\n<!--/codeinclude-->\n\nNow your tests or any other process running on your machine can get access to running Kafka broker by using the following bootstrap server location:\n\n<!--codeinclude-->\n[Bootstrap Servers](../../modules/kafka/src/test/java/org/testcontainers/containers/KafkaContainerTest.java) inside_block:getBootstrapServers\n<!--/codeinclude-->\n\n### Using org.testcontainers.kafka.ConfluentKafkaContainer\n\n!!! note\n    Compatible with `confluentinc/cp-kafka` images version `7.4.0` and later.\n\nCreate a `ConfluentKafkaContainer` to use it in your tests:\n\n<!--codeinclude-->\n[Creating a ConfluentKafkaContainer](../../modules/kafka/src/test/java/org/testcontainers/kafka/ConfluentKafkaContainerTest.java) inside_block:constructorWithVersion\n<!--/codeinclude-->\n\n## Options\n        \n### Using Kraft mode\n\n!!! note\n    Only available for `org.testcontainers.containers.KafkaContainer`\n\nKRaft mode was declared production ready in 3.3.1 (confluentinc/cp-kafka:7.3.x) \n\n<!--codeinclude-->\n[Kraft mode](../../modules/kafka/src/test/java/org/testcontainers/containers/KafkaContainerTest.java) inside_block:withKraftMode\n<!--/codeinclude-->\n\nSee the [versions interoperability matrix](https://docs.confluent.io/platform/current/installation/versions-interoperability.html) for more details.\n\n### Register listeners\n\nThere are scenarios where additional listeners are needed because the consumer/producer can be in another\ncontainer in the same network or a different process where the port to connect differs from the default exposed port. E.g [Toxiproxy](../../modules/toxiproxy/).\n\n<!--codeinclude-->\n[Register additional listener](../../modules/kafka/src/test/java/org/testcontainers/kafka/KafkaContainerTest.java) inside_block:registerListener\n<!--/codeinclude-->\n\nContainer defined in the same network:\n\n<!--codeinclude-->\n[Create kcat container](../../modules/kafka/src/test/java/org/testcontainers/containers/KafkaContainerTest.java) inside_block:createKCatContainer\n<!--/codeinclude-->\n\nClient using the new registered listener:\n\n<!--codeinclude-->\n[Produce/Consume via new listener](../../modules/kafka/src/test/java/org/testcontainers/containers/KafkaContainerTest.java) inside_block:produceConsumeMessage\n<!--/codeinclude-->\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-kafka:{{latest_version}}\"\n    ```\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-kafka</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n"
  },
  {
    "path": "docs/modules/ldap.md",
    "content": "# LDAP\n\nTestcontainers module for [LLDAP](https://hub.docker.com/r/lldap/lldap).\n\n## LLdapContainer's usage examples\n\nYou can start a LLDAP container instance from any Java application by using:\n\n<!--codeinclude-->\n[LLDAP container](../../modules/ldap/src/test/java/org/testcontainers/ldap/LLdapContainerTest.java) inside_block:container\n<!--/codeinclude-->\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-ldap:{{latest_version}}\"\n    ```\n\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-ldap</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n"
  },
  {
    "path": "docs/modules/localstack.md",
    "content": "# LocalStack Module\n\nTestcontainers module for [LocalStack](http://localstack.cloud/), 'a fully functional local AWS cloud stack', to develop and test your cloud and serverless apps without actually using the cloud.\n\n## Usage example\n\nYou can start a LocalStack container instance from any Java application by using:\n\n<!--codeinclude-->\n[Container creation](../../modules/localstack/src/test/java/org/testcontainers/localstack/LocalStackContainerTest.java) inside_block:container\n<!--/codeinclude-->\n\n## Creating a client using AWS SDK\n\n<!--codeinclude-->\n[AWS SDK V2](../../modules/localstack/src/test/java/org/testcontainers/localstack/LocalStackContainerTest.java) inside_block:with_aws_sdk_v2\n<!--/codeinclude-->\n\nEnvironment variables listed in [Localstack's README](https://github.com/localstack/localstack#configurations) may be used to customize Localstack's configuration. \nUse the `.withEnv(key, value)` method on `LocalStackContainer` to apply configuration settings.\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-localstack:{{latest_version}}\"\n    ```\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-localstack</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n"
  },
  {
    "path": "docs/modules/milvus.md",
    "content": "# Milvus\n\nTestcontainers module for [Milvus](https://hub.docker.com/r/milvusdb/milvus).\n\n## Milvus's usage examples\n\nYou can start a Milvus container instance from any Java application by using:\n\n<!--codeinclude-->\n[Default config](../../modules/milvus/src/test/java/org/testcontainers/milvus/MilvusContainerTest.java) inside_block:milvusContainer\n<!--/codeinclude-->\n\nWith external Etcd:\n\n<!--codeinclude-->\n[External Etcd](../../modules/milvus/src/test/java/org/testcontainers/milvus/MilvusContainerTest.java) inside_block:externalEtcd\n<!--/codeinclude-->\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-milvus:{{latest_version}}\"\n    ```\n\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-milvus</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n"
  },
  {
    "path": "docs/modules/minio.md",
    "content": "# MinIO Containers\n\nTestcontainers can be used to automatically instantiate and manage [MinIO](https://min.io) containers.\n\n## Usage example\n\nCreate a `MinIOContainer` to use it in your tests:\n<!--codeinclude-->\n[Starting a MinIO container](../../modules/minio/src/test/java/org/testcontainers/containers/MinIOContainerTest.java) inside_block:minioContainer\n<!--/codeinclude-->\n\nThe [MinIO Java client](https://min.io/docs/minio/linux/developers/java/API.html) can be configured with the container as such:\n<!--codeinclude-->\n[Configuring a MinIO client](../../modules/minio/src/test/java/org/testcontainers/containers/MinIOContainerTest.java) inside_block:configuringClient\n<!--/codeinclude-->\n\nIf needed the username and password can be overridden as such:\n<!--codeinclude-->\n[Overriding a MinIO container](../../modules/minio/src/test/java/org/testcontainers/containers/MinIOContainerTest.java) inside_block:minioOverrides\n<!--/codeinclude-->\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n     testImplementation \"org.testcontainers:testcontainers-minio:{{latest_version}}\"\n    ```\n\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-minio</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n"
  },
  {
    "path": "docs/modules/mockserver.md",
    "content": "# Mockserver Module\n\nMock Server can be used to mock HTTP services by matching requests against user-defined expectations.\n\n## Usage example\n\nThe following example shows how to start Mockserver.\n\n<!--codeinclude-->\n[Creating a MockServer container](../../modules/mockserver/src/test/java/org/testcontainers/mockserver/MockServerContainerTest.java) inside_block:creatingProxy\n<!--/codeinclude-->\n\nAnd how to set a simple expectation using the Java MockServerClient.\n\n<!--codeinclude-->\n[Setting a simple expectation](../../modules/mockserver/src/test/java/org/testcontainers/mockserver/MockServerContainerTest.java) inside_block:testSimpleExpectation\n<!--/codeinclude-->\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-mockserver:{{latest_version}}\"\n    ```\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-mockserver</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n\nAdditionally, don't forget to add a [client dependency `org.mock-server:mockserver-client-java`](https://search.maven.org/search?q=mockserver-client-java) \nto be able to set expectations, it's not provided by the testcontainers module. Client version should match to the version in a container tag.\n"
  },
  {
    "path": "docs/modules/nginx.md",
    "content": "# Nginx Module\n\nNginx is a web server, reverse proxy and mail proxy and http cache.\n\n## Usage example\n\nThe following example shows how to start Nginx.\n\n<!--codeinclude-->\n[Creating a Nginx container](../../modules/nginx/src/test/java/org/testcontainers/nginx/NginxContainerTest.java) inside_block:creatingContainer\n<!--/codeinclude-->\n\nHow to add custom content to the Nginx server.\n\n<!--codeinclude-->\n[Creating the static content to serve](../../modules/nginx/src/test/java/org/testcontainers/nginx/NginxContainerTest.java) inside_block:addCustomContent\n<!--/codeinclude-->\n\nAnd how to query the Nginx server for the custom content added.\n\n<!--codeinclude-->\n[Creating the static content to serve](../../modules/nginx/src/test/java/org/testcontainers/nginx/NginxContainerTest.java) inside_block:getFromNginxServer\n<!--/codeinclude-->\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-nginx:{{latest_version}}\"\n    ```\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-nginx</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n"
  },
  {
    "path": "docs/modules/ollama.md",
    "content": "# Ollama\n\nTestcontainers module for [Ollama](https://hub.docker.com/r/ollama/ollama) .\n\n## Ollama's usage examples\n\nYou can start an Ollama container instance from any Java application by using:\n\n<!--codeinclude-->\n[Ollama container](../../modules/ollama/src/test/java/org/testcontainers/ollama/OllamaContainerTest.java) inside_block:container\n<!--/codeinclude-->\n\n### Pulling the model\n\nTestcontainers allows [executing commands in the container](../features/commands.md). So, pulling the model is as simple as: \n\n<!--codeinclude-->\n[Pull model](../../modules/ollama/src/test/java/org/testcontainers/ollama/OllamaContainerTest.java) inside_block:pullModel\n<!--/codeinclude-->\n\n### Create a new Image\n\nIn order to create a new image that contains the model, you can use the following code:\n\n<!--codeinclude-->\n[Commit Image](../../modules/ollama/src/test/java/org/testcontainers/ollama/OllamaContainerTest.java) inside_block:commitToImage\n<!--/codeinclude-->\n\nAnd use the new image along with [Image name Substitution](../features/image_name_substitution.md#manual-substitution)\n\n<!--codeinclude-->\n[Use new Image](../../modules/ollama/src/test/java/org/testcontainers/ollama/OllamaContainerTest.java) inside_block:substitute\n<!--/codeinclude-->\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-ollama:{{latest_version}}\"\n    ```\n\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-ollama</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n"
  },
  {
    "path": "docs/modules/openfga.md",
    "content": "# OpenFGA\n\nTestcontainers module for [OpenFGA](https://hub.docker.com/r/openfga/openfga).\n\n## OpenFGAContainer's usage examples\n\nYou can start an OpenFGA container instance from any Java application by using:\n\n<!--codeinclude-->\n[OpenFGA container](../../modules/openfga/src/test/java/org/testcontainers/openfga/OpenFGAContainerTest.java) inside_block:container\n<!--/codeinclude-->\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-openfga:{{latest_version}}\"\n    ```\n\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-openfga</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n"
  },
  {
    "path": "docs/modules/pinecone.md",
    "content": "# Pinecone\n\nTestcontainers module for [Pinecone Local](https://github.com/orgs/pinecone-io/packages/container/package/pinecone-local).\n\n## PineconeLocalContainer's usage examples\n\nYou can start a Pinecone container instance from any Java application by using:\n\n<!--codeinclude-->\n[Pinecone container](../../modules/pinecone/src/test/java/org/testcontainers/pinecone/PineconeLocalContainerTest.java) inside_block:container\n<!--/codeinclude-->\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n```groovy\ntestImplementation \"org.testcontainers:testcontainers-pinecone:{{latest_version}}\"\n```\n\n=== \"Maven\"\n```xml\n<dependency>\n    <groupId>org.testcontainers</groupId>\n    <artifactId>testcontainers-pinecone</artifactId>\n    <version>{{latest_version}}</version>\n    <scope>test</scope>\n</dependency>\n```\n"
  },
  {
    "path": "docs/modules/pulsar.md",
    "content": "# Apache Pulsar Module\n\nTestcontainers can be used to automatically create [Apache Pulsar](https://pulsar.apache.org) containers without external services.\n\nIt's based on the official Apache Pulsar docker image, it is recommended to read the [official guide](https://pulsar.apache.org/docs/next/getting-started-docker/).\n\n## Example\n\nCreate a `PulsarContainer` to use it in your tests:\n\n<!--codeinclude-->\n[Create a Pulsar container](../../modules/pulsar/src/test/java/org/testcontainers/pulsar/PulsarContainerTest.java) inside_block:constructorWithVersion\n<!--/codeinclude-->\n\nThen you can retrieve the broker and the admin url:\n\n<!--codeinclude-->\n[Get broker and admin urls](../../modules/pulsar/src/test/java/org/testcontainers/pulsar/PulsarContainerTest.java) inside_block:coordinates\n<!--/codeinclude-->\n\n## Options\n\n### Configuration\nIf you need to set Pulsar configuration variables you can use the native APIs and set each variable with `PULSAR_PREFIX_` as prefix.\n\nFor example, if you want to enable `brokerDeduplicationEnabled`:\n\n<!--codeinclude-->\n[Set configuration variables](../../modules/pulsar/src/test/java/org/testcontainers/pulsar/PulsarContainerTest.java) inside_block:constructorWithEnv\n<!--/codeinclude-->\n\n### Pulsar IO\n\nIf you need to test Pulsar IO framework you can enable the Pulsar Functions Worker:\n\n<!--codeinclude-->\n[Create a Pulsar container with functions worker](../../modules/pulsar/src/test/java/org/testcontainers/pulsar/PulsarContainerTest.java) inside_block:constructorWithFunctionsWorker\n<!--/codeinclude-->\n\n### Pulsar Transactions\n\nIf you need to test Pulsar Transactions you can enable the transactions feature:\n\n<!--codeinclude-->\n[Create a Pulsar container with transactions](../../modules/pulsar/src/test/java/org/testcontainers/pulsar/PulsarContainerTest.java) inside_block:constructorWithTransactions\n<!--/codeinclude-->\n\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-pulsar:{{latest_version}}\"\n    ```\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-pulsar</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n"
  },
  {
    "path": "docs/modules/qdrant.md",
    "content": "# Qdrant\n\nTestcontainers module for [Qdrant](https://registry.hub.docker.com/r/qdrant/qdrant)\n\n## Qdrant's usage examples\n\nYou can start a Qdrant container instance from any Java application by using:\n\n<!--codeinclude-->\n[Default QDrant container](../../modules/qdrant/src/test/java/org/testcontainers/qdrant/QdrantContainerTest.java) inside_block:qdrantContainer\n<!--/codeinclude-->\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-qdrant:{{latest_version}}\"\n    ```\n\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-qdrant</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n"
  },
  {
    "path": "docs/modules/rabbitmq.md",
    "content": "# RabbitMQ Module\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-rabbitmq:{{latest_version}}\"\n    ```\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-rabbitmq</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n"
  },
  {
    "path": "docs/modules/redpanda.md",
    "content": "# Redpanda\n\nTestcontainers can be used to automatically instantiate and manage [Redpanda](https://redpanda.com/) containers.\nMore precisely Testcontainers uses the official Docker images for [Redpanda](https://hub.docker.com/r/redpandadata/redpanda)\n\n!!! note\n    This module uses features provided in `docker.redpanda.com/redpandadata/redpanda`.\n\n## Example\n\nCreate a `Redpanda` to use it in your tests:\n<!--codeinclude-->\n[Creating a Redpanda](../../modules/redpanda/src/test/java/org/testcontainers/redpanda/RedpandaContainerTest.java) inside_block:constructorWithVersion\n<!--/codeinclude-->\n\nNow your tests or any other process running on your machine can get access to running Redpanda broker by using the following bootstrap server location:\n\n<!--codeinclude-->\n[Bootstrap Servers](../../modules/redpanda/src/test/java/org/testcontainers/redpanda/RedpandaContainerTest.java) inside_block:getBootstrapServers\n<!--/codeinclude-->\n\nRedpanda also provides a schema registry implementation. Like the Redpanda broker, you can access by using the following schema registry location:\n\n<!--codeinclude-->\n[Schema Registry](../../modules/redpanda/src/test/java/org/testcontainers/redpanda/RedpandaContainerTest.java) inside_block:getSchemaRegistryAddress\n<!--/codeinclude-->\n\nIt is also possible to enable security capabilities of Redpanda by using:\n\n<!--codeinclude-->\n[Enable security](../../modules/redpanda/src/test/java/org/testcontainers/redpanda/RedpandaContainerTest.java) inside_block:security\n<!--/codeinclude-->\n\nSuperusers can be created by using:\n\n<!--codeinclude-->\n[Register Superuser](../../modules/redpanda/src/test/java/org/testcontainers/redpanda/RedpandaContainerTest.java) inside_block:createSuperUser\n<!--/codeinclude-->\n\nBelow is an example of how to create the `AdminClient`:\n\n<!--codeinclude-->\n[Create Admin Client](../../modules/redpanda/src/test/java/org/testcontainers/redpanda/RedpandaContainerTest.java) inside_block:createAdminClient\n<!--/codeinclude-->\n\nThere are scenarios where additional listeners are needed because the consumer/producer can be another\ncontainer in the same network or a different process where the port to connect differs from the default\nexposed port `9092`. E.g [Toxiproxy](../modules/toxiproxy.md).\n\n<!--codeinclude-->\n[Register additional listener](../../modules/redpanda/src/test/java/org/testcontainers/redpanda/RedpandaContainerTest.java) inside_block:registerListener\n<!--/codeinclude-->\n\nContainer defined in the same network:\n\n<!--codeinclude-->\n[Create kcat container](../../modules/redpanda/src/test/java/org/testcontainers/redpanda/RedpandaContainerTest.java) inside_block:createKCatContainer\n<!--/codeinclude-->\n\nClient using the new registered listener:\n\n<!--codeinclude-->\n[Produce/Consume via new listener](../../modules/redpanda/src/test/java/org/testcontainers/redpanda/RedpandaContainerTest.java) inside_block:produceConsumeMessage\n<!--/codeinclude-->\n\nThe following examples shows how to register a proxy as a new listener in `RedpandaContainer`:\n\nUse `SocatContainer` to create the proxy\n\n<!--codeinclude-->\n[Create Proxy](../../modules/redpanda/src/test/java/org/testcontainers/redpanda/RedpandaContainerTest.java) inside_block:createProxy\n<!--/codeinclude-->\n\nRegister the listener and advertised listener\n\n<!--codeinclude-->\n[Register Listener](../../modules/redpanda/src/test/java/org/testcontainers/redpanda/RedpandaContainerTest.java) inside_block:registerListenerAndAdvertisedListener\n<!--/codeinclude-->\n\nClient using the new registered listener:\n\n<!--codeinclude-->\n[Produce/Consume via new listener](../../modules/redpanda/src/test/java/org/testcontainers/redpanda/RedpandaContainerTest.java) inside_block:produceConsumeMessageFromProxy\n<!--/codeinclude-->\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n```groovy\ntestImplementation \"org.testcontainers:testcontainers-redpanda:{{latest_version}}\"\n```\n=== \"Maven\"\n```xml\n<dependency>\n    <groupId>org.testcontainers</groupId>\n    <artifactId>testcontainers-redpanda</artifactId>\n    <version>{{latest_version}}</version>\n    <scope>test</scope>\n</dependency>\n```\n"
  },
  {
    "path": "docs/modules/solace.md",
    "content": "# Solace Container\n\nThis module helps running [Solace PubSub+](https://solace.com/products/event-broker/software/) using Testcontainers.\n\nNote that it's based on the [official Docker image](https://hub.docker.com/r/solace/solace-pubsub-standard).\n\n## Usage example\n\nYou can start a solace container instance from any Java application by using:\n\n<!--codeinclude-->\n[Solace container setup with simple authentication](../../modules/solace/src/test/java/org/testcontainers/solace/SolaceContainerSMFTest.java) inside_block:solaceContainerSetup\n<!--/codeinclude-->\n\n<!--codeinclude-->\n[Solace container setup with SSL](../../modules/solace/src/test/java/org/testcontainers/solace/SolaceContainerSMFTest.java) inside_block:solaceContainerUsageSSL\n<!--/codeinclude-->\n\n<!--codeinclude-->\n[Using a Solace container](../../modules/solace/src/test/java/org/testcontainers/solace/SolaceContainerAMQPTest.java) inside_block:solaceContainerUsage\n<!--/codeinclude-->\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-solace:{{latest_version}}\"\n    ```\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-solace</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n"
  },
  {
    "path": "docs/modules/solr.md",
    "content": "# Solr Container\n\nThis module helps running [solr](https://solr.apache.org/) using Testcontainers.\n\nNote that it's based on the [official Docker image](https://hub.docker.com/_/solr/).\n\n## Usage example\n\nYou can start a solr container instance from any Java application by using:\n\n<!--codeinclude-->\n[Using a Solr container](../../modules/solr/src/test/java/org/testcontainers/solr/SolrContainerTest.java) inside_block:solrContainerUsage\n<!--/codeinclude-->\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-solr:{{latest_version}}\"\n    ```\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-solr</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n"
  },
  {
    "path": "docs/modules/toxiproxy.md",
    "content": "# Toxiproxy Module\n\nTestcontainers module for Shopify's [Toxiproxy](https://github.com/Shopify/toxiproxy). \nThis TCP proxy can be used to simulate network failure conditions.\n\nYou can simulate network failures:\n\n* between Java code and containers, ideal for testing resilience features of client code\n* between containers, for testing resilience and emergent behaviour of multi-container systems\n* if desired, between Java code/containers and external resources (non-Dockerized!), for scenarios where not all dependencies can be/have been dockerized\n\nTestcontainers Toxiproxy support allows resilience features to be easily verified as part of isolated dev/CI testing. This allows earlier testing of resilience features, and broader sets of failure conditions to be covered.\n \n## Usage example\n\nA Toxiproxy container can be placed in between test code and a container, or in between containers.\nIn either scenario, it is necessary to create a `ToxiproxyContainer` instance on the same Docker network, as follows:\n\n<!--codeinclude-->\n[Creating a Toxiproxy container](../../modules/toxiproxy/src/test/java/org/testcontainers/toxiproxy/ToxiproxyContainerTest.java) inside_block:creatingProxy\n<!--/codeinclude-->\n\nNext, it is necessary to instruct Toxiproxy to start proxying connections. \nEach `ToxiproxyContainer` can proxy to many target containers if necessary.\n\nWe do this as follows:\n\n<!--codeinclude-->\n[Starting proxying connections to a target container](../../modules/toxiproxy/src/test/java/org/testcontainers/toxiproxy/ToxiproxyContainerTest.java) inside_block:obtainProxyObject\n<!--/codeinclude-->\n\nTo establish a connection from the test code (on the host machine) to the target container via Toxiproxy, we obtain **Toxiproxy's** proxy host IP and port:\n\n<!--codeinclude-->\n[Obtaining proxied host and port](../../modules/toxiproxy/src/test/java/org/testcontainers/toxiproxy/ToxiproxyContainerTest.java) inside_block:obtainProxiedHostAndPortForHostMachine\n<!--/codeinclude-->\n\nCode under test should connect to this proxied host IP and port.\n\n!!! note\n    Currently, `ToxiProxyContainer` will reserve 31 ports, starting at 8666.\n\nOther containers should connect to this proxied host and port.\n\nHaving done this, it is possible to trigger failure conditions ('Toxics') through the `proxy.toxics()` object:\n\n* `bandwidth` - Limit a connection to a maximum number of kilobytes per second.\n* `latency` - Add a delay to all data going through the proxy. The delay is equal to `latency +/- jitter`.\n* `slicer` - Slices TCP data up into small bits, optionally adding a delay between each sliced \"packet\".\n* `slowClose` - Delay the TCP socket from closing until `delay` milliseconds has elapsed.\n* `timeout` - Stops all data from getting through, and closes the connection after `timeout`. If `timeout` is `0`, the connection won't close, and data will be delayed until the toxic is removed.\n* `limitData` - Closes connection when transmitted data exceeded limit.\n\nPlease see the [Toxiproxy documentation](https://github.com/Shopify/toxiproxy#toxics) for full details on the available Toxics.\n\nAs one example, we can introduce latency and random jitter to proxied connections as follows:\n\n<!--codeinclude-->\n[Adding latency to a connection](../../modules/toxiproxy/src/test/java/org/testcontainers/toxiproxy/ToxiproxyContainerTest.java) inside_block:addingLatency\n<!--/codeinclude-->\n\nAdditionally we can disable the proxy to simulate a complete interruption to the network connection:\n\n<!--codeinclude-->\n[Cutting a connection](../../modules/toxiproxy/src/test/java/org/testcontainers/toxiproxy/ToxiproxyContainerTest.java) inside_block:disableProxy\n<!--/codeinclude-->\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-toxiproxy:{{latest_version}}\"\n    ```\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-toxiproxy</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n\n## Acknowledgements\n\nThis module was inspired by a [hotels.com blog post](https://medium.com/hotels-com-technology/i-dont-know-about-resilience-testing-and-so-can-you-b3c59d80012d).\n"
  },
  {
    "path": "docs/modules/typesense.md",
    "content": "# Typesense\n\nTestcontainers module for [Typesense](https://hub.docker.com/r/typesense/typesense).\n\n## TypesenseContainer's usage examples\n\nYou can start a Typesense container instance from any Java application by using:\n\n<!--codeinclude-->\n[Typesense container](../../modules/typesense/src/test/java/org/testcontainers/typesense/TypesenseContainerTest.java) inside_block:container\n<!--/codeinclude-->\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-typesense:{{latest_version}}\"\n    ```\n\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-typesense</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n"
  },
  {
    "path": "docs/modules/vault.md",
    "content": "# Hashicorp Vault Module\n\nTestcontainers module for [Vault](https://github.com/hashicorp/vault). Vault is a tool for managing secrets. More information on Vault [here](https://www.vaultproject.io/).\n\n## Usage example\n\nStart Vault container as a `@ClassRule`:\n\n<!--codeinclude-->\n[Starting a Vault container](../../modules/vault/src/test/java/org/testcontainers/vault/VaultContainerTest.java) inside_block:vaultContainer\n<!--/codeinclude-->\n\nUse CLI to read data from Vault container:\n\n<!--codeinclude-->\n[Use CLI to read data](../../modules/vault/src/test/java/org/testcontainers/vault/VaultContainerTest.java) inside_block:readFirstSecretPathWithCli\n<!--/codeinclude-->\n\nUse Http API to read data from Vault container:\n\n<!--codeinclude-->\n[Use Http API to read data](../../modules/vault/src/test/java/org/testcontainers/vault/VaultContainerTest.java) inside_block:readFirstSecretPathOverHttpApi\n<!--/codeinclude-->\n\nUse client library to read data from Vault container:\n\n<!--codeinclude-->\n[Use library to read data](../../modules/vault/src/test/java/org/testcontainers/vault/VaultContainerTest.java) inside_block:readWithLibrary\n<!--/codeinclude-->\n\n[See full example.](https://github.com/testcontainers/testcontainers-java/blob/master/modules/vault/src/test/java/org/testcontainers/vault/VaultContainerTest.java)\n\n## Why Vault in Junit tests?\n\nWith the increasing popularity of Vault and secret management, applications are now needing to source secrets from Vault.\nThis can prove challenging in the development phase without a running Vault instance readily on hand. This library \naims to solve your apps integration testing with Vault. You can also use it to\ntest how your application behaves with Vault by writing different test scenarios in Junit.\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-vault:{{latest_version}}\"\n    ```\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-vault</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n\n## License\n\nSee [LICENSE](https://raw.githubusercontent.com/testcontainers/testcontainers-java/main/modules/vault/LICENSE).\n\n## Copyright\n\nCopyright (c) 2017 Capital One Services, LLC and other authors.\n\nSee [AUTHORS](https://raw.githubusercontent.com/testcontainers/testcontainers-java/main/modules/vault/AUTHORS) for contributors.\n\n"
  },
  {
    "path": "docs/modules/weaviate.md",
    "content": "# Weaviate\n\nTestcontainers module for [Weaviate](https://hub.docker.com/r/semitechnologies/weaviate)\n\n## WeaviateContainer's usage examples\n\nYou can start a Weaviate container instance from any Java application by using:\n\n<!--codeinclude-->\n[Default Weaviate container](../../modules/weaviate/src/test/java/org/testcontainers/weaviate/WeaviateContainerTest.java) inside_block:container\n<!--/codeinclude-->\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n```groovy\ntestImplementation \"org.testcontainers:testcontainers-weaviate:{{latest_version}}\"\n```\n\n=== \"Maven\"\n```xml\n<dependency>\n<groupId>org.testcontainers</groupId>\n<artifactId>testcontainers-weaviate</artifactId>\n<version>{{latest_version}}</version>\n<scope>test</scope>\n</dependency>\n```\n"
  },
  {
    "path": "docs/modules/webdriver_containers.md",
    "content": "# Webdriver Containers\n\nTestcontainers can be used to automatically instantiate and manage containers that include web browsers, such as those\nfrom SeleniumHQ's [docker-selenium](https://github.com/SeleniumHQ/docker-selenium) project.\n\n## Benefits\n\n* Fully compatible with Selenium 3 & 4 tests for Chrome and Firefox and Selenium 4 tests for Edge, by providing a `RemoteWebDriver` instance\n* No need to have specific web browsers, or even a desktop environment, installed on test servers. The only dependency\n  is a working Docker installation and your Java JUnit test suite.\n* Browsers are always launched from a fixed, clean image. This means no configuration drift from user changes or\n  automatic browser upgrades.\n* Compatibility between browser version and the Selenium API is assured: a compatible version of the browser docker\n  images will be automatically selected to match the version of `selenium-api-*.jar` on the classpath\n* Additionally the use of a clean browser prevents leakage of cookies, cached data or other state between tests.\n* **VNC screen recording**: Testcontainers can automatically record video of test runs (optionally capturing just\n  failing tests)\n\nCreation of browser containers is fast, so it's actually quite feasible to have a totally fresh browser instance for\nevery test.\n\n## Example\n\nThe following field in your JUnit UI test class will prepare a container running Chrome:\n<!--codeinclude-->\n[Chrome](../../modules/selenium/src/test/java/org/testcontainers/selenium/ChromeWebDriverContainerTest.java) inside_block:junitRule\n<!--/codeinclude-->\n\n        \nNow, instead of instantiating an instance of WebDriver directly, use the following to obtain an instance inside your\ntest methods:\n<!--codeinclude-->\n[RemoteWebDriver](../../modules/selenium/src/test/java/org/testcontainers/selenium/LocalServerWebDriverContainerTest.java) inside_block:getWebDriver\n<!--/codeinclude-->\n\nYou can then use this driver instance like a regular WebDriver.\n\nNote that, if you want to test a **web application running on the host machine** (the machine the JUnit tests are\nrunning on - which is quite likely), you'll need to use [the host exposing](../features/networking.md#exposing-host-ports-to-the-container) feature of Testcontainers, e.g.:\n<!--codeinclude-->\n[Open Web Page](../../modules/selenium/src/test/java/org/testcontainers/selenium/LocalServerWebDriverContainerTest.java) inside_block:getPage\n<!--/codeinclude-->\n\n\n## Options\n\n### Other browsers\n\nAt the moment, Chrome, Firefox and Edge are supported. To switch, simply change the first parameter to the rule constructor:\n<!--codeinclude-->\n[Chrome](../../modules/selenium/src/test/java/org/testcontainers/selenium/ChromeWebDriverContainerTest.java) inside_block:junitRule\n[Firefox](../../modules/selenium/src/test/java/org/testcontainers/selenium/FirefoxWebDriverContainerTest.java) inside_block:junitRule\n[Edge](../../modules/selenium/src/test/java/org/testcontainers/selenium/EdgeWebDriverContainerTest.java) inside_block:junitRule\n<!--/codeinclude-->\n\n### Recording videos\n\nBy default, no videos will be recorded. However, you can instruct Testcontainers to capture videos for all tests or\njust for failing tests.\n\n<!--codeinclude-->\n[Record all Tests](../../modules/selenium/src/test/java/org/testcontainers/selenium/ChromeRecordingWebDriverContainerTest.java) inside_block:recordAll\n[Record failing Tests](../../modules/selenium/src/test/java/org/testcontainers/selenium/ChromeRecordingWebDriverContainerTest.java) inside_block:recordFailing\n<!--/codeinclude-->\n\nNote that the second parameter of `withRecordingMode` should be a directory where recordings can be saved.\n\nBy default, the video will be recorded in [FLV](https://en.wikipedia.org/wiki/Flash_Video) format, but you can specify it explicitly or change it to [MP4](https://en.wikipedia.org/wiki/MPEG-4_Part_14) using `withRecordingMode` method with `VncRecordingFormat` option:\n\n<!--codeinclude-->\n[Video Format in MP4](../../modules/selenium/src/test/java/org/testcontainers/selenium/ChromeRecordingWebDriverContainerTest.java) inside_block:recordMp4\n[Video Format in FLV](../../modules/selenium/src/test/java/org/testcontainers/selenium/ChromeRecordingWebDriverContainerTest.java) inside_block:recordFlv\n<!--/codeinclude-->\n\nIf you would like to customise the file name of the recording, or provide a different directory at runtime based on the description of the test and/or its success or failure, you may provide a custom recording file factory as follows:\n<!--codeinclude-->\n[CustomRecordingFileFactory](../../modules/selenium/src/test/java/org/testcontainers/selenium/ChromeRecordingWebDriverContainerTest.java) inside_block:withRecordingFileFactory\n<!--/codeinclude-->\n\n\nNote the factory must implement `org.testcontainers.containers.RecordingFileFactory`.\n\n## More examples\n\nA few different examples are shown in [ChromeWebDriverContainerTest.java](https://github.com/testcontainers/testcontainers-java/blob/main/modules/selenium/src/test/java/org/testcontainers/selenium/ChromeWebDriverContainerTest.java).\n\n## Adding this module to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-selenium:{{latest_version}}\"\n    ```\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-selenium</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n\n!!! hint\n    Adding this Testcontainers library JAR will not automatically add a Selenium Webdriver JAR to your project. You should ensure that your project also has suitable Selenium dependencies in place, for example:\n\n    === \"Gradle\"\n        ```groovy\n        compile \"org.seleniumhq.selenium:selenium-remote-driver:3.141.59\"\n        ```\n    \n    === \"Maven\"\n        ```xml\n        <dependency>\n            <groupId>org.seleniumhq.selenium</groupId>\n            <artifactId>selenium-remote-driver</artifactId>\n            <version>3.141.59</version>\n        </dependency>\n        ```\n    \n    Testcontainers will try and match the version of the Dockerized browser to whichever version of Selenium is found on the classpath\n"
  },
  {
    "path": "docs/quickstart/junit_4_quickstart.md",
    "content": "# JUnit 4 Quickstart\n\nIt's easy to add Testcontainers to your project - let's walk through a quick example to see how.\n\nLet's imagine we have a simple program that has a dependency on Redis, and we want to add some tests for it.\nIn our imaginary program, there is a `RedisBackedCache` class which stores data in Redis.\n \nYou can see an example test that could have been written for it (without using Testcontainers):\n\n<!--codeinclude-->\n[Pre-Testcontainers test code](../examples/junit4/redis/src/test/java/quickstart/RedisBackedCacheIntTestStep0.java) block:RedisBackedCacheIntTestStep0\n<!--/codeinclude-->\n\nNotice that the existing test has a problem - it's relying on a local installation of Redis, which is a red flag for test reliability.\nThis may work if we were sure that every developer and CI machine had Redis installed, but would fail otherwise.\nWe might also have problems if we attempted to run tests in parallel, such as state bleeding between tests, or port clashes.\n\nLet's start from here, and see how to improve the test with Testcontainers:  \n\n## 1. Add Testcontainers as a test-scoped dependency\n\nFirst, add Testcontainers as a dependency as follows:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers:{{latest_version}}\"\n    ```\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n\n## 2. Get Testcontainers to run a Redis container during our tests\n\nSimply add the following to the body of our test class:\n\n<!--codeinclude-->\n[JUnit 4 Rule](../examples/junit4/redis/src/test/java/quickstart/RedisBackedCacheIntTest.java) inside_block:rule\n<!--/codeinclude-->\n\nThe `@Rule` annotation tells JUnit to notify this field about various events in the test lifecycle.\nIn this case, our rule object is a Testcontainers `GenericContainer`, configured to use a specific Redis image from Docker Hub, and configured to expose a port.\n\nIf we run our test as-is, then regardless of the actual test outcome, we'll see logs showing us that Testcontainers:\n\n* was activated before our test method ran\n* discovered and quickly tested our local Docker setup\n* pulled the image if necessary\n* started a new container and waited for it to be ready\n* shut down and deleted the container after the test\n\n## 3. Make sure our code can talk to the container\n\nBefore Testcontainers, we might have hardcoded an address like `localhost:6379` into our tests.\n\nTestcontainers uses *randomized ports* for each container it starts, but makes it easy to obtain the actual port at runtime.\nWe can do this in our test `setUp` method, to set up our component under test:\n\n<!--codeinclude-->\n[Obtaining a mapped port](../examples/junit4/redis/src/test/java/quickstart/RedisBackedCacheIntTest.java) inside_block:setUp\n<!--/codeinclude-->\n\n!!! tip\n    Notice that we also ask Testcontainers for the container's actual address with `redis.getHost();`, \n    rather than hard-coding `localhost`. `localhost` may work in some environments but not others - for example it may\n    not work on your current or future CI environment. As such, **avoid hard-coding** the address, and use \n    `getHost()` instead.\n\n## 4. Run the tests!\n\nThat's it!\n\nLet's look at our complete test class to see how little we had to add to get up and running with Testcontainers:\n\n<!--codeinclude-->\n[RedisBackedCacheIntTest](../examples/junit4/redis/src/test/java/quickstart/RedisBackedCacheIntTest.java) block:RedisBackedCacheIntTest\n<!--/codeinclude-->\n\n"
  },
  {
    "path": "docs/quickstart/junit_5_quickstart.md",
    "content": "# JUnit 5 Quickstart\n\nIt's easy to add Testcontainers to your project - let's walk through a quick example to see how.\n\nLet's imagine we have a simple program that has a dependency on Redis, and we want to add some tests for it.\nIn our imaginary program, there is a `RedisBackedCache` class which stores data in Redis.\n \nYou can see an example test that could have been written for it (without using Testcontainers):\n\n<!--codeinclude-->\n[Pre-Testcontainers test code](../examples/junit5/redis/src/test/java/quickstart/RedisBackedCacheIntTestStep0.java) block:RedisBackedCacheIntTestStep0\n<!--/codeinclude-->\n\nNotice that the existing test has a problem - it's relying on a local installation of Redis, which is a red flag for test reliability.\nThis may work if we were sure that every developer and CI machine had Redis installed, but would fail otherwise.\nWe might also have problems if we attempted to run tests in parallel, such as state bleeding between tests, or port clashes.\n\nLet's start from here, and see how to improve the test with Testcontainers:  \n\n## 1. Add Testcontainers as a test-scoped dependency\n\nFirst, add Testcontainers as a dependency as follows:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.junit.jupiter:junit-jupiter:5.8.1\"\n    testImplementation \"org.testcontainers:testcontainers:{{latest_version}}\"\n    testImplementation \"org.testcontainers:testcontainers-junit-jupiter:{{latest_version}}\"\n    ```\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.junit.jupiter</groupId>\n        <artifactId>junit-jupiter</artifactId>\n        <version>5.8.1</version>\n        <scope>test</scope>\n    </dependency>\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-junit-jupiter</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n\n## 2. Get Testcontainers to run a Redis container during our tests\n\nFirst, you'll need to annotate the test class with `@Testcontainers`. Furthermore, add the following to the body of our test class:\n\n<!--codeinclude-->\n[JUnit 5 Rule](../examples/junit5/redis/src/test/java/quickstart/RedisBackedCacheIntTest.java) inside_block:container\n<!--/codeinclude-->\n\nThe `@Container` annotation tells JUnit to notify this field about various events in the test lifecycle.\nIn this case, our rule object is a Testcontainers `GenericContainer`, configured to use a specific Redis image from Docker Hub, and configured to expose a port.\n\nIf we run our test as-is, then regardless of the actual test outcome, we'll see logs showing us that Testcontainers:\n\n* was activated before our test method ran\n* discovered and quickly tested our local Docker setup\n* pulled the image if necessary\n* started a new container and waited for it to be ready\n* shut down and deleted the container after the test\n\n## 3. Make sure our code can talk to the container\n\nBefore Testcontainers, we might have hardcoded an address like `localhost:6379` into our tests.\n\nTestcontainers uses *randomized ports* for each container it starts, but makes it easy to obtain the actual port at runtime.\nWe can do this in our test `setUp` method, to set up our component under test:\n\n<!--codeinclude-->\n[Obtaining a mapped port](../examples/junit5/redis/src/test/java/quickstart/RedisBackedCacheIntTest.java) inside_block:setUp\n<!--/codeinclude-->\n\n!!! tip\n    Notice that we also ask Testcontainers for the container's actual address with `redis.getHost();`, \n    rather than hard-coding `localhost`. `localhost` may work in some environments but not others - for example it may\n    not work on your current or future CI environment. As such, **avoid hard-coding** the address, and use \n    `getHost()` instead.\n\n## 4. Additional attributes\n\nAdditional attributes are available for the `@Testcontainers` annotation.\nThose attributes can be helpful when:\n\n* Tests should be skipped instead of failing because Docker is unavailable in the\ncurrent environment. Set `disabledWithoutDocker` to `true`.\n* Enable parallel container initialization instead of sequential (by default). Set `parallel` to `true`.\n\n## 5. Run the tests!\n\nThat's it!\n\nLet's look at our complete test class to see how little we had to add to get up and running with Testcontainers:\n\n<!--codeinclude-->\n[RedisBackedCacheIntTest](../examples/junit5/redis/src/test/java/quickstart/RedisBackedCacheIntTest.java) inside_block:class\n<!--/codeinclude-->\n\n"
  },
  {
    "path": "docs/quickstart/spock_quickstart.md",
    "content": "# Spock Quickstart\n\nIt's easy to add Testcontainers to your project - let's walk through a quick example to see how.\n\nLet's imagine we have a simple program that has a dependency on Redis, and we want to add some tests for it.\nIn our imaginary program, there is a `RedisBackedCache` class which stores data in Redis.\n \nYou can see an example test that could have been written for it (without using Testcontainers):\n\n<!--codeinclude-->\n[Pre-Testcontainers test code](../examples/spock/redis/src/test/groovy/quickstart/RedisBackedCacheIntTestStep0.groovy) block:RedisBackedCacheIntTestStep0\n<!--/codeinclude-->\n\nNotice that the existing test has a problem - it's relying on a local installation of Redis, which is a red flag for test reliability.\nThis may work if we were sure that every developer and CI machine had Redis installed, but would fail otherwise.\nWe might also have problems if we attempted to run tests in parallel, such as state bleeding between tests, or port clashes.\n\nLet's start from here, and see how to improve the test with Testcontainers:  \n\n## 1. Add Testcontainers as a test-scoped dependency\n\nFirst, add Testcontainers as a dependency as follows:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-spock:{{latest_version}}\"\n    ```\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-spock</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n\n## 2. Get Testcontainers to run a Redis container during our tests\n\nAnnotate the Spock specification class with the Testcontainers extension:\n\n=== \"Spock Testcontainers annotation\"\n    ```groovy\n    @org.testcontainers.spock.Testcontainers\n    class RedisBackedCacheIntTest extends Specification {\n    ```\n\nAnd add the following field to the body of our test class:\n\n<!--codeinclude-->\n[Spock Testcontainers init](../examples/spock/redis/src/test/groovy/quickstart/RedisBackedCacheIntTest.groovy) inside_block:init\n<!--/codeinclude-->\n\nThis tells Spock to start a Testcontainers `GenericContainer`, configured to use a specific Redis image from Docker Hub, and configured to expose a port.\n\nIf we run our test as-is, then regardless of the actual test outcome, we'll see logs showing us that Testcontainers:\n\n* was activated before our test method ran\n* discovered and quickly tested our local Docker setup\n* pulled the image if necessary\n* started a new container and waited for it to be ready\n* shut down and deleted the container after the test\n\n## 3. Make sure our code can talk to the container\n\nBefore Testcontainers, we might have hardcoded an address like `localhost:6379` into our tests.\n\nTestcontainers uses *randomized ports* for each container it starts, but makes it easy to obtain the actual port at runtime.\nWe can do this in our test `setup` method, to set up our component under test:\n\n<!--codeinclude-->\n[Obtaining a mapped port](../examples/spock/redis/src/test/groovy/quickstart/RedisBackedCacheIntTest.groovy) inside_block:setup\n<!--/codeinclude-->\n\n!!! tip\n    Notice that we also ask Testcontainers for the container's actual address with `redis.containerIpAddress`, \n    rather than hard-coding `localhost`. `localhost` may work in some environments but not others - for example it may\n    not work on your current or future CI environment. As such, **avoid hard-coding** the address, and use \n    `containerIpAddress` instead.\n\n## 4. Run the tests!\n\nThat's it!\n\nLet's look at our complete test class to see how little we had to add to get up and running with Testcontainers:\n\n<!--codeinclude-->\n[RedisBackedCacheIntTest](../examples/spock/redis/src/test/groovy/quickstart/RedisBackedCacheIntTest.groovy) block:complete\n<!--/codeinclude-->\n"
  },
  {
    "path": "docs/supported_docker_environment/continuous_integration/aws_codebuild.md",
    "content": "# AWS CodeBuild\n\nTo enable access to Docker in AWS CodeBuild, go to `Privileged` section and check  \n`Enable this flag if you want to build Docker images or want your builds to get elevated privileges`.\n\nThis is a sample `buildspec.yml` config:\n\n```yaml\nversion: 0.2\n\nphases:\n  install:\n    runtime-versions:\n      java: corretto17\n  build:\n    commands:\n      - ./mvnw test\n```\n"
  },
  {
    "path": "docs/supported_docker_environment/continuous_integration/bitbucket_pipelines.md",
    "content": "# Bitbucket Pipelines\n\nTo enable access to Docker in Bitbucket Pipelines, you need to add `docker` as a service on the step.\n\nFurthermore, Ryuk needs to be turned off since Bitbucket Pipelines does not allow starting privileged containers (see [Disabling Ryuk](../../features/configuration.md#disabling-ryuk)). This can either be done by setting a repository variable in Bitbucket's project settings or by explicitly exporting the variable on a step.\n\nIn some cases the memory available to Docker needs to be increased.\n\nHere is a sample Bitbucket Pipeline configuration that does a checkout of a project and runs maven:\n\n```yml\nimage: maven:3.6.1\n\npipelines:\n  default:\n    - step:\n        script:\n          - export TESTCONTAINERS_RYUK_DISABLED=true\n          - mvn clean install\n        services:\n          - docker\ndefinitions:\n  services:\n    docker:\n      memory: 2048\n```\n"
  },
  {
    "path": "docs/supported_docker_environment/continuous_integration/circle_ci.md",
    "content": "# CircleCI (Cloud, Server v2.x, and Server v3.x)\n\nYour CircleCI configuration should use a dedicated VM for Testcontainers to work. You can achieve this by specifying the \nexecutor type in your `.circleci/config.yml` to be `machine` instead of the default `docker` executor (see [Choosing an Executor Type](https://circleci.com/docs/executor-intro) for more info).  \n\nHere is a sample CircleCI configuration that does a checkout of a project and runs Maven:\n\n```yml\njobs:\n  build:\n    # Check https://circleci.com/docs/executor-intro#linux-vm for more details\n    machine: true\n    steps:\n      - checkout\n      - run: mvn -B clean install\n```\n\nYou can learn more about the best practices of using Testcontainers together with CircleCI in [this article](https://www.atomicjar.com/2022/12/testcontainers-with-circleci/).\n"
  },
  {
    "path": "docs/supported_docker_environment/continuous_integration/concourse_ci.md",
    "content": "# Concourse CI\n\nThis is an example to run Testcontainers tests on [Concourse CI](https://concourse-ci.org/).\n\nA possible `pipeline.yml` config looks like this:\n\n```yaml\nresources:\n- name: repo\n  type: git\n  source:\n    uri: https://github.com/testcontainers/testcontainers-java-repro.git\n\njobs:\n- name: testcontainers-job\n  plan:\n  # Add a get step referencing the resource\n  - get: repo\n  - task: testcontainers-task\n    privileged: true\n    config:\n      platform: linux\n      image_resource:\n        type: docker-image\n        source:\n          repository: amidos/dcind\n          tag: 2.1.0\n      inputs:\n      - name: repo\n      run:\n        path: /bin/sh\n        args: \n          - -c\n          - |\n            source /docker-lib.sh\n            start_docker\n\n            cd repo\n            docker run -it --rm -v \"$PWD:$PWD\" -w \"$PWD\" -v /var/run/docker.sock:/var/run/docker.sock eclipse-temurin:17.0.5_8-jdk-alpine ./mvnw clean package\n```\n\n```bash\nfly -t tutorial set-pipeline -p testcontainers-pipeline -c pipeline.yml\nfly -t tutorial unpause-pipeline -p testcontainers-pipeline\nfly -t tutorial trigger-job --job testcontainers-pipeline/testcontainers-job --watch\n```\n"
  },
  {
    "path": "docs/supported_docker_environment/continuous_integration/dind_patterns.md",
    "content": "# Patterns for running tests inside a Docker container\n\n## 'Docker wormhole' pattern - Sibling docker containers\n\nTestcontainers itself can be used from inside a container.\nThis is very useful for different CI scenarios like running everything in containers on Jenkins, or Docker-based CI tools such as Drone.\n\nTestcontainers will automatically detect if it's inside a container and instead of \"localhost\" will use the default gateway's IP.\n\nHowever, additional configuration is required if you use [volume mapping](../../features/files.md). The following points need to be considered:\n\n* The docker socket must be available via a volume mount\n* The 'local' source code directory must be volume mounted *at the same path* inside the container that Testcontainers runs in, so that Testcontainers is able to set up the correct volume mounts for the containers it spawns.\n\n### Docker-only example\nIf you run the tests with just `docker run ...` then make sure you add `-v $PWD:$PWD -w $PWD -v /var/run/docker.sock:/var/run/docker.sock` to the command, so it will look like this:\n```bash\n$ tree .\n.\n├── pom.xml\n└── src\n    └── test\n        └── java\n            └── MyTestWithTestcontainers.java\n\n$ docker run -it --rm -v $PWD:$PWD -w $PWD -v /var/run/docker.sock:/var/run/docker.sock maven:3 mvn test\n```\n\nWhere:\n\n* `-v $PWD:$PWD` will add your current directory as a volume inside the container\n* `-w $PWD` will set the current directory to this volume\n* `-v /var/run/docker.sock:/var/run/docker.sock` will map the Docker socket\n\n\n!!! note\n    If you are using Docker Desktop, you need to configure the `TESTCONTAINERS_HOST_OVERRIDE` environment variable to use the special DNS name\n    `host.docker.internal` for accessing the host from within a container, which is provided by Docker Desktop:\n    `-e TESTCONTAINERS_HOST_OVERRIDE=host.docker.internal`\n\n### Docker Compose example\nThe same can be achieved with Docker Compose:\n```yaml\ntests:\n  image: maven:3\n  stop_signal: SIGKILL\n  stdin_open: true\n  tty: true\n  working_dir: $PWD\n  volumes:\n    - $PWD:$PWD\n    - /var/run/docker.sock:/var/run/docker.sock\n    # Maven cache (optional)\n    - ~/.m2:/root/.m2\n  command: mvn test\n```\n\n## Docker-in-Docker\n\nWhile Docker-in-Docker (DinD) is generally considered an instrument of last resort, it is necessary for some CI environments.\n\n[Drone CI](./drone.md) is one such example. Testcontainers has a Docker-in-Docker plugin (build image) for use with Drone,\nwhich could be used as inspiration for setting up other similar testing using DinD.\n"
  },
  {
    "path": "docs/supported_docker_environment/continuous_integration/drone.md",
    "content": "# Drone CI\n\nDrone CI 0.8 is supported via the use of a general purpose Docker-in-Docker plugin.\n\nPlease see [testcontainers/dind-drone-plugin](https://github.com/testcontainers/dind-drone-plugin) for further details and usage instructions.\n\n"
  },
  {
    "path": "docs/supported_docker_environment/continuous_integration/gitlab_ci.md",
    "content": "# GitLab CI\n\n## Example using Docker socket\nThis applies if you have your own GitlabCI runner installed, use the Docker executor and you have `/var/run/docker.sock` mounted in the runner configuration.\n\nSee below for an example runner configuration: \n```toml\n[[runners]]\n  name = \"MACHINE_NAME\"\n  url = \"https://gitlab.com/\"\n  token = \"GENERATED_GITLAB_RUNNER_TOKEN\"\n  executor = \"docker\"\n  [runners.docker]\n    tls_verify = false\n    image = \"docker:latest\"\n    privileged = false\n    disable_entrypoint_overwrite = false\n    oom_kill_disable = false\n    disable_cache = false\n    volumes = [\"/var/run/docker.sock:/var/run/docker.sock\", \"/cache\"]\n    shm_size = 0\n```\n\nPlease also include the following in your GitlabCI pipeline definitions (`.gitlab-ci.yml`) that use Testcontainers:\n```yml\nvariables:\n  TESTCONTAINERS_HOST_OVERRIDE: \"<ip-docker-host>\"\n```\n\nThe environment variable `TESTCONTAINERS_HOST_OVERRIDE` needs to be configured, otherwise, a wrong IP address would be used to resolve the Docker host, which will likely lead to failing tests. For Windows and MacOS, use `host.docker.internal`.\n\n## Example using DinD (Docker-in-Docker)\n\nIn order to use Testcontainers in a Gitlab CI pipeline, you need to run the job as a Docker container (see [Patterns for running inside Docker](dind_patterns.md)).\nSo edit your `.gitlab-ci.yml` to include the [Docker-In-Docker service](https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#use-docker-in-docker-workflow-with-docker-executor) (`docker:dind`) and set the `DOCKER_HOST` variable to `tcp://docker:2375` and `DOCKER_TLS_CERTDIR` to empty string. \n\nCaveat: Current docker releases (verified for 20.10.9) intentionally delay the startup, if the docker api is bound to a network address but not TLS protected. To avoid this delay, the docker process needs to be started with the argument `--tls=false`.  Otherwise jobs which access the docker api at the very beginning might fail.\n\nHere is a sample `.gitlab-ci.yml` that executes test with gradle:\n\n```yml\n# DinD service is required for Testcontainers\nservices:\n  - name: docker:dind\n    # explicitly disable tls to avoid docker startup interruption\n    command: [\"--tls=false\"]\n\nvariables:\n  # Instruct Testcontainers to use the daemon of DinD, use port 2375 for non-tls connections.\n  DOCKER_HOST: \"tcp://docker:2375\"\n  # Instruct Docker not to start over TLS.\n  DOCKER_TLS_CERTDIR: \"\"\n  # Improve performance with overlayfs.\n  DOCKER_DRIVER: overlay2\n\ntest:\n image: gradle:5.0\n stage: test\n script: ./gradlew test\n```\n"
  },
  {
    "path": "docs/supported_docker_environment/continuous_integration/tekton.md",
    "content": "# Tekton\n\nTo enable access to Docker in Tekton, a dind sidecar needs to be added. An example of it can be found \n[here](https://github.com/tektoncd/pipeline/blob/main/examples/v1beta1/taskruns/dind-sidecar.yaml)\n\nThis is an example\n\n```yaml\napiVersion: tekton.dev/v1beta1\nkind: Task\nmetadata:\n  name: run-tests\n  description: Run Tests\nspec:\n  workspaces:\n    - name: source\n  steps:\n    - name: read\n      image: eclipse-temurin:17.0.3_7-jdk-alpine\n      workingDir: $(workspaces.source.path)\n      script: ./mvnw test\n      volumeMounts:\n        - mountPath: /var/run/\n          name: dind-socket\n  sidecars:\n    - image: docker:20.10-dind\n      name: docker\n      securityContext:\n        privileged: true\n      volumeMounts:\n        - mountPath: /var/lib/docker\n          name: dind-storage\n        - mountPath: /var/run/\n          name: dind-socket\n  volumes:\n    - name: dind-storage\n      emptyDir: { }\n    - name: dind-socket\n      emptyDir: { }\n---\napiVersion: tekton.dev/v1beta1\nkind: Pipeline\nmetadata:\n  name: testcontainers-demo\nspec:\n  description: |\n    This pipeline clones a git repo, run testcontainers.\n  params:\n    - name: repo-url\n      type: string\n      description: The git repo URL to clone from.\n  workspaces:\n    - name: shared-data\n      description: |\n        This workspace contains the cloned repo files, so they can be read by the\n        next task.\n  tasks:\n    - name: fetch-source\n      taskRef:\n        name: git-clone\n      workspaces:\n        - name: output\n          workspace: shared-data\n      params:\n        - name: url\n          value: $(params.repo-url)\n    - name: run-tests\n      runAfter: [\"fetch-source\"]\n      taskRef:\n        name: run-tests\n      workspaces:\n        - name: source\n          workspace: shared-data\n---\napiVersion: tekton.dev/v1beta1\nkind: PipelineRun\nmetadata:\n  name: testcontainers-demo-run\nspec:\n  pipelineRef:\n    name: testcontainers-demo\n  workspaces:\n    - name: shared-data\n      volumeClaimTemplate:\n        spec:\n          accessModes:\n            - ReadWriteOnce\n          resources:\n            requests:\n              storage: 1Gi\n  params:\n    - name: repo-url\n      value: https://github.com/testcontainers/testcontainers-java-repro.git\n```\n"
  },
  {
    "path": "docs/supported_docker_environment/continuous_integration/travis.md",
    "content": "# Travis\n\nTo run Testcontainers on TravisCI, docker needs to be installed. The configuration below\nis the minimal required config.\n\n```yaml\nlanguage: java\njdk:\n- openjdk8\n\nservices:\n- docker\n\nscript: ./mvnw verify\n```\n"
  },
  {
    "path": "docs/supported_docker_environment/image_registry_rate_limiting.md",
    "content": "# Image Registry rate limiting\n\nAs of November 2020 Docker Hub pulls are rate limited. \nAs Testcontainers uses Docker Hub for standard images, some users may hit these rate limits and should mitigate accordingly.\n\nSuggested mitigations are noted in [this issue](https://github.com/testcontainers/testcontainers-java/issues/3099) at present.\n\n## Which images are used by Testcontainers?\n\nAs of the current version of Testcontainers ({{latest_version}}):\n\n* every image directly used by your tests\n* images pulled by Testcontainers itself to support functionality:\n    * [`testcontainers/ryuk`](https://hub.docker.com/r/testcontainers/ryuk) - performs fail-safe cleanup of containers, and always required (unless [Ryuk is disabled](../features/configuration.md#disabling-ryuk))\n    * [`alpine`](https://hub.docker.com/r/_/alpine) - used to check whether images can be pulled at startup, and always required (unless [startup checks are disabled](../features/configuration.md#disabling-the-startup-checks))\n    * [`testcontainers/sshd`](https://hub.docker.com/r/testcontainers/sshd) - required if [exposing host ports to containers](../features/networking.md#exposing-host-ports-to-the-container)\n    * [`testcontainers/vnc-recorder`](https://hub.docker.com/r/testcontainers/vnc-recorder) - required if using [Webdriver containers](../modules/webdriver_containers.md) and using the screen recording feature\n    * [`docker/compose`](https://hub.docker.com/r/docker/compose) - required if using [Docker Compose](../modules/docker_compose.md)\n    * [`alpine/socat`](https://hub.docker.com/r/alpine/socat) - required if using [Docker Compose](../modules/docker_compose.md)\n"
  },
  {
    "path": "docs/supported_docker_environment/index.md",
    "content": "# General Container runtime requirements\n\n## Overview\n\nTo run Testcontainers-based tests, \nyou need a Docker-API compatible container runtime, \nsuch as using [Testcontainers Cloud](https://www.testcontainers.cloud/) or installing Docker locally.\nDuring development, Testcontainers is actively tested against recent versions of Docker on Linux,\nas well as against Docker Desktop on Mac and Windows.\nThese Docker environments are automatically detected and used by Testcontainers without any additional configuration being necessary.\n\nIt is possible to configure Testcontainers to work with alternative container runtimes.\nMaking use of the free [Testcontainers Desktop](https://testcontainers.com/desktop/) app will take care of most of the manual configuration.\nWhen using those alternatives without Testcontainers Desktop, \nsometimes some manual configuration might be necessary \n(see further down for specific runtimes, or [Customizing Docker host detection](/features/configuration/#customizing-docker-host-detection) for general configuration mechanisms).\nAlternative container runtimes are not actively tested in the main development workflow,\nso not all Testcontainers features might be available.\nIf you have further questions about configuration details for your setup or whether it supports running Testcontainers-based tests,\nplease contact the Testcontainers team and other users from the Testcontainers community on [Slack](https://slack.testcontainers.org/).\n\n## Colima\n\nIn order to run testcontainers against [colima](https://github.com/abiosoft/colima) the env vars below should be set\n\n```bash\ncolima start --network-address\nexport TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE=/var/run/docker.sock\nexport TESTCONTAINERS_HOST_OVERRIDE=$(colima ls -j | jq -r '.address')\nexport DOCKER_HOST=\"unix://${HOME}/.colima/default/docker.sock\"\n```\n\n## Podman\n\nIn order to run testcontainers against [podman](https://podman.io/) the env vars bellow should be set\n\nMacOS:\n\n```bash\n{% raw %}\nexport DOCKER_HOST=unix://$(podman machine inspect --format '{{.ConnectionInfo.PodmanSocket.Path}}')\nexport TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE=/var/run/docker.sock\n{% endraw %}\n```\n\nLinux:\n\n```bash\nexport DOCKER_HOST=unix://${XDG_RUNTIME_DIR}/podman/podman.sock\n```\n\nIf you're running Podman in rootless mode, ensure to include the following line to disable Ryuk:\n\n```bash\nexport TESTCONTAINERS_RYUK_DISABLED=true\n```\n\n!!! note\n    Previous to version 1.19.0, `export TESTCONTAINERS_RYUK_PRIVILEGED=true`\n    was required for rootful mode. Starting with 1.19.0, this is no longer required.\n\n## Rancher Desktop\n\nIn order to run testcontainers against [Rancher Desktop](https://rancherdesktop.io/) the env vars below should be set.\n\nIf you're running Rancher Desktop as an administrator in a MacOS (M1) machine:\n\nUsing QEMU emulation\n\n```bash\nexport TESTCONTAINERS_HOST_OVERRIDE=$(rdctl shell ip a show rd0 | awk '/inet / {sub(\"/.*\",\"\"); print $2}')\n```\n\nUsing VZ emulation\n\n```bash\nexport TESTCONTAINERS_HOST_OVERRIDE=$(rdctl shell ip a show vznat | awk '/inet / {sub(\"/.*\",\"\"); print $2}')\n```\n\nIf you're not running Rancher Desktop as an administrator in a MacOS (M1) machine:\n\nUsing VZ emulation\n\n```bash\nexport DOCKER_HOST=unix://$HOME/.rd/docker.sock\nexport TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE=/var/run/docker.sock\nexport TESTCONTAINERS_HOST_OVERRIDE=$(rdctl shell ip a show vznat | awk '/inet / {sub(\"/.*\",\"\"); print $2}')\n```\n\n## Docker environment discovery\n\nTestcontainers will try to connect to a Docker daemon using the following strategies in order:\n\n* Environment variables:\n\t* `DOCKER_HOST`\n\t* `DOCKER_TLS_VERIFY`\n\t* `DOCKER_CERT_PATH`\n* Defaults:\n\t* `DOCKER_HOST=https://localhost:2376`\n\t* `DOCKER_TLS_VERIFY=1`\n\t* `DOCKER_CERT_PATH=~/.docker`\n* If Docker Machine is installed, the docker machine environment for the *first* machine found. Docker Machine needs to be on the PATH for this to succeed.\n* If you're going to run your tests inside a container, please read [Patterns for running tests inside a docker container](continuous_integration/dind_patterns.md) first.\n\n## Docker registry authentication\n\nTestcontainers will try to authenticate to registries with supplied config using the following strategies in order:\n\n* Environment variables:\n    * `DOCKER_AUTH_CONFIG`\n* Docker config\n\t* At location specified in `DOCKER_CONFIG` or at `{HOME}/.docker/config.json`\n"
  },
  {
    "path": "docs/supported_docker_environment/logging_config.md",
    "content": "# Recommended logback configuration\n\nTestcontainers, and many of the libraries it uses, utilize SLF4J for logging. In order to see logs from Testcontainers,\nyour project should include an SLF4J implementation (Logback is recommended). The following example `logback-test.xml`\nshould be included in your classpath to show a reasonable level of log output:\n\n```xml\n<configuration>\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"info\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n    <!-- The following logger can be used for containers logs since 1.18.0 -->\n    <logger name=\"tc\" level=\"INFO\"/>\n    <logger name=\"com.github.dockerjava\" level=\"WARN\"/>\n    <logger name=\"com.github.dockerjava.zerodep.shaded.org.apache.hc.client5.http.wire\" level=\"OFF\"/>\n</configuration>\n```\n\nIn order to troubleshoot issues with Testcontainers, increase the logging level of `org.testcontainers` to `DEBUG`:\n\n```xml\n<logger name=\"org.testcontainers\" level=\"DEBUG\"/>\n```\n\nAvoid changing the root logger's level to `DEBUG`, because this turns on debug logging for every package whose level isn't explicitly configured here, resulting in a large amount of log data.\n"
  },
  {
    "path": "docs/supported_docker_environment/windows.md",
    "content": "# Windows Support\n\n## Prerequisites\n* [Docker for Windows](https://docs.docker.com/docker-for-windows/) needs to be installed\n  * Docker version 17.06 is confirmed to work on Windows 10 with Hyper-V.\n  * Testcontainers supports communication with Docker on Docker for Windows using named pipes.\n  * WSL2 backend is supported starting with Windows 10 2004. (**Beta**)\n  * Docker on Windows Server 2019 is currently **not supported** (also note [this issue](https://github.com/testcontainers/testcontainers-java/issues/2960)).\n\n## Limitations\nThe following features are not available or do not work correctly so make sure you do not use them or use them with \ncaution. The list may not be complete.\n\n### MySQL containers\n* MySQL server prevents custom configuration file (ini-script) from being loaded due to security measures ([link to feature description](../modules/databases/index.md#using-an-init-script-from-a-file))\n\n### Windows Container on Windows (WCOW)\n\n* WCOW is currently not supported, since Testcontainers uses auxiliary Linux containers for certain tasks and Docker for Windows does not support hybrid engine mode at the time of writing.\n\n## WSL2 backend\n\nUsing Docker for Windows with WSL2 backend should work out of the box.\n\nHowever, there is an [existing issue](https://github.com/microsoft/WSL/issues/4694) in WSL/WSL2 that effects certain older Docker images. \nThe currently proposed workaround is to enable vsyscall emulation in the WSL2 kernel:\n```\n[wsl2]\nkernelCommandLine = vsyscall=emulate\n```\n\n## Windows Subsystem for Linux (WSL)\n\nTestcontainers supports communicating with Docker for Windows within the Windows Subsystem for Linux *([**WSL**](https://docs.microsoft.com/en-us/windows/wsl/about))*.\nThe following additional configurations steps are required:\n\n+ Expose the Docker for Windows daemon on tcp port `2375` without **TLS**.\n  *(Right-click the Docker for Windows icon on the task bar, click setting and go to `General`)*.\n\n+ Set the `DOCKER_HOST` environment variable inside the **WSL** shell to `tcp://localhost:2375`.\n  It is recommended to add this to your `~/.bashrc` file, so it’s available every time you open your terminal.\n\n+ **Optional** - Only if volumes are required:  \n  Inside the **WSL** shell, modify the `/ect/wsl.conf` file to mount the Windows drives on `/` instead of on `/mnt/`.\n  *(Reboot required after this step)*.  \n  Remember to share the drives, on which you will store your volumes, with Docker for Windows.\n  *(Right-click the Docker for Windows icon on the task bar, click setting and go to `Shared Drives`)*.\n\nMore information about running Docker within the **WSL** can be found [here](https://nickjanetakis.com/blog/setting-up-docker-for-windows-and-wsl-to-work-flawlessly).\n\n## Reporting issues\n\nPlease report any issues with the Windows build of Testcontainers [here](https://github.com/testcontainers/testcontainers-java/issues)\nand be sure to note that you are using this on Windows.\n"
  },
  {
    "path": "docs/test_framework_integration/external.md",
    "content": "# External Integrations\n\nThe following Open Source frameworks add direct integration to Testcontainers\n\n| Framework | Source Code | Documentation |\n| --- | --- | --- |\n| jqwik | [jqwik-testcontainers](https://github.com/jqwik-team/jqwik-testcontainers) | [README](https://github.com/jqwik-team/jqwik-testcontainers) |\n| Kotest | [Kotest Extensions Testcontainers](https://github.com/kotest/kotest/tree/master/kotest-extensions/kotest-extensions-testcontainers) | [kotest.io](https://kotest.io/docs/extensions/test_containers.html) |\n| Synthesized | [Synthesized TDK-Testcontainers integration](https://github.com/synthesized-io/tdk-tc) | [synthesized.io](https://docs.synthesized.io/tdk/latest/user_guide/integrations/testcontainers) |\n| TCI | [Testcontainers Infrastructure (TCI) Framework](https://github.com/xdev-software/tci-base) | [README](https://github.com/xdev-software/tci-base) |\n"
  },
  {
    "path": "docs/test_framework_integration/junit_4.md",
    "content": "# JUnit 4\n\n## `@Rule`/`@ClassRule` integration\n\n**JUnit4 `@Rule`/`@ClassRule`**: This mode starts the container before your tests and tears it down afterwards.\n\nAdd a `@Rule` or `@ClassRule` annotated field to your test class, e.g.:\n\n```java\npublic class SimpleMySQLTest {\n    @Rule\n    public MySQLContainer mysql = new MySQLContainer();\n    \n    // [...]\n}\n```\n\n\n## Manually controlling container lifecycle\n\nAs an alternative, you can manually start the container in a `@BeforeClass`/`@Before` annotated method in your tests. Tear down will be done automatically on JVM exit, but you can of course also use an `@AfterClass`/`@After` annotated method to manually call the `stop()` method on your container.\n\n*Example of starting a container in a `@Before` annotated method:*\n\n```java\nclass SimpleMySQLTest {\n    private MySQLContainer mysql = new MySQLContainer();\n    \n    @Before\n    void before() {\n        mysql.start();\n    }\n    \n    @After\n    void after() {\n        mysql.stop();\n    }\n    \n    // [...]\n}\n```\n\n## Singleton containers\n\nNote that the [singleton container pattern](manual_lifecycle_control.md#singleton-containers) is also an option when\nusing JUnit 4.\n"
  },
  {
    "path": "docs/test_framework_integration/junit_5.md",
    "content": "# Jupiter / JUnit 5\n\nWhile Testcontainers is tightly coupled with the JUnit 4.x rule API, this module provides\nan API that is based on the [JUnit Jupiter](https://junit.org/junit5/) extension model.\n\nThe extension supports two modes:\n\n- containers that are restarted for every test method\n- containers that are shared between all methods of a test class\n\nNote that Jupiter/JUnit 5 integration is packaged as a separate library JAR; see [below](#adding-testcontainers-junit-5-support-to-your-project-dependencies) for details.\n\n## Extension\n\nJupiter integration is provided by means of the `@Testcontainers` annotation.\n  \nThe extension finds all fields that are annotated with `@Container` and calls their container lifecycle \nmethods (methods on the `Startable` interface). Containers declared as static fields will be shared between test \nmethods. They will be started only once before any test method is executed and stopped after the last test method has \nexecuted. Containers declared as instance fields will be started and stopped for every test method.\n  \n**Note:** This extension has only been tested with sequential test execution. Using it with parallel test execution is \nunsupported and may have unintended side effects.\n  \n*Example:*\n<!--codeinclude-->\n[Mixed Lifecycle](../../modules/junit-jupiter/src/test/java/org/testcontainers/junit/jupiter/MixedLifecycleTests.java) inside_block:testClass\n<!--/codeinclude-->\n\n## Examples\n\nTo use the Testcontainers extension annotate your test class with `@Testcontainers`.\n\n### Restarted containers\n\nTo define a restarted container, define an instance field inside your test class and annotate it with\nthe `@Container` annotation.\n\n<!--codeinclude-->\n[Restarted Containers](../../modules/junit-jupiter/src/test/java/org/testcontainers/junit/jupiter/TestcontainersNestedRestartedContainerTests.java) inside_block:testClass\n<!--/codeinclude-->\n\n\n### Shared containers\n\nShared containers are defined as static fields in a top level test class and have to be annotated with `@Container`.\nNote that shared containers can't be declared inside nested test classes.\nThis is because nested test classes have to be defined non-static and can't therefore have static fields.\n\n<!--codeinclude-->\n[Shared Container](../../modules/junit-jupiter/src/test/java/org/testcontainers/junit/jupiter/MixedLifecycleTests.java) lines:18-23,32-33,35-36\n<!--/codeinclude-->\n\n## Singleton containers\n\nNote that the [singleton container pattern](manual_lifecycle_control.md#singleton-containers) is also an option when\nusing JUnit 5.\n\n## Limitations\n\nSince this module has a dependency onto JUnit Jupiter and on Testcontainers core, which\nhas a dependency onto JUnit 4.x, projects using this module will end up with both, JUnit Jupiter\nand JUnit 4.x in the test classpath.\n\nThis extension has only been tested with sequential test execution. Using it with parallel test execution is unsupported and may have unintended side effects.\n\n## Adding Testcontainers JUnit 5 support to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-junit-jupiter:{{latest_version}}\"\n    ```\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-junit-jupiter</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n"
  },
  {
    "path": "docs/test_framework_integration/manual_lifecycle_control.md",
    "content": "# Manual container lifecycle control\n\nWhile Testcontainers was originally built with JUnit 4 integration in mind, it is fully usable with other test \nframeworks, or with no framework at all.\n\n## Manually starting/stopping containers\n\nContainers can be started and stopped in code using `start()` and `stop()` methods. Additionally, container classes\nimplement `AutoCloseable`. This enables better assurance that the container will be stopped at the appropriate time.\n\n```java\ntry (GenericContainer container = new GenericContainer(\"imagename\")) {\n    container.start();\n    // ... use the container\n    // no need to call stop() afterwards\n}\n```\n\n## Singleton containers\n\nSometimes it might be useful to define a container that is only started once for several test classes.\nThere is no special support for this use case provided by the Testcontainers extension.\nInstead this can be implemented using the following pattern:\n\n```java\nabstract class AbstractContainerBaseTest {\n\n    static final MySQLContainer MY_SQL_CONTAINER;\n\n    static {\n        MY_SQL_CONTAINER = new MySQLContainer();\n        MY_SQL_CONTAINER.start();\n    }\n}\n\nclass FirstTest extends AbstractContainerBaseTest {\n\n    @Test\n    void someTestMethod() {\n        String url = MY_SQL_CONTAINER.getJdbcUrl();\n\n        // create a connection and run test as normal\n    }\n}\n```\n\nThe singleton container is started only once when the base class is loaded.\nThe container can then be used by all inheriting test classes.\nAt the end of the test suite the [Ryuk container](https://github.com/testcontainers/moby-ryuk)\nthat is started by Testcontainers core will take care of stopping the singleton container.\n"
  },
  {
    "path": "docs/test_framework_integration/spock.md",
    "content": "# Spock\n\n[Spock](https://github.com/spockframework/spock) extension for [Testcontainers](https://github.com/testcontainers/testcontainers-java) library, which allows to use Docker containers inside of Spock tests.\n\n## Usage\n\n### `@Testcontainers` class-annotation\n\nSpecifying the `@Testcontainers` annotation will instruct Spock to start and stop all testcontainers accordingly. This annotation \ncan be mixed with Spock's `@Shared` annotation to indicate, that containers shouldn't be restarted between tests.\n\n<!--codeinclude-->\n[PostgresContainerIT](../../modules/spock/src/test/groovy/org/testcontainers/spock/PostgresContainerIT.groovy) inside_block:PostgresContainerIT\n<!--/codeinclude-->\n\n## Adding Testcontainers Spock support to your project dependencies\n\nAdd the following dependency to your `pom.xml`/`build.gradle` file:\n\n=== \"Gradle\"\n    ```groovy\n    testImplementation \"org.testcontainers:testcontainers-spock:{{latest_version}}\"\n    ```\n=== \"Maven\"\n    ```xml\n    <dependency>\n        <groupId>org.testcontainers</groupId>\n        <artifactId>testcontainers-spock</artifactId>\n        <version>{{latest_version}}</version>\n        <scope>test</scope>\n    </dependency>\n    ```\n\n\n## Attributions\nThe initial version of this project was heavily inspired by the excellent [JUnit5 docker extension](https://github.com/FaustXVI/junit5-docker) by [FaustXVI](https://github.com/FaustXVI).\n"
  },
  {
    "path": "docs/theme/main.html",
    "content": "{% extends \"base.html\" %}\n\n{% block analytics %}\n<!-- Cloudflare Web Analytics --><script defer src='https://static.cloudflareinsights.com/beacon.min.js' data-cf-beacon='{\"token\": \"e44a95720257486eab94698a6b4c6ca6\"}'></script><!-- End Cloudflare Web Analytics -->\n{% endblock %}\n\n{% block extrahead %}\n<link href=\"https://fonts.googleapis.com/css2?family=Rubik:wght@400;500;600&display=swap\" rel=\"stylesheet\">\n<script rel=\"text/javascript\" src=\"/js/tc-header.js\" defer></script>\n{% endblock %}"
  },
  {
    "path": "docs/theme/partials/header.html",
    "content": "<!--\n  Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>\n\n  Permission is hereby granted, free of charge, to any person obtaining a copy\n  of this software and associated documentation files (the \"Software\"), to\n  deal in the Software without restriction, including without limitation the\n  rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n  sell copies of the Software, and to permit persons to whom the Software is\n  furnished to do so, subject to the following conditions:\n\n  The above copyright notice and this permission notice shall be included in\n  all copies or substantial portions of the Software.\n\n  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n  FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n  IN THE SOFTWARE.\n-->\n\n<!-- Determine base classes -->\n{% set class = \"md-header\" %}\n{% if \"navigation.tabs.sticky\" in features %}\n  {% set class = class ~ \" md-header--shadow md-header--lifted\" %}\n{% elif \"navigation.tabs\" not in features %}\n  {% set class = class ~ \" md-header--shadow\" %}\n{% endif %}\n\n{% include \"partials/tc-header.html\" %}\n\n<!-- Header -->\n<header class=\"{{ class }}\" data-md-component=\"header\">\n  <nav\n    class=\"md-header__inner md-grid\"\n    aria-label=\"{{ lang.t('header') }}\"\n  >\n  \n    <!-- Button to open drawer -->\n    <label class=\"md-header__button md-icon\" for=\"__drawer\">\n      {% include \".icons/material/menu\" ~ \".svg\" %}\n    </label>\n\n    <!-- Header title -->\n    <div class=\"md-header__title\" data-md-component=\"header-title\">\n      <div class=\"md-header__ellipsis\">\n        <div class=\"md-header__topic\">\n            <a href=\"{{ config.extra.homepage | d(nav.homepage.url, true) | url }}\">\n                <span class=\"md-ellipsis\">\n                    {{ config.site_name }}\n                </span>\n            </a>\n        </div>\n      </div>\n    </div>\n\n    <!-- Color palette -->\n    {% if config.theme.palette %}\n      {% if not config.theme.palette is mapping %}\n        <form class=\"md-header__option\" data-md-component=\"palette\">\n          {% for option in config.theme.palette %}\n            {% set scheme  = option.scheme  | d(\"default\", true) %}\n            {% set primary = option.primary | d(\"indigo\", true) %}\n            {% set accent  = option.accent  | d(\"indigo\", true) %}\n            <input\n              class=\"md-option\"\n              data-md-color-media=\"{{ option.media }}\"\n              data-md-color-scheme=\"{{ scheme | replace(' ', '-') }}\"\n              data-md-color-primary=\"{{ primary | replace(' ', '-') }}\"\n              data-md-color-accent=\"{{ accent | replace(' ', '-') }}\"\n              {% if option.toggle %}\n                aria-label=\"{{ option.toggle.name }}\"\n              {% else  %}\n                aria-hidden=\"true\"\n              {% endif %}\n              type=\"radio\"\n              name=\"__palette\"\n              id=\"__palette_{{ loop.index }}\"\n            />\n            {% if option.toggle %}\n              <label\n                class=\"md-header__button md-icon\"\n                title=\"{{ option.toggle.name }}\"\n                for=\"__palette_{{ loop.index0 or loop.length }}\"\n                hidden\n              >\n                {% include \".icons/\" ~ option.toggle.icon ~ \".svg\" %}\n              </label>\n            {% endif %}\n          {% endfor %}\n        </form>\n      {% endif %}\n    {% endif %}\n\n    <!-- Site language selector -->\n    {% if config.extra.alternate %}\n      <div class=\"md-header__option\">\n        <div class=\"md-select\">\n          {% set icon = config.theme.icon.alternate or \"material/translate\" %}\n          <button\n            class=\"md-header__button md-icon\"\n            aria-label=\"{{ lang.t('select.language') }}\"\n          >\n            {% include \".icons/\" ~ icon ~ \".svg\" %}\n          </button>\n          <div class=\"md-select__inner\">\n            <ul class=\"md-select__list\">\n              {% for alt in config.extra.alternate %}\n                <li class=\"md-select__item\">\n                  <a\n                    href=\"{{ alt.link | url }}\"\n                    hreflang=\"{{ alt.lang }}\"\n                    class=\"md-select__link\"\n                  >\n                    {{ alt.name }}\n                  </a>\n                </li>\n              {% endfor %}\n            </ul>\n          </div>\n        </div>\n      </div>\n    {% endif %}\n\n    <!-- Button to open search modal -->\n    {% if \"search\" in config.plugins %}\n      <label class=\"md-header__button md-icon\" for=\"__search\">\n        {% include \".icons/material/magnify.svg\" %}\n      </label>\n\n      <!-- Search interface -->\n      {% include \"partials/search.html\" %}\n    {% endif %}\n\n    <!-- Repository information -->\n    {% if config.repo_url %}\n      <div class=\"md-header__source\">\n        {% include \"partials/source.html\" %}\n      </div>\n    {% endif %}\n  </nav>\n\n  <!-- Navigation tabs (sticky) -->\n  {% if \"navigation.tabs.sticky\" in features %}\n    {% if \"navigation.tabs\" in features %}\n      {% include \"partials/tabs.html\" %}\n    {% endif %}\n  {% endif %}\n</header>"
  },
  {
    "path": "docs/theme/partials/nav.html",
    "content": "<!--\n  Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com>\n  Permission is hereby granted, free of charge, to any person obtaining a copy\n  of this software and associated documentation files (the \"Software\"), to\n  deal in the Software without restriction, including without limitation the\n  rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n  sell copies of the Software, and to permit persons to whom the Software is\n  furnished to do so, subject to the following conditions:\n  The above copyright notice and this permission notice shall be included in\n  all copies or substantial portions of the Software.\n  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n  FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n  IN THE SOFTWARE.\n-->\n\n<!-- Determine class according to configuration -->\n{% set class = \"md-nav md-nav--primary\" %}\n{% if \"navigation.tabs\" in features %}\n{% set class = class ~ \" md-nav--lifted\" %}\n{% endif %}\n{% if \"toc.integrate\" in features %}\n{% set class = class ~ \" md-nav--integrated\" %}\n{% endif %}\n\n<!-- Main navigation -->\n<nav class=\"{{ class }}\" aria-label=\"{{ lang.t('nav') }}\" data-md-level=\"0\">\n\n    <!-- Site title -->\n    <label class=\"md-nav__title\" for=\"__drawer\">\n        Content\n    </label>\n\n    <!-- Repository information -->\n    {% if config.repo_url %}\n    <div class=\"md-nav__source\">\n        {% include \"partials/source.html\" %}\n    </div>\n    {% endif %}\n\n    <!-- Render item list -->\n    <ul class=\"md-nav__list\" data-md-scrollfix>\n        {% for nav_item in nav %}\n        {% set path = \"__nav_\" ~ loop.index %}\n        {% set level = 1 %}\n        {% include \"partials/nav-item.html\" %}\n        {% endfor %}\n        <li class=\"community-callout-wrapper\">\n            <div class=\"community-callout\">\n                <h2>Join the community</h2>\n                <ul>\n                    <li>\n                        <a href=\"https://slack.testcontainers.org/\" target=\"_blank\">\n                            <img src=\"/icons/slack.svg\" alt=\"Slack\" width=\"30\" height=\"31\">\n                        </a>\n                    </li>\n                    <li>\n                        <a href=\"https://github.com/testcontainers\" target=\"_blank\">\n                            <img src=\"/icons/github.svg\" alt=\"GitHub\" width=\"30\" height=\"31\">\n                        </a>\n                    </li>\n                    <li>\n                        <a href=\"https://stackoverflow.com/questions/tagged/testcontainers\" target=\"_blank\">\n                            <img src=\"/icons/stackoverflow.svg\" alt=\"StackOverflow\" width=\"26\" height=\"31\">\n                        </a>\n                    </li>\n                    <li>\n                        <a href=\"https://twitter.com/testcontainers\" target=\"_blank\">\n                            <img src=\"/icons/twitter.svg\" alt=\"Twitter\" width=\"37\" height=\"31\">\n                        </a>\n                    </li>\n                </ul>\n            </div>\n        </li>\n    </ul>\n</nav>"
  },
  {
    "path": "docs/theme/partials/tc-header.html",
    "content": "{% set header = ({\n    \"siteUrl\": \"https://testcontainers.com/\",\n    \"menuItems\": [\n        {\n            \"label\": \"Desktop <span class=\\\"badge rounded-pill\\\">NEW</span>\",\n            \"url\": \"https://testcontainers.com/desktop/\"\n        },\n        {\n            \"label\": \"Cloud\",\n            \"url\": \"https://testcontainers.com/cloud/\"\n        },\n        {\n            \"label\": \"Getting Started\",\n            \"url\": \"https://testcontainers.com/getting-started/\"\n        },\n        {\n            \"label\": \"Guides\",\n            \"url\": \"https://testcontainers.com/guides/\"\n        },\n        {\n            \"label\": \"Modules\",\n            \"url\": \"https://testcontainers.com/modules/\"\n        },\n        {\n            \"label\": \"Docs\",\n            \"children\": [\n                {\n                    \"label\": \"Testcontainers for Java\",\n                    \"url\": \"https://java.testcontainers.org/\",\n                    \"image\": \"/language-logos/java.svg\",\n                },\n                {\n                    \"label\": \"Testcontainers for Go\",\n                    \"url\": \"https://golang.testcontainers.org/\",\n                    \"image\": \"/language-logos/go.svg\",\n                },\n                {\n                    \"label\": \"Testcontainers for .NET\",\n                    \"url\": \"https://dotnet.testcontainers.org/\",\n                    \"image\": \"/language-logos/dotnet.svg\",\n                },\n                {\n                    \"label\": \"Testcontainers for Node.js\",\n                    \"url\": \"https://node.testcontainers.org/\",\n                    \"image\": \"/language-logos/nodejs.svg\",\n                },\n                {\n                    \"label\": \"Testcontainers for Python\",\n                    \"url\": \"https://testcontainers-python.readthedocs.io/en/latest/\",\n                    \"image\": \"/language-logos/python.svg\",\n                    \"external\": true,\n                },\n                {\n                    \"label\": \"Testcontainers for Rust\",\n                    \"url\": \"https://docs.rs/testcontainers/latest/testcontainers/\",\n                    \"image\": \"/language-logos/rust.svg\",\n                    \"external\": true,\n                },\n                {\n                    \"label\": \"Testcontainers for Haskell\",\n                    \"url\": \"https://github.com/testcontainers/testcontainers-hs\",\n                    \"image\": \"/language-logos/haskell.svg\",\n                    \"external\": true,\n                },\n                {\n                    \"label\": \"Testcontainers for Ruby\",\n                    \"url\": \"https://github.com/testcontainers/testcontainers-ruby\",\n                    \"image\": \"/language-logos/ruby.svg\",\n                    \"external\": true,\n                },\n            ]\n        },\n        {\n            \"label\": \"Slack\",\n            \"url\": \"https://slack.testcontainers.org/\",\n            \"icon\": \"icon-slack\",\n        },\n        {\n            \"label\": \"GitHub\",\n            \"url\": \"https://github.com/testcontainers\",\n            \"icon\": \"icon-github\",\n        },\n    ]\n}) %}\n<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" style=\"display: none\">\n    <symbol id=\"icon-external\" viewBox=\"0 0 15 17\" width=\"15\" height=\"17\" fill=\"currentColor\">\n        <path  d=\"M9.334 1.254a.999.999 0 1 0 0 2h1.584l-5.29 5.293a1.002 1.002 0 0 0 1.415 1.416l5.29-5.294v1.585a.999.999 0 1 0 2 0v-4c0-.553-.446-1-1-1h-4Zm-6.5 1a2.5 2.5 0 0 0-2.5 2.5v8a2.5 2.5 0 0 0 2.5 2.5h8a2.5 2.5 0 0 0 2.5-2.5v-2.5a.999.999 0 1 0-2 0v2.5c0 .275-.226.5-.5.5h-8a.501.501 0 0 1-.5-.5v-8c0-.275.224-.5.5-.5h2.5a.999.999 0 1 0 0-2h-2.5Z\" />\n    </symbol>\n    <symbol id=\"icon-caret\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"currentColor\">\n        <path d=\"M.487 2.157a1.662 1.662 0 0 1 2.357 0L5 4.313l2.156-2.156a1.657 1.657 0 0 1 2.354 0 1.657 1.657 0 0 1 0 2.354L6.177 7.847a1.66 1.66 0 0 1-1.815.36 1.658 1.658 0 0 1-.54-.361L.487 4.51a1.66 1.66 0 0 1 .001-2.353Z\" />\n    </symbol>\n    <symbol id=\"icon-slack\" class=\"icon-slack\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" aria-hidden=\"true\" focusable=\"false\" fill=\"currentColor\">\n        <path d=\"M8.76.06a2.392 2.392 0 0 0-2.207 3.309A2.392 2.392 0 0 0 8.76 4.847h2.39V2.453A2.392 2.392 0 0 0 8.76.06Zm0 6.384H2.39A2.392 2.392 0 0 0 .18 9.753a2.392 2.392 0 0 0 2.208 1.479h6.372a2.391 2.391 0 0 0 2.208-3.31 2.39 2.39 0 0 0-2.208-1.478ZM23.893 8.837a2.392 2.392 0 0 0-3.304-2.21 2.392 2.392 0 0 0-1.474 2.21v2.395h2.39a2.391 2.391 0 0 0 2.388-2.395Zm-6.371 0V2.453a2.39 2.39 0 1 0-4.779 0v6.384a2.39 2.39 0 1 0 4.779 0ZM15.133 24a2.391 2.391 0 0 0 2.389-2.394 2.39 2.39 0 0 0-2.39-2.394h-2.389v2.394A2.39 2.39 0 0 0 15.133 24Zm0-6.384h6.371a2.39 2.39 0 0 0 2.39-2.394 2.392 2.392 0 0 0-2.39-2.394h-6.372a2.391 2.391 0 0 0-2.389 2.394 2.39 2.39 0 0 0 2.39 2.394ZM0 15.222a2.391 2.391 0 0 0 4.08 1.691 2.391 2.391 0 0 0 .699-1.691v-2.394h-2.39A2.392 2.392 0 0 0 0 15.222m6.372 0v6.384a2.391 2.391 0 0 0 3.304 2.211 2.39 2.39 0 0 0 1.474-2.211v-6.384a2.392 2.392 0 0 0-2.39-2.394 2.391 2.391 0 0 0-2.388 2.394\" />\n    </symbol>\n    <symbol id=\"icon-github\" class=\"icon-github\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" aria-hidden=\"true\" focusable=\"false\" fill=\"currentColor\">\n        <path d=\"M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57C20.565 21.795 24 17.295 24 12c0-6.63-5.37-12-12-12Z\" />\n    </symbol>\n</svg>\n<header id=\"site-header\">\n    <div class=\"brand\">\n        <a href=\"https://testcontainers.com/\" class=\"logo\">\n            <img src=\"/testcontainers-logo.svg\" alt=\"Testcontainers\" width=\"183\" height=\"48\"/>\n        </a>\n        <button id=\"mobile-menu-toggle\">\n            Menu\n            <svg width=\"30\" height=\"30\" viewBox=\"0 0 30 30\" fill=\"currentColor\" xmlns=\"http://www.w3.org/2000/svg\">\n                <rect y=\"22\" width=\"30\" height=\"4\" rx=\"2\"/>\n                <rect y=\"13\" width=\"30\" height=\"4\" rx=\"2\"/>\n                <rect y=\"4\" width=\"30\" height=\"4\" rx=\"2\"/>\n            </svg>\n        </button>\n    </div>\n    <nav>\n        <ul class=\"menu\">\n            {% for menuItem in header[\"menuItems\"] %}\n                {% if menuItem.children %}\n                    <li class=\"menu-item has-children\">\n                        <button>\n                            {{ menuItem.label }}\n                            <svg class=\"icon-caret\" width=\"10\" height=\"10\" viewBox=\"0 0 10 10\">\n                                <use href=\"#icon-caret\"></use>\n                            </svg>\n                        </button>\n                        <ul class=\"menu-dropdown\">\n                            {% for child in menuItem.children %}\n                                <li class=\"menu-dropdown-item\">\n                                    <a href=\"{{ child.url }}\" {% if child.external %}target=\"_blank\"{% endif %}>\n                                        {% if child.image %}\n                                            <img src=\"{{ child.image }}\" alt=\"\" width=\"24\" height=\"24\"/>\n                                        {% endif %}\n                                        {{ child.label }}\n                                        {% if child.external %}\n                                            <svg class=\"icon-external\" width=\"15\" height=\"17\" viewBox=\"0 0 15 17\" aria-hidden=\"true\" focusable=\"false\">\n                                                <use href=\"#icon-external\"></use>\n                                            </svg>\n                                        {% endif %}\n                                    </a>\n                                </li>\n                            {% endfor %}\n                        </ul>\n                    </li>\n                {% else %}\n                    <li class=\"menu-item\">\n                        <a href=\"{{ menuItem.url }}\">\n                            {% if menuItem.icon %}\n                                <svg class=\"{{ menuItem.icon }}\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\">\n                                    <use href=\"#{{ menuItem.icon }}\"></use>\n                                </svg>\n                            {% endif %}\n                            {{ menuItem.label }}\n                        </a>\n                    </li>\n                {% endif %}\n            {% endfor %}\n        </ul>\n    </nav>\n</header>\n"
  },
  {
    "path": "examples/README.md",
    "content": "# testcontainers-java-examples\n\n> Practical examples of using Testcontainers.\n\nThe code in this repository accompanies the following blog posts:\n\n* [JUnit integration testing with Docker and Testcontainers](https://rnorth.org/junit-integration-testing-with-docker-and-testcontainers)\n* [Fun with Disque, Java and Spinach](https://rnorth.org/fun-with-disque-java-and-spinach)\n* [Better JUnit Selenium testing with Docker and Testcontainers](https://rnorth.org/better-junit-selenium-testing-with-docker-and-testcontainers)\n"
  },
  {
    "path": "examples/build.gradle",
    "content": "// empty build.gradle for dependabot\nplugins {\n    id 'com.diffplug.spotless' version '6.22.0' apply false\n}\n\napply from: \"$rootDir/../gradle/ci-support.gradle\"\n\nsubprojects {\n    apply plugin:\"java\"\n    apply from: \"$rootDir/../gradle/spotless.gradle\"\n    apply plugin: 'checkstyle'\n\n    repositories {\n        mavenCentral()\n    }\n\n    test {\n        defaultCharacterEncoding = \"UTF-8\"\n        testLogging {\n            displayGranularity 1\n            showStackTraces = true\n            exceptionFormat = 'full'\n            events \"STARTED\", \"PASSED\", \"FAILED\", \"SKIPPED\"\n        }\n    }\n\n    checkstyle {\n        toolVersion = \"10.23.0\"\n        configFile = rootProject.file('../config/checkstyle/checkstyle.xml')\n    }\n}\n"
  },
  {
    "path": "examples/cucumber/build.gradle",
    "content": "plugins {\n    id 'java'\n}\n\nrepositories {\n    mavenCentral()\n}\n\ndependencies {\n    implementation platform('org.seleniumhq.selenium:selenium-bom:4.35.0')\n    implementation 'org.seleniumhq.selenium:selenium-remote-driver'\n    implementation 'org.seleniumhq.selenium:selenium-firefox-driver'\n    implementation 'org.seleniumhq.selenium:selenium-chrome-driver'\n\n    testImplementation platform('org.junit:junit-bom:5.13.4')\n    testImplementation 'org.junit.platform:junit-platform-suite'\n    testImplementation platform('io.cucumber:cucumber-bom:7.30.0')\n    testImplementation 'io.cucumber:cucumber-java'\n    testImplementation 'io.cucumber:cucumber-junit-platform-engine'\n    testImplementation 'org.testcontainers:testcontainers-selenium'\n    testImplementation 'org.assertj:assertj-core:3.27.4'\n    testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.10.3'\n}\n\ntest {\n    useJUnitPlatform()\n}\n"
  },
  {
    "path": "examples/cucumber/src/test/java/org/testcontainers/examples/CucumberTest.java",
    "content": "package org.testcontainers.examples;\n\nimport io.cucumber.junit.platform.engine.Constants;\nimport org.junit.platform.suite.api.ConfigurationParameter;\nimport org.junit.platform.suite.api.SelectPackages;\nimport org.junit.platform.suite.api.Suite;\n\n@Suite\n@SelectPackages(\"org.testcontainers.examples\")\n@ConfigurationParameter(key = Constants.PLUGIN_PROPERTY_NAME, value = \"pretty\")\npublic class CucumberTest {}\n"
  },
  {
    "path": "examples/cucumber/src/test/java/org/testcontainers/examples/Stepdefs.java",
    "content": "package org.testcontainers.examples;\n\nimport io.cucumber.java.After;\nimport io.cucumber.java.Before;\nimport io.cucumber.java.Scenario;\nimport io.cucumber.java.en.Given;\nimport io.cucumber.java.en.Then;\nimport io.cucumber.java.en.When;\nimport org.openqa.selenium.By;\nimport org.openqa.selenium.WebElement;\nimport org.openqa.selenium.chrome.ChromeOptions;\nimport org.openqa.selenium.remote.RemoteWebDriver;\nimport org.testcontainers.containers.BrowserWebDriverContainer;\nimport org.testcontainers.containers.BrowserWebDriverContainer.VncRecordingMode;\nimport org.testcontainers.lifecycle.TestDescription;\n\nimport java.io.File;\nimport java.util.List;\nimport java.util.Optional;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\npublic class Stepdefs {\n\n    private BrowserWebDriverContainer container = new BrowserWebDriverContainer()\n        .withCapabilities(new ChromeOptions())\n        .withRecordingMode(VncRecordingMode.RECORD_ALL, new File(\"build\"));\n\n    private String location;\n\n    private String answer;\n\n    @Before\n    public void beforeScenario() {\n        container.start();\n    }\n\n    @After\n    public void afterScenario(Scenario scenario) {\n        container.afterTest(\n            new TestDescription() {\n                @Override\n                public String getTestId() {\n                    return scenario.getId();\n                }\n\n                @Override\n                public String getFilesystemFriendlyName() {\n                    return scenario.getName();\n                }\n            },\n            Optional.of(scenario).filter(Scenario::isFailed).map(__ -> new RuntimeException())\n        );\n    }\n\n    @Given(\"^location is \\\"([^\\\"]*)\\\"$\")\n    public void locationIs(String location) throws Exception {\n        this.location = location;\n    }\n\n    @When(\"^I ask is it possible to search here$\")\n    public void iAskIsItPossibleToSearchHere() throws Exception {\n        RemoteWebDriver driver = new RemoteWebDriver(container.getSeleniumAddress(), new ChromeOptions());\n        driver.get(location);\n        List<WebElement> searchInputs = driver.findElements(By.tagName(\"input\"));\n        answer = searchInputs != null && searchInputs.size() > 0 ? \"YES\" : \"NOPE\";\n    }\n\n    @Then(\"^I should be told \\\"([^\\\"]*)\\\"$\")\n    public void iShouldBeTold(String expected) throws Exception {\n        assertThat(answer).isEqualTo(expected);\n    }\n}\n"
  },
  {
    "path": "examples/cucumber/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "examples/cucumber/src/test/resources/org/testcontainers/examples/is_search_possible.feature",
    "content": "Feature: Is it possible to search here?\n  Everybody wants to search something\n\n   Scenario Outline: Is search possible here\n    Given location is \"<location>\"\n    When I ask is it possible to search here\n    Then I should be told \"<answer>\"\n\n  Examples:\n    | location | answer |\n    | https://www.google.com/ | YES |\n    | https://www.google.com/appsstatus | NOPE |"
  },
  {
    "path": "examples/gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionSha256Sum=bd71102213493060956ec229d946beee57158dbd89d0e62b91bca0fa2c5f3531\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-8.14.3-bin.zip\nnetworkTimeout=10000\nvalidateDistributionUrl=true\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "examples/gradle.properties",
    "content": "org.gradle.parallel=false\norg.gradle.caching=true\n"
  },
  {
    "path": "examples/gradlew",
    "content": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n# SPDX-License-Identifier: Apache-2.0\n#\n\n##############################################################################\n#\n#   Gradle start up script for POSIX generated by Gradle.\n#\n#   Important for running:\n#\n#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n#       noncompliant, but you have some other compliant shell such as ksh or\n#       bash, then to run this script, type that shell name before the whole\n#       command line, like:\n#\n#           ksh Gradle\n#\n#       Busybox and similar reduced shells will NOT work, because this script\n#       requires all of these POSIX shell features:\n#         * functions;\n#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n#         * compound commands having a testable exit status, especially «case»;\n#         * various built-in commands including «command», «set», and «ulimit».\n#\n#   Important for patching:\n#\n#   (2) This script targets any POSIX shell, so it avoids extensions provided\n#       by Bash, Ksh, etc; in particular arrays are avoided.\n#\n#       The \"traditional\" practice of packing multiple parameters into a\n#       space-separated string is a well documented source of bugs and security\n#       problems, so this is (mostly) avoided, by progressively accumulating\n#       options in \"$@\", and eventually passing that to Java.\n#\n#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n#       see the in-line comments for details.\n#\n#       There are tweaks for specific operating systems such as AIX, CygWin,\n#       Darwin, MinGW, and NonStop.\n#\n#   (3) This script is generated from the Groovy template\n#       https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n#       within the Gradle project.\n#\n#       You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n    APP_HOME=${app_path%\"${app_path##*/}\"}  # leaves a trailing /; empty if no leading path\n    [ -h \"$app_path\" ]\ndo\n    ls=$( ls -ld \"$app_path\" )\n    link=${ls#*' -> '}\n    case $link in             #(\n      /*)   app_path=$link ;; #(\n      *)    app_path=$APP_HOME$link ;;\n    esac\ndone\n\n# This is normally unused\n# shellcheck disable=SC2034\nAPP_BASE_NAME=${0##*/}\n# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)\nAPP_HOME=$( cd -P \"${APP_HOME:-./}\" > /dev/null && printf '%s\\n' \"$PWD\" ) || exit\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n    echo \"$*\"\n} >&2\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in                #(\n  CYGWIN* )         cygwin=true  ;; #(\n  Darwin* )         darwin=true  ;; #(\n  MSYS* | MINGW* )  msys=true    ;; #(\n  NONSTOP* )        nonstop=true ;;\nesac\n\nCLASSPATH=\"\\\\\\\"\\\\\\\"\"\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=$JAVA_HOME/jre/sh/java\n    else\n        JAVACMD=$JAVA_HOME/bin/java\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=java\n    if ! command -v java >/dev/null 2>&1\n    then\n        die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n    case $MAX_FD in #(\n      max*)\n        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        MAX_FD=$( ulimit -H -n ) ||\n            warn \"Could not query maximum file descriptor limit\"\n    esac\n    case $MAX_FD in  #(\n      '' | soft) :;; #(\n      *)\n        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        ulimit -n \"$MAX_FD\" ||\n            warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n    esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n#   * args from the command line\n#   * the main class name\n#   * -classpath\n#   * -D...appname settings\n#   * --module-path (only if needed)\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n    APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n    CLASSPATH=$( cygpath --path --mixed \"$CLASSPATH\" )\n\n    JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    for arg do\n        if\n            case $arg in                                #(\n              -*)   false ;;                            # don't mess with options #(\n              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath\n                    [ -e \"$t\" ] ;;                      #(\n              *)    false ;;\n            esac\n        then\n            arg=$( cygpath --path --ignore --mixed \"$arg\" )\n        fi\n        # Roll the args list around exactly as many times as the number of\n        # args, so each arg winds up back in the position where it started, but\n        # possibly modified.\n        #\n        # NB: a `for` loop captures its iteration list before it begins, so\n        # changing the positional parameters here affects neither the number of\n        # iterations, nor the values presented in `arg`.\n        shift                   # remove old arg\n        set -- \"$@\" \"$arg\"      # push replacement arg\n    done\nfi\n\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Collect all arguments for the java command:\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,\n#     and any embedded shellness will be escaped.\n#   * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be\n#     treated as '${Hostname}' itself on the command line.\n\nset -- \\\n        \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n        -classpath \"$CLASSPATH\" \\\n        -jar \"$APP_HOME/gradle/wrapper/gradle-wrapper.jar\" \\\n        \"$@\"\n\n# Stop when \"xargs\" is not available.\nif ! command -v xargs >/dev/null 2>&1\nthen\n    die \"xargs is not available\"\nfi\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n#   readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n#   set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n        printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n        xargs -n1 |\n        sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n        tr '\\n' ' '\n    )\" '\"$@\"'\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "examples/gradlew.bat",
    "content": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\r\n@rem you may not use this file except in compliance with the License.\r\n@rem You may obtain a copy of the License at\r\n@rem\r\n@rem      https://www.apache.org/licenses/LICENSE-2.0\r\n@rem\r\n@rem Unless required by applicable law or agreed to in writing, software\r\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\r\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n@rem See the License for the specific language governing permissions and\r\n@rem limitations under the License.\r\n@rem\r\n@rem SPDX-License-Identifier: Apache-2.0\r\n@rem\r\n\r\n@if \"%DEBUG%\"==\"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\"==\"\" set DIRNAME=.\r\n@rem This is normally unused\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\r\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif %ERRORLEVEL% equ 0 goto execute\r\n\r\necho. 1>&2\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2\r\necho. 1>&2\r\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\r\necho location of your Java installation. 1>&2\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto execute\r\n\r\necho. 1>&2\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2\r\necho. 1>&2\r\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\r\necho location of your Java installation. 1>&2\r\n\r\ngoto fail\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=\r\n\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" -jar \"%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\" %*\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif %ERRORLEVEL% equ 0 goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nset EXIT_CODE=%ERRORLEVEL%\r\nif %EXIT_CODE% equ 0 set EXIT_CODE=1\r\nif not \"\"==\"%GRADLE_EXIT_CONSOLE%\" exit %EXIT_CODE%\r\nexit /b %EXIT_CODE%\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "examples/hazelcast/build.gradle",
    "content": "plugins {\n    id 'java'\n}\n\nrepositories {\n    mavenCentral()\n}\n\ndependencies {\n    testImplementation 'org.testcontainers:testcontainers'\n    testImplementation 'com.hazelcast:hazelcast:5.3.8'\n    testImplementation 'ch.qos.logback:logback-classic:1.3.15'\n    testImplementation 'org.assertj:assertj-core:3.27.4'\n    testImplementation 'org.junit.jupiter:junit-jupiter:5.13.4'\n    testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.11.0'\n}\n\ntest {\n    useJUnitPlatform()\n}\n"
  },
  {
    "path": "examples/hazelcast/src/test/java/org/testcontainers/examples/HazelcastTest.java",
    "content": "package org.testcontainers.examples;\n\nimport com.hazelcast.client.HazelcastClient;\nimport com.hazelcast.client.config.ClientConfig;\nimport com.hazelcast.core.HazelcastInstance;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.Network;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.lifecycle.Startables;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.util.concurrent.BlockingQueue;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Examples with Hazelcast using both a single container and a cluster with two containers.\n */\nclass HazelcastTest {\n\n    // Hazelcast values\n    private static final String HZ_IMAGE_NAME = \"hazelcast/hazelcast:5.2.0-slim\";\n\n    private static final String HZ_CLUSTERNAME_ENV_NAME = \"HZ_CLUSTERNAME\";\n\n    private static final String HZ_NETWORK_JOIN_AZURE_ENABLED_ENV_NAME = \"HZ_NETWORK_JOIN_AZURE_ENABLED\";\n\n    private static final String HZ_NETWORK_JOIN_MULTICAST_ENABLED_ENV_NAME = \"HZ_NETWORK_JOIN_MULTICAST_ENABLED\";\n\n    private static final int DEFAULT_EXPOSED_PORT = 5701;\n\n    // Test values\n    private static final String CLUSTER_STARTUP_LOG_MESSAGE_REGEX = \".*Members \\\\{size:2.*\";\n\n    private static final String HOST_PORT_SEPARATOR = \":\";\n\n    private static final String TEST_QUEUE_NAME = \"test-queue\";\n\n    private static final String TEST_CLUSTER_NAME = \"test-cluster\";\n\n    private static final String TEST_VALUE = \"Hello!\";\n\n    private static final String FALSE_VALUE = \"false\";\n\n    private static final String TRUE_VALUE = \"true\";\n\n    @AfterEach\n    void cleanUp() {\n        HazelcastClient.shutdownAll();\n    }\n\n    @Test\n    void singleHazelcastContainer() {\n        try (\n            GenericContainer<?> container = new GenericContainer<>(DockerImageName.parse(HZ_IMAGE_NAME))\n                .withExposedPorts(DEFAULT_EXPOSED_PORT)\n        ) {\n            container.start();\n            assertThat(container.isRunning()).isTrue();\n\n            ClientConfig clientConfig = new ClientConfig();\n            clientConfig\n                .getNetworkConfig()\n                .addAddress(container.getHost() + HOST_PORT_SEPARATOR + container.getFirstMappedPort());\n            HazelcastInstance client = HazelcastClient.newHazelcastClient(clientConfig);\n\n            BlockingQueue<String> queue = client.getQueue(TEST_QUEUE_NAME);\n            queue.put(TEST_VALUE);\n            assertThat(queue.take()).isEqualTo(TEST_VALUE);\n        } catch (InterruptedException e) {\n            Thread.currentThread().interrupt();\n            throw new RuntimeException(\"Interrupted during singleHazelcastContainer test\", e);\n        }\n    }\n\n    @Test\n    void hazelcastCluster() {\n        try (\n            Network network = Network.newNetwork();\n            GenericContainer<?> container1 = new GenericContainer<>(DockerImageName.parse(HZ_IMAGE_NAME))\n                .withExposedPorts(DEFAULT_EXPOSED_PORT)\n                .withEnv(HZ_CLUSTERNAME_ENV_NAME, TEST_CLUSTER_NAME)\n                // Flags necessary to run on Github Actions which runs on an Azure VM\n                .withEnv(HZ_NETWORK_JOIN_AZURE_ENABLED_ENV_NAME, FALSE_VALUE)\n                .withEnv(HZ_NETWORK_JOIN_MULTICAST_ENABLED_ENV_NAME, TRUE_VALUE)\n                .waitingFor(Wait.forLogMessage(CLUSTER_STARTUP_LOG_MESSAGE_REGEX, 1))\n                .withNetwork(network);\n            GenericContainer<?> container2 = new GenericContainer<>(DockerImageName.parse(HZ_IMAGE_NAME))\n                .withExposedPorts(DEFAULT_EXPOSED_PORT)\n                .withEnv(HZ_CLUSTERNAME_ENV_NAME, TEST_CLUSTER_NAME)\n                // Flags necessary to run on Github Actions which runs on an Azure VM\n                .withEnv(HZ_NETWORK_JOIN_AZURE_ENABLED_ENV_NAME, FALSE_VALUE)\n                .withEnv(HZ_NETWORK_JOIN_MULTICAST_ENABLED_ENV_NAME, TRUE_VALUE)\n                .waitingFor(Wait.forLogMessage(CLUSTER_STARTUP_LOG_MESSAGE_REGEX, 1))\n                .withNetwork(network)\n        ) {\n            Startables.deepStart(container1, container2).join();\n            assertThat(container1.isRunning()).isTrue();\n            assertThat(container2.isRunning()).isTrue();\n\n            ClientConfig clientConfig = new ClientConfig();\n            clientConfig\n                .setClusterName(TEST_CLUSTER_NAME)\n                .getNetworkConfig()\n                // Uncomment the next line to remove the \"WARNING: ...Could not connect to member...\" message\n                //.setSmartRouting(false)\n                .addAddress(container1.getHost() + HOST_PORT_SEPARATOR + container1.getFirstMappedPort())\n                .addAddress(container2.getHost() + HOST_PORT_SEPARATOR + container2.getFirstMappedPort());\n\n            HazelcastInstance client = HazelcastClient.newHazelcastClient(clientConfig);\n\n            assertThat(client.getCluster().getMembers()).hasSize(2);\n\n            BlockingQueue<String> queue = client.getQueue(TEST_QUEUE_NAME);\n            queue.put(TEST_VALUE);\n\n            assertThat(queue.take()).isEqualTo(TEST_VALUE);\n        } catch (InterruptedException e) {\n            Thread.currentThread().interrupt();\n            throw new RuntimeException(\"Interrupted during hazelcastCluster test\", e);\n        }\n    }\n}\n"
  },
  {
    "path": "examples/hazelcast/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "examples/immudb/build.gradle",
    "content": "plugins {\n    id 'java'\n}\n\nrepositories {\n    mavenCentral()\n}\n\ndependencies {\n    implementation 'io.codenotary:immudb4j:1.0.1'\n    testImplementation 'org.testcontainers:testcontainers'\n    testImplementation 'org.testcontainers:testcontainers-junit-jupiter'\n    testImplementation 'org.assertj:assertj-core:3.27.4'\n    testImplementation 'com.google.guava:guava:23.0'\n    testImplementation 'ch.qos.logback:logback-classic:1.3.15'\n    testImplementation 'org.junit.jupiter:junit-jupiter:5.13.4'\n    testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.11.0'\n    testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.11.0'\n}\n\ntest {\n    useJUnitPlatform()\n}\n"
  },
  {
    "path": "examples/immudb/src/test/java/ImmuDbTest.java",
    "content": "import io.codenotary.immudb4j.Entry;\nimport io.codenotary.immudb4j.ImmuClient;\nimport io.codenotary.immudb4j.exceptions.VerificationException;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Assertions;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.junit.jupiter.Container;\nimport org.testcontainers.junit.jupiter.Testcontainers;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Test class for the ImmuDbClient.\n */\n@Testcontainers\nclass ImmuDbTest {\n\n    // Default port for the ImmuDb server\n    private static final int IMMUDB_PORT = 3322;\n\n    // Default username for the ImmuDb server\n    private final String IMMUDB_USER = \"immudb\";\n\n    // Default password for the ImmuDb server\n    private final String IMMUDB_PASSWORD = \"immudb\";\n\n    // Default database name for the ImmuDb server\n    private final String IMMUDB_DATABASE = \"defaultdb\";\n\n    // Test container for the ImmuDb database, with the latest version of the image and exposed port\n    @Container\n    public static final GenericContainer<?> immuDbContainer = new GenericContainer<>(\"codenotary/immudb:1.3\")\n        .withExposedPorts(IMMUDB_PORT)\n        .waitingFor(Wait.forLogMessage(\".*Web API server enabled.*\", 1));\n\n    // ImmuClient used to interact with the DB\n    private ImmuClient immuClient;\n\n    @BeforeEach\n    void setUp() {\n        this.immuClient =\n            ImmuClient\n                .newBuilder()\n                .withServerUrl(immuDbContainer.getHost())\n                .withServerPort(immuDbContainer.getMappedPort(IMMUDB_PORT))\n                .build();\n        this.immuClient.openSession(IMMUDB_DATABASE, IMMUDB_USER, IMMUDB_PASSWORD);\n    }\n\n    @AfterEach\n    void tearDown() {\n        this.immuClient.closeSession();\n    }\n\n    @Test\n    void testGetValue() {\n        try {\n            immuClient.set(\"test1\", \"test2\".getBytes());\n\n            Entry entry = immuClient.verifiedGet(\"test1\");\n\n            if (entry != null) {\n                byte[] value = entry.getValue();\n                assertThat(new String(value)).isEqualTo(\"test2\");\n            } else {\n                Assertions.fail();\n            }\n        } catch (VerificationException e) {\n            Assertions.fail();\n        }\n    }\n}\n"
  },
  {
    "path": "examples/immudb/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "examples/kafka-cluster/build.gradle",
    "content": "plugins {\n    id 'java'\n}\n\nrepositories {\n    mavenCentral()\n}\n\ndependencies {\n    testCompileOnly \"org.projectlombok:lombok:1.18.38\"\n    testAnnotationProcessor \"org.projectlombok:lombok:1.18.38\"\n    testImplementation 'org.testcontainers:testcontainers-kafka'\n    testImplementation 'org.apache.kafka:kafka-clients:4.1.0'\n    testImplementation 'org.assertj:assertj-core:3.27.4'\n    testImplementation 'com.google.guava:guava:23.0'\n    testImplementation 'ch.qos.logback:logback-classic:1.3.15'\n    testImplementation 'org.junit.jupiter:junit-jupiter:5.13.4'\n    testImplementation 'org.awaitility:awaitility:4.3.0'\n    testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.11.0'\n}\n\ntest {\n    useJUnitPlatform()\n}\n"
  },
  {
    "path": "examples/kafka-cluster/src/test/java/com/example/kafkacluster/ApacheKafkaContainerCluster.java",
    "content": "package com.example.kafkacluster;\n\nimport org.apache.kafka.common.Uuid;\nimport org.awaitility.Awaitility;\nimport org.testcontainers.containers.Container;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.Network;\nimport org.testcontainers.kafka.KafkaContainer;\nimport org.testcontainers.lifecycle.Startable;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.time.Duration;\nimport java.util.Collection;\nimport java.util.stream.Collectors;\nimport java.util.stream.IntStream;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\npublic class ApacheKafkaContainerCluster implements Startable {\n\n    private final int brokersNum;\n\n    private final Network network;\n\n    private final Collection<KafkaContainer> brokers;\n\n    public ApacheKafkaContainerCluster(String version, int brokersNum, int internalTopicsRf) {\n        if (brokersNum <= 0) {\n            throw new IllegalArgumentException(\"brokersNum '\" + brokersNum + \"' must be greater than 0\");\n        }\n        if (internalTopicsRf <= 0 || internalTopicsRf > brokersNum) {\n            throw new IllegalArgumentException(\n                \"internalTopicsRf '\" +\n                internalTopicsRf +\n                \"' must be less than or equal to brokersNum and greater than 0\"\n            );\n        }\n\n        this.brokersNum = brokersNum;\n        this.network = Network.newNetwork();\n\n        String controllerQuorumVoters = IntStream\n            .range(0, brokersNum)\n            .mapToObj(brokerNum -> String.format(\"%d@broker-%d:9094\", brokerNum, brokerNum))\n            .collect(Collectors.joining(\",\"));\n\n        String clusterId = Uuid.randomUuid().toString();\n\n        this.brokers =\n            IntStream\n                .range(0, brokersNum)\n                .mapToObj(brokerNum -> {\n                    return new KafkaContainer(DockerImageName.parse(\"apache/kafka\").withTag(version))\n                        .withNetwork(this.network)\n                        .withNetworkAliases(\"broker-\" + brokerNum)\n                        .withEnv(\"CLUSTER_ID\", clusterId)\n                        .withEnv(\"KAFKA_BROKER_ID\", brokerNum + \"\")\n                        .withEnv(\"KAFKA_NODE_ID\", brokerNum + \"\")\n                        .withEnv(\"KAFKA_CONTROLLER_QUORUM_VOTERS\", controllerQuorumVoters)\n                        .withEnv(\"KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR\", internalTopicsRf + \"\")\n                        .withEnv(\"KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS\", \"0\")\n                        .withEnv(\"KAFKA_OFFSETS_TOPIC_NUM_PARTITIONS\", internalTopicsRf + \"\")\n                        .withEnv(\"KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR\", internalTopicsRf + \"\")\n                        .withEnv(\"KAFKA_TRANSACTION_STATE_LOG_MIN_ISR\", internalTopicsRf + \"\")\n                        .withStartupTimeout(Duration.ofMinutes(1));\n                })\n                .collect(Collectors.toList());\n    }\n\n    public Collection<KafkaContainer> getBrokers() {\n        return this.brokers;\n    }\n\n    public String getBootstrapServers() {\n        return brokers.stream().map(KafkaContainer::getBootstrapServers).collect(Collectors.joining(\",\"));\n    }\n\n    @Override\n    public void start() {\n        // Needs to start all the brokers at once\n        brokers.parallelStream().forEach(GenericContainer::start);\n\n        Awaitility\n            .await()\n            .atMost(Duration.ofSeconds(30))\n            .untilAsserted(() -> {\n                Container.ExecResult result =\n                    this.brokers.stream()\n                        .findFirst()\n                        .get()\n                        .execInContainer(\n                            \"sh\",\n                            \"-c\",\n                            \"/opt/kafka/bin/kafka-log-dirs.sh --bootstrap-server localhost:9093 --describe | grep -o '\\\"broker\\\"' | wc -l\"\n                        );\n                String brokers = result.getStdout().replace(\"\\n\", \"\");\n\n                assertThat(brokers).asInt().isEqualTo(this.brokersNum);\n            });\n    }\n\n    @Override\n    public void stop() {\n        this.brokers.parallelStream().forEach(GenericContainer::stop);\n    }\n}\n"
  },
  {
    "path": "examples/kafka-cluster/src/test/java/com/example/kafkacluster/ApacheKafkaContainerClusterTest.java",
    "content": "package com.example.kafkacluster;\n\nimport com.google.common.collect.ImmutableMap;\nimport org.apache.kafka.clients.admin.AdminClient;\nimport org.apache.kafka.clients.admin.AdminClientConfig;\nimport org.apache.kafka.clients.admin.NewTopic;\nimport org.apache.kafka.clients.consumer.ConsumerConfig;\nimport org.apache.kafka.clients.consumer.ConsumerRecord;\nimport org.apache.kafka.clients.consumer.ConsumerRecords;\nimport org.apache.kafka.clients.consumer.KafkaConsumer;\nimport org.apache.kafka.clients.producer.KafkaProducer;\nimport org.apache.kafka.clients.producer.ProducerConfig;\nimport org.apache.kafka.clients.producer.ProducerRecord;\nimport org.apache.kafka.common.serialization.StringDeserializer;\nimport org.apache.kafka.common.serialization.StringSerializer;\nimport org.awaitility.Awaitility;\nimport org.junit.jupiter.api.Test;\n\nimport java.time.Duration;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.UUID;\nimport java.util.concurrent.TimeUnit;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.tuple;\n\nclass ApacheKafkaContainerClusterTest {\n\n    @Test\n    void testKafkaContainerCluster() throws Exception {\n        try (ApacheKafkaContainerCluster cluster = new ApacheKafkaContainerCluster(\"3.8.0\", 3, 2)) {\n            cluster.start();\n            String bootstrapServers = cluster.getBootstrapServers();\n\n            assertThat(cluster.getBrokers()).hasSize(3);\n\n            testKafkaFunctionality(bootstrapServers, 3, 2);\n        }\n    }\n\n    protected void testKafkaFunctionality(String bootstrapServers, int partitions, int rf) throws Exception {\n        try (\n            AdminClient adminClient = AdminClient.create(\n                ImmutableMap.of(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers)\n            );\n            KafkaProducer<String, String> producer = new KafkaProducer<>(\n                ImmutableMap.of(\n                    ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,\n                    bootstrapServers,\n                    ProducerConfig.CLIENT_ID_CONFIG,\n                    UUID.randomUUID().toString()\n                ),\n                new StringSerializer(),\n                new StringSerializer()\n            );\n            KafkaConsumer<String, String> consumer = new KafkaConsumer<>(\n                ImmutableMap.of(\n                    ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,\n                    bootstrapServers,\n                    ConsumerConfig.GROUP_ID_CONFIG,\n                    \"tc-\" + UUID.randomUUID(),\n                    ConsumerConfig.AUTO_OFFSET_RESET_CONFIG,\n                    \"earliest\"\n                ),\n                new StringDeserializer(),\n                new StringDeserializer()\n            );\n        ) {\n            String topicName = \"messages\";\n\n            Collection<NewTopic> topics = Collections.singletonList(new NewTopic(topicName, partitions, (short) rf));\n            adminClient.createTopics(topics).all().get(30, TimeUnit.SECONDS);\n\n            consumer.subscribe(Collections.singletonList(topicName));\n\n            producer.send(new ProducerRecord<>(topicName, \"testcontainers\", \"rulezzz\")).get();\n\n            Awaitility\n                .await()\n                .atMost(Duration.ofSeconds(10))\n                .untilAsserted(() -> {\n                    ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));\n\n                    assertThat(records)\n                        .hasSize(1)\n                        .extracting(ConsumerRecord::topic, ConsumerRecord::key, ConsumerRecord::value)\n                        .containsExactly(tuple(topicName, \"testcontainers\", \"rulezzz\"));\n                });\n\n            consumer.unsubscribe();\n        }\n    }\n}\n"
  },
  {
    "path": "examples/kafka-cluster/src/test/java/com/example/kafkacluster/ConfluentKafkaContainerCluster.java",
    "content": "package com.example.kafkacluster;\n\nimport org.apache.kafka.common.Uuid;\nimport org.awaitility.Awaitility;\nimport org.testcontainers.containers.Container;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.Network;\nimport org.testcontainers.kafka.ConfluentKafkaContainer;\nimport org.testcontainers.lifecycle.Startable;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.time.Duration;\nimport java.util.Collection;\nimport java.util.stream.Collectors;\nimport java.util.stream.IntStream;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\npublic class ConfluentKafkaContainerCluster implements Startable {\n\n    private final int brokersNum;\n\n    private final Network network;\n\n    private final Collection<ConfluentKafkaContainer> brokers;\n\n    public ConfluentKafkaContainerCluster(String confluentPlatformVersion, int brokersNum, int internalTopicsRf) {\n        if (brokersNum <= 0) {\n            throw new IllegalArgumentException(\"brokersNum '\" + brokersNum + \"' must be greater than 0\");\n        }\n        if (internalTopicsRf <= 0 || internalTopicsRf > brokersNum) {\n            throw new IllegalArgumentException(\n                \"internalTopicsRf '\" +\n                internalTopicsRf +\n                \"' must be less than or equal to brokersNum and greater than 0\"\n            );\n        }\n\n        this.brokersNum = brokersNum;\n        this.network = Network.newNetwork();\n\n        String controllerQuorumVoters = IntStream\n            .range(0, brokersNum)\n            .mapToObj(brokerNum -> String.format(\"%d@broker-%d:9094\", brokerNum, brokerNum))\n            .collect(Collectors.joining(\",\"));\n\n        String clusterId = Uuid.randomUuid().toString();\n\n        this.brokers =\n            IntStream\n                .range(0, brokersNum)\n                .mapToObj(brokerNum -> {\n                    return new ConfluentKafkaContainer(\n                        DockerImageName.parse(\"confluentinc/cp-kafka\").withTag(confluentPlatformVersion)\n                    )\n                        .withNetwork(this.network)\n                        .withNetworkAliases(\"broker-\" + brokerNum)\n                        .withEnv(\"CLUSTER_ID\", clusterId)\n                        .withEnv(\"KAFKA_BROKER_ID\", brokerNum + \"\")\n                        .withEnv(\"KAFKA_NODE_ID\", brokerNum + \"\")\n                        .withEnv(\"KAFKA_CONTROLLER_QUORUM_VOTERS\", controllerQuorumVoters)\n                        .withEnv(\"KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR\", internalTopicsRf + \"\")\n                        .withEnv(\"KAFKA_OFFSETS_TOPIC_NUM_PARTITIONS\", internalTopicsRf + \"\")\n                        .withEnv(\"KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR\", internalTopicsRf + \"\")\n                        .withEnv(\"KAFKA_TRANSACTION_STATE_LOG_MIN_ISR\", internalTopicsRf + \"\")\n                        .withStartupTimeout(Duration.ofMinutes(1));\n                })\n                .collect(Collectors.toList());\n    }\n\n    public Collection<ConfluentKafkaContainer> getBrokers() {\n        return this.brokers;\n    }\n\n    public String getBootstrapServers() {\n        return brokers.stream().map(ConfluentKafkaContainer::getBootstrapServers).collect(Collectors.joining(\",\"));\n    }\n\n    @Override\n    public void start() {\n        // Needs to start all the brokers at once\n        brokers.parallelStream().forEach(GenericContainer::start);\n\n        Awaitility\n            .await()\n            .atMost(Duration.ofSeconds(30))\n            .untilAsserted(() -> {\n                Container.ExecResult result =\n                    this.brokers.stream()\n                        .findFirst()\n                        .get()\n                        .execInContainer(\n                            \"sh\",\n                            \"-c\",\n                            \"kafka-metadata-shell --snapshot /var/lib/kafka/data/__cluster_metadata-0/00000000000000000000.log ls /brokers | wc -l\"\n                        );\n                String brokers = result.getStdout().replace(\"\\n\", \"\");\n\n                assertThat(brokers).asInt().isEqualTo(this.brokersNum);\n            });\n    }\n\n    @Override\n    public void stop() {\n        this.brokers.parallelStream().forEach(GenericContainer::stop);\n    }\n}\n"
  },
  {
    "path": "examples/kafka-cluster/src/test/java/com/example/kafkacluster/ConfluentKafkaContainerClusterTest.java",
    "content": "package com.example.kafkacluster;\n\nimport com.google.common.collect.ImmutableMap;\nimport org.apache.kafka.clients.admin.AdminClient;\nimport org.apache.kafka.clients.admin.AdminClientConfig;\nimport org.apache.kafka.clients.admin.NewTopic;\nimport org.apache.kafka.clients.consumer.ConsumerConfig;\nimport org.apache.kafka.clients.consumer.ConsumerRecord;\nimport org.apache.kafka.clients.consumer.ConsumerRecords;\nimport org.apache.kafka.clients.consumer.KafkaConsumer;\nimport org.apache.kafka.clients.producer.KafkaProducer;\nimport org.apache.kafka.clients.producer.ProducerConfig;\nimport org.apache.kafka.clients.producer.ProducerRecord;\nimport org.apache.kafka.common.serialization.StringDeserializer;\nimport org.apache.kafka.common.serialization.StringSerializer;\nimport org.awaitility.Awaitility;\nimport org.junit.jupiter.api.Test;\n\nimport java.time.Duration;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.UUID;\nimport java.util.concurrent.TimeUnit;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.tuple;\n\nclass ConfluentKafkaContainerClusterTest {\n\n    @Test\n    void testKafkaContainerCluster() throws Exception {\n        try (ConfluentKafkaContainerCluster cluster = new ConfluentKafkaContainerCluster(\"7.4.0\", 3, 2)) {\n            cluster.start();\n            String bootstrapServers = cluster.getBootstrapServers();\n\n            assertThat(cluster.getBrokers()).hasSize(3);\n\n            testKafkaFunctionality(bootstrapServers, 3, 2);\n        }\n    }\n\n    protected void testKafkaFunctionality(String bootstrapServers, int partitions, int rf) throws Exception {\n        try (\n            AdminClient adminClient = AdminClient.create(\n                ImmutableMap.of(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers)\n            );\n            KafkaProducer<String, String> producer = new KafkaProducer<>(\n                ImmutableMap.of(\n                    ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,\n                    bootstrapServers,\n                    ProducerConfig.CLIENT_ID_CONFIG,\n                    UUID.randomUUID().toString()\n                ),\n                new StringSerializer(),\n                new StringSerializer()\n            );\n            KafkaConsumer<String, String> consumer = new KafkaConsumer<>(\n                ImmutableMap.of(\n                    ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,\n                    bootstrapServers,\n                    ConsumerConfig.GROUP_ID_CONFIG,\n                    \"tc-\" + UUID.randomUUID(),\n                    ConsumerConfig.AUTO_OFFSET_RESET_CONFIG,\n                    \"earliest\"\n                ),\n                new StringDeserializer(),\n                new StringDeserializer()\n            );\n        ) {\n            String topicName = \"messages\";\n\n            Collection<NewTopic> topics = Collections.singletonList(new NewTopic(topicName, partitions, (short) rf));\n            adminClient.createTopics(topics).all().get(30, TimeUnit.SECONDS);\n\n            consumer.subscribe(Collections.singletonList(topicName));\n\n            producer.send(new ProducerRecord<>(topicName, \"testcontainers\", \"rulezzz\")).get();\n\n            Awaitility\n                .await()\n                .atMost(Duration.ofSeconds(10))\n                .untilAsserted(() -> {\n                    ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));\n\n                    assertThat(records)\n                        .hasSize(1)\n                        .extracting(ConsumerRecord::topic, ConsumerRecord::key, ConsumerRecord::value)\n                        .containsExactly(tuple(topicName, \"testcontainers\", \"rulezzz\"));\n                });\n\n            consumer.unsubscribe();\n        }\n    }\n}\n"
  },
  {
    "path": "examples/kafka-cluster/src/test/java/com/example/kafkacluster/KafkaContainerCluster.java",
    "content": "package com.example.kafkacluster;\n\nimport lombok.SneakyThrows;\nimport org.awaitility.Awaitility;\nimport org.testcontainers.containers.Container;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.KafkaContainer;\nimport org.testcontainers.containers.Network;\nimport org.testcontainers.lifecycle.Startable;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.time.Duration;\nimport java.util.Collection;\nimport java.util.stream.Collectors;\nimport java.util.stream.IntStream;\nimport java.util.stream.Stream;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Provides an easy way to launch a Kafka cluster with multiple brokers.\n */\npublic class KafkaContainerCluster implements Startable {\n\n    private final int brokersNum;\n\n    private final Network network;\n\n    private final GenericContainer<?> zookeeper;\n\n    private final Collection<KafkaContainer> brokers;\n\n    public KafkaContainerCluster(String confluentPlatformVersion, int brokersNum, int internalTopicsRf) {\n        if (brokersNum <= 0) {\n            throw new IllegalArgumentException(\"brokersNum '\" + brokersNum + \"' must be greater than 0\");\n        }\n        if (internalTopicsRf <= 0 || internalTopicsRf > brokersNum) {\n            throw new IllegalArgumentException(\n                \"internalTopicsRf '\" +\n                internalTopicsRf +\n                \"' must be less than or equal to brokersNum and greater than 0\"\n            );\n        }\n\n        this.brokersNum = brokersNum;\n        this.network = Network.newNetwork();\n\n        this.zookeeper =\n            new GenericContainer<>(DockerImageName.parse(\"confluentinc/cp-zookeeper\").withTag(confluentPlatformVersion))\n                .withNetwork(network)\n                .withNetworkAliases(\"zookeeper\")\n                .withEnv(\"ZOOKEEPER_CLIENT_PORT\", String.valueOf(KafkaContainer.ZOOKEEPER_PORT));\n\n        this.brokers =\n            IntStream\n                .range(0, this.brokersNum)\n                .mapToObj(brokerNum -> {\n                    return new KafkaContainer(\n                        DockerImageName.parse(\"confluentinc/cp-kafka\").withTag(confluentPlatformVersion)\n                    )\n                        .withNetwork(this.network)\n                        .withNetworkAliases(\"broker-\" + brokerNum)\n                        .dependsOn(this.zookeeper)\n                        .withExternalZookeeper(\"zookeeper:\" + KafkaContainer.ZOOKEEPER_PORT)\n                        .withEnv(\"KAFKA_BROKER_ID\", brokerNum + \"\")\n                        .withEnv(\"KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR\", internalTopicsRf + \"\")\n                        .withEnv(\"KAFKA_OFFSETS_TOPIC_NUM_PARTITIONS\", internalTopicsRf + \"\")\n                        .withEnv(\"KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR\", internalTopicsRf + \"\")\n                        .withEnv(\"KAFKA_TRANSACTION_STATE_LOG_MIN_ISR\", internalTopicsRf + \"\")\n                        .withStartupTimeout(Duration.ofMinutes(1));\n                })\n                .collect(Collectors.toList());\n    }\n\n    public Collection<KafkaContainer> getBrokers() {\n        return this.brokers;\n    }\n\n    public String getBootstrapServers() {\n        return brokers.stream().map(KafkaContainer::getBootstrapServers).collect(Collectors.joining(\",\"));\n    }\n\n    private Stream<GenericContainer<?>> allContainers() {\n        return Stream.concat(this.brokers.stream(), Stream.of(this.zookeeper));\n    }\n\n    @Override\n    @SneakyThrows\n    public void start() {\n        // sequential start to avoid resource contention on CI systems with weaker hardware\n        brokers.forEach(GenericContainer::start);\n\n        Awaitility\n            .await()\n            .atMost(Duration.ofSeconds(30))\n            .untilAsserted(() -> {\n                Container.ExecResult result =\n                    this.zookeeper.execInContainer(\n                            \"sh\",\n                            \"-c\",\n                            \"zookeeper-shell zookeeper:\" +\n                            KafkaContainer.ZOOKEEPER_PORT +\n                            \" ls /brokers/ids | tail -n 1\"\n                        );\n                String brokers = result.getStdout();\n\n                assertThat(brokers.split(\",\")).hasSize(this.brokersNum);\n            });\n    }\n\n    @Override\n    public void stop() {\n        allContainers().parallel().forEach(GenericContainer::stop);\n    }\n}\n"
  },
  {
    "path": "examples/kafka-cluster/src/test/java/com/example/kafkacluster/KafkaContainerClusterTest.java",
    "content": "package com.example.kafkacluster;\n\nimport com.google.common.collect.ImmutableMap;\nimport org.apache.kafka.clients.admin.AdminClient;\nimport org.apache.kafka.clients.admin.AdminClientConfig;\nimport org.apache.kafka.clients.admin.NewTopic;\nimport org.apache.kafka.clients.consumer.ConsumerConfig;\nimport org.apache.kafka.clients.consumer.ConsumerRecord;\nimport org.apache.kafka.clients.consumer.ConsumerRecords;\nimport org.apache.kafka.clients.consumer.KafkaConsumer;\nimport org.apache.kafka.clients.producer.KafkaProducer;\nimport org.apache.kafka.clients.producer.ProducerConfig;\nimport org.apache.kafka.clients.producer.ProducerRecord;\nimport org.apache.kafka.common.serialization.StringDeserializer;\nimport org.apache.kafka.common.serialization.StringSerializer;\nimport org.awaitility.Awaitility;\nimport org.junit.jupiter.api.Test;\n\nimport java.time.Duration;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.UUID;\nimport java.util.concurrent.TimeUnit;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.tuple;\n\nclass KafkaContainerClusterTest {\n\n    @Test\n    void testKafkaContainerCluster() throws Exception {\n        try (KafkaContainerCluster cluster = new KafkaContainerCluster(\"6.2.1\", 3, 2)) {\n            cluster.start();\n            String bootstrapServers = cluster.getBootstrapServers();\n\n            assertThat(cluster.getBrokers()).hasSize(3);\n\n            testKafkaFunctionality(bootstrapServers, 3, 2);\n        }\n    }\n\n    @Test\n    void testKafkaContainerKraftCluster() throws Exception {\n        try (KafkaContainerKraftCluster cluster = new KafkaContainerKraftCluster(\"7.0.0\", 3, 2)) {\n            cluster.start();\n            String bootstrapServers = cluster.getBootstrapServers();\n\n            assertThat(cluster.getBrokers()).hasSize(3);\n\n            testKafkaFunctionality(bootstrapServers, 3, 2);\n        }\n    }\n\n    @Test\n    void testKafkaContainerKraftClusterAfterConfluentPlatform740() throws Exception {\n        try (KafkaContainerKraftCluster cluster = new KafkaContainerKraftCluster(\"7.4.0\", 3, 2)) {\n            cluster.start();\n            String bootstrapServers = cluster.getBootstrapServers();\n\n            assertThat(cluster.getBrokers()).hasSize(3);\n\n            testKafkaFunctionality(bootstrapServers, 3, 2);\n        }\n    }\n\n    protected void testKafkaFunctionality(String bootstrapServers, int partitions, int rf) throws Exception {\n        try (\n            AdminClient adminClient = AdminClient.create(\n                ImmutableMap.of(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers)\n            );\n            KafkaProducer<String, String> producer = new KafkaProducer<>(\n                ImmutableMap.of(\n                    ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,\n                    bootstrapServers,\n                    ProducerConfig.CLIENT_ID_CONFIG,\n                    UUID.randomUUID().toString()\n                ),\n                new StringSerializer(),\n                new StringSerializer()\n            );\n            KafkaConsumer<String, String> consumer = new KafkaConsumer<>(\n                ImmutableMap.of(\n                    ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,\n                    bootstrapServers,\n                    ConsumerConfig.GROUP_ID_CONFIG,\n                    \"tc-\" + UUID.randomUUID(),\n                    ConsumerConfig.AUTO_OFFSET_RESET_CONFIG,\n                    \"earliest\"\n                ),\n                new StringDeserializer(),\n                new StringDeserializer()\n            );\n        ) {\n            String topicName = \"messages\";\n\n            Collection<NewTopic> topics = Collections.singletonList(new NewTopic(topicName, partitions, (short) rf));\n            adminClient.createTopics(topics).all().get(30, TimeUnit.SECONDS);\n\n            consumer.subscribe(Collections.singletonList(topicName));\n\n            producer.send(new ProducerRecord<>(topicName, \"testcontainers\", \"rulezzz\")).get();\n\n            Awaitility\n                .await()\n                .atMost(Duration.ofSeconds(10))\n                .untilAsserted(() -> {\n                    ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));\n\n                    assertThat(records)\n                        .hasSize(1)\n                        .extracting(ConsumerRecord::topic, ConsumerRecord::key, ConsumerRecord::value)\n                        .containsExactly(tuple(topicName, \"testcontainers\", \"rulezzz\"));\n                });\n\n            consumer.unsubscribe();\n        }\n    }\n}\n"
  },
  {
    "path": "examples/kafka-cluster/src/test/java/com/example/kafkacluster/KafkaContainerKraftCluster.java",
    "content": "package com.example.kafkacluster;\n\nimport org.apache.kafka.common.Uuid;\nimport org.awaitility.Awaitility;\nimport org.testcontainers.containers.Container;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.KafkaContainer;\nimport org.testcontainers.containers.Network;\nimport org.testcontainers.lifecycle.Startable;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.time.Duration;\nimport java.util.Collection;\nimport java.util.stream.Collectors;\nimport java.util.stream.IntStream;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\npublic class KafkaContainerKraftCluster implements Startable {\n\n    private final int brokersNum;\n\n    private final Network network;\n\n    private final Collection<KafkaContainer> brokers;\n\n    public KafkaContainerKraftCluster(String confluentPlatformVersion, int brokersNum, int internalTopicsRf) {\n        if (brokersNum <= 0) {\n            throw new IllegalArgumentException(\"brokersNum '\" + brokersNum + \"' must be greater than 0\");\n        }\n        if (internalTopicsRf <= 0 || internalTopicsRf > brokersNum) {\n            throw new IllegalArgumentException(\n                \"internalTopicsRf '\" +\n                internalTopicsRf +\n                \"' must be less than or equal to brokersNum and greater than 0\"\n            );\n        }\n\n        this.brokersNum = brokersNum;\n        this.network = Network.newNetwork();\n\n        String controllerQuorumVoters = IntStream\n            .range(0, brokersNum)\n            .mapToObj(brokerNum -> String.format(\"%d@broker-%d:9094\", brokerNum, brokerNum))\n            .collect(Collectors.joining(\",\"));\n\n        String clusterId = Uuid.randomUuid().toString();\n\n        this.brokers =\n            IntStream\n                .range(0, brokersNum)\n                .mapToObj(brokerNum -> {\n                    return new KafkaContainer(\n                        DockerImageName.parse(\"confluentinc/cp-kafka\").withTag(confluentPlatformVersion)\n                    )\n                        .withNetwork(this.network)\n                        .withNetworkAliases(\"broker-\" + brokerNum)\n                        .withKraft()\n                        .withClusterId(clusterId)\n                        .withEnv(\"KAFKA_BROKER_ID\", brokerNum + \"\")\n                        .withEnv(\"KAFKA_NODE_ID\", brokerNum + \"\")\n                        .withEnv(\"KAFKA_CONTROLLER_QUORUM_VOTERS\", controllerQuorumVoters)\n                        .withEnv(\"KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR\", internalTopicsRf + \"\")\n                        .withEnv(\"KAFKA_OFFSETS_TOPIC_NUM_PARTITIONS\", internalTopicsRf + \"\")\n                        .withEnv(\"KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR\", internalTopicsRf + \"\")\n                        .withEnv(\"KAFKA_TRANSACTION_STATE_LOG_MIN_ISR\", internalTopicsRf + \"\")\n                        .withStartupTimeout(Duration.ofMinutes(1));\n                })\n                .collect(Collectors.toList());\n    }\n\n    public Collection<KafkaContainer> getBrokers() {\n        return this.brokers;\n    }\n\n    public String getBootstrapServers() {\n        return brokers.stream().map(KafkaContainer::getBootstrapServers).collect(Collectors.joining(\",\"));\n    }\n\n    @Override\n    public void start() {\n        // Needs to start all the brokers at once\n        brokers.parallelStream().forEach(GenericContainer::start);\n\n        Awaitility\n            .await()\n            .atMost(Duration.ofSeconds(30))\n            .untilAsserted(() -> {\n                Container.ExecResult result =\n                    this.brokers.stream()\n                        .findFirst()\n                        .get()\n                        .execInContainer(\n                            \"sh\",\n                            \"-c\",\n                            \"kafka-metadata-shell --snapshot /var/lib/kafka/data/__cluster_metadata-0/00000000000000000000.log ls /brokers | wc -l\"\n                        );\n                String brokers = result.getStdout().replace(\"\\n\", \"\");\n\n                assertThat(brokers).asInt().isEqualTo(this.brokersNum);\n            });\n    }\n\n    @Override\n    public void stop() {\n        this.brokers.parallelStream().forEach(GenericContainer::stop);\n    }\n}\n"
  },
  {
    "path": "examples/kafka-cluster/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "examples/nats/build.gradle",
    "content": "plugins {\n    id 'java'\n}\n\nrepositories {\n    mavenCentral()\n}\n\ndependencies {\n    testImplementation 'org.assertj:assertj-core:3.27.4'\n    testImplementation 'org.testcontainers:testcontainers'\n    testImplementation 'io.nats:jnats:2.23.0'\n    testImplementation 'ch.qos.logback:logback-classic:1.3.15'\n    testImplementation 'org.apache.httpcomponents:httpclient:4.5.14'\n    testImplementation 'org.junit.jupiter:junit-jupiter:5.13.4'\n    testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.11.0'\n}\n\ntest {\n    useJUnitPlatform()\n}\n"
  },
  {
    "path": "examples/nats/src/test/java/com/example/NatsContainerTest.java",
    "content": "package com.example;\n\nimport io.nats.client.Connection;\nimport io.nats.client.Nats;\nimport io.nats.client.Options;\nimport org.apache.http.HttpResponse;\nimport org.apache.http.HttpStatus;\nimport org.apache.http.client.methods.HttpGet;\nimport org.apache.http.client.methods.HttpUriRequest;\nimport org.apache.http.impl.client.HttpClientBuilder;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.GenericContainer;\n\nimport java.io.IOException;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass NatsContainerTest {\n\n    public static final Integer NATS_PORT = 4222;\n\n    public static final Integer NATS_MGMT_PORT = 8222;\n\n    @Test\n    void test() throws IOException, InterruptedException {\n        try (\n            GenericContainer<?> nats = new GenericContainer<>(\"nats:2.9.8-alpine3.16\")\n                .withExposedPorts(NATS_PORT, NATS_MGMT_PORT)\n        ) {\n            nats.start();\n\n            Connection connection = Nats.connect(\n                new Options.Builder().server(\"nats://\" + nats.getHost() + \":\" + nats.getMappedPort(NATS_PORT)).build()\n            );\n\n            assertThat(connection.getStatus()).isEqualTo(Connection.Status.CONNECTED);\n        }\n    }\n\n    @Test\n    void testServerStatus() throws IOException {\n        try (\n            GenericContainer<?> nats = new GenericContainer<>(\"nats:2.9.8-alpine3.16\")\n                .withExposedPorts(NATS_PORT, NATS_MGMT_PORT)\n        ) {\n            nats.start();\n\n            HttpUriRequest request = new HttpGet(\n                String.format(\"http://%s:%d/varz\", nats.getHost(), nats.getMappedPort(NATS_MGMT_PORT))\n            );\n            HttpResponse httpResponse = HttpClientBuilder.create().build().execute(request);\n\n            assertThat(httpResponse.getStatusLine().getStatusCode()).isEqualTo(HttpStatus.SC_OK);\n        }\n    }\n}\n"
  },
  {
    "path": "examples/nats/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "examples/neo4j-container/build.gradle",
    "content": "plugins {\n    id 'java'\n}\n\nrepositories {\n    mavenCentral()\n}\n\ndependencies {\n    testImplementation 'org.assertj:assertj-core:3.27.4'\n    testImplementation 'org.neo4j.driver:neo4j-java-driver:4.4.20'\n    testImplementation 'org.testcontainers:testcontainers-neo4j'\n    testImplementation 'org.testcontainers:testcontainers-junit-jupiter'\n    testImplementation 'org.junit.jupiter:junit-jupiter:5.13.4'\n    testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.11.0'\n}\n"
  },
  {
    "path": "examples/neo4j-container/src/test/java/org/testcontainers/containers/Neo4jExampleTest.java",
    "content": "package org.testcontainers.containers;\n\nimport org.junit.jupiter.api.Test;\nimport org.neo4j.driver.AuthTokens;\nimport org.neo4j.driver.Driver;\nimport org.neo4j.driver.GraphDatabase;\nimport org.neo4j.driver.Session;\nimport org.testcontainers.junit.jupiter.Container;\nimport org.testcontainers.junit.jupiter.Testcontainers;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.io.OutputStreamWriter;\nimport java.io.Writer;\nimport java.net.HttpURLConnection;\nimport java.net.URL;\nimport java.util.Collections;\nimport java.util.stream.Collectors;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.fail;\n\n// junitExample {\n@Testcontainers\nclass Neo4jExampleTest {\n\n    @Container\n    private static Neo4jContainer<?> neo4jContainer = new Neo4jContainer<>(DockerImageName.parse(\"neo4j:4.4\"))\n        .withoutAuthentication(); // Disable password\n\n    @Test\n    void testSomethingUsingBolt() {\n        // Retrieve the Bolt URL from the container\n        String boltUrl = neo4jContainer.getBoltUrl();\n        try (Driver driver = GraphDatabase.driver(boltUrl, AuthTokens.none()); Session session = driver.session()) {\n            long one = session.run(\"RETURN 1\", Collections.emptyMap()).next().get(0).asLong();\n            assertThat(one).isEqualTo(1L);\n        } catch (Exception e) {\n            fail(e.getMessage());\n        }\n    }\n\n    @Test\n    void testSomethingUsingHttp() throws IOException {\n        // Retrieve the HTTP URL from the container\n        String httpUrl = neo4jContainer.getHttpUrl();\n\n        URL url = new URL(httpUrl + \"/db/data/transaction/commit\");\n        HttpURLConnection con = (HttpURLConnection) url.openConnection();\n\n        con.setRequestMethod(\"POST\");\n        con.setRequestProperty(\"Content-Type\", \"application/json\");\n        con.setDoOutput(true);\n\n        try (Writer out = new OutputStreamWriter(con.getOutputStream())) {\n            out.write(\"{\\\"statements\\\":[{\\\"statement\\\":\\\"RETURN 1\\\"}]}\");\n            out.flush();\n        }\n\n        assertThat(con.getResponseCode()).isEqualTo(HttpURLConnection.HTTP_OK);\n        try (BufferedReader buffer = new BufferedReader(new InputStreamReader(con.getInputStream()))) {\n            String expectedResponse =\n                \"{\\\"results\\\":[{\\\"columns\\\":[\\\"1\\\"],\\\"data\\\":[{\\\"row\\\":[1],\\\"meta\\\":[null]}]}],\\\"errors\\\":[]}\";\n            String response = buffer.lines().collect(Collectors.joining(\"\\n\"));\n            assertThat(response).isEqualTo(expectedResponse);\n        }\n    }\n}\n// }\n"
  },
  {
    "path": "examples/neo4j-container/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "examples/ollama-hugging-face/build.gradle",
    "content": "plugins {\n    id 'java'\n}\n\nrepositories {\n    mavenCentral()\n}\n\ndependencies {\n    testImplementation 'org.testcontainers:testcontainers-ollama'\n    testImplementation 'org.assertj:assertj-core:3.27.4'\n    testImplementation 'ch.qos.logback:logback-classic:1.3.15'\n    testImplementation 'org.junit.jupiter:junit-jupiter:5.13.4'\n    testImplementation 'io.rest-assured:rest-assured:5.5.6'\n    testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.11.0'\n}\n\ntest {\n    useJUnitPlatform()\n}\n"
  },
  {
    "path": "examples/ollama-hugging-face/src/test/java/com/example/ollamahf/OllamaHuggingFaceContainer.java",
    "content": "package com.example.ollamahf;\n\nimport com.github.dockerjava.api.command.InspectContainerResponse;\nimport org.testcontainers.containers.ContainerLaunchException;\nimport org.testcontainers.ollama.OllamaContainer;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.IOException;\n\npublic class OllamaHuggingFaceContainer extends OllamaContainer {\n\n    private final HuggingFaceModel huggingFaceModel;\n\n    public OllamaHuggingFaceContainer(HuggingFaceModel model) {\n        super(DockerImageName.parse(\"ollama/ollama:0.1.47\"));\n        this.huggingFaceModel = model;\n    }\n\n    @Override\n    protected void containerIsStarted(InspectContainerResponse containerInfo, boolean reused) {\n        super.containerIsStarted(containerInfo, reused);\n        if (reused || huggingFaceModel == null) {\n            return;\n        }\n\n        try {\n            executeCommand(\"apt-get\", \"update\");\n            executeCommand(\"apt-get\", \"upgrade\", \"-y\");\n            executeCommand(\"apt-get\", \"install\", \"-y\", \"python3-pip\");\n            executeCommand(\"pip\", \"install\", \"huggingface-hub\");\n            executeCommand(\"hf\", \"download\", huggingFaceModel.repository, huggingFaceModel.model, \"--local-dir\", \".\");\n            executeCommand(\"sh\", \"-c\", String.format(\"echo '%s' > Modelfile\", huggingFaceModel.modelfileContent));\n            executeCommand(\"ollama\", \"create\", huggingFaceModel.model, \"-f\", \"Modelfile\");\n            executeCommand(\"rm\", huggingFaceModel.model);\n        } catch (IOException | InterruptedException e) {\n            throw new ContainerLaunchException(e.getMessage());\n        }\n    }\n\n    private void executeCommand(String... command) throws ContainerLaunchException, IOException, InterruptedException {\n        ExecResult execResult = execInContainer(command);\n        if (execResult.getExitCode() > 0) {\n            throw new ContainerLaunchException(\n                \"Failed to execute \" + String.join(\" \", command) + \": \" + execResult.getStdout()\n            );\n        }\n    }\n\n    public static class HuggingFaceModel {\n\n        public final String repository;\n\n        public final String model;\n\n        public String modelfileContent;\n\n        public HuggingFaceModel(String repository, String model) {\n            this.repository = repository;\n            this.model = model;\n            this.modelfileContent = \"FROM \" + model;\n        }\n    }\n}\n"
  },
  {
    "path": "examples/ollama-hugging-face/src/test/java/com/example/ollamahf/OllamaHuggingFaceTest.java",
    "content": "package com.example.ollamahf;\n\nimport io.restassured.http.Header;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.ollama.OllamaContainer;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.util.List;\n\nimport static io.restassured.RestAssured.given;\nimport static org.assertj.core.api.Assertions.assertThat;\n\npublic class OllamaHuggingFaceTest {\n\n    @Test\n    public void embeddingModelWithHuggingFace() {\n        String repository = \"CompendiumLabs/bge-small-en-v1.5-gguf\";\n        String model = \"bge-small-en-v1.5-q4_k_m.gguf\";\n        String imageName = \"embedding-model-from-hugging-face\";\n        OllamaContainer ollama = new OllamaContainer(\n            DockerImageName.parse(imageName).asCompatibleSubstituteFor(\"ollama/ollama:0.1.47\")\n        );\n        boolean imageExists = ollama\n            .getDockerClient()\n            .listImagesCmd()\n            .exec()\n            .stream()\n            .anyMatch(image -> image.getRepoTags()[0].equals(imageName + \":latest\"));\n        if (!imageExists) {\n            createImage(imageName, repository, model);\n        }\n        ollama.start();\n\n        String modelName = given()\n            .baseUri(ollama.getEndpoint())\n            .get(\"/api/tags\")\n            .jsonPath()\n            .getString(\"models[0].name\");\n        assertThat(modelName).contains(model + \":latest\");\n\n        List<Float> embedding = given()\n            .baseUri(ollama.getEndpoint())\n            .header(new Header(\"Content-Type\", \"application/json\"))\n            .body(new EmbeddingRequest(model + \":latest\", \"Hello from Testcontainers!\"))\n            .post(\"/api/embeddings\")\n            .jsonPath()\n            .getList(\"embedding\");\n\n        assertThat(embedding).isNotNull();\n        assertThat(embedding.isEmpty()).isFalse();\n    }\n\n    private static void createImage(String imageName, String repository, String model) {\n        OllamaHuggingFaceContainer.HuggingFaceModel hfModel = new OllamaHuggingFaceContainer.HuggingFaceModel(\n            repository,\n            model\n        );\n        OllamaHuggingFaceContainer huggingFaceContainer = new OllamaHuggingFaceContainer(hfModel);\n        huggingFaceContainer.start();\n        huggingFaceContainer.commitToImage(imageName);\n    }\n\n    public static class EmbeddingRequest {\n\n        public final String model;\n\n        public final String prompt;\n\n        public EmbeddingRequest(String model, String prompt) {\n            this.model = model;\n            this.prompt = prompt;\n        }\n    }\n}\n"
  },
  {
    "path": "examples/ollama-hugging-face/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "examples/redis-backed-cache/build.gradle",
    "content": "plugins {\n    id 'java'\n}\n\nrepositories {\n    mavenCentral()\n}\n\ndependencies {\n    compileOnly 'org.slf4j:slf4j-api:1.7.36'\n    implementation 'redis.clients:jedis:6.2.0'\n    implementation 'com.google.code.gson:gson:2.13.2'\n    implementation 'com.google.guava:guava:23.0'\n    testImplementation 'org.testcontainers:testcontainers'\n    testImplementation 'org.testcontainers:testcontainers-junit-jupiter'\n    testImplementation 'org.junit.jupiter:junit-jupiter:5.13.4'\n    testImplementation 'ch.qos.logback:logback-classic:1.3.15'\n    testImplementation 'org.assertj:assertj-core:3.27.4'\n    testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.11.0'\n}\n\ntest {\n    useJUnitPlatform()\n}\n"
  },
  {
    "path": "examples/redis-backed-cache/src/main/java/com/mycompany/cache/Cache.java",
    "content": "package com.mycompany.cache;\n\nimport java.util.Optional;\n\n/**\n * Cache, for storing data associated with keys.\n */\npublic interface Cache {\n    /**\n     * Store a value object in the cache with no specific expiry time. The object may be evicted by the cache any time,\n     * if necessary.\n     *\n     * @param key   key that may be used to retrieve the object in the future\n     * @param value the value object to be stored\n     */\n    void put(String key, Object value);\n\n    /**\n     * Retrieve a value object from the cache.\n     * @param key               the key that was used to insert the object initially\n     * @param expectedClass     for convenience, a class that the object should be cast to before being returned\n     * @param <T>               the class of the returned object\n     * @return                  the object if it was in the cache, or an empty Optional if not found.\n     */\n    <T> Optional<T> get(String key, Class<T> expectedClass);\n}\n"
  },
  {
    "path": "examples/redis-backed-cache/src/main/java/com/mycompany/cache/RedisBackedCache.java",
    "content": "package com.mycompany.cache;\n\nimport com.google.gson.Gson;\nimport redis.clients.jedis.Jedis;\n\nimport java.util.Optional;\n\n/**\n * An implementation of {@link Cache} that stores data in Redis.\n */\npublic class RedisBackedCache implements Cache {\n\n    private final Jedis jedis;\n\n    private final String cacheName;\n\n    private final Gson gson;\n\n    public RedisBackedCache(Jedis jedis, String cacheName) {\n        this.jedis = jedis;\n        this.cacheName = cacheName;\n        this.gson = new Gson();\n    }\n\n    @Override\n    public void put(String key, Object value) {\n        String jsonValue = gson.toJson(value);\n        this.jedis.hset(this.cacheName, key, jsonValue);\n    }\n\n    @Override\n    public <T> Optional<T> get(String key, Class<T> expectedClass) {\n        String foundJson = this.jedis.hget(this.cacheName, key);\n\n        if (foundJson == null) {\n            return Optional.empty();\n        }\n\n        return Optional.of(gson.fromJson(foundJson, expectedClass));\n    }\n}\n"
  },
  {
    "path": "examples/redis-backed-cache/src/test/java/RedisBackedCacheTest.java",
    "content": "import com.mycompany.cache.Cache;\nimport com.mycompany.cache.RedisBackedCache;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.junit.jupiter.Container;\nimport org.testcontainers.junit.jupiter.Testcontainers;\nimport org.testcontainers.utility.DockerImageName;\nimport redis.clients.jedis.Jedis;\n\nimport java.util.Optional;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Integration test for Redis-backed cache implementation.\n */\n@Testcontainers\nclass RedisBackedCacheTest {\n\n    @Container\n    public GenericContainer<?> redis = new GenericContainer<>(DockerImageName.parse(\"redis:6-alpine\"))\n        .withExposedPorts(6379);\n\n    private Cache cache;\n\n    @BeforeEach\n    void setUp() throws Exception {\n        Jedis jedis = new Jedis(redis.getHost(), redis.getMappedPort(6379));\n\n        cache = new RedisBackedCache(jedis, \"test\");\n    }\n\n    @Test\n    void testFindingAnInsertedValue() {\n        cache.put(\"foo\", \"FOO\");\n        Optional<String> foundObject = cache.get(\"foo\", String.class);\n\n        assertThat(foundObject.isPresent()).as(\"When an object in the cache is retrieved, it can be found\").isTrue();\n        assertThat(foundObject.get())\n            .as(\"When we put a String in to the cache and retrieve it, the value is the same\")\n            .isEqualTo(\"FOO\");\n    }\n\n    @Test\n    void testNotFindingAValueThatWasNotInserted() {\n        Optional<String> foundObject = cache.get(\"bar\", String.class);\n\n        assertThat(foundObject.isPresent())\n            .as(\"When an object that's not in the cache is retrieved, nothing is found\")\n            .isFalse();\n    }\n}\n"
  },
  {
    "path": "examples/redis-backed-cache/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "examples/redis-backed-cache-testng/build.gradle",
    "content": "plugins {\n    id 'java'\n}\n\nrepositories {\n    mavenCentral()\n}\n\ndependencies {\n    compileOnly 'org.slf4j:slf4j-api:1.7.36'\n    implementation 'redis.clients:jedis:6.2.0'\n    implementation 'com.google.code.gson:gson:2.13.2'\n    implementation 'com.google.guava:guava:23.0'\n    testImplementation 'org.testcontainers:testcontainers'\n    testImplementation 'ch.qos.logback:logback-classic:1.3.15'\n    testImplementation 'org.testng:testng:7.5.1'\n    testImplementation 'org.assertj:assertj-core:3.27.4'\n}\n\ntest {\n    useTestNG()\n}\n"
  },
  {
    "path": "examples/redis-backed-cache-testng/src/main/java/com/mycompany/cache/Cache.java",
    "content": "package com.mycompany.cache;\n\nimport java.util.Optional;\n\n/**\n * Cache, for storing data associated with keys.\n */\npublic interface Cache {\n    /**\n     * Store a value object in the cache with no specific expiry time. The object may be evicted by the cache any time,\n     * if necessary.\n     *\n     * @param key   key that may be used to retrieve the object in the future\n     * @param value the value object to be stored\n     */\n    void put(String key, Object value);\n\n    /**\n     * Retrieve a value object from the cache.\n     * @param key               the key that was used to insert the object initially\n     * @param expectedClass     for convenience, a class that the object should be cast to before being returned\n     * @param <T>               the class of the returned object\n     * @return                  the object if it was in the cache, or an empty Optional if not found.\n     */\n    <T> Optional<T> get(String key, Class<T> expectedClass);\n}\n"
  },
  {
    "path": "examples/redis-backed-cache-testng/src/main/java/com/mycompany/cache/RedisBackedCache.java",
    "content": "package com.mycompany.cache;\n\nimport com.google.gson.Gson;\nimport redis.clients.jedis.Jedis;\n\nimport java.util.Optional;\n\n/**\n * An implementation of {@link Cache} that stores data in Redis.\n */\npublic class RedisBackedCache implements Cache {\n\n    private final Jedis jedis;\n\n    private final String cacheName;\n\n    private final Gson gson;\n\n    public RedisBackedCache(Jedis jedis, String cacheName) {\n        this.jedis = jedis;\n        this.cacheName = cacheName;\n        this.gson = new Gson();\n    }\n\n    @Override\n    public void put(String key, Object value) {\n        String jsonValue = gson.toJson(value);\n        this.jedis.hset(this.cacheName, key, jsonValue);\n    }\n\n    @Override\n    public <T> Optional<T> get(String key, Class<T> expectedClass) {\n        String foundJson = this.jedis.hget(this.cacheName, key);\n\n        if (foundJson == null) {\n            return Optional.empty();\n        }\n\n        return Optional.of(gson.fromJson(foundJson, expectedClass));\n    }\n}\n"
  },
  {
    "path": "examples/redis-backed-cache-testng/src/test/java/RedisBackedCacheTest.java",
    "content": "import com.mycompany.cache.Cache;\nimport com.mycompany.cache.RedisBackedCache;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testng.annotations.AfterClass;\nimport org.testng.annotations.BeforeClass;\nimport org.testng.annotations.BeforeMethod;\nimport org.testng.annotations.Test;\nimport redis.clients.jedis.Jedis;\n\nimport java.util.Optional;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Integration test for Redis-backed cache implementation.\n */\npublic class RedisBackedCacheTest {\n\n    private static GenericContainer<?> redis = new GenericContainer<>(DockerImageName.parse(\"redis:6-alpine\"))\n        .withExposedPorts(6379);\n\n    private Cache cache;\n\n    @BeforeClass\n    public static void startContainer() {\n        redis.start();\n    }\n\n    @AfterClass\n    public static void stopContainer() {\n        redis.stop();\n    }\n\n    @BeforeMethod\n    public void setUp() {\n        Jedis jedis = new Jedis(redis.getHost(), redis.getMappedPort(6379));\n\n        cache = new RedisBackedCache(jedis, \"test\");\n    }\n\n    @Test\n    public void testFindingAnInsertedValue() {\n        cache.put(\"foo\", \"FOO\");\n        Optional<String> foundObject = cache.get(\"foo\", String.class);\n\n        assertThat(foundObject).as(\"When an object in the cache is retrieved, it can be found\").isPresent();\n        assertThat(foundObject)\n            .as(\"When we put a String in to the cache and retrieve it, the value is the same\")\n            .contains(\"FOO\");\n    }\n\n    @Test\n    public void testNotFindingAValueThatWasNotInserted() {\n        Optional<String> foundObject = cache.get(\"bar\", String.class);\n\n        assertThat(foundObject)\n            .as(\"When an object that's not in the cache is retrieved, nothing is found\")\n            .isNotPresent();\n    }\n}\n"
  },
  {
    "path": "examples/redis-backed-cache-testng/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "examples/selenium-container/build.gradle",
    "content": "plugins {\n    id 'java'\n    id 'org.springframework.boot' version '3.5.6'\n}\napply plugin: 'io.spring.dependency-management'\n\nrepositories {\n    mavenCentral()\n}\n\ndependencies {\n    implementation 'org.seleniumhq.selenium:selenium-remote-driver'\n    implementation 'org.seleniumhq.selenium:selenium-firefox-driver'\n    implementation 'org.seleniumhq.selenium:selenium-chrome-driver'\n    implementation 'org.springframework.boot:spring-boot-starter-web'\n    testImplementation 'org.springframework.boot:spring-boot-starter-test'\n    testImplementation 'org.testcontainers:testcontainers-selenium'\n    testImplementation 'org.testcontainers:testcontainers-junit-jupiter'\n    testImplementation 'org.assertj:assertj-core:3.27.4'\n}\n\ntest {\n    useJUnitPlatform()\n}\n"
  },
  {
    "path": "examples/selenium-container/src/main/java/com/example/DemoApplication.java",
    "content": "package com.example;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\n\n@SpringBootApplication\npublic class DemoApplication {\n\n    public static void main(String[] args) {\n        SpringApplication.run(DemoApplication.class, args);\n    }\n}\n"
  },
  {
    "path": "examples/selenium-container/src/main/resources/static/foo.html",
    "content": "<!DOCTYPE HTML>\n<html>\n<head>\n    <title>Getting Started: Serving Web Content</title>\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/>\n</head>\n<body>\n<h>Hello World</h>\n</body>\n</html>"
  },
  {
    "path": "examples/selenium-container/src/test/java/SeleniumContainerTest.java",
    "content": "import com.example.DemoApplication;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.openqa.selenium.By;\nimport org.openqa.selenium.WebElement;\nimport org.openqa.selenium.chrome.ChromeOptions;\nimport org.openqa.selenium.remote.RemoteWebDriver;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.boot.test.web.server.LocalServerPort;\nimport org.springframework.boot.web.context.WebServerInitializedEvent;\nimport org.springframework.context.ApplicationContextInitializer;\nimport org.springframework.context.ApplicationListener;\nimport org.springframework.context.ConfigurableApplicationContext;\nimport org.springframework.test.context.ContextConfiguration;\nimport org.springframework.test.context.junit.jupiter.SpringExtension;\nimport org.testcontainers.Testcontainers;\nimport org.testcontainers.containers.BrowserWebDriverContainer;\nimport org.testcontainers.containers.BrowserWebDriverContainer.VncRecordingMode;\nimport org.testcontainers.junit.jupiter.Container;\n\nimport java.io.File;\nimport java.time.Duration;\nimport java.util.List;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Simple example of plain Selenium usage.\n */\n@org.testcontainers.junit.jupiter.Testcontainers\n@ExtendWith(SpringExtension.class)\n@SpringBootTest(classes = DemoApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)\n@ContextConfiguration(initializers = SeleniumContainerTest.Initializer.class)\nclass SeleniumContainerTest {\n\n    @LocalServerPort\n    private int port;\n\n    @Container\n    public BrowserWebDriverContainer chrome = new BrowserWebDriverContainer()\n        .withCapabilities(new ChromeOptions())\n        .withRecordingMode(VncRecordingMode.RECORD_ALL, new File(\"build\"));\n\n    @Test\n    void simplePlainSeleniumTest() {\n        RemoteWebDriver driver = new RemoteWebDriver(chrome.getSeleniumAddress(), new ChromeOptions());\n        driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(30));\n\n        driver.get(\"http://host.testcontainers.internal:\" + port + \"/foo.html\");\n        List<WebElement> hElement = driver.findElements(By.tagName(\"h\"));\n\n        assertThat(hElement).as(\"The h element is found\").isNotEmpty();\n    }\n\n    static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {\n\n        @Override\n        public void initialize(ConfigurableApplicationContext applicationContext) {\n            applicationContext.addApplicationListener(\n                (ApplicationListener<WebServerInitializedEvent>) event -> {\n                    Testcontainers.exposeHostPorts(event.getWebServer().getPort());\n                }\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "examples/selenium-container/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "examples/settings.gradle",
    "content": "buildscript {\n    repositories {\n        maven {\n            url \"https://plugins.gradle.org/m2/\"\n        }\n    }\n    dependencies {\n        classpath \"gradle.plugin.ch.myniva.gradle:s3-build-cache:0.10.0\"\n        classpath \"com.gradle.enterprise:com.gradle.enterprise.gradle.plugin:3.17.4\"\n        classpath \"com.gradle:common-custom-user-data-gradle-plugin:2.0.1\"\n    }\n}\n\napply plugin: 'com.gradle.develocity'\napply plugin: \"com.gradle.common-custom-user-data-gradle-plugin\"\n\nrootProject.name = 'testcontainers-examples'\n\nincludeBuild '..'\n\n// explicit include to allow Dependabot to autodiscover subprojects\ninclude 'kafka-cluster'\ninclude 'neo4j-container'\ninclude 'redis-backed-cache'\ninclude 'redis-backed-cache-testng'\ninclude 'selenium-container'\ninclude 'singleton-container'\ninclude 'solr-container'\ninclude 'spring-boot'\ninclude 'cucumber'\ninclude 'spring-boot-kotlin-redis'\ninclude 'immudb'\ninclude 'zookeeper'\ninclude 'hazelcast'\ninclude 'nats'\ninclude 'sftp'\ninclude 'ollama-hugging-face'\n\next.isCI = System.getenv(\"CI\") != null\n\nbuildCache {\n    local {\n        enabled = !isCI\n    }\n    remote(develocity.buildCache) {\n        push = isCI && !System.getenv(\"READ_ONLY_REMOTE_GRADLE_CACHE\") && System.getenv(\"DEVELOCITY_ACCESS_KEY\")\n        enabled = true\n    }\n}\n\ndevelocity {\n    buildScan {\n        server = \"https://ge.testcontainers.org/\"\n        publishing.onlyIf {\n            it.authenticated\n        }\n        uploadInBackground = !isCI\n        capture.fileFingerprints = true\n    }\n\n}\n"
  },
  {
    "path": "examples/sftp/build.gradle",
    "content": "plugins {\n    id 'java'\n}\n\nrepositories {\n    mavenCentral()\n}\n\ndependencies {\n    testImplementation 'com.github.mwiede:jsch:2.27.2'\n    testImplementation 'org.testcontainers:testcontainers'\n    testImplementation 'org.assertj:assertj-core:3.27.4'\n    testImplementation 'ch.qos.logback:logback-classic:1.3.15'\n    testImplementation 'org.junit.jupiter:junit-jupiter:5.13.4'\n    testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.11.0'\n}\n\ntest {\n    useJUnitPlatform()\n}\n"
  },
  {
    "path": "examples/sftp/src/test/java/org/example/SftpContainerTest.java",
    "content": "package org.example;\n\nimport com.jcraft.jsch.ChannelSftp;\nimport com.jcraft.jsch.HostKey;\nimport com.jcraft.jsch.JSch;\nimport com.jcraft.jsch.Session;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.utility.MountableFile;\n\nimport java.io.BufferedReader;\nimport java.io.InputStreamReader;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Base64;\nimport java.util.stream.Collectors;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass SftpContainerTest {\n\n    @Test\n    void test() throws Exception {\n        try (\n            GenericContainer<?> sftp = new GenericContainer<>(\"atmoz/sftp:alpine-3.7\")\n                .withCopyFileToContainer(\n                    MountableFile.forClasspathResource(\"testcontainers/\", 0777),\n                    \"/home/foo/upload/testcontainers\"\n                )\n                .withExposedPorts(22)\n                .withCommand(\"foo:pass:::upload\")\n        ) {\n            sftp.start();\n            JSch jsch = new JSch();\n            Session jschSession = jsch.getSession(\"foo\", sftp.getHost(), sftp.getMappedPort(22));\n            jschSession.setPassword(\"pass\");\n            jschSession.setConfig(\"StrictHostKeyChecking\", \"no\");\n            jschSession.connect();\n            ChannelSftp channel = (ChannelSftp) jschSession.openChannel(\"sftp\");\n            channel.connect();\n            assertThat(channel.ls(\"/upload/testcontainers\")).anyMatch(item -> item.toString().contains(\"file.txt\"));\n            assertThat(\n                new BufferedReader(\n                    new InputStreamReader(channel.get(\"/upload/testcontainers/file.txt\"), StandardCharsets.UTF_8)\n                )\n                    .lines()\n                    .collect(Collectors.joining(\"\\n\"))\n            )\n                .contains(\"Testcontainers\");\n            channel.rm(\"/upload/testcontainers/file.txt\");\n            assertThat(channel.ls(\"/upload/testcontainers/\"))\n                .noneMatch(item -> item.toString().contains(\"testcontainers/file.txt\"));\n        }\n    }\n\n    @Test\n    void testHostKeyCheck() throws Exception {\n        try (\n            GenericContainer<?> sftp = new GenericContainer<>(\"atmoz/sftp:alpine-3.7\")\n                .withCopyFileToContainer(\n                    MountableFile.forClasspathResource(\"testcontainers/\", 0777),\n                    \"/home/foo/upload/testcontainers\"\n                )\n                .withCopyFileToContainer(\n                    MountableFile.forClasspathResource(\"./ssh_host_rsa_key\", 0400),\n                    \"/etc/ssh/ssh_host_rsa_key\"\n                )\n                .withExposedPorts(22)\n                .withCommand(\"foo:pass:::upload\")\n        ) {\n            sftp.start();\n            JSch jsch = new JSch();\n            Session jschSession = jsch.getSession(\"foo\", sftp.getHost(), sftp.getMappedPort(22));\n            jschSession.setPassword(\"pass\");\n            // hostKeyString is string starting with AAAA from file known_hosts or ssh_host_*_key.pub\n            // generate the files with:\n            // ssh-keygen -t rsa -b 3072 -f ssh_host_rsa_key < /dev/null\n            String hostKeyString =\n                \"AAAAB3NzaC1yc2EAAAADAQABAAABgQCXMxVRzmFWxfrRB9XiZ/3HNM+xkYYE+IMGuOZD\" +\n                \"04M2ezU25XjT6cPajzpFmzTxR2qEpRCKHeVnSG5nT6UXQp7760brTN7m5sDasbMnHgYh\" +\n                \"fC/3of2k6qTR9X/JHRpgwzq5+6FtEe41w1H1dXoNIr4YTKnLijSp8MKqBtPPNUpzEVb9\" +\n                \"5YKZGdCDoCbbYOyS/Dc8azUDo0mqM542J3nA2Sq9HCP0BAv43hrTAtCZodkB5wo18exb\" +\n                \"fPKsjGtA3de2npybFoSRbavZmT8L/b2iHZX6FRaqLsbYGKtszCWu5OU7WBX5g5QVlLfO\" +\n                \"nGQ+LsF6d6pX5LlMwEU14uu4gNPvZFOaZXtHNHZqnBcjd/sMaw5N/atFsPgtQ0vYnrEA\" +\n                \"D6oDjj0uXMsnmgUWTZBi3q2GBWWPqhE+0ASb2xBQGa+tWWTVYbuuYlA7hUX0URK8FcLw\" +\n                \"4UOYJjscDjnjlvQkghd2esP5NxV1NXkG2XYNHnf1E/tH4+AHJzy+qOQom7ehda96FZ8=\";\n            HostKey hostKey = new HostKey(sftp.getHost(), Base64.getDecoder().decode(hostKeyString));\n            jschSession.getHostKeyRepository().add(hostKey, null);\n            jschSession.connect();\n            ChannelSftp channel = (ChannelSftp) jschSession.openChannel(\"sftp\");\n            channel.connect();\n            assertThat(channel.ls(\"/upload/testcontainers\")).anyMatch(item -> item.toString().contains(\"file.txt\"));\n            assertThat(\n                new BufferedReader(\n                    new InputStreamReader(channel.get(\"/upload/testcontainers/file.txt\"), StandardCharsets.UTF_8)\n                )\n                    .lines()\n                    .collect(Collectors.joining(\"\\n\"))\n            )\n                .contains(\"Testcontainers\");\n            channel.rm(\"/upload/testcontainers/file.txt\");\n            assertThat(channel.ls(\"/upload/testcontainers/\"))\n                .noneMatch(item -> item.toString().contains(\"testcontainers/file.txt\"));\n        }\n    }\n}\n"
  },
  {
    "path": "examples/sftp/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "examples/sftp/src/test/resources/ssh_host_rsa_key",
    "content": "-----BEGIN OPENSSH PRIVATE KEY-----\nb3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn\nNhAAAAAwEAAQAAAYEAlzMVUc5hVsX60QfV4mf9xzTPsZGGBPiDBrjmQ9ODNns1NuV40+nD\n2o86RZs08UdqhKUQih3lZ0huZ0+lF0Ke++tG60ze5ubA2rGzJx4GIXwv96H9pOqk0fV/yR\n0aYMM6ufuhbRHuNcNR9XV6DSK+GEypy4o0qfDCqgbTzzVKcxFW/eWCmRnQg6Am22Dskvw3\nPGs1A6NJqjOeNid5wNkqvRwj9AQL+N4a0wLQmaHZAecKNfHsW3zyrIxrQN3Xtp6cmxaEkW\n2r2Zk/C/29oh2V+hUWqi7G2BirbMwlruTlO1gV+YOUFZS3zpxkPi7BeneqV+S5TMBFNeLr\nuIDT72RTmmV7RzR2apwXI3f7DGsOTf2rRbD4LUNL2J6xAA+qA449LlzLJ5oFFk2QYt6thg\nVlj6oRPtAEm9sQUBmvrVlk1WG7rmJQO4VF9FESvBXC8OFDmCY7HA4545b0JIIXdnrD+TcV\ndTV5Btl2DR539RP7R+PgByc8vqjkKJu3oXWvehWfAAAFiPUCzjT1As40AAAAB3NzaC1yc2\nEAAAGBAJczFVHOYVbF+tEH1eJn/cc0z7GRhgT4gwa45kPTgzZ7NTbleNPpw9qPOkWbNPFH\naoSlEIod5WdIbmdPpRdCnvvrRutM3ubmwNqxsyceBiF8L/eh/aTqpNH1f8kdGmDDOrn7oW\n0R7jXDUfV1eg0ivhhMqcuKNKnwwqoG0881SnMRVv3lgpkZ0IOgJttg7JL8NzxrNQOjSaoz\nnjYnecDZKr0cI/QEC/jeGtMC0Jmh2QHnCjXx7Ft88qyMa0Dd17aenJsWhJFtq9mZPwv9va\nIdlfoVFqouxtgYq2zMJa7k5TtYFfmDlBWUt86cZD4uwXp3qlfkuUzARTXi67iA0+9kU5pl\ne0c0dmqcFyN3+wxrDk39q0Ww+C1DS9iesQAPqgOOPS5cyyeaBRZNkGLerYYFZY+qET7QBJ\nvbEFAZr61ZZNVhu65iUDuFRfRRErwVwvDhQ5gmOxwOOeOW9CSCF3Z6w/k3FXU1eQbZdg0e\nd/UT+0fj4AcnPL6o5Cibt6F1r3oVnwAAAAMBAAEAAAGALcv8wKcUx6423tqTN70M2qpN4H\nh2Egpd0YruwAuQWk+uWh7eXr2XI5uvaEbvHcfmZSAEJvmQMxz2x9cRZ763nhFxDTNe7qxl\nLLiXTZlj/P97HfQUej/SRYApQPbONxHbN1sW1Y0RTHqJWCJJojHsRzrtUSfe9Lxmkg54WH\nJJRxow8b1zNcFibYP0UQ2GCq1XY7cLOztZxDJXUQra74U300jzQOV65NoNYO2g1m/15YQg\nDR/mWf26GXZ8xAyN2pQm3wiI86kY1UP+2kVr38tGcJ+Xrm08Pav06IiEUdFAdDRLL0AWXY\nZG25BBJn2VaPZoE5+MH7xRQ2BrqNUZ6ec8jTPZXWN6VyZCmn06KRblIRnv/NcMV5GH/lE9\nJbP/MnQQzsQAO0REfhcrdb66I6l0jMTwQcvSJyPXLVl1UvobzcF+CpcExsoaQj5U9cwhkG\nXRLqPhI76+L0L2kNefQ4yN5MhxWiajKUOknRITkvmNR+jJYsUN/ziODRevbakBzyqtAAAA\nwCpC6P+iJg19HdhNf6I2IUQErPoltUhA5bsUGmuseCn19Y3V5RmNa8+HHfbnMkUSoFzTvS\nj0l7rkxl0vvPmz0zr/2ehWiMbReFRy3hGl55AGPLE7pjIy08JIUcQm2jH8C3oeSKNwCrYV\n+HWsOsQu4+/uOTgp6I46+iSLLG+xjH+5zLtvxa6+o+zLjAOSW4aweAw1WAXy8J4ylAv2nA\nn3g3Rfa7C0qZG1bZ63phcgv2BNzN+QgmORoh5v5ICvT+qJ5wAAAMEAwvdI3XsLV0uzNkAq\nC9aWyK4cAdphvCb8n0oz5Vrm6j/qFRXzcDZLtkMboCRE2qVqNLQjMiTJo/QjX9jxe7LD6c\nVxtlcl2Ts8qrixFhKXJNwC/lq/TTe2dpMSYm61OINK3TiofZi6eff/ubcpq7zr3iVyWk5b\nwAVSun8q+Su7ziYYb+MuBQsKn5VWyoYK+E/LFItY26ulOxbrntB805JsXpjbYrL0KoXJCx\n6ZWdBVsvbD733WipNbPQZ+4JYDbun7AAAAwQDGiFOALlS5nidWFqMeMm/dGsHpwri0b10Z\nBf/DPPxK6EuFKLUppt6KMl2zJjwVa2NqSTppz7TpUP6jC5pSglxtcvatEIRVF8KBxuIJ/G\n8Wav3Xuxu9nrRyKAzXjrjU+4TjAH1jBfTj3/tDdRagxt7JESirE+sYW5nie9XpzW4ehsf6\nfJacmwoiGdSCc4dldD8ZkEXcmCChFTH+PY3uYtiJr+znzbUZ1RLL3Uk2xHWOWSHz/1tUBy\nBFP58e3rYvNa0AAAAPYWFAMjMtMDcxNTMtMDA5AQIDBA==\n-----END OPENSSH PRIVATE KEY-----\n"
  },
  {
    "path": "examples/sftp/src/test/resources/ssh_host_rsa_key.pub",
    "content": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCXMxVRzmFWxfrRB9XiZ/3HNM+xkYYE+IMGuOZD04M2ezU25XjT6cPajzpFmzTxR2qEpRCKHeVnSG5nT6UXQp7760brTN7m5sDasbMnHgYhfC/3of2k6qTR9X/JHRpgwzq5+6FtEe41w1H1dXoNIr4YTKnLijSp8MKqBtPPNUpzEVb95YKZGdCDoCbbYOyS/Dc8azUDo0mqM542J3nA2Sq9HCP0BAv43hrTAtCZodkB5wo18exbfPKsjGtA3de2npybFoSRbavZmT8L/b2iHZX6FRaqLsbYGKtszCWu5OU7WBX5g5QVlLfOnGQ+LsF6d6pX5LlMwEU14uu4gNPvZFOaZXtHNHZqnBcjd/sMaw5N/atFsPgtQ0vYnrEAD6oDjj0uXMsnmgUWTZBi3q2GBWWPqhE+0ASb2xBQGa+tWWTVYbuuYlA7hUX0URK8FcLw4UOYJjscDjnjlvQkghd2esP5NxV1NXkG2XYNHnf1E/tH4+AHJzy+qOQom7ehda96FZ8= someone@localhost\n"
  },
  {
    "path": "examples/sftp/src/test/resources/testcontainers/file.txt",
    "content": "Testcontainers\n"
  },
  {
    "path": "examples/singleton-container/build.gradle",
    "content": "plugins {\n    id 'java'\n}\n\nrepositories {\n    mavenCentral()\n}\n\ndependencies {\n\n    implementation 'redis.clients:jedis:6.2.0'\n    implementation 'com.google.code.gson:gson:2.13.2'\n    implementation 'com.google.guava:guava:23.0'\n    compileOnly 'org.slf4j:slf4j-api:1.7.36'\n\n    testImplementation 'ch.qos.logback:logback-classic:1.3.15'\n    testImplementation 'org.testcontainers:testcontainers'\n    testImplementation 'org.assertj:assertj-core:3.27.4'\n    testImplementation 'org.junit.jupiter:junit-jupiter:5.13.4'\n    testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.11.0'\n}\n\ntest {\n    useJUnitPlatform()\n}\n"
  },
  {
    "path": "examples/singleton-container/src/main/java/com/example/cache/Cache.java",
    "content": "package com.example.cache;\n\nimport java.util.Optional;\n\npublic interface Cache {\n    void put(String key, Object value);\n\n    <T> Optional<T> get(String key, Class<T> expectedClass);\n}\n"
  },
  {
    "path": "examples/singleton-container/src/main/java/com/example/cache/RedisBackedCache.java",
    "content": "package com.example.cache;\n\nimport com.google.gson.Gson;\nimport redis.clients.jedis.Jedis;\n\nimport java.util.Optional;\n\npublic class RedisBackedCache implements Cache {\n\n    private final Jedis jedis;\n\n    private final String cacheName;\n\n    private final Gson gson;\n\n    public RedisBackedCache(Jedis jedis, String cacheName) {\n        this.jedis = jedis;\n        this.cacheName = cacheName;\n        this.gson = new Gson();\n    }\n\n    public void put(String key, Object value) {\n        String jsonValue = gson.toJson(value);\n        this.jedis.hset(this.cacheName, key, jsonValue);\n    }\n\n    public <T> Optional<T> get(String key, Class<T> expectedClass) {\n        String foundJson = this.jedis.hget(this.cacheName, key);\n\n        if (foundJson == null) {\n            return Optional.empty();\n        }\n\n        return Optional.of(gson.fromJson(foundJson, expectedClass));\n    }\n}\n"
  },
  {
    "path": "examples/singleton-container/src/test/java/com/example/AbstractIntegrationTest.java",
    "content": "package com.example;\n\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.utility.DockerImageName;\n\npublic abstract class AbstractIntegrationTest {\n\n    public static final GenericContainer<?> redis = new GenericContainer<>(DockerImageName.parse(\"redis:6-alpine\"))\n        .withExposedPorts(6379);\n\n    static {\n        redis.start();\n    }\n}\n"
  },
  {
    "path": "examples/singleton-container/src/test/java/com/example/BarConcreteTestClass.java",
    "content": "package com.example;\n\nimport com.example.cache.Cache;\nimport com.example.cache.RedisBackedCache;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.Jedis;\n\nimport java.util.Optional;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass BarConcreteTestClass extends AbstractIntegrationTest {\n\n    private Cache cache;\n\n    @BeforeEach\n    void setUp() {\n        Jedis jedis = new Jedis(redis.getHost(), redis.getMappedPort(6379));\n\n        cache = new RedisBackedCache(jedis, \"bar\");\n    }\n\n    @Test\n    void testInsertValue() {\n        cache.put(\"bar\", \"BAR\");\n        Optional<String> foundObject = cache.get(\"bar\", String.class);\n\n        assertThat(foundObject).as(\"When inserting an object into the cache, it can be retrieved\").isPresent();\n        assertThat(foundObject)\n            .as(\"When accessing the value of a retrieved object, the value must be the same\")\n            .contains(\"BAR\");\n    }\n}\n"
  },
  {
    "path": "examples/singleton-container/src/test/java/com/example/FooConcreteTestClass.java",
    "content": "package com.example;\n\nimport com.example.cache.Cache;\nimport com.example.cache.RedisBackedCache;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport redis.clients.jedis.Jedis;\n\nimport java.util.Optional;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass FooConcreteTestClass extends AbstractIntegrationTest {\n\n    private Cache cache;\n\n    @BeforeEach\n    void setUp() {\n        Jedis jedis = new Jedis(redis.getHost(), redis.getMappedPort(6379));\n\n        cache = new RedisBackedCache(jedis, \"foo\");\n    }\n\n    @Test\n    void testInsertValue() {\n        cache.put(\"foo\", \"FOO\");\n        Optional<String> foundObject = cache.get(\"foo\", String.class);\n\n        assertThat(foundObject).as(\"When inserting an object into the cache, it can be retrieved\").isPresent();\n        assertThat(foundObject)\n            .as(\"When accessing the value of a retrieved object, the value must be the same\")\n            .contains(\"FOO\");\n    }\n}\n"
  },
  {
    "path": "examples/singleton-container/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "examples/solr-container/build.gradle",
    "content": "plugins {\n    id 'java'\n}\n\nrepositories {\n    mavenCentral()\n}\n\ndependencies {\n    compileOnly \"org.projectlombok:lombok:1.18.38\"\n    annotationProcessor \"org.projectlombok:lombok:1.18.38\"\n\n    implementation 'org.apache.solr:solr-solrj:8.11.4'\n\n    testImplementation 'org.testcontainers:testcontainers'\n    testImplementation 'org.testcontainers:testcontainers-solr'\n    testImplementation 'org.assertj:assertj-core:3.27.4'\n    testImplementation 'org.junit.jupiter:junit-jupiter:5.13.4'\n    testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.11.0'\n}\n\ntest {\n    useJUnitPlatform()\n}\n"
  },
  {
    "path": "examples/solr-container/src/main/java/com/example/SearchEngine.java",
    "content": "package com.example;\n\npublic interface SearchEngine {\n    public SearchResult search(String term);\n}\n"
  },
  {
    "path": "examples/solr-container/src/main/java/com/example/SearchResult.java",
    "content": "package com.example;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\nimport java.util.List;\nimport java.util.Map;\n\n@Data\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class SearchResult {\n\n    private long totalHits;\n\n    private List<Map<String, Object>> results;\n}\n"
  },
  {
    "path": "examples/solr-container/src/main/java/com/example/SolrSearchEngine.java",
    "content": "package com.example;\n\nimport lombok.RequiredArgsConstructor;\nimport lombok.SneakyThrows;\nimport org.apache.solr.client.solrj.SolrClient;\nimport org.apache.solr.client.solrj.SolrQuery;\nimport org.apache.solr.client.solrj.response.QueryResponse;\nimport org.apache.solr.client.solrj.util.ClientUtils;\nimport org.apache.solr.common.SolrDocument;\n\nimport java.util.stream.Collectors;\n\n@RequiredArgsConstructor\npublic class SolrSearchEngine implements SearchEngine {\n\n    public static final String COLLECTION_NAME = \"products\";\n\n    private final SolrClient client;\n\n    @SneakyThrows\n    public SearchResult search(String term) {\n        SolrQuery query = new SolrQuery();\n        query.setQuery(\"title:\" + ClientUtils.escapeQueryChars(term));\n        QueryResponse response = client.query(COLLECTION_NAME, query);\n        return createResult(response);\n    }\n\n    private SearchResult createResult(QueryResponse response) {\n        return SearchResult\n            .builder()\n            .totalHits(response.getResults().getNumFound())\n            .results(response.getResults().stream().map(SolrDocument::getFieldValueMap).collect(Collectors.toList()))\n            .build();\n    }\n}\n"
  },
  {
    "path": "examples/solr-container/src/test/java/com/example/SolrQueryTest.java",
    "content": "package com.example;\n\nimport org.apache.solr.client.solrj.SolrClient;\nimport org.apache.solr.client.solrj.SolrServerException;\nimport org.apache.solr.client.solrj.impl.Http2SolrClient;\nimport org.apache.solr.common.SolrInputDocument;\nimport org.apache.solr.common.SolrInputField;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.SolrContainer;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.IOException;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass SolrQueryTest {\n\n    private static final DockerImageName SOLR_IMAGE = DockerImageName.parse(\"solr:8.3.0\");\n\n    public static final SolrContainer solrContainer = new SolrContainer(SOLR_IMAGE)\n        .withCollection(SolrSearchEngine.COLLECTION_NAME);\n\n    private static SolrClient solrClient;\n\n    @BeforeAll\n    static void setUp() throws IOException, SolrServerException {\n        solrContainer.start();\n        solrClient =\n            new Http2SolrClient.Builder(\n                \"http://\" + solrContainer.getHost() + \":\" + solrContainer.getSolrPort() + \"/solr\"\n            )\n                .build();\n\n        // Add Sample Data\n        solrClient.add(\n            SolrSearchEngine.COLLECTION_NAME,\n            Collections.singletonList(\n                new SolrInputDocument(\n                    createMap(\n                        \"id\",\n                        createInputField(\"id\", \"1\"),\n                        \"title\",\n                        createInputField(\"title\", \"old skool - trainers - shoes\")\n                    )\n                )\n            )\n        );\n\n        solrClient.add(\n            SolrSearchEngine.COLLECTION_NAME,\n            Collections.singletonList(\n                new SolrInputDocument(\n                    createMap(\"id\", createInputField(\"id\", \"2\"), \"title\", createInputField(\"title\", \"print t-shirt\"))\n                )\n            )\n        );\n\n        solrClient.commit(SolrSearchEngine.COLLECTION_NAME);\n    }\n\n    @Test\n    void testQueryForShoes() {\n        SolrSearchEngine searchEngine = new SolrSearchEngine(solrClient);\n\n        SearchResult result = searchEngine.search(\"shoes\");\n        assertThat(result.getTotalHits()).as(\"When searching for shoes we expect one result\").isEqualTo(1L);\n        assertThat(result.getResults().get(0).get(\"id\")).as(\"The result should have the id 1\").isEqualTo(\"1\");\n    }\n\n    @Test\n    void testQueryForTShirt() {\n        SolrSearchEngine searchEngine = new SolrSearchEngine(solrClient);\n\n        SearchResult result = searchEngine.search(\"t-shirt\");\n        assertThat(result.getTotalHits()).as(\"When searching for t-shirt we expect one result\").isEqualTo(1L);\n        assertThat(result.getResults().get(0).get(\"id\")).as(\"The result should have the id 2\").isEqualTo(\"2\");\n    }\n\n    @Test\n    void testQueryForAsterisk() {\n        SolrSearchEngine searchEngine = new SolrSearchEngine(solrClient);\n\n        SearchResult result = searchEngine.search(\"*\");\n        assertThat(result.getTotalHits()).as(\"When searching for * we expect no results\").isEqualTo(0L);\n    }\n\n    private static SolrInputField createInputField(String key, String value) {\n        SolrInputField inputField = new SolrInputField(key);\n        inputField.setValue(value);\n        return inputField;\n    }\n\n    private static Map<String, SolrInputField> createMap(String k0, SolrInputField v0, String k1, SolrInputField v1) {\n        Map<String, SolrInputField> result = new HashMap<>();\n        result.put(k0, v0);\n        result.put(k1, v1);\n        return result;\n    }\n}\n"
  },
  {
    "path": "examples/solr-container/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "examples/spring-boot/build.gradle",
    "content": "plugins {\n    id 'java'\n    id 'org.springframework.boot' version '2.7.18'\n}\napply plugin: 'io.spring.dependency-management'\n\nrepositories {\n    mavenCentral()\n}\n\ndependencies {\n    compileOnly \"org.projectlombok:lombok\"\n    annotationProcessor \"org.projectlombok:lombok\"\n    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'\n    implementation 'org.springframework.boot:spring-boot-starter-data-redis'\n    implementation 'org.springframework.boot:spring-boot-starter-web'\n    runtimeOnly 'org.postgresql:postgresql'\n    testImplementation 'org.springframework.boot:spring-boot-starter-test'\n    testImplementation 'org.testcontainers:testcontainers-postgresql'\n    testRuntimeOnly \"org.junit.platform:junit-platform-launcher:1.8.2\"\n}\n\ntest {\n    useJUnitPlatform()\n}\n"
  },
  {
    "path": "examples/spring-boot/src/main/java/com/example/DemoApplication.java",
    "content": "package com.example;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.data.jpa.repository.config.EnableJpaRepositories;\n\n@SpringBootApplication\n@EnableJpaRepositories\npublic class DemoApplication {\n\n    public static void main(String[] args) {\n        SpringApplication.run(DemoApplication.class, args);\n    }\n}\n"
  },
  {
    "path": "examples/spring-boot/src/main/java/com/example/DemoController.java",
    "content": "package com.example;\n\nimport org.springframework.data.redis.core.StringRedisTemplate;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.PathVariable;\nimport org.springframework.web.bind.annotation.PutMapping;\nimport org.springframework.web.bind.annotation.RequestBody;\nimport org.springframework.web.bind.annotation.RestController;\n\n@RestController\npublic class DemoController {\n\n    private final StringRedisTemplate stringRedisTemplate;\n\n    private final DemoService demoService;\n\n    public DemoController(StringRedisTemplate stringRedisTemplate, DemoService demoService) {\n        this.stringRedisTemplate = stringRedisTemplate;\n        this.demoService = demoService;\n    }\n\n    @GetMapping(\"/foo\")\n    public String get() {\n        return stringRedisTemplate.opsForValue().get(\"foo\");\n    }\n\n    @PutMapping(\"/foo\")\n    public void set(@RequestBody String value) {\n        stringRedisTemplate.opsForValue().set(\"foo\", value);\n    }\n\n    @GetMapping(\"/{id}\")\n    public DemoEntity getDemoEntity(@PathVariable(\"id\") Long id) {\n        return demoService.getDemoEntity(id);\n    }\n}\n"
  },
  {
    "path": "examples/spring-boot/src/main/java/com/example/DemoEntity.java",
    "content": "package com.example;\n\nimport lombok.Data;\n\nimport javax.persistence.Column;\nimport javax.persistence.Entity;\nimport javax.persistence.GeneratedValue;\nimport javax.persistence.Id;\n\n@Data\n@Entity\npublic class DemoEntity {\n\n    @Id\n    @GeneratedValue\n    private Long id;\n\n    @Column\n    private String value;\n}\n"
  },
  {
    "path": "examples/spring-boot/src/main/java/com/example/DemoRepository.java",
    "content": "package com.example;\n\nimport org.springframework.data.jpa.repository.JpaRepository;\n\npublic interface DemoRepository extends JpaRepository<DemoEntity, Long> {}\n"
  },
  {
    "path": "examples/spring-boot/src/main/java/com/example/DemoService.java",
    "content": "package com.example;\n\nimport org.springframework.stereotype.Service;\n\n@Service\npublic class DemoService {\n\n    private final DemoRepository demoRepository;\n\n    public DemoService(DemoRepository demoRepository) {\n        this.demoRepository = demoRepository;\n    }\n\n    public DemoEntity getDemoEntity(Long id) {\n        return demoRepository.findById(id).orElseThrow(() -> new RuntimeException(\"Entity not found\"));\n    }\n}\n"
  },
  {
    "path": "examples/spring-boot/src/main/resources/application.yml",
    "content": "spring:\n    jpa:\n        hibernate:\n            ddl-auto: create\n        show-sql: true\n        properties:\n            hibernate:\n                jdbc:\n                    lob:\n                        non_contextual_creation: true\n\n"
  },
  {
    "path": "examples/spring-boot/src/test/java/com/example/AbstractIntegrationTest.java",
    "content": "package com.example;\n\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.boot.test.context.SpringBootTest.WebEnvironment;\nimport org.springframework.test.context.ActiveProfiles;\nimport org.springframework.test.context.DynamicPropertyRegistry;\nimport org.springframework.test.context.DynamicPropertySource;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.utility.DockerImageName;\n\n@SpringBootTest(\n    classes = DemoApplication.class,\n    webEnvironment = WebEnvironment.RANDOM_PORT,\n    properties = { \"spring.datasource.url=jdbc:tc:postgresql:11-alpine:///databasename\" }\n)\n@ActiveProfiles(\"test\")\nabstract class AbstractIntegrationTest {\n\n    static GenericContainer<?> redis = new GenericContainer<>(DockerImageName.parse(\"redis:6-alpine\"))\n        .withExposedPorts(6379);\n\n    @DynamicPropertySource\n    static void redisProperties(DynamicPropertyRegistry registry) {\n        redis.start();\n        registry.add(\"spring.redis.host\", redis::getHost);\n        registry.add(\"spring.redis.port\", redis::getFirstMappedPort);\n    }\n}\n"
  },
  {
    "path": "examples/spring-boot/src/test/java/com/example/DemoControllerTest.java",
    "content": "package com.example;\n\nimport org.junit.jupiter.api.Test;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.web.client.TestRestTemplate;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass DemoControllerTest extends AbstractIntegrationTest {\n\n    @Autowired\n    TestRestTemplate restTemplate;\n\n    @Autowired\n    DemoRepository demoRepository;\n\n    @Test\n    void simpleTest() {\n        String fooResource = \"/foo\";\n\n        restTemplate.put(fooResource, \"bar\");\n\n        assertThat(restTemplate.getForObject(fooResource, String.class)).as(\"value is set\").isEqualTo(\"bar\");\n    }\n\n    @Test\n    void simpleJPATest() {\n        DemoEntity demoEntity = new DemoEntity();\n        demoEntity.setValue(\"Some value\");\n        demoRepository.save(demoEntity);\n\n        DemoEntity result = restTemplate.getForObject(\"/\" + demoEntity.getId(), DemoEntity.class);\n\n        assertThat(result.getValue()).as(\"value is set\").isEqualTo(\"Some value\");\n    }\n}\n"
  },
  {
    "path": "examples/spring-boot/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "examples/spring-boot-kotlin-redis/build.gradle.kts",
    "content": "import org.jetbrains.kotlin.gradle.tasks.KotlinCompile\n\nplugins {\n    id(\"org.springframework.boot\") version \"2.7.18\"\n    kotlin(\"jvm\") version \"1.8.22\"\n    kotlin(\"plugin.spring\") version \"1.8.22\"\n}\n\njava.sourceCompatibility = JavaVersion.VERSION_1_8\n\nrepositories {\n    mavenCentral()\n}\n\ndependencies {\n    apply(plugin = \"io.spring.dependency-management\")\n    implementation(\"org.springframework.boot:spring-boot-starter\")\n    implementation(\"org.jetbrains.kotlin:kotlin-stdlib-jdk8\")\n    implementation(\"org.springframework.boot:spring-boot-starter-data-redis\")\n    implementation(\"org.springframework.boot:spring-boot-starter-web\")\n\n    testImplementation(\"org.springframework.boot:spring-boot-starter-test\")\n    testImplementation(\"org.testcontainers:testcontainers\")\n    testRuntimeOnly(\"org.junit.platform:junit-platform-launcher:1.8.2\")\n}\n\ntasks.withType<KotlinCompile> {\n    kotlinOptions {\n        freeCompilerArgs = listOf(\"-Xjsr305=strict\")\n        jvmTarget = \"1.8\"\n    }\n}\n\ntasks.withType<Test> {\n    useJUnitPlatform()\n}\n"
  },
  {
    "path": "examples/spring-boot-kotlin-redis/src/main/kotlin/com/example/redis/ExampleController.kt",
    "content": "package com.example.redis\n\nimport org.springframework.data.redis.core.RedisTemplate\nimport org.springframework.web.bind.annotation.GetMapping\nimport org.springframework.web.bind.annotation.PostMapping\nimport org.springframework.web.bind.annotation.RequestBody\nimport org.springframework.web.bind.annotation.RestController\n\n@RestController\nclass ExampleController(\n    private val redisTemplate: RedisTemplate<String, String>\n) {\n\n    companion object {\n        const val key = \"testcontainers\"\n    }\n\n    @PostMapping(\"/set-foo\")\n    fun setFoo(@RequestBody value: String) {\n        redisTemplate.opsForValue().set(key, value)\n    }\n\n    @GetMapping(\"/get-foo\")\n    fun getFoo(): String? {\n        return redisTemplate.opsForValue().get(key)\n    }\n}\n"
  },
  {
    "path": "examples/spring-boot-kotlin-redis/src/main/kotlin/com/example/redis/RedisApplication.kt",
    "content": "package com.example.redis\n\nimport org.springframework.boot.autoconfigure.SpringBootApplication\nimport org.springframework.boot.runApplication\n\n@SpringBootApplication\nclass RedisApplication\n\nfun main(args: Array<String>) {\n    runApplication<RedisApplication>(*args)\n}\n"
  },
  {
    "path": "examples/spring-boot-kotlin-redis/src/test/kotlin/com/example/redis/AbstractIntegrationTest.kt",
    "content": "package com.example.redis\n\nimport org.junit.jupiter.api.extension.ExtendWith\nimport org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc\nimport org.springframework.boot.test.context.SpringBootTest\nimport org.springframework.boot.test.util.TestPropertyValues\nimport org.springframework.context.ApplicationContextInitializer\nimport org.springframework.context.ConfigurableApplicationContext\nimport org.springframework.test.context.ContextConfiguration\nimport org.springframework.test.context.junit.jupiter.SpringExtension\nimport org.testcontainers.containers.GenericContainer\n\n@SpringBootTest\n@ContextConfiguration(initializers = [AbstractIntegrationTest.Initializer::class])\n@AutoConfigureMockMvc\nabstract class AbstractIntegrationTest {\n\n    companion object {\n        val redisContainer = GenericContainer<Nothing>(\"redis:6-alpine\")\n                .apply { withExposedPorts(6379) }\n    }\n\n    internal class Initializer : ApplicationContextInitializer<ConfigurableApplicationContext> {\n        override fun initialize(configurableApplicationContext: ConfigurableApplicationContext) {\n            redisContainer.start()\n\n            TestPropertyValues.of(\n                \"spring.redis.host=${redisContainer.host}\",\n                \"spring.redis.port=${redisContainer.firstMappedPort}\"\n            ).applyTo(configurableApplicationContext.environment)\n        }\n    }\n}\n"
  },
  {
    "path": "examples/spring-boot-kotlin-redis/src/test/kotlin/com/example/redis/RedisApplicationTests.kt",
    "content": "package com.example.redis\n\nimport org.junit.jupiter.api.Test\n\nclass RedisApplicationTests : AbstractIntegrationTest() {\n\n    @Test\n    fun contextLoads() {\n    }\n\n}\n"
  },
  {
    "path": "examples/spring-boot-kotlin-redis/src/test/kotlin/com/example/redis/RedisTest.kt",
    "content": "package com.example.redis\n\nimport org.hamcrest.CoreMatchers.containsString\nimport org.junit.jupiter.api.Test\nimport org.springframework.beans.factory.annotation.Autowired\nimport org.springframework.test.web.servlet.MockMvc\nimport org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get\nimport org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post\nimport org.springframework.test.web.servlet.result.MockMvcResultMatchers.content\nimport org.springframework.test.web.servlet.result.MockMvcResultMatchers.status\n\n\nclass RedisTest : AbstractIntegrationTest() {\n\n    @Autowired\n    private lateinit var mockMvc: MockMvc\n\n    @Test\n    fun testRedisFunctionality() {\n        val greeting = \"Hello Testcontainers with Kotlin\"\n        mockMvc.perform(post(\"/set-foo\").content(greeting))\n            .andExpect(status().isOk)\n\n        mockMvc.perform(get(\"/get-foo\"))\n            .andExpect(status().isOk)\n            .andExpect(content().string(containsString(greeting)))\n    }\n}\n"
  },
  {
    "path": "examples/spring-boot-kotlin-redis/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "examples/zookeeper/build.gradle",
    "content": "plugins {\n    id 'java'\n}\n\nrepositories {\n    mavenCentral()\n}\n\ndependencies {\n    testImplementation 'org.apache.curator:curator-framework:5.9.0'\n    testImplementation 'org.testcontainers:testcontainers'\n    testImplementation 'org.assertj:assertj-core:3.27.4'\n    testImplementation 'ch.qos.logback:logback-classic:1.3.15'\n    testImplementation 'org.junit.jupiter:junit-jupiter:5.13.4'\n    testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.11.0'\n}\n\ntest {\n    useJUnitPlatform()\n}\n"
  },
  {
    "path": "examples/zookeeper/src/test/java/com/example/ZookeeperContainerTest.java",
    "content": "package com.example;\n\nimport org.apache.curator.framework.CuratorFramework;\nimport org.apache.curator.framework.CuratorFrameworkFactory;\nimport org.apache.curator.retry.RetryOneTime;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.GenericContainer;\n\nimport java.nio.charset.StandardCharsets;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass ZookeeperContainerTest {\n\n    private static final int ZOOKEEPER_PORT = 2181;\n\n    @Test\n    void test() throws Exception {\n        String path = \"/messages/zk-tc\";\n        String content = \"Running Zookeeper with Testcontainers\";\n        try (\n            GenericContainer<?> zookeeper = new GenericContainer<>(\"zookeeper:3.8.0\").withExposedPorts(ZOOKEEPER_PORT)\n        ) {\n            zookeeper.start();\n\n            String connectionString = zookeeper.getHost() + \":\" + zookeeper.getMappedPort(ZOOKEEPER_PORT);\n            CuratorFramework curatorFramework = CuratorFrameworkFactory\n                .builder()\n                .connectString(connectionString)\n                .retryPolicy(new RetryOneTime(100))\n                .build();\n            curatorFramework.start();\n            curatorFramework.create().creatingParentsIfNeeded().forPath(path, content.getBytes());\n\n            byte[] bytes = curatorFramework.getData().forPath(path);\n            curatorFramework.close();\n\n            assertThat(new String(bytes, StandardCharsets.UTF_8)).isEqualTo(content);\n        }\n    }\n}\n"
  },
  {
    "path": "examples/zookeeper/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "gradle/ci-support.gradle",
    "content": "import groovy.json.JsonOutput\n\n// Emit a JSON-formatted list of check tasks to be run in CI\ntask testMatrix {\n    project.afterEvaluate {\n        def checkTasks = subprojects.collect {\n            it.tasks.findByName(\"check\")\n        }.findAll { it != null }\n\n        dependsOn(checkTasks)\n        doLast {\n            def checkTaskPaths = checkTasks\n                .collect { it.path }\n\n            println(JsonOutput.toJson(checkTaskPaths))\n        }\n    }\n}\n\n// If we're executing the `testMatrix` task, disable tests and other slow tasks\n//  so that we can get a result quickly.\ngradle.taskGraph.whenReady {\n    if (it.hasTask(tasks.testMatrix)) {\n        for (subproject in subprojects) {\n            subproject.tasks.withType(Test).all {\n                testExecuter([execute: { spec, processor -> }, stopNow:{}] as org.gradle.api.internal.tasks.testing.TestExecuter)\n            }\n            subproject.tasks.findByName(\"shadowJar\")?.enabled = false\n            subproject.tasks.findByName(\"javadoc\")?.enabled = false\n            subproject.tasks.findByName(\"delombok\")?.enabled = false\n            subproject.tasks.findByName(\"japicmp\")?.enabled = false\n        }\n    }\n}\n"
  },
  {
    "path": "gradle/japicmp.gradle",
    "content": "configurations {\n    baseline\n}\n\ndependencies {\n    baseline \"org.testcontainers:${project.name}:${project['testcontainers.version']}\", {\n        exclude group: \"*\", module: \"*\"\n    }\n}\n\ntasks.japicmp {\n    dependsOn(tasks.shadowJar)\n\n    // Disable if baseline dependencies cannot be resolved - such as when developing a new module that doesn't\n    // have an existing published version.\n    enabled = ! configurations.baseline.copy().resolvedConfiguration.lenientConfiguration.getFiles().empty\n\n    oldClasspath.from(configurations.baseline)\n    newClasspath.from(shadowJar.outputs.files)\n    ignoreMissingClasses = true\n\n    accessModifier = \"protected\"\n    failOnModification = true\n    failOnSourceIncompatibility = true\n\n    onlyBinaryIncompatibleModified = true\n    htmlOutputFile = file(\"$buildDir/reports/japi.html\")\n    packageExcludes = [\n        \"org.testcontainers.shaded.*\",\n    ]\n}\n// do not run on Windows by default\n// TODO investigate zip issue on Windows\nif (!org.gradle.internal.os.OperatingSystem.current().isWindows()) {\n    project.tasks.check.dependsOn(japicmp)\n}\n"
  },
  {
    "path": "gradle/publishing.gradle",
    "content": "apply plugin: 'maven-publish'\napply plugin: 'org.jreleaser'\n\ntask sourceJar(type: Jar) {\n    archiveClassifier.set( 'sources')\n    from sourceSets.main.allJava\n}\n\ntask javadocJar(type: Jar, dependsOn: javadoc) {\n    archiveClassifier.set('javadoc')\n    from javadoc\n}\n\njar.archiveClassifier.set(\"original\")\n\npublishing {\n    publications {\n        mavenJava(MavenPublication) { publication ->\n            artifactId = project.name\n            artifact sourceJar\n            artifact javadocJar\n\n            artifact project.tasks.jar\n            artifacts.removeAll { it.classifier == jar.archiveClassifier.get() }\n            artifact project.tasks.shadowJar\n\n            pom.withXml {\n                def rootNode = asNode()\n                rootNode.children().last() + {\n                    resolveStrategy = Closure.DELEGATE_FIRST\n\n                    name project.description\n                    description 'Isolated container management for Java code testing'\n                    url 'https://java.testcontainers.org'\n                    issueManagement {\n                        system 'GitHub'\n                        url 'https://github.com/testcontainers/testcontainers-java/issues'\n                    }\n                    licenses {\n                        license {\n                            name 'MIT'\n                            url 'http://opensource.org/licenses/MIT'\n                        }\n                    }\n                    scm {\n                        url 'https://github.com/testcontainers/testcontainers-java/'\n                        connection 'scm:git:git://github.com/testcontainers/testcontainers-java.git'\n                        developerConnection 'scm:git:ssh://git@github.com/testcontainers/testcontainers-java.git'\n                    }\n                    developers {\n                        developer {\n                            id 'rnorth'\n                            name 'Richard North'\n                            email 'rich.north@gmail.com'\n                        }\n                    }\n                }\n\n                def dependenciesNode = rootNode.appendNode('dependencies')\n\n                def apiDeps= project.configurations.api.resolvedConfiguration.firstLevelModuleDependencies\n                def providedDeps = project.configurations.provided.resolvedConfiguration.firstLevelModuleDependencies\n                def newApiDeps = apiDeps - providedDeps\n\n                def addDependencies = { Set<ResolvedDependency> resolvedDependencies, scope ->\n                    for (dependency in resolvedDependencies) {\n                        if (dependency.configuration.startsWith(\"platform-\")) {\n                            continue\n                        }\n                        dependenciesNode.appendNode('dependency').with {\n                            if (!dependency.moduleGroup || !dependency.moduleName || !dependency.moduleVersion) {\n                                throw new IllegalStateException(\"Wrong dependency: $dependency\")\n                            }\n\n                            appendNode('groupId', dependency.moduleGroup)\n                            appendNode('artifactId', dependency.moduleName)\n                            appendNode('version', dependency.moduleVersion)\n                            appendNode('scope', scope)\n\n                            if (dependency instanceof ModuleDependency && !dependency.excludeRules.empty) {\n                                def excludesNode = appendNode('exclusions')\n                                for (rule in dependency.excludeRules) {\n                                    excludesNode.appendNode('exclusion').with {\n                                        appendNode('groupId', rule.group)\n                                        appendNode('artifactId', rule.module)\n                                    }\n                                }\n                            }\n                        }\n                    }\n                }\n                addDependencies(newApiDeps, 'compile')\n                addDependencies(providedDeps, 'provided')\n            }\n        }\n    }\n    repositories {\n        maven {\n            url = rootProject.layout.buildDirectory.dir('staging-deploy')\n        }\n    }\n}\n\njreleaser {\n    signing {\n        active = 'ALWAYS'\n        armored = true\n    }\n    deploy {\n        maven {\n            mavenCentral {\n                central {\n                    active = 'ALWAYS'\n                    url = 'https://central.sonatype.com/api/v1/publisher'\n                    stagingRepository(rootProject.layout.buildDirectory.dir(\"staging-deploy\").get().toString())\n                    stage = 'UPLOAD'\n                    applyMavenCentralRules = true\n                    namespace = 'org.testcontainers'\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "gradle/shading.gradle",
    "content": "import java.util.jar.JarFile\n\napply plugin: 'com.gradleup.shadow'\n\nconfigurations {\n    shaded\n    [apiElements, implementation]*.extendsFrom shaded\n}\nconfigurations.api.canBeResolved = true\n\nshadowJar {\n    configurations = [project.configurations.shaded]\n    archiveClassifier.set(null)\n\n    mergeServiceFiles()\n}\n\nproject.afterEvaluate {\n    Set<ModuleIdentifier> apiDependencies = project.configurations.api.resolvedConfiguration.resolvedArtifacts*.moduleVersion*.id*.module\n\n    // Exclude `api` dependencies, to prevent them being included into the final JAR\n    shadowJar.dependencies {\n        for (id in apiDependencies) {\n            exclude(dependency(\"${id.group}:${id.name}\"))\n        }\n    }\n\n    // Inherit dependencies' relocators\n    shadowJar.relocators = configurations.api.dependencies.withType(ProjectDependency).collectMany {\n        return it.dependencyProject.tasks.findByName(\"shadowJar\")?.relocators ?: []\n    }\n\n    // See https://github.com/GradleUp/shadow/blob/5.0.0/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/tasks/ConfigureShadowRelocation.groovy\n    Set<String> packages = []\n\n    for (artifact in project.configurations.shaded.resolvedConfiguration.resolvedArtifacts) {\n        if (apiDependencies.contains(artifact.moduleVersion.id.module)) {\n            continue\n        }\n\n        try (def jf = new JarFile(artifact.file)) {\n            for (entry in jf.entries()) {\n                def name = entry.name\n                if (name.endsWith(\".class\")) {\n                    def index = name.lastIndexOf('/')\n                    if (index != -1) {\n                        packages.add(name.substring(0, index))\n                    }\n                }\n            }\n        }\n    }\n    for (pkg in packages) {\n        pkg = pkg.replaceAll('/', '.')\n\n        tasks.shadowJar.relocate(pkg, \"org.testcontainers.shaded.${pkg}\")\n    }\n\n    // Add shaded JARs first\n    tasks.test.classpath = findShadedProjects(project)\n        .plus(project)\n        .sum { it.tasks.findByName(\"shadowJar\").outputs.files }\n        .plus(sourceSets.test.runtimeClasspath)\n}\n\nstatic Set<Project> findShadedProjects(Project project) {\n    Set<Project> dependencies = project.configurations.api.dependencies.withType(ProjectDependency)*.dependencyProject\n\n    return dependencies + dependencies.collectMany { it -> findShadedProjects(it) }\n}\n"
  },
  {
    "path": "gradle/spotless.gradle",
    "content": "apply plugin: 'com.diffplug.spotless'\n\nspotless {\n    java {\n        toggleOffOn()\n        removeUnusedImports()\n        trimTrailingWhitespace()\n        endWithNewline()\n\n        prettier(['prettier': '2.5.1', 'prettier-plugin-java': '1.6.1'])\n            .config([\n                'parser'    : 'java',\n                'tabWidth'  : 4,\n                'printWidth': 120\n            ])\n\n        importOrder('', 'java', 'javax', '\\\\#')\n    }\n    groovyGradle {\n        target '**/*.groovy'\n        greclipse('4.19')\n    }\n}\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionSha256Sum=ed1a8d686605fd7c23bdf62c7fc7add1c5b23b2bbc3721e661934ef4a4911d7c\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-8.14.3-all.zip\nnetworkTimeout=10000\nvalidateDistributionUrl=true\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "gradle.properties",
    "content": "org.gradle.parallel=false\norg.gradle.caching=true\norg.gradle.configureondemand=true\norg.gradle.jvmargs=-Xmx2g\n\ntestcontainers.version=2.0.4\n"
  },
  {
    "path": "gradlew",
    "content": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n# SPDX-License-Identifier: Apache-2.0\n#\n\n##############################################################################\n#\n#   Gradle start up script for POSIX generated by Gradle.\n#\n#   Important for running:\n#\n#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n#       noncompliant, but you have some other compliant shell such as ksh or\n#       bash, then to run this script, type that shell name before the whole\n#       command line, like:\n#\n#           ksh Gradle\n#\n#       Busybox and similar reduced shells will NOT work, because this script\n#       requires all of these POSIX shell features:\n#         * functions;\n#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n#         * compound commands having a testable exit status, especially «case»;\n#         * various built-in commands including «command», «set», and «ulimit».\n#\n#   Important for patching:\n#\n#   (2) This script targets any POSIX shell, so it avoids extensions provided\n#       by Bash, Ksh, etc; in particular arrays are avoided.\n#\n#       The \"traditional\" practice of packing multiple parameters into a\n#       space-separated string is a well documented source of bugs and security\n#       problems, so this is (mostly) avoided, by progressively accumulating\n#       options in \"$@\", and eventually passing that to Java.\n#\n#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n#       see the in-line comments for details.\n#\n#       There are tweaks for specific operating systems such as AIX, CygWin,\n#       Darwin, MinGW, and NonStop.\n#\n#   (3) This script is generated from the Groovy template\n#       https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n#       within the Gradle project.\n#\n#       You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n    APP_HOME=${app_path%\"${app_path##*/}\"}  # leaves a trailing /; empty if no leading path\n    [ -h \"$app_path\" ]\ndo\n    ls=$( ls -ld \"$app_path\" )\n    link=${ls#*' -> '}\n    case $link in             #(\n      /*)   app_path=$link ;; #(\n      *)    app_path=$APP_HOME$link ;;\n    esac\ndone\n\n# This is normally unused\n# shellcheck disable=SC2034\nAPP_BASE_NAME=${0##*/}\n# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)\nAPP_HOME=$( cd -P \"${APP_HOME:-./}\" > /dev/null && printf '%s\\n' \"$PWD\" ) || exit\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n    echo \"$*\"\n} >&2\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in                #(\n  CYGWIN* )         cygwin=true  ;; #(\n  Darwin* )         darwin=true  ;; #(\n  MSYS* | MINGW* )  msys=true    ;; #(\n  NONSTOP* )        nonstop=true ;;\nesac\n\nCLASSPATH=\"\\\\\\\"\\\\\\\"\"\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=$JAVA_HOME/jre/sh/java\n    else\n        JAVACMD=$JAVA_HOME/bin/java\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=java\n    if ! command -v java >/dev/null 2>&1\n    then\n        die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n    case $MAX_FD in #(\n      max*)\n        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        MAX_FD=$( ulimit -H -n ) ||\n            warn \"Could not query maximum file descriptor limit\"\n    esac\n    case $MAX_FD in  #(\n      '' | soft) :;; #(\n      *)\n        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        ulimit -n \"$MAX_FD\" ||\n            warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n    esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n#   * args from the command line\n#   * the main class name\n#   * -classpath\n#   * -D...appname settings\n#   * --module-path (only if needed)\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n    APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n    CLASSPATH=$( cygpath --path --mixed \"$CLASSPATH\" )\n\n    JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    for arg do\n        if\n            case $arg in                                #(\n              -*)   false ;;                            # don't mess with options #(\n              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath\n                    [ -e \"$t\" ] ;;                      #(\n              *)    false ;;\n            esac\n        then\n            arg=$( cygpath --path --ignore --mixed \"$arg\" )\n        fi\n        # Roll the args list around exactly as many times as the number of\n        # args, so each arg winds up back in the position where it started, but\n        # possibly modified.\n        #\n        # NB: a `for` loop captures its iteration list before it begins, so\n        # changing the positional parameters here affects neither the number of\n        # iterations, nor the values presented in `arg`.\n        shift                   # remove old arg\n        set -- \"$@\" \"$arg\"      # push replacement arg\n    done\nfi\n\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Collect all arguments for the java command:\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,\n#     and any embedded shellness will be escaped.\n#   * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be\n#     treated as '${Hostname}' itself on the command line.\n\nset -- \\\n        \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n        -classpath \"$CLASSPATH\" \\\n        -jar \"$APP_HOME/gradle/wrapper/gradle-wrapper.jar\" \\\n        \"$@\"\n\n# Stop when \"xargs\" is not available.\nif ! command -v xargs >/dev/null 2>&1\nthen\n    die \"xargs is not available\"\nfi\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n#   readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n#   set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n        printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n        xargs -n1 |\n        sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n        tr '\\n' ' '\n    )\" '\"$@\"'\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\r\n@rem you may not use this file except in compliance with the License.\r\n@rem You may obtain a copy of the License at\r\n@rem\r\n@rem      https://www.apache.org/licenses/LICENSE-2.0\r\n@rem\r\n@rem Unless required by applicable law or agreed to in writing, software\r\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\r\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n@rem See the License for the specific language governing permissions and\r\n@rem limitations under the License.\r\n@rem\r\n@rem SPDX-License-Identifier: Apache-2.0\r\n@rem\r\n\r\n@if \"%DEBUG%\"==\"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\"==\"\" set DIRNAME=.\r\n@rem This is normally unused\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\r\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif %ERRORLEVEL% equ 0 goto execute\r\n\r\necho. 1>&2\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2\r\necho. 1>&2\r\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\r\necho location of your Java installation. 1>&2\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto execute\r\n\r\necho. 1>&2\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2\r\necho. 1>&2\r\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\r\necho location of your Java installation. 1>&2\r\n\r\ngoto fail\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=\r\n\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" -jar \"%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\" %*\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif %ERRORLEVEL% equ 0 goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nset EXIT_CODE=%ERRORLEVEL%\r\nif %EXIT_CODE% equ 0 set EXIT_CODE=1\r\nif not \"\"==\"%GRADLE_EXIT_CONSOLE%\" exit %EXIT_CODE%\r\nexit /b %EXIT_CODE%\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "mkdocs.yml",
    "content": "site_name: Testcontainers for Java\nsite_url: https://java.testcontainers.org\nplugins:\n    - search\n    - codeinclude\n    - markdownextradata\ntheme:\n    name: 'material'\n    custom_dir: 'docs/theme'\n    palette:\n        scheme: testcontainers\n    font:\n      text: Roboto\n      code: Roboto Mono\n    logo: 'logo.svg'\n    favicon: 'favicon.ico'\nextra_css:\n    - 'css/extra.css'\n    - 'css/tc-header.css'\nrepo_name: 'testcontainers-java'\nrepo_url: 'https://github.com/testcontainers/testcontainers-java'\nmarkdown_extensions:\n    - admonition\n    - codehilite:\n          linenums: False\n    - pymdownx.superfences\n    - pymdownx.tabbed:\n        alternate_style: true\n    - pymdownx.snippets\nnav:\n    - Home: index.md\n    - Quickstart:\n          - quickstart/junit_4_quickstart.md\n          - quickstart/junit_5_quickstart.md\n          - quickstart/spock_quickstart.md\n    - Features:\n          - features/creating_container.md\n          - features/networking.md\n          - features/commands.md\n          - features/files.md\n          - features/startup_and_waits.md\n          - features/container_logs.md\n          - features/creating_images.md\n          - features/jib.md\n          - features/configuration.md\n          - features/image_name_substitution.md\n          - features/advanced_options.md\n          - features/reuse.md\n    - Modules:\n          - Databases:\n                - modules/databases/index.md\n                - modules/databases/jdbc.md\n                - modules/databases/r2dbc.md\n                - modules/databases/cassandra.md\n                - modules/databases/cockroachdb.md\n                - modules/databases/couchbase.md\n                - modules/databases/clickhouse.md\n                - modules/databases/cratedb.md\n                - modules/databases/databend.md\n                - modules/databases/db2.md\n                - modules/databases/influxdb.md\n                - modules/databases/mariadb.md\n                - modules/databases/mongodb.md\n                - modules/databases/mssqlserver.md\n                - modules/databases/mysql.md\n                - modules/databases/neo4j.md\n                - modules/databases/oceanbase.md\n                - modules/databases/oraclefree.md\n                - modules/databases/oraclexe.md\n                - modules/databases/orientdb.md\n                - modules/databases/postgres.md\n                - modules/databases/presto.md\n                - modules/databases/questdb.md\n                - modules/databases/scylladb.md\n                - modules/databases/tidb.md\n                - modules/databases/timeplus.md\n                - modules/databases/trino.md\n                - modules/databases/yugabytedb.md\n          - modules/activemq.md\n          - modules/azure.md\n          - modules/chromadb.md\n          - modules/consul.md\n          - modules/docker_compose.md\n          - modules/docker_mcp_gateway.md\n          - modules/docker_model_runner.md\n          - modules/elasticsearch.md\n          - modules/gcloud.md\n          - modules/grafana.md\n          - modules/hivemq.md\n          - modules/k3s.md\n          - modules/k6.md\n          - modules/kafka.md\n          - modules/ldap.md\n          - modules/localstack.md\n          - modules/milvus.md\n          - modules/minio.md\n          - modules/mockserver.md\n          - modules/nginx.md\n          - modules/ollama.md\n          - modules/openfga.md\n          - modules/pinecone.md\n          - modules/pulsar.md\n          - modules/qdrant.md\n          - modules/rabbitmq.md\n          - modules/redpanda.md\n          - modules/solace.md\n          - modules/solr.md\n          - modules/toxiproxy.md\n          - modules/typesense.md\n          - modules/vault.md\n          - modules/weaviate.md\n          - modules/webdriver_containers.md\n    - Test framework integration:\n          - test_framework_integration/junit_4.md\n          - test_framework_integration/junit_5.md\n          - test_framework_integration/spock.md\n          - test_framework_integration/manual_lifecycle_control.md\n          - test_framework_integration/external.md\n    - Examples: examples.md\n    - System Requirements:\n          - supported_docker_environment/index.md\n          - error_missing_container_runtime_environment.md\n          - Continuous Integration:\n                - supported_docker_environment/continuous_integration/aws_codebuild.md\n                - supported_docker_environment/continuous_integration/dind_patterns.md\n                - supported_docker_environment/continuous_integration/circle_ci.md\n                - supported_docker_environment/continuous_integration/concourse_ci.md\n                - supported_docker_environment/continuous_integration/drone.md\n                - supported_docker_environment/continuous_integration/gitlab_ci.md\n                - supported_docker_environment/continuous_integration/bitbucket_pipelines.md\n                - supported_docker_environment/continuous_integration/tekton.md\n                - supported_docker_environment/continuous_integration/travis.md\n          - supported_docker_environment/windows.md\n          - supported_docker_environment/logging_config.md\n          - supported_docker_environment/image_registry_rate_limiting.md\n    - Getting help: getting_help.md\n    - Contributing:\n          - contributing.md\n          - contributing_docs.md\n          - bounty.md\nedit_uri: edit/main/docs/\nextra:\n    latest_version: 2.0.4\n"
  },
  {
    "path": "modules/activemq/build.gradle",
    "content": "description = \"Testcontainers :: ActiveMQ\"\n\ndependencies {\n    api project(':testcontainers')\n\n    testImplementation \"org.apache.activemq:activemq-client:6.2.0\"\n    testImplementation \"org.apache.activemq:artemis-jakarta-client:2.44.0\"\n}\n"
  },
  {
    "path": "modules/activemq/src/main/java/org/testcontainers/activemq/ActiveMQContainer.java",
    "content": "package org.testcontainers.activemq;\n\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.time.Duration;\n\n/**\n * Testcontainers implementation for Apache ActiveMQ.\n * <p>\n * Exposed ports:\n * <ul>\n *     <li>Console: 8161</li>\n *     <li>TCP: 61616</li>\n *     <li>AMQP: 5672</li>\n *     <li>STOMP: 61613</li>\n *     <li>MQTT: 1883</li>\n *     <li>WS: 61614</li>\n * </ul>\n */\npublic class ActiveMQContainer extends GenericContainer<ActiveMQContainer> {\n\n    private static final DockerImageName APACHE_ACTIVEMQ_CLASSIC_IMAGE = DockerImageName.parse(\n        \"apache/activemq-classic\"\n    );\n\n    private static final DockerImageName DEFAULT_IMAGE = DockerImageName.parse(\"apache/activemq\");\n\n    private static final int WEB_CONSOLE_PORT = 8161;\n\n    private static final int TCP_PORT = 61616;\n\n    private static final int AMQP_PORT = 5672;\n\n    private static final int STOMP_PORT = 61613;\n\n    private static final int MQTT_PORT = 1883;\n\n    private static final int WS_PORT = 61614;\n\n    private String username;\n\n    private String password;\n\n    public ActiveMQContainer(String image) {\n        this(DockerImageName.parse(image));\n    }\n\n    public ActiveMQContainer(DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE, APACHE_ACTIVEMQ_CLASSIC_IMAGE);\n\n        withExposedPorts(WEB_CONSOLE_PORT, TCP_PORT, AMQP_PORT, STOMP_PORT, MQTT_PORT, WS_PORT);\n        waitingFor(Wait.forLogMessage(\".*Apache ActiveMQ.*started.*\", 1).withStartupTimeout(Duration.ofMinutes(1)));\n    }\n\n    @Override\n    protected void configure() {\n        if (this.username != null) {\n            addEnv(\"ACTIVEMQ_CONNECTION_USER\", this.username);\n        }\n        if (this.password != null) {\n            addEnv(\"ACTIVEMQ_CONNECTION_PASSWORD\", this.password);\n        }\n    }\n\n    public ActiveMQContainer withUser(String username) {\n        this.username = username;\n        return this;\n    }\n\n    public ActiveMQContainer withPassword(String password) {\n        this.password = password;\n        return this;\n    }\n\n    public String getBrokerUrl() {\n        return String.format(\"tcp://%s:%s\", getHost(), getMappedPort(TCP_PORT));\n    }\n\n    public String getUser() {\n        return this.username;\n    }\n\n    public String getPassword() {\n        return this.password;\n    }\n}\n"
  },
  {
    "path": "modules/activemq/src/main/java/org/testcontainers/activemq/ArtemisContainer.java",
    "content": "package org.testcontainers.activemq;\n\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.time.Duration;\n\n/**\n * Testcontainers implementation for Apache ActiveMQ Artemis.\n * <p>\n * Exposed ports:\n * <ul>\n *     <li>Console: 8161</li>\n *     <li>TCP: 61616</li>\n *     <li>HORNETQ: 5445</li>\n *     <li>AMQP: 5672</li>\n *     <li>STOMP: 61613</li>\n *     <li>MQTT: 1883</li>\n *     <li>WS: 61614</li>\n * </ul>\n */\npublic class ArtemisContainer extends GenericContainer<ArtemisContainer> {\n\n    private static final DockerImageName DEFAULT_IMAGE = DockerImageName.parse(\"apache/activemq-artemis\");\n\n    private static final int WEB_CONSOLE_PORT = 8161;\n\n    // CORE,MQTT,AMQP,HORNETQ,STOMP,OPENWIRE\n    private static final int TCP_PORT = 61616;\n\n    private static final int HORNETQ_STOMP_PORT = 5445;\n\n    private static final int AMQP_PORT = 5672;\n\n    private static final int STOMP_PORT = 61613;\n\n    private static final int MQTT_PORT = 1883;\n\n    private static final int WS_PORT = 61614;\n\n    private String username = \"artemis\";\n\n    private String password = \"artemis\";\n\n    public ArtemisContainer(String image) {\n        this(DockerImageName.parse(image));\n    }\n\n    public ArtemisContainer(DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE);\n\n        withExposedPorts(WEB_CONSOLE_PORT, TCP_PORT, HORNETQ_STOMP_PORT, AMQP_PORT, STOMP_PORT, MQTT_PORT, WS_PORT);\n        waitingFor(Wait.forLogMessage(\".*HTTP Server started.*\", 1).withStartupTimeout(Duration.ofMinutes(1)));\n    }\n\n    @Override\n    protected void configure() {\n        withEnv(\"ARTEMIS_USER\", this.username);\n        withEnv(\"ARTEMIS_PASSWORD\", this.password);\n    }\n\n    public ArtemisContainer withUser(String username) {\n        this.username = username;\n        return this;\n    }\n\n    public ArtemisContainer withPassword(String password) {\n        this.password = password;\n        return this;\n    }\n\n    public String getBrokerUrl() {\n        return String.format(\"tcp://%s:%s\", getHost(), getMappedPort(TCP_PORT));\n    }\n\n    public String getUser() {\n        return getEnvMap().get(\"ARTEMIS_USER\");\n    }\n\n    public String getPassword() {\n        return getEnvMap().get(\"ARTEMIS_PASSWORD\");\n    }\n}\n"
  },
  {
    "path": "modules/activemq/src/test/java/org/testcontainers/activemq/ActiveMQContainerTest.java",
    "content": "package org.testcontainers.activemq;\n\nimport jakarta.jms.Connection;\nimport jakarta.jms.ConnectionFactory;\nimport jakarta.jms.Destination;\nimport jakarta.jms.MessageConsumer;\nimport jakarta.jms.MessageProducer;\nimport jakarta.jms.Session;\nimport jakarta.jms.TextMessage;\nimport lombok.SneakyThrows;\nimport org.apache.activemq.ActiveMQConnectionFactory;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass ActiveMQContainerTest {\n\n    @Test\n    void test() {\n        try ( // container {\n            ActiveMQContainer activemq = new ActiveMQContainer(\"apache/activemq:5.18.7\")\n            // }\n        ) {\n            activemq.start();\n\n            assertThat(activemq.getUser()).isNull();\n            assertThat(activemq.getPassword()).isNull();\n            assertFunctionality(activemq, false);\n        }\n    }\n\n    @ParameterizedTest\n    @ValueSource(strings = { \"apache/activemq-classic:5.18.7\", \"apache/activemq:5.18.7\" })\n    void compatibility(String image) {\n        try (ActiveMQContainer activemq = new ActiveMQContainer(image)) {\n            activemq.start();\n            assertFunctionality(activemq, false);\n        }\n    }\n\n    @Test\n    void customCredentials() {\n        try (\n            // settingCredentials {\n            ActiveMQContainer activemq = new ActiveMQContainer(\"apache/activemq:5.18.7\")\n                .withUser(\"testcontainers\")\n                .withPassword(\"testcontainers\")\n            // }\n        ) {\n            activemq.start();\n\n            assertThat(activemq.getUser()).isEqualTo(\"testcontainers\");\n            assertThat(activemq.getPassword()).isEqualTo(\"testcontainers\");\n            assertFunctionality(activemq, true);\n        }\n    }\n\n    @SneakyThrows\n    private void assertFunctionality(ActiveMQContainer activemq, boolean useCredentials) {\n        ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(activemq.getBrokerUrl());\n        Connection connection;\n        if (useCredentials) {\n            connection = connectionFactory.createConnection(activemq.getUser(), activemq.getPassword());\n        } else {\n            connection = connectionFactory.createConnection();\n        }\n        connection.start();\n\n        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);\n\n        Destination destination = session.createQueue(\"test-queue\");\n        MessageProducer producer = session.createProducer(destination);\n\n        String contentMessage = \"Testcontainers\";\n        TextMessage message = session.createTextMessage(contentMessage);\n        producer.send(message);\n\n        MessageConsumer consumer = session.createConsumer(destination);\n        TextMessage messageReceived = (TextMessage) consumer.receive();\n        assertThat(messageReceived.getText()).isEqualTo(contentMessage);\n    }\n}\n"
  },
  {
    "path": "modules/activemq/src/test/java/org/testcontainers/activemq/ArtemisContainerTest.java",
    "content": "package org.testcontainers.activemq;\n\nimport jakarta.jms.Connection;\nimport jakarta.jms.Destination;\nimport jakarta.jms.MessageConsumer;\nimport jakarta.jms.MessageProducer;\nimport jakarta.jms.Session;\nimport jakarta.jms.TextMessage;\nimport lombok.SneakyThrows;\nimport org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory;\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass ArtemisContainerTest {\n\n    @Test\n    void defaultCredentials() {\n        try (\n            // container {\n            ArtemisContainer artemis = new ArtemisContainer(\"apache/activemq-artemis:2.30.0-alpine\")\n            // }\n        ) {\n            artemis.start();\n\n            assertThat(artemis.getUser()).isEqualTo(\"artemis\");\n            assertThat(artemis.getPassword()).isEqualTo(\"artemis\");\n            assertFunctionality(artemis, false);\n        }\n    }\n\n    @Test\n    void customCredentials() {\n        try (\n            // settingCredentials {\n            ArtemisContainer artemis = new ArtemisContainer(\"apache/activemq-artemis:2.30.0-alpine\")\n                .withUser(\"testcontainers\")\n                .withPassword(\"testcontainers\")\n            // }\n        ) {\n            artemis.start();\n\n            assertThat(artemis.getUser()).isEqualTo(\"testcontainers\");\n            assertThat(artemis.getPassword()).isEqualTo(\"testcontainers\");\n            assertFunctionality(artemis, false);\n        }\n    }\n\n    @Test\n    void allowAnonymousLogin() {\n        try (\n            // enableAnonymousLogin {\n            ArtemisContainer artemis = new ArtemisContainer(\"apache/activemq-artemis:2.30.0-alpine\")\n                .withEnv(\"ANONYMOUS_LOGIN\", \"true\")\n            // }\n        ) {\n            artemis.start();\n\n            assertFunctionality(artemis, true);\n        }\n    }\n\n    @SneakyThrows\n    private void assertFunctionality(ArtemisContainer artemis, boolean anonymousLogin) {\n        ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(artemis.getBrokerUrl());\n        if (!anonymousLogin) {\n            connectionFactory.setUser(artemis.getUser());\n            connectionFactory.setPassword(artemis.getPassword());\n        }\n        Connection connection = connectionFactory.createConnection();\n        connection.start();\n\n        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);\n\n        Destination destination = session.createQueue(\"test-queue\");\n        MessageProducer producer = session.createProducer(destination);\n\n        String contentMessage = \"Testcontainers\";\n        TextMessage message = session.createTextMessage(contentMessage);\n        producer.send(message);\n\n        MessageConsumer consumer = session.createConsumer(destination);\n        TextMessage messageReceived = (TextMessage) consumer.receive();\n        assertThat(messageReceived.getText()).isEqualTo(contentMessage);\n    }\n}\n"
  },
  {
    "path": "modules/activemq/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/azure/build.gradle",
    "content": "description = \"Testcontainers :: Azure\"\n\ndependencies {\n    api project(':testcontainers')\n    api project(':testcontainers-mssqlserver')\n    // TODO use JDK's HTTP client and/or Apache HttpClient5\n    shaded 'com.squareup.okhttp3:okhttp:5.3.2'\n\n    testImplementation platform(\"com.azure:azure-sdk-bom:1.2.32\")\n    testImplementation 'com.azure:azure-cosmos'\n    testImplementation 'com.azure:azure-storage-blob'\n    testImplementation 'com.azure:azure-storage-queue'\n    testImplementation 'com.azure:azure-data-tables'\n    testImplementation 'com.azure:azure-messaging-eventhubs'\n    testImplementation 'com.azure:azure-messaging-servicebus'\n    testImplementation 'com.microsoft.sqlserver:mssql-jdbc:13.3.0.jre8-preview'\n}\n\ntasks.japicmp {\n\tmethodExcludes = [\"org.testcontainers.azure.ServiceBusEmulatorContainer#withMsSqlServerContainer(org.testcontainers.containers.MSSQLServerContainer)\"]\n}\n"
  },
  {
    "path": "modules/azure/src/main/java/org/testcontainers/azure/AzuriteContainer.java",
    "content": "package org.testcontainers.azure;\n\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.MountableFile;\n\n/**\n * Testcontainers implementation for Azurite Emulator.\n * <p>\n * Supported image: {@code mcr.microsoft.com/azure-storage/azurite}\n * <p>\n * Exposed ports:\n * <ul>\n *     <li>Blob: 10000</li>\n *     <li>Queue: 10001</li>\n *     <li>Table: 10002</li>\n * </ul>\n */\npublic class AzuriteContainer extends GenericContainer<AzuriteContainer> {\n\n    private static final String ALLOW_ALL_CONNECTIONS = \"0.0.0.0\";\n\n    private static final int DEFAULT_BLOB_PORT = 10000;\n\n    private static final int DEFAULT_QUEUE_PORT = 10001;\n\n    private static final int DEFAULT_TABLE_PORT = 10002;\n\n    private static final String CONNECTION_STRING_FORMAT =\n        \"DefaultEndpointsProtocol=%s;AccountName=%s;AccountKey=%s;BlobEndpoint=%s://%s:%d/%s;QueueEndpoint=%s://%s:%d/%s;TableEndpoint=%s://%s:%d/%s;\";\n\n    /**\n     * The account name of the default credentials.\n     */\n    private static final String WELL_KNOWN_ACCOUNT_NAME = \"devstoreaccount1\";\n\n    /**\n     * The account key of the default credentials.\n     */\n    private static final String WELL_KNOWN_ACCOUNT_KEY =\n        \"Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==\";\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\n        \"mcr.microsoft.com/azure-storage/azurite\"\n    );\n\n    private MountableFile cert = null;\n\n    private String certExtension = null;\n\n    private MountableFile key = null;\n\n    private String pwd = null;\n\n    /**\n     * @param dockerImageName specified docker image name to run\n     */\n    public AzuriteContainer(String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    /**\n     * @param dockerImageName specified docker image name to run\n     */\n    public AzuriteContainer(DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n        withExposedPorts(DEFAULT_BLOB_PORT, DEFAULT_QUEUE_PORT, DEFAULT_TABLE_PORT);\n    }\n\n    /**\n     * Configure SSL with a custom certificate and password.\n     *\n     * @param pfxCert  The PFX certificate file\n     * @param password The password securing the certificate\n     * @return this\n     */\n    public AzuriteContainer withSsl(final MountableFile pfxCert, final String password) {\n        this.cert = pfxCert;\n        this.pwd = password;\n        this.certExtension = \".pfx\";\n        return this;\n    }\n\n    /**\n     * Configure SSL with a custom certificate and private key.\n     *\n     * @param pemCert The PEM certificate file\n     * @param pemKey  The PEM key file\n     * @return this\n     */\n    public AzuriteContainer withSsl(final MountableFile pemCert, final MountableFile pemKey) {\n        this.cert = pemCert;\n        this.key = pemKey;\n        this.certExtension = \".pem\";\n        return this;\n    }\n\n    @Override\n    protected void configure() {\n        withCommand(getCommandLine());\n        if (this.cert != null) {\n            logger().info(\"Using path for cert file: '{}'\", this.cert);\n            withCopyFileToContainer(this.cert, \"/cert\" + this.certExtension);\n            if (this.key != null) {\n                logger().info(\"Using path for key file: '{}'\", this.key);\n                withCopyFileToContainer(this.key, \"/key.pem\");\n            }\n        }\n    }\n\n    /**\n     * Returns the connection string for the default credentials.\n     *\n     * @return connection string\n     */\n    public String getConnectionString() {\n        return getConnectionString(WELL_KNOWN_ACCOUNT_NAME, WELL_KNOWN_ACCOUNT_KEY);\n    }\n\n    /**\n     * Returns the connection string for the account name and key specified.\n     *\n     * @param accountName The name of the account\n     * @param accountKey  The account key\n     * @return connection string\n     */\n    public String getConnectionString(final String accountName, final String accountKey) {\n        final String protocol = cert != null ? \"https\" : \"http\";\n        return String.format(\n            CONNECTION_STRING_FORMAT,\n            protocol,\n            accountName,\n            accountKey,\n            protocol,\n            getHost(),\n            getMappedPort(DEFAULT_BLOB_PORT),\n            accountName,\n            protocol,\n            getHost(),\n            getMappedPort(DEFAULT_QUEUE_PORT),\n            accountName,\n            protocol,\n            getHost(),\n            getMappedPort(DEFAULT_TABLE_PORT),\n            accountName\n        );\n    }\n\n    String getCommandLine() {\n        final StringBuilder args = new StringBuilder(\"azurite\");\n        args.append(\" --blobHost \").append(ALLOW_ALL_CONNECTIONS);\n        args.append(\" --queueHost \").append(ALLOW_ALL_CONNECTIONS);\n        args.append(\" --tableHost \").append(ALLOW_ALL_CONNECTIONS);\n        if (this.cert != null) {\n            args.append(\" --cert \").append(\"/cert\").append(this.certExtension);\n            if (this.pwd != null) {\n                args.append(\" --pwd \").append(this.pwd);\n            } else {\n                args.append(\" --key \").append(\"/key.pem\");\n            }\n        }\n        final String cmd = args.toString();\n        logger().debug(\"Using command line: '{}'\", cmd);\n        return cmd;\n    }\n}\n"
  },
  {
    "path": "modules/azure/src/main/java/org/testcontainers/azure/EventHubsEmulatorContainer.java",
    "content": "package org.testcontainers.azure;\n\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.images.builder.Transferable;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.LicenseAcceptance;\n\n/**\n * Testcontainers implementation for Azure Eventhubs Emulator.\n * <p>\n * Supported image: {@code \"mcr.microsoft.com/azure-messaging/eventhubs-emulator\"}\n * <p>\n * Exposed ports:\n * <ul>\n *     <li>AMQP: 5672</li>\n * </ul>\n */\npublic class EventHubsEmulatorContainer extends GenericContainer<EventHubsEmulatorContainer> {\n\n    private static final int DEFAULT_AMQP_PORT = 5672;\n\n    private static final String CONNECTION_STRING_FORMAT =\n        \"Endpoint=sb://%s:%d;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=SAS_KEY_VALUE;UseDevelopmentEmulator=true;\";\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\n        \"mcr.microsoft.com/azure-messaging/eventhubs-emulator\"\n    );\n\n    private AzuriteContainer azuriteContainer;\n\n    /**\n     * @param dockerImageName specified docker image name to run\n     */\n    public EventHubsEmulatorContainer(final String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    /**\n     * @param dockerImageName specified docker image name to run\n     */\n    public EventHubsEmulatorContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n        waitingFor(Wait.forLogMessage(\".*Emulator Service is Successfully Up!.*\", 1));\n        withExposedPorts(DEFAULT_AMQP_PORT);\n    }\n\n    /**\n     * * Sets the Azurite dependency needed by the Event Hubs Container,\n     *\n     * @param azuriteContainer The Azurite container used by Event HUbs as a dependency\n     * @return this\n     */\n    public EventHubsEmulatorContainer withAzuriteContainer(final AzuriteContainer azuriteContainer) {\n        this.azuriteContainer = azuriteContainer;\n        dependsOn(this.azuriteContainer);\n        return this;\n    }\n\n    /**\n     * Provide the broker configuration to the container.\n     *\n     * @param config The file containing the broker configuration\n     * @return this\n     */\n    public EventHubsEmulatorContainer withConfig(final Transferable config) {\n        withCopyToContainer(config, \"/Eventhubs_Emulator/ConfigFiles/Config.json\");\n        return this;\n    }\n\n    /**\n     * Accepts the EULA of the container.\n     *\n     * @return this\n     */\n    public EventHubsEmulatorContainer acceptLicense() {\n        withEnv(\"ACCEPT_EULA\", \"Y\");\n        return this;\n    }\n\n    @Override\n    protected void configure() {\n        if (azuriteContainer == null) {\n            throw new IllegalStateException(\n                \"The image \" +\n                getDockerImageName() +\n                \" requires an Azurite container. Please provide one with the withAzuriteContainer method!\"\n            );\n        }\n        final String azuriteHost = azuriteContainer.getNetworkAliases().get(0);\n        withEnv(\"BLOB_SERVER\", azuriteHost);\n        withEnv(\"METADATA_SERVER\", azuriteHost);\n        // If license was not accepted programmatically, check if it was accepted via resource file\n        if (!getEnvMap().containsKey(\"ACCEPT_EULA\")) {\n            LicenseAcceptance.assertLicenseAccepted(this.getDockerImageName());\n            acceptLicense();\n        }\n    }\n\n    /**\n     * Returns the connection string.\n     *\n     * @return connection string\n     */\n    public String getConnectionString() {\n        return String.format(CONNECTION_STRING_FORMAT, getHost(), getMappedPort(DEFAULT_AMQP_PORT));\n    }\n}\n"
  },
  {
    "path": "modules/azure/src/main/java/org/testcontainers/azure/ServiceBusEmulatorContainer.java",
    "content": "package org.testcontainers.azure;\n\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.images.builder.Transferable;\nimport org.testcontainers.mssqlserver.MSSQLServerContainer;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.LicenseAcceptance;\n\n/**\n * Testcontainers implementation for Azure Service Bus Emulator.\n * <p>\n * Supported image: {@code mcr.microsoft.com/azure-messaging/servicebus-emulator}\n * <p>\n * Exposed port: 5672\n */\npublic class ServiceBusEmulatorContainer extends GenericContainer<ServiceBusEmulatorContainer> {\n\n    private static final String CONNECTION_STRING_FORMAT =\n        \"Endpoint=sb://%s:%d;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=SAS_KEY_VALUE;UseDevelopmentEmulator=true;\";\n\n    private static final int DEFAULT_PORT = 5672;\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\n        \"mcr.microsoft.com/azure-messaging/servicebus-emulator\"\n    );\n\n    private MSSQLServerContainer msSqlServerContainer;\n\n    /**\n     * @param dockerImageName The specified docker image name to run\n     */\n    public ServiceBusEmulatorContainer(final String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    /**\n     * @param dockerImageName The specified docker image name to run\n     */\n    public ServiceBusEmulatorContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n        withExposedPorts(DEFAULT_PORT);\n        withEnv(\"SQL_WAIT_INTERVAL\", \"0\");\n        waitingFor(Wait.forLogMessage(\".*Emulator Service is Successfully Up!.*\", 1));\n    }\n\n    /**\n     * Sets the MS SQL Server dependency needed by the Service Bus Container,\n     *\n     * @param msSqlServerContainer The MS SQL Server container used by Service Bus as a dependency\n     * @return this\n     */\n    public ServiceBusEmulatorContainer withMsSqlServerContainer(final MSSQLServerContainer msSqlServerContainer) {\n        dependsOn(msSqlServerContainer);\n        this.msSqlServerContainer = msSqlServerContainer;\n        return this;\n    }\n\n    /**\n     * Provide the Service Bus configuration JSON.\n     *\n     * @param config The configuration\n     * @return this\n     */\n    public ServiceBusEmulatorContainer withConfig(final Transferable config) {\n        withCopyToContainer(config, \"/ServiceBus_Emulator/ConfigFiles/Config.json\");\n        return this;\n    }\n\n    /**\n     * Accepts the EULA of the container.\n     *\n     * @return this\n     */\n    public ServiceBusEmulatorContainer acceptLicense() {\n        withEnv(\"ACCEPT_EULA\", \"Y\");\n        return this;\n    }\n\n    @Override\n    protected void configure() {\n        if (msSqlServerContainer == null) {\n            throw new IllegalStateException(\n                \"The image \" +\n                getDockerImageName() +\n                \" requires a Microsoft SQL Server container. Please provide one with the withMsSqlServerContainer method!\"\n            );\n        }\n        withEnv(\"SQL_SERVER\", msSqlServerContainer.getNetworkAliases().get(0));\n        withEnv(\"MSSQL_SA_PASSWORD\", msSqlServerContainer.getPassword());\n        // If license was not accepted programmatically, check if it was accepted via resource file\n        if (!getEnvMap().containsKey(\"ACCEPT_EULA\")) {\n            LicenseAcceptance.assertLicenseAccepted(this.getDockerImageName());\n            acceptLicense();\n        }\n    }\n\n    /**\n     * Returns the connection string.\n     *\n     * @return connection string\n     */\n    public String getConnectionString() {\n        return String.format(CONNECTION_STRING_FORMAT, getHost(), getMappedPort(DEFAULT_PORT));\n    }\n}\n"
  },
  {
    "path": "modules/azure/src/main/java/org/testcontainers/containers/CosmosDBEmulatorContainer.java",
    "content": "package org.testcontainers.containers;\n\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.security.KeyStore;\n\n/**\n * Testcontainers implementation for CosmosDB Emulator.\n * <p>\n * Supported image: {@code mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator}\n * <p>\n * Exposed ports: 8081\n */\npublic class CosmosDBEmulatorContainer extends GenericContainer<CosmosDBEmulatorContainer> {\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\n        \"mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator\"\n    );\n\n    private static final int PORT = 8081;\n\n    /**\n     * @param dockerImageName specified docker image name to run\n     */\n    public CosmosDBEmulatorContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n        withExposedPorts(PORT);\n        waitingFor(Wait.forLogMessage(\".*Started\\\\r\\\\n$\", 1));\n    }\n\n    /**\n     * @return new KeyStore built with PKCS12\n     */\n    public KeyStore buildNewKeyStore() {\n        return KeyStoreBuilder.buildByDownloadingCertificate(getEmulatorEndpoint(), getEmulatorKey());\n    }\n\n    /**\n     * Emulator key is a known constant and specified in Azure Cosmos DB Documents.\n     * This key is also used as password for emulator certificate file.\n     *\n     * @return predefined emulator key\n     * @see <a href=\"https://docs.microsoft.com/en-us/azure/cosmos-db/local-emulator?tabs=ssl-netstd21#authenticate-requests\">Azure Cosmos DB Documents</a>\n     */\n    public String getEmulatorKey() {\n        return \"C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==\";\n    }\n\n    /**\n     * @return secure https emulator endpoint to send requests\n     */\n    public String getEmulatorEndpoint() {\n        return \"https://\" + getHost() + \":\" + getMappedPort(PORT);\n    }\n}\n"
  },
  {
    "path": "modules/azure/src/main/java/org/testcontainers/containers/KeyStoreBuilder.java",
    "content": "package org.testcontainers.containers;\n\nimport okhttp3.Cache;\nimport okhttp3.OkHttpClient;\nimport okhttp3.Request;\nimport okhttp3.Response;\n\nimport java.io.InputStream;\nimport java.security.KeyStore;\nimport java.security.SecureRandom;\nimport java.security.cert.Certificate;\nimport java.security.cert.CertificateFactory;\nimport java.security.cert.X509Certificate;\nimport java.util.Objects;\n\nimport javax.net.ssl.SSLContext;\nimport javax.net.ssl.SSLSocketFactory;\nimport javax.net.ssl.TrustManager;\nimport javax.net.ssl.X509TrustManager;\n\nfinal class KeyStoreBuilder {\n\n    static KeyStore buildByDownloadingCertificate(String endpoint, String keyStorePassword) {\n        OkHttpClient client = null;\n        Response response = null;\n        try {\n            TrustManager[] trustAllManagers = buildTrustAllManagers();\n            client = buildTrustAllClient(trustAllManagers);\n            Request request = buildRequest(endpoint);\n            response = client.newCall(request).execute();\n            return buildKeyStore(response.body().byteStream(), keyStorePassword);\n        } catch (Exception ex) {\n            throw new IllegalStateException(ex);\n        } finally {\n            closeResponseSilently(response);\n            closeClientSilently(client);\n        }\n    }\n\n    private static TrustManager[] buildTrustAllManagers() {\n        return new TrustManager[] {\n            new X509TrustManager() {\n                @Override\n                public void checkClientTrusted(X509Certificate[] chain, String authType) {}\n\n                @Override\n                public void checkServerTrusted(X509Certificate[] chain, String authType) {}\n\n                @Override\n                public X509Certificate[] getAcceptedIssuers() {\n                    return new X509Certificate[] {};\n                }\n            },\n        };\n    }\n\n    private static OkHttpClient buildTrustAllClient(TrustManager[] trustManagers) throws Exception {\n        SSLContext sslContext = SSLContext.getInstance(\"SSL\");\n        sslContext.init(null, trustManagers, new SecureRandom());\n        SSLSocketFactory socketFactory = sslContext.getSocketFactory();\n        return new OkHttpClient.Builder()\n            .sslSocketFactory(socketFactory, (X509TrustManager) trustManagers[0])\n            .hostnameVerifier((s, sslSession) -> true)\n            .build();\n    }\n\n    private static Request buildRequest(String endpoint) {\n        return new Request.Builder().get().url(endpoint + \"/_explorer/emulator.pem\").build();\n    }\n\n    private static KeyStore buildKeyStore(InputStream certificateStream, String keyStorePassword) throws Exception {\n        Certificate certificate = CertificateFactory.getInstance(\"X.509\").generateCertificate(certificateStream);\n        KeyStore keystore = KeyStore.getInstance(\"PKCS12\");\n        keystore.load(null, keyStorePassword.toCharArray());\n        keystore.setCertificateEntry(\"azure-cosmos-emulator\", certificate);\n        return keystore;\n    }\n\n    private static void closeResponseSilently(Response response) {\n        try {\n            if (Objects.nonNull(response)) {\n                response.close();\n            }\n        } catch (Exception ignored) {}\n    }\n\n    private static void closeClientSilently(OkHttpClient client) {\n        try {\n            if (Objects.nonNull(client)) {\n                client.dispatcher().executorService().shutdown();\n                client.connectionPool().evictAll();\n                Cache cache = client.cache();\n                if (Objects.nonNull(cache)) {\n                    cache.close();\n                }\n            }\n        } catch (Exception ignored) {}\n    }\n}\n"
  },
  {
    "path": "modules/azure/src/test/java/org/testcontainers/azure/AzuriteContainerTest.java",
    "content": "package org.testcontainers.azure;\n\nimport com.azure.core.util.BinaryData;\nimport com.azure.data.tables.TableClient;\nimport com.azure.data.tables.TableServiceClient;\nimport com.azure.data.tables.TableServiceClientBuilder;\nimport com.azure.storage.blob.BlobClient;\nimport com.azure.storage.blob.BlobContainerClient;\nimport com.azure.storage.blob.BlobServiceClient;\nimport com.azure.storage.blob.BlobServiceClientBuilder;\nimport com.azure.storage.queue.QueueClient;\nimport com.azure.storage.queue.QueueServiceClient;\nimport com.azure.storage.queue.QueueServiceClientBuilder;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.utility.MountableFile;\n\nimport java.util.Properties;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass AzuriteContainerTest {\n\n    private static final String PASSWORD = \"changeit\";\n\n    private static Properties originalSystemProperties;\n\n    @BeforeAll\n    public static void captureOriginalSystemProperties() {\n        originalSystemProperties = (Properties) System.getProperties().clone();\n        System.setProperty(\n            \"javax.net.ssl.trustStore\",\n            MountableFile.forClasspathResource(\"/keystore.pfx\").getFilesystemPath()\n        );\n        System.setProperty(\"javax.net.ssl.trustStorePassword\", PASSWORD);\n        System.setProperty(\"javax.net.ssl.trustStoreType\", \"PKCS12\");\n    }\n\n    @AfterAll\n    public static void restoreOriginalSystemProperties() {\n        System.setProperties(originalSystemProperties);\n    }\n\n    @Test\n    void testWithBlobServiceClient() {\n        try (\n            // emulatorContainer {\n            AzuriteContainer emulator = new AzuriteContainer(\"mcr.microsoft.com/azure-storage/azurite:3.33.0\")\n            // }\n        ) {\n            emulator.start();\n            assertThat(emulator.getConnectionString()).contains(\"BlobEndpoint=http://\");\n            testBlob(emulator);\n        }\n    }\n\n    @Test\n    void testWithQueueServiceClient() {\n        try (AzuriteContainer emulator = new AzuriteContainer(\"mcr.microsoft.com/azure-storage/azurite:3.33.0\")) {\n            emulator.start();\n            assertThat(emulator.getConnectionString()).contains(\"QueueEndpoint=http://\");\n            testQueue(emulator);\n        }\n    }\n\n    @Test\n    void testWithTableServiceClient() {\n        try (AzuriteContainer emulator = new AzuriteContainer(\"mcr.microsoft.com/azure-storage/azurite:3.33.0\")) {\n            emulator.start();\n            assertThat(emulator.getConnectionString()).contains(\"TableEndpoint=http://\");\n            testTable(emulator);\n        }\n    }\n\n    @Test\n    void testWithBlobServiceClientWithSslUsingPfx() {\n        try (\n            AzuriteContainer emulator = new AzuriteContainer(\"mcr.microsoft.com/azure-storage/azurite:3.33.0\")\n                .withSsl(MountableFile.forClasspathResource(\"/keystore.pfx\"), PASSWORD)\n        ) {\n            emulator.start();\n            assertThat(emulator.getConnectionString()).contains(\"BlobEndpoint=https://\");\n            testBlob(emulator);\n        }\n    }\n\n    @Test\n    void testWithQueueServiceClientWithSslUsingPfx() {\n        try (\n            AzuriteContainer emulator = new AzuriteContainer(\"mcr.microsoft.com/azure-storage/azurite:3.33.0\")\n                .withSsl(MountableFile.forClasspathResource(\"/keystore.pfx\"), PASSWORD)\n        ) {\n            emulator.start();\n            assertThat(emulator.getConnectionString()).contains(\"QueueEndpoint=https://\");\n            testQueue(emulator);\n        }\n    }\n\n    @Test\n    void testWithTableServiceClientWithSslUsingPfx() {\n        try (\n            AzuriteContainer emulator = new AzuriteContainer(\"mcr.microsoft.com/azure-storage/azurite:3.33.0\")\n                .withSsl(MountableFile.forClasspathResource(\"/keystore.pfx\"), PASSWORD)\n        ) {\n            emulator.start();\n            assertThat(emulator.getConnectionString()).contains(\"TableEndpoint=https://\");\n            testTable(emulator);\n        }\n    }\n\n    @Test\n    void testWithBlobServiceClientWithSslUsingPem() {\n        try (\n            AzuriteContainer emulator = new AzuriteContainer(\"mcr.microsoft.com/azure-storage/azurite:3.33.0\")\n                .withSsl(\n                    MountableFile.forClasspathResource(\"/certificate.pem\"),\n                    MountableFile.forClasspathResource(\"/key.pem\")\n                )\n        ) {\n            emulator.start();\n            assertThat(emulator.getConnectionString()).contains(\"BlobEndpoint=https://\");\n            testBlob(emulator);\n        }\n    }\n\n    @Test\n    void testWithQueueServiceClientWithSslUsingPem() {\n        try (\n            AzuriteContainer emulator = new AzuriteContainer(\"mcr.microsoft.com/azure-storage/azurite:3.33.0\")\n                .withSsl(\n                    MountableFile.forClasspathResource(\"/certificate.pem\"),\n                    MountableFile.forClasspathResource(\"/key.pem\")\n                )\n        ) {\n            emulator.start();\n            assertThat(emulator.getConnectionString()).contains(\"QueueEndpoint=https://\");\n            testQueue(emulator);\n        }\n    }\n\n    @Test\n    void testWithTableServiceClientWithSslUsingPem() {\n        try (\n            AzuriteContainer emulator = new AzuriteContainer(\"mcr.microsoft.com/azure-storage/azurite:3.33.0\")\n                .withSsl(\n                    MountableFile.forClasspathResource(\"/certificate.pem\"),\n                    MountableFile.forClasspathResource(\"/key.pem\")\n                )\n        ) {\n            emulator.start();\n            assertThat(emulator.getConnectionString()).contains(\"TableEndpoint=https://\");\n            testTable(emulator);\n        }\n    }\n\n    @Test\n    void testTwoAccountKeysWithBlobServiceClient() {\n        try (\n            // withTwoAccountKeys {\n            AzuriteContainer emulator = new AzuriteContainer(\"mcr.microsoft.com/azure-storage/azurite:3.33.0\")\n                .withEnv(\"AZURITE_ACCOUNTS\", \"account1:key1:key2\")\n            // }\n        ) {\n            emulator.start();\n\n            String connectionString1 = emulator.getConnectionString(\"account1\", \"key1\");\n            // the second account will have access to the same container using a different key\n            String connectionString2 = emulator.getConnectionString(\"account1\", \"key2\");\n\n            BlobServiceClient blobServiceClient1 = new BlobServiceClientBuilder()\n                .connectionString(connectionString1)\n                .buildClient();\n\n            BlobContainerClient containerClient1 = blobServiceClient1.createBlobContainer(\"test-container\");\n            BlobClient blobClient1 = containerClient1.getBlobClient(\"test-blob.txt\");\n            blobClient1.upload(BinaryData.fromString(\"content\"));\n            boolean existsWithAccount1 = blobClient1.exists();\n            String contentWithAccount1 = blobClient1.downloadContent().toString();\n\n            BlobServiceClient blobServiceClient2 = new BlobServiceClientBuilder()\n                .connectionString(connectionString2)\n                .buildClient();\n            BlobContainerClient containerClient2 = blobServiceClient2.getBlobContainerClient(\"test-container\");\n            BlobClient blobClient2 = containerClient2.getBlobClient(\"test-blob.txt\");\n            boolean existsWithAccount2 = blobClient2.exists();\n            String contentWithAccount2 = blobClient2.downloadContent().toString();\n\n            assertThat(existsWithAccount1).isTrue();\n            assertThat(contentWithAccount1).isEqualTo(\"content\");\n            assertThat(existsWithAccount2).isTrue();\n            assertThat(contentWithAccount2).isEqualTo(\"content\");\n        }\n    }\n\n    @Test\n    void testMultipleAccountsWithBlobServiceClient() {\n        try (\n            // withMoreAccounts {\n            AzuriteContainer emulator = new AzuriteContainer(\"mcr.microsoft.com/azure-storage/azurite:3.33.0\")\n                .withEnv(\"AZURITE_ACCOUNTS\", \"account1:key1;account2:key2\")\n            // }\n        ) {\n            emulator.start();\n\n            // useNonDefaultCredentials {\n            String connectionString1 = emulator.getConnectionString(\"account1\", \"key1\");\n            // the second account will not have access to the same container\n            String connectionString2 = emulator.getConnectionString(\"account2\", \"key2\");\n            // }\n            BlobServiceClient blobServiceClient1 = new BlobServiceClientBuilder()\n                .connectionString(connectionString1)\n                .buildClient();\n\n            BlobContainerClient containerClient1 = blobServiceClient1.createBlobContainer(\"test-container\");\n            BlobClient blobClient1 = containerClient1.getBlobClient(\"test-blob.txt\");\n            blobClient1.upload(BinaryData.fromString(\"content\"));\n            boolean existsWithAccount1 = blobClient1.exists();\n            String contentWithAccount1 = blobClient1.downloadContent().toString();\n\n            BlobServiceClient blobServiceClient2 = new BlobServiceClientBuilder()\n                .connectionString(connectionString2)\n                .buildClient();\n            BlobContainerClient containerClient2 = blobServiceClient2.createBlobContainer(\"test-container\");\n            BlobClient blobClient2 = containerClient2.getBlobClient(\"test-blob.txt\");\n            boolean existsWithAccount2 = blobClient2.exists();\n\n            assertThat(existsWithAccount1).isTrue();\n            assertThat(contentWithAccount1).isEqualTo(\"content\");\n            assertThat(existsWithAccount2).isFalse();\n        }\n    }\n\n    private void testBlob(AzuriteContainer container) {\n        // createBlobClient {\n        BlobServiceClient blobServiceClient = new BlobServiceClientBuilder()\n            .connectionString(container.getConnectionString())\n            .buildClient();\n        // }\n        BlobContainerClient containerClient = blobServiceClient.createBlobContainer(\"test-container\");\n\n        assertThat(containerClient.exists()).isTrue();\n    }\n\n    private void testQueue(AzuriteContainer container) {\n        // createQueueClient {\n        QueueServiceClient queueServiceClient = new QueueServiceClientBuilder()\n            .connectionString(container.getConnectionString())\n            .buildClient();\n        // }\n        QueueClient queueClient = queueServiceClient.createQueue(\"test-queue\");\n\n        assertThat(queueClient.getQueueUrl()).isNotNull();\n    }\n\n    private void testTable(AzuriteContainer container) {\n        // createTableClient {\n        TableServiceClient tableServiceClient = new TableServiceClientBuilder()\n            .connectionString(container.getConnectionString())\n            .buildClient();\n        // }\n        TableClient tableClient = tableServiceClient.createTable(\"testtable\");\n\n        assertThat(tableClient.getTableEndpoint()).isNotNull();\n    }\n}\n"
  },
  {
    "path": "modules/azure/src/test/java/org/testcontainers/azure/EventHubsEmulatorContainerTest.java",
    "content": "package org.testcontainers.azure;\n\nimport com.azure.core.util.IterableStream;\nimport com.azure.messaging.eventhubs.EventData;\nimport com.azure.messaging.eventhubs.EventHubClientBuilder;\nimport com.azure.messaging.eventhubs.EventHubConsumerClient;\nimport com.azure.messaging.eventhubs.EventHubProducerClient;\nimport com.azure.messaging.eventhubs.models.EventPosition;\nimport com.azure.messaging.eventhubs.models.PartitionEvent;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.Network;\nimport org.testcontainers.utility.MountableFile;\n\nimport java.time.Duration;\nimport java.util.Collections;\nimport java.util.Optional;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.waitAtMost;\n\nclass EventHubsEmulatorContainerTest {\n\n    @Test\n    public void testWithEventHubsClient() {\n        try (\n            // network {\n            Network network = Network.newNetwork();\n            // }\n            // azuriteContainer {\n            AzuriteContainer azuriteContainer = new AzuriteContainer(\"mcr.microsoft.com/azure-storage/azurite:3.33.0\")\n                .withNetwork(network);\n            // }\n            // emulatorContainer {\n            EventHubsEmulatorContainer emulator = new EventHubsEmulatorContainer(\n                \"mcr.microsoft.com/azure-messaging/eventhubs-emulator:2.0.1\"\n            )\n                .acceptLicense()\n                .withNetwork(network)\n                .withConfig(MountableFile.forClasspathResource(\"/eventhubs_config.json\"))\n                .withAzuriteContainer(azuriteContainer);\n            // }\n        ) {\n            emulator.start();\n            // createProducerAndConsumer {\n            EventHubProducerClient producer = new EventHubClientBuilder()\n                .connectionString(emulator.getConnectionString())\n                .fullyQualifiedNamespace(\"emulatorNs1\")\n                .eventHubName(\"eh1\")\n                .buildProducerClient();\n            EventHubConsumerClient consumer = new EventHubClientBuilder()\n                .connectionString(emulator.getConnectionString())\n                .fullyQualifiedNamespace(\"emulatorNs1\")\n                .eventHubName(\"eh1\")\n                .consumerGroup(\"cg1\")\n                .buildConsumerClient();\n            // }\n            producer.send(Collections.singletonList(new EventData(\"test\")));\n\n            waitAtMost(Duration.ofSeconds(30))\n                .pollDelay(Duration.ofSeconds(5))\n                .untilAsserted(() -> {\n                    IterableStream<PartitionEvent> events = consumer.receiveFromPartition(\n                        \"0\",\n                        1,\n                        EventPosition.earliest(),\n                        Duration.ofSeconds(2)\n                    );\n                    Optional<PartitionEvent> event = events.stream().findFirst();\n                    assertThat(event).isPresent();\n                    assertThat(event.get().getData().getBodyAsString()).isEqualTo(\"test\");\n                });\n        }\n    }\n}\n"
  },
  {
    "path": "modules/azure/src/test/java/org/testcontainers/azure/ServiceBusEmulatorContainerTest.java",
    "content": "package org.testcontainers.azure;\n\nimport com.azure.messaging.servicebus.ServiceBusClientBuilder;\nimport com.azure.messaging.servicebus.ServiceBusErrorContext;\nimport com.azure.messaging.servicebus.ServiceBusException;\nimport com.azure.messaging.servicebus.ServiceBusMessage;\nimport com.azure.messaging.servicebus.ServiceBusProcessorClient;\nimport com.azure.messaging.servicebus.ServiceBusReceivedMessageContext;\nimport com.azure.messaging.servicebus.ServiceBusSenderClient;\nimport com.github.dockerjava.api.model.Capability;\nimport org.assertj.core.api.Assertions;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.Network;\nimport org.testcontainers.mssqlserver.MSSQLServerContainer;\nimport org.testcontainers.utility.MountableFile;\n\nimport java.util.List;\nimport java.util.concurrent.CopyOnWriteArrayList;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.Consumer;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\n\nclass ServiceBusEmulatorContainerTest {\n\n    @Test\n    void testWithClient() {\n        try (\n            // network {\n            Network network = Network.newNetwork();\n            // }\n            // sqlContainer {\n            MSSQLServerContainer mssqlServerContainer = new MSSQLServerContainer(\n                \"mcr.microsoft.com/mssql/server:2022-CU14-ubuntu-22.04\"\n            )\n                .acceptLicense()\n                .withPassword(\"yourStrong(!)Password\")\n                .withCreateContainerCmdModifier(cmd -> {\n                    cmd.getHostConfig().withCapAdd(Capability.SYS_PTRACE);\n                })\n                .withNetwork(network);\n            // }\n            // emulatorContainer {\n            ServiceBusEmulatorContainer emulator = new ServiceBusEmulatorContainer(\n                \"mcr.microsoft.com/azure-messaging/servicebus-emulator:1.1.2\"\n            )\n                .acceptLicense()\n                .withConfig(MountableFile.forClasspathResource(\"/service-bus-config.json\"))\n                .withNetwork(network)\n                .withMsSqlServerContainer(mssqlServerContainer);\n            // }\n        ) {\n            emulator.start();\n            assertThat(emulator.getConnectionString()).startsWith(\"Endpoint=sb://\");\n\n            // senderClient {\n            ServiceBusSenderClient senderClient = new ServiceBusClientBuilder()\n                .connectionString(emulator.getConnectionString())\n                .sender()\n                .queueName(\"queue.1\")\n                .buildClient();\n            // }\n\n            await()\n                .atMost(20, TimeUnit.SECONDS)\n                .ignoreException(ServiceBusException.class)\n                .until(() -> {\n                    senderClient.sendMessage(new ServiceBusMessage(\"Hello, Testcontainers!\"));\n                    return true;\n                });\n            senderClient.close();\n\n            final List<String> received = new CopyOnWriteArrayList<>();\n            Consumer<ServiceBusReceivedMessageContext> messageConsumer = m -> {\n                received.add(m.getMessage().getBody().toString());\n                m.complete();\n            };\n            Consumer<ServiceBusErrorContext> errorConsumer = e -> Assertions.fail(\"Unexpected error: \" + e);\n            // processorClient {\n            ServiceBusProcessorClient processorClient = new ServiceBusClientBuilder()\n                .connectionString(emulator.getConnectionString())\n                .processor()\n                .queueName(\"queue.1\")\n                .processMessage(messageConsumer)\n                .processError(errorConsumer)\n                .buildProcessorClient();\n            // }\n            processorClient.start();\n\n            await()\n                .atMost(20, TimeUnit.SECONDS)\n                .untilAsserted(() -> {\n                    assertThat(received).hasSize(1).containsExactlyInAnyOrder(\"Hello, Testcontainers!\");\n                });\n            processorClient.close();\n        }\n    }\n}\n"
  },
  {
    "path": "modules/azure/src/test/java/org/testcontainers/containers/CosmosDBEmulatorContainerTest.java",
    "content": "package org.testcontainers.containers;\n\nimport com.azure.cosmos.CosmosAsyncClient;\nimport com.azure.cosmos.CosmosClientBuilder;\nimport com.azure.cosmos.models.CosmosContainerResponse;\nimport com.azure.cosmos.models.CosmosDatabaseResponse;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.FileOutputStream;\nimport java.nio.file.Path;\nimport java.security.KeyStore;\nimport java.util.Properties;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass CosmosDBEmulatorContainerTest {\n\n    private static Properties originalSystemProperties;\n\n    @BeforeAll\n    public static void captureOriginalSystemProperties() {\n        originalSystemProperties = (Properties) System.getProperties().clone();\n    }\n\n    @AfterAll\n    public static void restoreOriginalSystemProperties() {\n        System.setProperties(originalSystemProperties);\n    }\n\n    @TempDir\n    public Path tempFolder;\n\n    @Test\n    void testWithCosmosClient() throws Exception {\n        try (\n            // emulatorContainer {\n            CosmosDBEmulatorContainer emulator = new CosmosDBEmulatorContainer(\n                DockerImageName.parse(\"mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator:latest\")\n            );\n            // }\n        ) {\n            emulator.start();\n            // buildAndSaveNewKeyStore {\n            Path keyStoreFile = tempFolder.resolve(\"azure-cosmos-emulator.keystore\");\n            KeyStore keyStore = emulator.buildNewKeyStore();\n            keyStore.store(new FileOutputStream(keyStoreFile.toFile()), emulator.getEmulatorKey().toCharArray());\n            // }\n            // setSystemTrustStoreParameters {\n            System.setProperty(\"javax.net.ssl.trustStore\", keyStoreFile.toString());\n            System.setProperty(\"javax.net.ssl.trustStorePassword\", emulator.getEmulatorKey());\n            System.setProperty(\"javax.net.ssl.trustStoreType\", \"PKCS12\");\n            // }\n            // buildClient {\n            CosmosAsyncClient client = new CosmosClientBuilder()\n                .gatewayMode()\n                .endpointDiscoveryEnabled(false)\n                .endpoint(emulator.getEmulatorEndpoint())\n                .key(emulator.getEmulatorKey())\n                .buildAsyncClient();\n            // }\n            // testWithClientAgainstEmulatorContainer {\n            CosmosDatabaseResponse databaseResponse = client.createDatabaseIfNotExists(\"Azure\").block();\n            assertThat(databaseResponse.getStatusCode()).isEqualTo(201);\n            CosmosContainerResponse containerResponse = client\n                .getDatabase(\"Azure\")\n                .createContainerIfNotExists(\"ServiceContainer\", \"/name\")\n                .block();\n            assertThat(containerResponse.getStatusCode()).isEqualTo(201);\n            // }\n        }\n    }\n}\n"
  },
  {
    "path": "modules/azure/src/test/resources/certificate.pem",
    "content": "Bag Attributes\n    friendlyName: localhost\n    localKeyID: 54 69 6D 65 20 31 37 33 34 37 32 32 33 32 31 33 31 39 \nsubject=CN = localhost\nissuer=CN = localhost\n-----BEGIN CERTIFICATE-----\nMIIC5zCCAc+gAwIBAgIILe7i2bhRE5cwDQYJKoZIhvcNAQEMBQAwFDESMBAGA1UE\nAxMJbG9jYWxob3N0MB4XDTI0MTIyMDE5MTg0MVoXDTQ0MTIxNTE5MTg0MVowFDES\nMBAGA1UEAxMJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC\nAQEAoqYNmLl8IiIoYrXdcoWiMQaM0lOHcV9v3A/THMremHxsR+JPm3FIOAuilFcy\nmy16kuXIWHfisPxUWr9Vbf8wP/WwZutoOofJrqmruZoorQcNLCs8mQweguRmL1ju\n/lDh/9bP626vP9OjwStC4UO4f8Jga8ENoH1U+j1RsIAswYnkk3YIN6YrYv66UvtH\nIfR0ERgid2LMBIM+2KD2zw4QRyqXH7Qvo7sCsxdYYHGa6GXfza4vgvce9kJwGqn5\nwiF0Uw9XQbr/LarnR2GCy020OB81KweQJNIh27FZSRLtT+XpsjDRcC2aLBd8CRHd\nhwO2zAPI04dLbLM5XAHlEdfT7wIDAQABoz0wOzAdBgNVHQ4EFgQUPqY5isb6Q11Q\nt6dbXYHEupxADdMwGgYDVR0RBBMwEYIJbG9jYWxob3N0hwR/AAABMA0GCSqGSIb3\nDQEBDAUAA4IBAQA2katMXrTJBukiNh9yceLO/MewsxvU3KOO/O89ngfjhKXm9T8E\nRtENCmp7hLbj1Aj4PRZx3AbmUt9+tRu8fmrRXJQWgUDSHJWjDwSTBOaHcC5LDWSU\nEx4co5Mnxvrimg7tqQg82Hw/yLH9j6gyTyh6v45QETP7IUkTZe4fg75/kPjng7Xg\nwp/QXFUx/f0dbvGRl2Fdgg0SnYFqHS3MFIjjFjv8SQlV7rZe+CD1Lxqy/Z6Fd/Fa\n33TzTuJeSAG43vdkGAvsNK/KdnxAW03T4l3pVHpNPcvsIvJUMeKOwYOjwHF/eowk\ntGrKbpUYFxUr9iKHTfu14t1oExhAsnda2Fcs\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "modules/azure/src/test/resources/eventhubs_config.json",
    "content": "{\n    \"UserConfig\": {\n        \"NamespaceConfig\": [\n            {\n                \"Type\": \"EventHub\",\n                \"Name\": \"emulatorNs1\",\n                \"Entities\": [\n                    {\n                        \"Name\": \"eh1\",\n                        \"PartitionCount\": \"1\",\n                        \"ConsumerGroups\": [\n                            {\n                                \"Name\": \"cg1\"\n                            }\n                        ]\n                    }\n                ]\n            }\n        ],\n        \"LoggingConfig\": {\n            \"Type\": \"File\"\n        }\n    }\n}\n"
  },
  {
    "path": "modules/azure/src/test/resources/key.pem",
    "content": "Bag Attributes\n    friendlyName: localhost\n    localKeyID: 54 69 6D 65 20 31 37 33 34 37 32 32 33 32 31 33 31 39 \nKey Attributes: <No Attributes>\n-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCipg2YuXwiIihi\ntd1yhaIxBozSU4dxX2/cD9Mcyt6YfGxH4k+bcUg4C6KUVzKbLXqS5chYd+Kw/FRa\nv1Vt/zA/9bBm62g6h8muqau5miitBw0sKzyZDB6C5GYvWO7+UOH/1s/rbq8/06PB\nK0LhQ7h/wmBrwQ2gfVT6PVGwgCzBieSTdgg3piti/rpS+0ch9HQRGCJ3YswEgz7Y\noPbPDhBHKpcftC+juwKzF1hgcZroZd/Nri+C9x72QnAaqfnCIXRTD1dBuv8tqudH\nYYLLTbQ4HzUrB5Ak0iHbsVlJEu1P5emyMNFwLZosF3wJEd2HA7bMA8jTh0tsszlc\nAeUR19PvAgMBAAECggEAFT8dzZKFTawqnGJncBtWyZKyeJMiwUOXSCblDADQPRkb\nx/QfNA4DQhb7AOe3G6BAP8o2dqAKg9YiasxNq5XHRsOgbIFZ1zN/vAo7/X3OzHN8\nXAW138Q+hBiz5IF4js4gB5yXAokt6WeLH6O4E9cV1dKdZ9YLIqjcnee+sRC9R/a3\nCexqLfC6b77JFbtePfq+5cn2RiK540tO/4k+F+kfJtTg78Wf2RB3A0pBAunhPSd5\neyjiSvOZtTcvl4GdYw9nKf24I1/WUvt9FH/r1XG0CM5iwuGodbBz1iSUDaQGi5Lf\nhFWofXt7eebgsPEKciG4xTyk51p9fy9y8asY+jCbkQKBgQDG2UqJToR8G4Fk6uaO\n/XJa15TibIwQDEota0OdlXg2ZR4864fkIBv+UTbymZEM/EBuSdMM1CUBDvYHcFQX\nAj8p1LUyKP2QYwxV/OoPfJ5fBqxqONNR1fLFg7xCxnf9kSvsni2WFneQUrTDl8+7\nqnHm4IKPkAxZ4Orxl5qIBmlpGQKBgQDRZUL3cHIVLLg/aZACpo6SYDYg2bztXmz4\nlRk9j17q1uS83Umzd2lPFmSt/Nr85EKraxXZ/lYPKrP/r1pf1/35eXOWqmYBWgo/\nHh7OzL12bhvv9UWEY/TvW+wNJNtXlJSjEFRN4tjoG2amYumyhwMO1lIulplUWvtw\nymm8hDjeRwKBgCq7n60KVqZlMtWBNbMc/GpRUgmm0iLQwVApcQp4iLEH4gutgjKg\nQ+PPiENyhR2JSD9rVhO3s4warvzCQw/+x5wxvg7diEBzSL9h7tsNKOu6/2qEc8Vu\neRHBUb/37ulrPUlIZPuQMHmvjHFMOrRV2MyJCwXXKxBVqafpsKfy2MxhAoGBAIHH\nCswk6u/ouYDDwjeCVxatfp65lHhhb5RZhD09IIzYBwhu9gC+34veyyNydZ8LMa7g\nPbjQAzJ/OvQbEB4a1hPKjDMzBOmNjpAz8NAm4L4H3FTKZP16nhHDnPdAgpkzQzQV\nKMrk755bbTFuWH0HZIPLnT+2ou0/PltXeFUYdc59AoGBAIGfWgSOiw7aXbSQZFrO\n4S0v3VTwTaiGDVS4pkNRLlhEJUhy8+gbLv/zYDmFmGtqVhXTb/nd6DOdylp+W/HS\n8xNWBMWdlX/hVdSK7M0TdJvAaCaMidlquf5qZ2tGNNDeTUN1qbRH26pm8vdNZ3gr\nY/WWJGo0iEmwyB8RcFhvNmuJ\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "modules/azure/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/azure/src/test/resources/service-bus-config.json",
    "content": "{\n    \"UserConfig\": {\n        \"Namespaces\": [\n            {\n                \"Name\": \"sbemulatorns\",\n                \"Queues\": [\n                    {\n                        \"Name\": \"queue.1\",\n                        \"Properties\": {\n                            \"DeadLetteringOnMessageExpiration\": false,\n                            \"DefaultMessageTimeToLive\": \"PT1H\",\n                            \"DuplicateDetectionHistoryTimeWindow\": \"PT20S\",\n                            \"ForwardDeadLetteredMessagesTo\": \"\",\n                            \"ForwardTo\": \"\",\n                            \"LockDuration\": \"PT1M\",\n                            \"MaxDeliveryCount\": 3,\n                            \"RequiresDuplicateDetection\": false,\n                            \"RequiresSession\": false\n                        }\n                    }\n                ],\n                \"Topics\": []\n            }\n        ],\n        \"Logging\": {\n            \"Type\": \"File\"\n        }\n    }\n}\n"
  },
  {
    "path": "modules/build.gradle",
    "content": ""
  },
  {
    "path": "modules/cassandra/build.gradle",
    "content": "description = \"Testcontainers :: Cassandra\"\n\nconfigurations.all {\n    resolutionStrategy {\n        force 'io.dropwizard.metrics:metrics-core:3.2.6'\n    }\n}\n\ndependencies {\n    api project(\":testcontainers-database-commons\")\n    api \"com.datastax.cassandra:cassandra-driver-core:3.10.0\"\n\n    testImplementation 'com.datastax.oss:java-driver-core:4.17.0'\n}\n"
  },
  {
    "path": "modules/cassandra/src/main/java/org/testcontainers/cassandra/CassandraContainer.java",
    "content": "package org.testcontainers.cassandra;\n\nimport com.github.dockerjava.api.command.InspectContainerResponse;\nimport org.apache.commons.lang3.StringUtils;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.ext.ScriptUtils;\nimport org.testcontainers.ext.ScriptUtils.ScriptLoadException;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.MountableFile;\n\nimport java.net.InetSocketAddress;\nimport java.util.Optional;\n\n/**\n * Testcontainers implementation for Apache Cassandra.\n * <p>\n * Supported image: {@code cassandra}\n * <p>\n * Exposed ports: 9042\n */\npublic class CassandraContainer extends GenericContainer<CassandraContainer> {\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"cassandra\");\n\n    private static final Integer CQL_PORT = 9042;\n\n    private static final String DEFAULT_LOCAL_DATACENTER = \"datacenter1\";\n\n    private static final String DEFAULT_INIT_SCRIPT_FILENAME = \"init.cql\";\n\n    private static final String CONTAINER_CONFIG_LOCATION = \"/etc/cassandra\";\n\n    private static final String USERNAME = \"cassandra\";\n\n    private static final String PASSWORD = \"cassandra\";\n\n    private String configLocation;\n\n    private String initScriptPath;\n\n    private String clientCertFile;\n\n    private String clientKeyFile;\n\n    public CassandraContainer(String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    public CassandraContainer(DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n\n        addExposedPort(CQL_PORT);\n\n        withEnv(\"CASSANDRA_SNITCH\", \"GossipingPropertyFileSnitch\");\n        withEnv(\"JVM_OPTS\", \"-Dcassandra.skip_wait_for_gossip_to_settle=0 -Dcassandra.initial_token=0\");\n        withEnv(\"HEAP_NEWSIZE\", \"128M\");\n        withEnv(\"MAX_HEAP_SIZE\", \"1024M\");\n        withEnv(\"CASSANDRA_ENDPOINT_SNITCH\", \"GossipingPropertyFileSnitch\");\n        withEnv(\"CASSANDRA_DC\", DEFAULT_LOCAL_DATACENTER);\n\n        // Use the CassandraQueryWaitStrategy by default to avoid potential issues when the authentication is enabled.\n        waitingFor(new CassandraQueryWaitStrategy());\n    }\n\n    @Override\n    protected void configure() {\n        // Map (effectively replace) directory in Docker with the content of resourceLocation if resource location is\n        // not null.\n        Optional\n            .ofNullable(configLocation)\n            .map(MountableFile::forClasspathResource)\n            .ifPresent(mountableFile -> withCopyFileToContainer(mountableFile, CONTAINER_CONFIG_LOCATION));\n\n        // If a secure connection is required by Cassandra configuration, copy the user certificate and key to a\n        // dedicated location and define a cqlshrc file with the appropriate SSL configuration.\n        // See: https://docs.datastax.com/en/cassandra-oss/3.x/cassandra/configuration/secureCqlshSSL.html\n        if (isSslRequired()) {\n            withCopyFileToContainer(MountableFile.forClasspathResource(clientCertFile), \"ssl/user_cert.pem\");\n            withCopyFileToContainer(MountableFile.forClasspathResource(clientKeyFile), \"ssl/user_key.pem\");\n            withCopyFileToContainer(MountableFile.forClasspathResource(\"cqlshrc\"), \"/root/.cassandra/cqlshrc\");\n        }\n    }\n\n    @Override\n    protected void containerIsStarted(InspectContainerResponse containerInfo) {\n        runInitScriptIfRequired();\n    }\n\n    /**\n     * Load init script content and apply it to the database if initScriptPath is set\n     */\n    private void runInitScriptIfRequired() {\n        if (this.initScriptPath != null) {\n            try {\n                final MountableFile originalInitScript = MountableFile.forClasspathResource(this.initScriptPath);\n                // The init script is executed as is by the cqlsh command, so copy it into the container. The name\n                // of the script is generic since it's not important to keep the original name.\n                copyFileToContainer(originalInitScript, DEFAULT_INIT_SCRIPT_FILENAME);\n                new CassandraDatabaseDelegate(this).execute(null, DEFAULT_INIT_SCRIPT_FILENAME, -1, false, false);\n            } catch (IllegalArgumentException e) {\n                // MountableFile.forClasspathResource will throw an IllegalArgumentException if the resource cannot\n                // be found.\n                logger().warn(\"Could not load classpath init script: {}\", this.initScriptPath);\n                throw new ScriptLoadException(\n                    \"Could not load classpath init script: \" + this.initScriptPath + \". Resource not found.\",\n                    e\n                );\n            } catch (ScriptUtils.ScriptStatementFailedException e) {\n                logger().error(\"Error while executing init script: {}\", this.initScriptPath, e);\n                throw new ScriptUtils.UncategorizedScriptException(\n                    \"Error while executing init script: \" + this.initScriptPath,\n                    e\n                );\n            }\n        }\n    }\n\n    /**\n     * Initialize Cassandra with the custom overridden Cassandra configuration\n     * <p>\n     * Be aware, that Docker effectively replaces all /etc/cassandra content with the content of config location, so if\n     * Cassandra.yaml in configLocation is absent or corrupted, then Cassandra just won't launch.\n     *\n     * @param configLocation relative classpath with the directory that contains cassandra.yaml and other configuration\n     *                       files\n     * @return The updated {@link CassandraContainer}.\n     */\n    public CassandraContainer withConfigurationOverride(String configLocation) {\n        this.configLocation = configLocation;\n        return self();\n    }\n\n    /**\n     * Initialize Cassandra with init CQL script\n     * <p>\n     *     CQL script will be applied after container is started (see using WaitStrategy).\n     * </p>\n     *\n     * @param initScriptPath relative classpath resource\n     * @return The updated {@link CassandraContainer}.\n     */\n    public CassandraContainer withInitScript(String initScriptPath) {\n        this.initScriptPath = initScriptPath;\n        return self();\n    }\n\n    /**\n     * Configure secured connection (TLS) when required by the Cassandra configuration\n     * (i.e. cassandra.yaml file contains the property {@code client_encryption_options.optional} with value\n     * {@code false}).\n     *\n     * @param clientCertFile The client certificate required to execute CQL scripts.\n     * @param clientKeyFile  The client key required to execute CQL scripts.\n     * @return The updated {@link CassandraContainer}.\n     */\n    public CassandraContainer withSsl(String clientCertFile, String clientKeyFile) {\n        this.clientCertFile = clientCertFile;\n        this.clientKeyFile = clientKeyFile;\n        return self();\n    }\n\n    /**\n     * @return Whether a secure connection is required between the client and the Cassandra server.\n     */\n    boolean isSslRequired() {\n        return StringUtils.isNoneBlank(this.clientCertFile, this.clientKeyFile);\n    }\n\n    /**\n     * Get username\n     * <p>\n     * By default, Cassandra has authenticator: AllowAllAuthenticator in cassandra.yaml\n     * If username and password need to be used, then authenticator should be set as PasswordAuthenticator\n     * (through custom Cassandra configuration) and through CQL with default cassandra-cassandra credentials\n     * user management should be modified\n     */\n    public String getUsername() {\n        return USERNAME;\n    }\n\n    /**\n     * Get password\n     * <p>\n     * By default, Cassandra has authenticator: AllowAllAuthenticator in cassandra.yaml\n     * If username and password need to be used, then authenticator should be set as PasswordAuthenticator\n     * (through custom Cassandra configuration) and through CQL with default cassandra-cassandra credentials\n     * user management should be modified\n     */\n    public String getPassword() {\n        return PASSWORD;\n    }\n\n    /**\n     * Retrieve an {@link InetSocketAddress} for connecting to the Cassandra container via the driver.\n     *\n     * @return A InetSocketAddress representation of this Cassandra container's host and port.\n     */\n    public InetSocketAddress getContactPoint() {\n        return new InetSocketAddress(getHost(), getMappedPort(CQL_PORT));\n    }\n\n    /**\n     * Retrieve the Local Datacenter for connecting to the Cassandra container via the driver.\n     *\n     * @return The configured local Datacenter name.\n     */\n    public String getLocalDatacenter() {\n        return getEnvMap().getOrDefault(\"CASSANDRA_DC\", DEFAULT_LOCAL_DATACENTER);\n    }\n}\n"
  },
  {
    "path": "modules/cassandra/src/main/java/org/testcontainers/cassandra/CassandraDatabaseDelegate.java",
    "content": "package org.testcontainers.cassandra;\n\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.apache.commons.lang3.ArrayUtils;\nimport org.apache.commons.lang3.StringUtils;\nimport org.testcontainers.containers.Container;\nimport org.testcontainers.containers.ContainerState;\nimport org.testcontainers.containers.ExecConfig;\nimport org.testcontainers.delegate.AbstractDatabaseDelegate;\nimport org.testcontainers.ext.ScriptUtils.ScriptStatementFailedException;\n\nimport java.io.IOException;\n\n/**\n * Cassandra database delegate\n */\n@Slf4j\n@RequiredArgsConstructor\npublic class CassandraDatabaseDelegate extends AbstractDatabaseDelegate<Void> {\n\n    private final ContainerState container;\n\n    @Override\n    protected Void createNewConnection() {\n        // Return null here, because we run scripts using cqlsh command directly in the container.\n        // So, we don't use connection object in the execute() method.\n        return null;\n    }\n\n    public void execute(\n        String statement,\n        String scriptPath,\n        int lineNumber,\n        boolean continueOnError,\n        boolean ignoreFailedDrops,\n        boolean silentErrorLogs\n    ) {\n        try {\n            // Use cqlsh command directly inside the container to execute statements\n            // See documentation here: https://cassandra.apache.org/doc/stable/cassandra/tools/cqlsh.html\n            String[] cqlshCommand = new String[] { \"cqlsh\" };\n\n            if (this.container instanceof CassandraContainer) {\n                CassandraContainer cassandraContainer = (CassandraContainer) this.container;\n                String username = cassandraContainer.getUsername();\n                String password = cassandraContainer.getPassword();\n                if (cassandraContainer.isSslRequired()) {\n                    cqlshCommand = ArrayUtils.add(cqlshCommand, \"--ssl\");\n                }\n                cqlshCommand = ArrayUtils.addAll(cqlshCommand, \"-u\", username, \"-p\", password);\n            }\n\n            // If no statement specified, directly execute the script specified into scriptPath (using -f argument),\n            // otherwise execute the given statement (using -e argument).\n            String executeArg = \"-e\";\n            String executeArgValue = statement;\n            if (StringUtils.isBlank(statement)) {\n                executeArg = \"-f\";\n                executeArgValue = scriptPath;\n            }\n            cqlshCommand = ArrayUtils.addAll(cqlshCommand, executeArg, executeArgValue);\n\n            Container.ExecResult result =\n                this.container.execInContainer(ExecConfig.builder().command(cqlshCommand).build());\n            if (result.getExitCode() == 0) {\n                if (StringUtils.isBlank(statement)) {\n                    log.info(\"CQL script {} successfully executed\", scriptPath);\n                } else {\n                    log.info(\"CQL statement {} was applied\", statement);\n                }\n            } else {\n                if (!silentErrorLogs) {\n                    log.error(\"CQL script execution failed with error: \\n{}\", result.getStderr());\n                }\n                throw new ScriptStatementFailedException(statement, lineNumber, scriptPath);\n            }\n        } catch (IOException | InterruptedException e) {\n            throw new ScriptStatementFailedException(statement, lineNumber, scriptPath, e);\n        }\n    }\n\n    @Override\n    public void execute(\n        String statement,\n        String scriptPath,\n        int lineNumber,\n        boolean continueOnError,\n        boolean ignoreFailedDrops\n    ) {\n        this.execute(statement, scriptPath, lineNumber, continueOnError, ignoreFailedDrops, false);\n    }\n\n    @Override\n    protected void closeConnectionQuietly(Void session) {\n        // Nothing to do here, because we run scripts using cqlsh command directly in the container.\n    }\n}\n"
  },
  {
    "path": "modules/cassandra/src/main/java/org/testcontainers/cassandra/CassandraQueryWaitStrategy.java",
    "content": "package org.testcontainers.cassandra;\n\nimport lombok.extern.slf4j.Slf4j;\nimport org.apache.commons.lang3.StringUtils;\nimport org.rnorth.ducttape.TimeoutException;\nimport org.testcontainers.containers.ContainerLaunchException;\nimport org.testcontainers.containers.wait.strategy.AbstractWaitStrategy;\nimport org.testcontainers.delegate.DatabaseDelegate;\n\nimport java.util.concurrent.TimeUnit;\n\nimport static org.rnorth.ducttape.unreliables.Unreliables.retryUntilSuccess;\n\n/**\n * Waits until Cassandra returns its version\n */\n@Slf4j\npublic class CassandraQueryWaitStrategy extends AbstractWaitStrategy {\n\n    private static final String SELECT_VERSION_QUERY = \"SELECT release_version FROM system.local\";\n\n    private static final String TIMEOUT_ERROR = \"Timed out waiting for Cassandra to be accessible for query execution\";\n\n    @Override\n    protected void waitUntilReady() {\n        // execute select version query until success or timeout\n        try {\n            retryUntilSuccess(\n                (int) startupTimeout.getSeconds(),\n                TimeUnit.SECONDS,\n                () -> {\n                    getRateLimiter()\n                        .doWhenReady(() -> {\n                            try (DatabaseDelegate databaseDelegate = getDatabaseDelegate()) {\n                                log.info(\"Checking connection is ready...\");\n                                ((CassandraDatabaseDelegate) databaseDelegate).execute(\n                                        SELECT_VERSION_QUERY,\n                                        StringUtils.EMPTY,\n                                        1,\n                                        false,\n                                        false,\n                                        true\n                                    );\n                            }\n                        });\n                    return true;\n                }\n            );\n        } catch (TimeoutException e) {\n            throw new ContainerLaunchException(TIMEOUT_ERROR);\n        }\n    }\n\n    private DatabaseDelegate getDatabaseDelegate() {\n        return new CassandraDatabaseDelegate(waitStrategyTarget);\n    }\n}\n"
  },
  {
    "path": "modules/cassandra/src/main/java/org/testcontainers/containers/CassandraContainer.java",
    "content": "package org.testcontainers.containers;\n\nimport com.datastax.driver.core.Cluster;\nimport com.github.dockerjava.api.command.InspectContainerResponse;\nimport org.apache.commons.io.IOUtils;\nimport org.testcontainers.containers.delegate.CassandraDatabaseDelegate;\nimport org.testcontainers.delegate.DatabaseDelegate;\nimport org.testcontainers.ext.ScriptUtils;\nimport org.testcontainers.ext.ScriptUtils.ScriptLoadException;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.MountableFile;\n\nimport java.io.IOException;\nimport java.net.InetSocketAddress;\nimport java.net.URL;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Optional;\n\nimport javax.script.ScriptException;\n\n/**\n * Testcontainers implementation for Apache Cassandra.\n * <p>\n * Supported image: {@code cassandra}\n * <p>\n * Exposed ports: 9042\n *\n * @deprecated use {@link org.testcontainers.cassandra.CassandraContainer} instead.\n */\n@Deprecated\npublic class CassandraContainer<SELF extends CassandraContainer<SELF>> extends GenericContainer<SELF> {\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"cassandra\");\n\n    private static final String DEFAULT_TAG = \"3.11.2\";\n\n    @Deprecated\n    public static final String IMAGE = DEFAULT_IMAGE_NAME.getUnversionedPart();\n\n    public static final Integer CQL_PORT = 9042;\n\n    private static final String DEFAULT_LOCAL_DATACENTER = \"datacenter1\";\n\n    private static final String CONTAINER_CONFIG_LOCATION = \"/etc/cassandra\";\n\n    private static final String USERNAME = \"cassandra\";\n\n    private static final String PASSWORD = \"cassandra\";\n\n    private String configLocation;\n\n    private String initScriptPath;\n\n    private boolean enableJmxReporting;\n\n    /**\n     * @deprecated use {@link #CassandraContainer(DockerImageName)} instead\n     */\n    @Deprecated\n    public CassandraContainer() {\n        this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG));\n    }\n\n    public CassandraContainer(String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    public CassandraContainer(DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n\n        addExposedPort(CQL_PORT);\n        this.enableJmxReporting = false;\n\n        withEnv(\"CASSANDRA_SNITCH\", \"GossipingPropertyFileSnitch\");\n        withEnv(\"JVM_OPTS\", \"-Dcassandra.skip_wait_for_gossip_to_settle=0 -Dcassandra.initial_token=0\");\n        withEnv(\"HEAP_NEWSIZE\", \"128M\");\n        withEnv(\"MAX_HEAP_SIZE\", \"1024M\");\n        withEnv(\"CASSANDRA_ENDPOINT_SNITCH\", \"GossipingPropertyFileSnitch\");\n        withEnv(\"CASSANDRA_DC\", DEFAULT_LOCAL_DATACENTER);\n    }\n\n    @Override\n    protected void configure() {\n        optionallyMapResourceParameterAsVolume(CONTAINER_CONFIG_LOCATION, configLocation);\n    }\n\n    @Override\n    protected void containerIsStarted(InspectContainerResponse containerInfo) {\n        runInitScriptIfRequired();\n    }\n\n    /**\n     * Load init script content and apply it to the database if initScriptPath is set\n     */\n    private void runInitScriptIfRequired() {\n        if (initScriptPath != null) {\n            try {\n                URL resource = Thread.currentThread().getContextClassLoader().getResource(initScriptPath);\n                if (resource == null) {\n                    logger().warn(\"Could not load classpath init script: {}\", initScriptPath);\n                    throw new ScriptLoadException(\n                        \"Could not load classpath init script: \" + initScriptPath + \". Resource not found.\"\n                    );\n                }\n                String cql = IOUtils.toString(resource, StandardCharsets.UTF_8);\n                DatabaseDelegate databaseDelegate = getDatabaseDelegate();\n                ScriptUtils.executeDatabaseScript(databaseDelegate, initScriptPath, cql);\n            } catch (IOException e) {\n                logger().warn(\"Could not load classpath init script: {}\", initScriptPath);\n                throw new ScriptLoadException(\"Could not load classpath init script: \" + initScriptPath, e);\n            } catch (ScriptException e) {\n                logger().error(\"Error while executing init script: {}\", initScriptPath, e);\n                throw new ScriptUtils.UncategorizedScriptException(\n                    \"Error while executing init script: \" + initScriptPath,\n                    e\n                );\n            }\n        }\n    }\n\n    /**\n     * Map (effectively replace) directory in Docker with the content of resourceLocation if resource location is not null\n     *\n     * Protected to allow for changing implementation by extending the class\n     *\n     * @param pathNameInContainer path in docker\n     * @param resourceLocation    relative classpath to resource\n     */\n    protected void optionallyMapResourceParameterAsVolume(String pathNameInContainer, String resourceLocation) {\n        Optional\n            .ofNullable(resourceLocation)\n            .map(MountableFile::forClasspathResource)\n            .ifPresent(mountableFile -> withCopyFileToContainer(mountableFile, pathNameInContainer));\n    }\n\n    /**\n     * Initialize Cassandra with the custom overridden Cassandra configuration\n     * <p>\n     * Be aware, that Docker effectively replaces all /etc/cassandra content with the content of config location, so if\n     * Cassandra.yaml in configLocation is absent or corrupted, then Cassandra just won't launch\n     *\n     * @param configLocation relative classpath with the directory that contains cassandra.yaml and other configuration files\n     */\n    public SELF withConfigurationOverride(String configLocation) {\n        this.configLocation = configLocation;\n        return self();\n    }\n\n    /**\n     * Initialize Cassandra with init CQL script\n     * <p>\n     * CQL script will be applied after container is started (see using WaitStrategy)\n     *\n     * @param initScriptPath relative classpath resource\n     */\n    public SELF withInitScript(String initScriptPath) {\n        this.initScriptPath = initScriptPath;\n        return self();\n    }\n\n    /**\n     * Initialize Cassandra client with JMX reporting enabled or disabled\n     */\n    public SELF withJmxReporting(boolean enableJmxReporting) {\n        this.enableJmxReporting = enableJmxReporting;\n        return self();\n    }\n\n    /**\n     * Get username\n     *\n     * By default Cassandra has authenticator: AllowAllAuthenticator in cassandra.yaml\n     * If username and password need to be used, then authenticator should be set as PasswordAuthenticator\n     * (through custom Cassandra configuration) and through CQL with default cassandra-cassandra credentials\n     * user management should be modified\n     */\n    public String getUsername() {\n        return USERNAME;\n    }\n\n    /**\n     * Get password\n     *\n     * By default Cassandra has authenticator: AllowAllAuthenticator in cassandra.yaml\n     * If username and password need to be used, then authenticator should be set as PasswordAuthenticator\n     * (through custom Cassandra configuration) and through CQL with default cassandra-cassandra credentials\n     * user management should be modified\n     */\n    public String getPassword() {\n        return PASSWORD;\n    }\n\n    /**\n     * Get configured Cluster\n     *\n     * Can be used to obtain connections to Cassandra in the container\n     *\n     * @deprecated For Cassandra driver 3.x, use {@link #getHost()} and {@link #getMappedPort(int)} with\n     * the driver's {@link Cluster#builder() Cluster.Builder} {@code addContactPoint(String)} and\n     * {@code withPort(int)} methods to create a Cluster object. For Cassandra driver 4.x, use\n     * {@link #getContactPoint()} and {@link #getLocalDatacenter()} with the driver's {@code CqlSession.builder()}\n     * {@code addContactPoint(InetSocketAddress)} and {@code withLocalDatacenter(String)} methods to create\n     * a Session Object. See https://docs.datastax.com/en/developer/java-driver/ for more on the driver.\n     */\n    @Deprecated\n    public Cluster getCluster() {\n        return getCluster(this, enableJmxReporting);\n    }\n\n    @Deprecated\n    public static Cluster getCluster(ContainerState containerState, boolean enableJmxReporting) {\n        final Cluster.Builder builder = Cluster\n            .builder()\n            .addContactPoint(containerState.getHost())\n            .withPort(containerState.getMappedPort(CQL_PORT));\n        if (!enableJmxReporting) {\n            builder.withoutJMXReporting();\n        }\n        return builder.build();\n    }\n\n    @Deprecated\n    public static Cluster getCluster(ContainerState containerState) {\n        return getCluster(containerState, false);\n    }\n\n    /**\n     * Retrieve an {@link InetSocketAddress} for connecting to the Cassandra container via the driver.\n     *\n     * @return A InetSocketAddress representation of this Cassandra container's host and port.\n     */\n    public InetSocketAddress getContactPoint() {\n        return new InetSocketAddress(getHost(), getMappedPort(CQL_PORT));\n    }\n\n    /**\n     * Retrieve the Local Datacenter for connecting to the Cassandra container via the driver.\n     *\n     * @return The configured local Datacenter name.\n     */\n    public String getLocalDatacenter() {\n        return getEnvMap().getOrDefault(\"CASSANDRA_DC\", DEFAULT_LOCAL_DATACENTER);\n    }\n\n    @Deprecated\n    private DatabaseDelegate getDatabaseDelegate() {\n        return new CassandraDatabaseDelegate(this);\n    }\n}\n"
  },
  {
    "path": "modules/cassandra/src/main/java/org/testcontainers/containers/delegate/CassandraDatabaseDelegate.java",
    "content": "package org.testcontainers.containers.delegate;\n\nimport com.datastax.driver.core.ResultSet;\nimport com.datastax.driver.core.Session;\nimport com.datastax.driver.core.exceptions.DriverException;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.testcontainers.containers.CassandraContainer;\nimport org.testcontainers.containers.ContainerState;\nimport org.testcontainers.delegate.AbstractDatabaseDelegate;\nimport org.testcontainers.exception.ConnectionCreationException;\nimport org.testcontainers.ext.ScriptUtils.ScriptStatementFailedException;\n\n/**\n * Cassandra database delegate\n *\n * @deprecated use {@link org.testcontainers.cassandra.CassandraDatabaseDelegate} instead.\n */\n@Slf4j\n@RequiredArgsConstructor\n@Deprecated\npublic class CassandraDatabaseDelegate extends AbstractDatabaseDelegate<Session> {\n\n    private final ContainerState container;\n\n    @Override\n    protected Session createNewConnection() {\n        try {\n            return CassandraContainer.getCluster(container).newSession();\n        } catch (DriverException e) {\n            log.error(\"Could not obtain cassandra connection\");\n            throw new ConnectionCreationException(\"Could not obtain cassandra connection\", e);\n        }\n    }\n\n    @Override\n    public void execute(\n        String statement,\n        String scriptPath,\n        int lineNumber,\n        boolean continueOnError,\n        boolean ignoreFailedDrops\n    ) {\n        try {\n            ResultSet result = getConnection().execute(statement);\n            if (result.wasApplied()) {\n                log.debug(\"Statement {} was applied\", statement);\n            } else {\n                throw new ScriptStatementFailedException(statement, lineNumber, scriptPath);\n            }\n        } catch (DriverException e) {\n            throw new ScriptStatementFailedException(statement, lineNumber, scriptPath, e);\n        }\n    }\n\n    @Override\n    protected void closeConnectionQuietly(Session session) {\n        try {\n            session.getCluster().close();\n        } catch (Exception e) {\n            log.error(\"Could not close cassandra connection\", e);\n        }\n    }\n}\n"
  },
  {
    "path": "modules/cassandra/src/main/java/org/testcontainers/containers/wait/CassandraQueryWaitStrategy.java",
    "content": "package org.testcontainers.containers.wait;\n\nimport org.rnorth.ducttape.TimeoutException;\nimport org.testcontainers.containers.ContainerLaunchException;\nimport org.testcontainers.containers.delegate.CassandraDatabaseDelegate;\nimport org.testcontainers.containers.wait.strategy.AbstractWaitStrategy;\nimport org.testcontainers.delegate.DatabaseDelegate;\n\nimport java.util.concurrent.TimeUnit;\n\nimport static org.rnorth.ducttape.unreliables.Unreliables.retryUntilSuccess;\n\n/**\n * Waits until Cassandra returns its version\n *\n * @deprecated use {@link org.testcontainers.cassandra.CassandraQueryWaitStrategy} instead.\n */\n@Deprecated\npublic class CassandraQueryWaitStrategy extends AbstractWaitStrategy {\n\n    private static final String SELECT_VERSION_QUERY = \"SELECT release_version FROM system.local\";\n\n    private static final String TIMEOUT_ERROR = \"Timed out waiting for Cassandra to be accessible for query execution\";\n\n    @Override\n    protected void waitUntilReady() {\n        // execute select version query until success or timeout\n        try {\n            retryUntilSuccess(\n                (int) startupTimeout.getSeconds(),\n                TimeUnit.SECONDS,\n                () -> {\n                    getRateLimiter()\n                        .doWhenReady(() -> {\n                            try (DatabaseDelegate databaseDelegate = getDatabaseDelegate()) {\n                                databaseDelegate.execute(SELECT_VERSION_QUERY, \"\", 1, false, false);\n                            }\n                        });\n                    return true;\n                }\n            );\n        } catch (TimeoutException e) {\n            throw new ContainerLaunchException(TIMEOUT_ERROR);\n        }\n    }\n\n    private DatabaseDelegate getDatabaseDelegate() {\n        return new CassandraDatabaseDelegate(waitStrategyTarget);\n    }\n}\n"
  },
  {
    "path": "modules/cassandra/src/main/resources/cqlshrc",
    "content": "[ssl]\ncertfile = ssl/user_cert.pem\nusercert = ssl/user_cert.pem\nuserkey = ssl/user_key.pem\n\n[connection]\nfactory = cqlshlib.ssl.ssl_transport_factory"
  },
  {
    "path": "modules/cassandra/src/test/java/org/testcontainers/cassandra/CassandraContainerTest.java",
    "content": "package org.testcontainers.cassandra;\n\nimport com.datastax.oss.driver.api.core.CqlSession;\nimport com.datastax.oss.driver.api.core.CqlSessionBuilder;\nimport com.datastax.oss.driver.api.core.config.DefaultDriverOption;\nimport com.datastax.oss.driver.api.core.config.DriverConfigLoader;\nimport com.datastax.oss.driver.api.core.config.ProgrammaticDriverConfigLoaderBuilder;\nimport com.datastax.oss.driver.api.core.context.DriverContext;\nimport com.datastax.oss.driver.api.core.cql.ResultSet;\nimport com.datastax.oss.driver.api.core.cql.Row;\nimport com.datastax.oss.driver.api.core.session.ProgrammaticArguments;\nimport com.datastax.oss.driver.internal.core.context.DefaultDriverContext;\nimport com.datastax.oss.driver.internal.core.ssl.DefaultSslEngineFactory;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.Container;\nimport org.testcontainers.containers.ContainerLaunchException;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.net.URL;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\nimport static org.assertj.core.api.Assertions.fail;\n\nclass CassandraContainerTest {\n\n    private static final String CASSANDRA_IMAGE = \"cassandra:3.11.15\";\n\n    private static final String TEST_CLUSTER_NAME_IN_CONF = \"Test Cluster Integration Test\";\n\n    private static final String BASIC_QUERY = \"SELECT release_version FROM system.local\";\n\n    @Test\n    void testSimple() {\n        try ( // container-definition {\n            CassandraContainer cassandraContainer = new CassandraContainer(CASSANDRA_IMAGE)\n            // }\n        ) {\n            cassandraContainer.start();\n            ResultSet resultSet = performQuery(cassandraContainer, BASIC_QUERY);\n            assertThat(resultSet.wasApplied()).as(\"Query was applied\").isTrue();\n            assertThat(resultSet.one().getString(0)).as(\"Result set has release_version\").isNotNull();\n        }\n    }\n\n    @Test\n    void testSpecificVersion() {\n        String cassandraVersion = \"3.0.15\";\n        try (\n            CassandraContainer cassandraContainer = new CassandraContainer(\n                DockerImageName.parse(\"cassandra\").withTag(cassandraVersion)\n            )\n        ) {\n            cassandraContainer.start();\n            ResultSet resultSet = performQuery(cassandraContainer, BASIC_QUERY);\n            assertThat(resultSet.wasApplied()).as(\"Query was applied\").isTrue();\n            assertThat(resultSet.one().getString(0)).as(\"Cassandra has right version\").isEqualTo(cassandraVersion);\n        }\n    }\n\n    @Test\n    void testConfigurationOverride() {\n        try (\n            CassandraContainer cassandraContainer = new CassandraContainer(CASSANDRA_IMAGE)\n                .withConfigurationOverride(\"cassandra-test-configuration-example\")\n        ) {\n            cassandraContainer.start();\n            ResultSet resultSet = performQuery(cassandraContainer, \"SELECT cluster_name FROM system.local\");\n            assertThat(resultSet.wasApplied()).as(\"Query was applied\").isTrue();\n            assertThat(resultSet.one().getString(0))\n                .as(\"Cassandra configuration is overridden\")\n                .isEqualTo(TEST_CLUSTER_NAME_IN_CONF);\n        }\n    }\n\n    @Test\n    public void testWithSslClientConfig() {\n        /*\n        Commands executed to generate certificates in 'cassandra-ssl-configuration' directory:\n        keytool -genkey -keyalg RSA -validity 36500 -alias localhost -keystore keystore.p12 -storepass cassandra \\\n            -keypass cassandra -dname \"CN=localhost, OU=Testcontainers, O=Testcontainers, L=None, C=None\"\n        keytool -export -alias localhost -file cassandra.cer -keystore keystore.p12\n        keytool -import -v -trustcacerts -alias localhost -file cassandra.cer -keystore truststore.p12\n\n        Commands executed to generate the client certificate and key in 'client-ssl' directory:\n        keytool -importkeystore -srckeystore keystore.p12 -destkeystore test_node.p12 -deststoretype PKCS12 \\\n            -srcstorepass cassandra -deststorepass cassandra\n        openssl pkcs12 -in test_node.p12 -nokeys -out cassandra.cer.pem -passin pass:cassandra\n        openssl pkcs12 -in test_node.p12 -nodes -nocerts -out cassandra.key.pem -passin pass:cassandra\n\n        Reference:\n        https://docs.datastax.com/en/cassandra-oss/3.x/cassandra/configuration/secureSSLCertificates.html\n        https://docs.datastax.com/en/cassandra-oss/3.x/cassandra/configuration/secureCqlshSSL.html\n        */\n        try (\n            // with-ssl-config {\n            CassandraContainer cassandraContainer = new CassandraContainer(CASSANDRA_IMAGE)\n                .withConfigurationOverride(\"cassandra-ssl-configuration\")\n                .withSsl(\"client-ssl/cassandra.cer.pem\", \"client-ssl/cassandra.key.pem\")\n            // }\n        ) {\n            cassandraContainer.start();\n            try {\n                ResultSet resultSet = performQueryWithSslClientConfig(\n                    cassandraContainer,\n                    \"SELECT cluster_name FROM system.local\"\n                );\n                assertThat(resultSet.wasApplied()).as(\"Query was applied\").isTrue();\n                assertThat(resultSet.one().getString(0))\n                    .as(\"Cassandra configuration is configured with secured connection\")\n                    .isEqualTo(TEST_CLUSTER_NAME_IN_CONF);\n            } catch (Exception e) {\n                fail(e);\n            }\n        }\n    }\n\n    @Test\n    public void testSimpleSslCqlsh() {\n        try (\n            CassandraContainer cassandraContainer = new CassandraContainer(CASSANDRA_IMAGE)\n                .withConfigurationOverride(\"cassandra-ssl-configuration\")\n                .withSsl(\"client-ssl/cassandra.cer.pem\", \"client-ssl/cassandra.key.pem\")\n        ) {\n            cassandraContainer.start();\n\n            Container.ExecResult execResult = cassandraContainer.execInContainer(\n                \"cqlsh\",\n                \"--ssl\",\n                \"-e\",\n                \"SELECT * FROM system_schema.keyspaces;\"\n            );\n            assertThat(execResult.getStdout()).contains(\"keyspace_name\");\n        } catch (Exception e) {\n            fail(e);\n        }\n    }\n\n    @Test\n    void testEmptyConfigurationOverride() {\n        try (\n            CassandraContainer cassandraContainer = new CassandraContainer(CASSANDRA_IMAGE)\n                .withConfigurationOverride(\"cassandra-empty-configuration\")\n        ) {\n            assertThatThrownBy(cassandraContainer::start).isInstanceOf(ContainerLaunchException.class);\n        }\n    }\n\n    @Test\n    void testInitScript() {\n        try (\n            CassandraContainer cassandraContainer = new CassandraContainer(CASSANDRA_IMAGE)\n                .withInitScript(\"initial.cql\")\n        ) {\n            cassandraContainer.start();\n            testInitScript(cassandraContainer, false);\n        }\n    }\n\n    @Test\n    void testNonexistentInitScript() {\n        try (\n            CassandraContainer cassandraContainer = new CassandraContainer(CASSANDRA_IMAGE)\n                .withInitScript(\"unknown_script.cql\")\n        ) {\n            assertThatThrownBy(cassandraContainer::start).isInstanceOf(ContainerLaunchException.class);\n        }\n    }\n\n    @Test\n    void testInitScriptWithRequiredAuthentication() {\n        try (\n            // init-with-auth {\n            CassandraContainer cassandraContainer = new CassandraContainer(CASSANDRA_IMAGE)\n                .withConfigurationOverride(\"cassandra-auth-required-configuration\")\n                .withInitScript(\"initial.cql\")\n            // }\n        ) {\n            cassandraContainer.start();\n            testInitScript(cassandraContainer, true);\n        }\n    }\n\n    @Test\n    void testInitScriptWithError() {\n        try (\n            CassandraContainer cassandraContainer = new CassandraContainer(CASSANDRA_IMAGE)\n                .withInitScript(\"initial-with-error.cql\")\n        ) {\n            assertThatThrownBy(cassandraContainer::start).isInstanceOf(ContainerLaunchException.class);\n        }\n    }\n\n    @Test\n    void testInitScriptWithLegacyCassandra() {\n        try (\n            CassandraContainer cassandraContainer = new CassandraContainer(\"cassandra:2.2.11\")\n                .withInitScript(\"initial.cql\")\n        ) {\n            cassandraContainer.start();\n            testInitScript(cassandraContainer, false);\n        }\n    }\n\n    private void testInitScript(CassandraContainer cassandraContainer, boolean withCredentials) {\n        String query = \"SELECT * FROM keySpaceTest.catalog_category\";\n        ResultSet resultSet;\n\n        if (withCredentials) {\n            resultSet = performQueryWithAuth(cassandraContainer, query);\n        } else {\n            resultSet = performQuery(cassandraContainer, query);\n        }\n\n        assertThat(resultSet.wasApplied()).as(\"Query was applied\").isTrue();\n        Row row = resultSet.one();\n        assertThat(row.getLong(0)).as(\"Inserted row is in expected state\").isEqualTo(1);\n        assertThat(row.getString(1)).as(\"Inserted row is in expected state\").isEqualTo(\"test_category\");\n    }\n\n    private ResultSet performQuery(CassandraContainer cassandraContainer, String cql) {\n        // cql-session {\n        final CqlSession cqlSession = CqlSession\n            .builder()\n            .addContactPoint(cassandraContainer.getContactPoint())\n            .withLocalDatacenter(cassandraContainer.getLocalDatacenter())\n            .build();\n        // }\n        return performQuery(cqlSession, cql);\n    }\n\n    private ResultSet performQueryWithAuth(CassandraContainer cassandraContainer, String cql) {\n        final CqlSession cqlSession = CqlSession\n            .builder()\n            .addContactPoint(cassandraContainer.getContactPoint())\n            .withLocalDatacenter(cassandraContainer.getLocalDatacenter())\n            .withAuthCredentials(cassandraContainer.getUsername(), cassandraContainer.getPassword())\n            .build();\n        return performQuery(cqlSession, cql);\n    }\n\n    private ResultSet performQueryWithSslClientConfig(CassandraContainer cassandraContainer, String cql) {\n        final ProgrammaticDriverConfigLoaderBuilder driverConfigLoaderBuilder = DriverConfigLoader.programmaticBuilder();\n        driverConfigLoaderBuilder.withBoolean(DefaultDriverOption.SSL_HOSTNAME_VALIDATION, false);\n        final URL trustStoreUrl =\n            this.getClass().getClassLoader().getResource(\"cassandra-ssl-configuration/truststore.p12\");\n        driverConfigLoaderBuilder.withString(DefaultDriverOption.SSL_TRUSTSTORE_PATH, trustStoreUrl.getFile());\n        driverConfigLoaderBuilder.withString(DefaultDriverOption.SSL_TRUSTSTORE_PASSWORD, \"cassandra\");\n        final URL keyStoreUrl =\n            this.getClass().getClassLoader().getResource(\"cassandra-ssl-configuration/keystore.p12\");\n        driverConfigLoaderBuilder.withString(DefaultDriverOption.SSL_KEYSTORE_PATH, keyStoreUrl.getFile());\n        driverConfigLoaderBuilder.withString(DefaultDriverOption.SSL_KEYSTORE_PASSWORD, \"cassandra\");\n        final DriverContext driverContext = new DefaultDriverContext(\n            driverConfigLoaderBuilder.build(),\n            ProgrammaticArguments.builder().build()\n        );\n\n        final CqlSessionBuilder sessionBuilder = CqlSession.builder();\n        final CqlSession cqlSession = sessionBuilder\n            .addContactPoint(cassandraContainer.getContactPoint())\n            .withLocalDatacenter(cassandraContainer.getLocalDatacenter())\n            .withSslEngineFactory(new DefaultSslEngineFactory(driverContext))\n            .build();\n        return performQuery(cqlSession, cql);\n    }\n\n    private ResultSet performQuery(CqlSession session, String cql) {\n        final ResultSet rs = session.execute(cql);\n        session.close();\n        return rs;\n    }\n}\n"
  },
  {
    "path": "modules/cassandra/src/test/java/org/testcontainers/cassandra/CompatibleCassandraImageTest.java",
    "content": "package org.testcontainers.cassandra;\n\nimport com.datastax.oss.driver.api.core.CqlIdentifier;\nimport com.datastax.oss.driver.api.core.CqlSession;\nimport com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass CompatibleCassandraImageTest {\n\n    public static String[] params() {\n        return new String[] { \"cassandra:3.11.2\", \"cassandra:4.1.1\", \"cassandra:5\" };\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"params\")\n    void testCassandraGetContactPoint(String imageName) {\n        try (CassandraContainer cassandra = new CassandraContainer(imageName)) {\n            cassandra.start();\n            assertCassandraFunctionality(cassandra);\n        }\n    }\n\n    private void assertCassandraFunctionality(CassandraContainer cassandra) {\n        try (\n            CqlSession session = CqlSession\n                .builder()\n                .addContactPoint(cassandra.getContactPoint())\n                .withLocalDatacenter(cassandra.getLocalDatacenter())\n                .build()\n        ) {\n            session.execute(\n                \"CREATE KEYSPACE IF NOT EXISTS test WITH replication = \\n\" +\n                \"{'class':'SimpleStrategy','replication_factor':'1'};\"\n            );\n\n            KeyspaceMetadata keyspace = session.getMetadata().getKeyspaces().get(CqlIdentifier.fromCql(\"test\"));\n\n            assertThat(keyspace).as(\"test keyspace created\").isNotNull();\n        }\n    }\n}\n"
  },
  {
    "path": "modules/cassandra/src/test/java/org/testcontainers/containers/CassandraContainerTest.java",
    "content": "package org.testcontainers.containers;\n\nimport com.datastax.driver.core.Cluster;\nimport com.datastax.driver.core.ResultSet;\nimport com.datastax.driver.core.Row;\nimport com.datastax.driver.core.Session;\nimport lombok.extern.slf4j.Slf4j;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.wait.CassandraQueryWaitStrategy;\nimport org.testcontainers.utility.DockerImageName;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\n\n@Slf4j\nclass CassandraContainerTest {\n\n    private static final DockerImageName CASSANDRA_IMAGE = DockerImageName.parse(\"cassandra:3.11.2\");\n\n    private static final String TEST_CLUSTER_NAME_IN_CONF = \"Test Cluster Integration Test\";\n\n    private static final String BASIC_QUERY = \"SELECT release_version FROM system.local\";\n\n    @Test\n    void testSimple() {\n        try (CassandraContainer<?> cassandraContainer = new CassandraContainer<>(CASSANDRA_IMAGE)) {\n            cassandraContainer.start();\n            ResultSet resultSet = performQuery(cassandraContainer, BASIC_QUERY);\n            assertThat(resultSet.wasApplied()).as(\"Query was applied\").isTrue();\n            assertThat(resultSet.one().getString(0)).as(\"Result set has release_version\").isNotNull();\n        }\n    }\n\n    @Test\n    void testSpecificVersion() {\n        String cassandraVersion = \"3.0.15\";\n        try (\n            CassandraContainer<?> cassandraContainer = new CassandraContainer<>(\n                CASSANDRA_IMAGE.withTag(cassandraVersion)\n            )\n        ) {\n            cassandraContainer.start();\n            ResultSet resultSet = performQuery(cassandraContainer, BASIC_QUERY);\n            assertThat(resultSet.wasApplied()).as(\"Query was applied\").isTrue();\n            assertThat(resultSet.one().getString(0)).as(\"Cassandra has right version\").isEqualTo(cassandraVersion);\n        }\n    }\n\n    @Test\n    void testConfigurationOverride() {\n        try (\n            CassandraContainer<?> cassandraContainer = new CassandraContainer<>(CASSANDRA_IMAGE)\n                .withConfigurationOverride(\"cassandra-test-configuration-example\")\n        ) {\n            cassandraContainer.start();\n            ResultSet resultSet = performQuery(cassandraContainer, \"SELECT cluster_name FROM system.local\");\n            assertThat(resultSet.wasApplied()).as(\"Query was applied\").isTrue();\n            assertThat(resultSet.one().getString(0))\n                .as(\"Cassandra configuration is overridden\")\n                .isEqualTo(TEST_CLUSTER_NAME_IN_CONF);\n        }\n    }\n\n    @Test\n    void testEmptyConfigurationOverride() {\n        try (\n            CassandraContainer<?> cassandraContainer = new CassandraContainer<>(CASSANDRA_IMAGE)\n                .withConfigurationOverride(\"cassandra-empty-configuration\")\n        ) {\n            assertThatThrownBy(cassandraContainer::start).isInstanceOf(ContainerLaunchException.class);\n        }\n    }\n\n    @Test\n    void testInitScript() {\n        try (\n            CassandraContainer<?> cassandraContainer = new CassandraContainer<>(CASSANDRA_IMAGE)\n                .withInitScript(\"initial.cql\")\n        ) {\n            cassandraContainer.start();\n            testInitScript(cassandraContainer);\n        }\n    }\n\n    @Test\n    void testInitScriptWithLegacyCassandra() {\n        try (\n            CassandraContainer<?> cassandraContainer = new CassandraContainer<>(\n                DockerImageName.parse(\"cassandra:2.2.11\")\n            )\n                .withInitScript(\"initial.cql\")\n        ) {\n            cassandraContainer.start();\n            testInitScript(cassandraContainer);\n        }\n    }\n\n    @SuppressWarnings(\"deprecation\") // Using deprecated constructor for verification of backwards compatibility\n    @Test\n    void testCassandraQueryWaitStrategy() {\n        try (\n            CassandraContainer<?> cassandraContainer = new CassandraContainer<>()\n                .waitingFor(new CassandraQueryWaitStrategy())\n        ) {\n            cassandraContainer.start();\n            ResultSet resultSet = performQuery(cassandraContainer, BASIC_QUERY);\n            assertThat(resultSet.wasApplied()).as(\"Query was applied\").isTrue();\n        }\n    }\n\n    @SuppressWarnings(\"deprecation\") // Using deprecated constructor for verification of backwards compatibility\n    @Test\n    void testCassandraGetCluster() {\n        try (CassandraContainer<?> cassandraContainer = new CassandraContainer<>()) {\n            cassandraContainer.start();\n            ResultSet resultSet = performQuery(cassandraContainer.getCluster(), BASIC_QUERY);\n            assertThat(resultSet.wasApplied()).as(\"Query was applied\").isTrue();\n            assertThat(resultSet.one().getString(0)).as(\"Result set has release_version\").isNotNull();\n        }\n    }\n\n    private void testInitScript(CassandraContainer<?> cassandraContainer) {\n        ResultSet resultSet = performQuery(cassandraContainer, \"SELECT * FROM keySpaceTest.catalog_category\");\n        assertThat(resultSet.wasApplied()).as(\"Query was applied\").isTrue();\n        Row row = resultSet.one();\n        assertThat(row.getLong(0)).as(\"Inserted row is in expected state\").isEqualTo(1);\n        assertThat(row.getString(1)).as(\"Inserted row is in expected state\").isEqualTo(\"test_category\");\n    }\n\n    private ResultSet performQuery(CassandraContainer<?> cassandraContainer, String cql) {\n        Cluster explicitCluster = Cluster\n            .builder()\n            .addContactPoint(cassandraContainer.getHost())\n            .withPort(cassandraContainer.getMappedPort(CassandraContainer.CQL_PORT))\n            .build();\n        return performQuery(explicitCluster, cql);\n    }\n\n    private ResultSet performQuery(Cluster cluster, String cql) {\n        try (Cluster closeableCluster = cluster) {\n            Session session = closeableCluster.newSession();\n            return session.execute(cql);\n        }\n    }\n}\n"
  },
  {
    "path": "modules/cassandra/src/test/java/org/testcontainers/containers/CompatibleCassandraImageTest.java",
    "content": "package org.testcontainers.containers;\n\nimport com.datastax.oss.driver.api.core.CqlIdentifier;\nimport com.datastax.oss.driver.api.core.CqlSession;\nimport com.datastax.oss.driver.api.core.metadata.schema.KeyspaceMetadata;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\npublic class CompatibleCassandraImageTest {\n\n    public static String[] params() {\n        return new String[] { \"cassandra:3.11.2\", \"cassandra:4.1.1\" };\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"params\")\n    void testCassandraGetContactPoint(String imageName) {\n        try (CassandraContainer<?> cassandra = new CassandraContainer<>(imageName)) {\n            cassandra.start();\n            assertCassandraFunctionality(cassandra);\n        }\n    }\n\n    private void assertCassandraFunctionality(CassandraContainer<?> cassandra) {\n        try (\n            CqlSession session = CqlSession\n                .builder()\n                .addContactPoint(cassandra.getContactPoint())\n                .withLocalDatacenter(cassandra.getLocalDatacenter())\n                .build()\n        ) {\n            session.execute(\n                \"CREATE KEYSPACE IF NOT EXISTS test WITH replication = \\n\" +\n                \"{'class':'SimpleStrategy','replication_factor':'1'};\"\n            );\n\n            KeyspaceMetadata keyspace = session.getMetadata().getKeyspaces().get(CqlIdentifier.fromCql(\"test\"));\n\n            assertThat(keyspace).as(\"test keyspace created\").isNotNull();\n        }\n    }\n}\n"
  },
  {
    "path": "modules/cassandra/src/test/resources/cassandra-auth-required-configuration/cassandra.yaml",
    "content": "# Cassandra storage config YAML\n\n# NOTE:\n#   See http://wiki.apache.org/cassandra/StorageConfiguration for\n#   full explanations of configuration directives\n# /NOTE\n\n# The name of the cluster. This is mainly used to prevent machines in\n# one logical cluster from joining another.\ncluster_name: 'Test Cluster Integration Test'\n\n# This defines the number of tokens randomly assigned to this node on the ring\n# The more tokens, relative to other nodes, the larger the proportion of data\n# that this node will store. You probably want all nodes to have the same number\n# of tokens assuming they have equal hardware capability.\n#\n# If you leave this unspecified, Cassandra will use the default of 1 token for legacy compatibility,\n# and will use the initial_token as described below.\n#\n# Specifying initial_token will override this setting on the node's initial start,\n# on subsequent starts, this setting will apply even if initial token is set.\n#\n# If you already have a cluster with 1 token per node, and wish to migrate to\n# multiple tokens per node, see http://wiki.apache.org/cassandra/Operations\nnum_tokens: 256\n\n# Triggers automatic allocation of num_tokens tokens for this node. The allocation\n# algorithm attempts to choose tokens in a way that optimizes replicated load over\n# the nodes in the datacenter for the replication strategy used by the specified\n# keyspace.\n#\n# The load assigned to each node will be close to proportional to its number of\n# vnodes.\n#\n# Only supported with the Murmur3Partitioner.\n# allocate_tokens_for_keyspace: KEYSPACE\n\n# initial_token allows you to specify tokens manually.  While you can use it with\n# vnodes (num_tokens > 1, above) -- in which case you should provide a\n# comma-separated list -- it's primarily used when adding nodes to legacy clusters\n# that do not have vnodes enabled.\n# initial_token:\n\n# See http://wiki.apache.org/cassandra/HintedHandoff\n# May either be \"true\" or \"false\" to enable globally\nhinted_handoff_enabled: true\n\n# When hinted_handoff_enabled is true, a black list of data centers that will not\n# perform hinted handoff\n# hinted_handoff_disabled_datacenters:\n#    - DC1\n#    - DC2\n\n# this defines the maximum amount of time a dead host will have hints\n# generated.  After it has been dead this long, new hints for it will not be\n# created until it has been seen alive and gone down again.\nmax_hint_window_in_ms: 10800000 # 3 hours\n\n# Maximum throttle in KBs per second, per delivery thread.  This will be\n# reduced proportionally to the number of nodes in the cluster.  (If there\n# are two nodes in the cluster, each delivery thread will use the maximum\n# rate; if there are three, each will throttle to half of the maximum,\n# since we expect two nodes to be delivering hints simultaneously.)\nhinted_handoff_throttle_in_kb: 1024\n\n# Number of threads with which to deliver hints;\n# Consider increasing this number when you have multi-dc deployments, since\n# cross-dc handoff tends to be slower\nmax_hints_delivery_threads: 2\n\n# Directory where Cassandra should store hints.\n# If not set, the default directory is $CASSANDRA_HOME/data/hints.\n# hints_directory: /var/lib/cassandra/hints\n\n# How often hints should be flushed from the internal buffers to disk.\n# Will *not* trigger fsync.\nhints_flush_period_in_ms: 10000\n\n# Maximum size for a single hints file, in megabytes.\nmax_hints_file_size_in_mb: 128\n\n# Compression to apply to the hint files. If omitted, hints files\n# will be written uncompressed. LZ4, Snappy, and Deflate compressors\n# are supported.\n#hints_compression:\n#   - class_name: LZ4Compressor\n#     parameters:\n#         -\n\n# Maximum throttle in KBs per second, total. This will be\n# reduced proportionally to the number of nodes in the cluster.\nbatchlog_replay_throttle_in_kb: 1024\n\n# Authentication backend, implementing IAuthenticator; used to identify users\n# Out of the box, Cassandra provides org.apache.cassandra.auth.{AllowAllAuthenticator,\n# PasswordAuthenticator}.\n#\n# - AllowAllAuthenticator performs no checks - set it to disable authentication.\n# - PasswordAuthenticator relies on username/password pairs to authenticate\n#   users. It keeps usernames and hashed passwords in system_auth.roles table.\n#   Please increase system_auth keyspace replication factor if you use this authenticator.\n#   If using PasswordAuthenticator, CassandraRoleManager must also be used (see below)\nauthenticator: PasswordAuthenticator\n\n# Authorization backend, implementing IAuthorizer; used to limit access/provide permissions\n# Out of the box, Cassandra provides org.apache.cassandra.auth.{AllowAllAuthorizer,\n# CassandraAuthorizer}.\n#\n# - AllowAllAuthorizer allows any action to any user - set it to disable authorization.\n# - CassandraAuthorizer stores permissions in system_auth.role_permissions table. Please\n#   increase system_auth keyspace replication factor if you use this authorizer.\nauthorizer: AllowAllAuthorizer\n\n# Part of the Authentication & Authorization backend, implementing IRoleManager; used\n# to maintain grants and memberships between roles.\n# Out of the box, Cassandra provides org.apache.cassandra.auth.CassandraRoleManager,\n# which stores role information in the system_auth keyspace. Most functions of the\n# IRoleManager require an authenticated login, so unless the configured IAuthenticator\n# actually implements authentication, most of this functionality will be unavailable.\n#\n# - CassandraRoleManager stores role data in the system_auth keyspace. Please\n#   increase system_auth keyspace replication factor if you use this role manager.\nrole_manager: CassandraRoleManager\n\n# Validity period for roles cache (fetching granted roles can be an expensive\n# operation depending on the role manager, CassandraRoleManager is one example)\n# Granted roles are cached for authenticated sessions in AuthenticatedUser and\n# after the period specified here, become eligible for (async) reload.\n# Defaults to 2000, set to 0 to disable caching entirely.\n# Will be disabled automatically for AllowAllAuthenticator.\nroles_validity_in_ms: 2000\n\n# Refresh interval for roles cache (if enabled).\n# After this interval, cache entries become eligible for refresh. Upon next\n# access, an async reload is scheduled and the old value returned until it\n# completes. If roles_validity_in_ms is non-zero, then this must be\n# also.\n# Defaults to the same value as roles_validity_in_ms.\n# roles_update_interval_in_ms: 2000\n\n# Validity period for permissions cache (fetching permissions can be an\n# expensive operation depending on the authorizer, CassandraAuthorizer is\n# one example). Defaults to 2000, set to 0 to disable.\n# Will be disabled automatically for AllowAllAuthorizer.\npermissions_validity_in_ms: 2000\n\n# Refresh interval for permissions cache (if enabled).\n# After this interval, cache entries become eligible for refresh. Upon next\n# access, an async reload is scheduled and the old value returned until it\n# completes. If permissions_validity_in_ms is non-zero, then this must be\n# also.\n# Defaults to the same value as permissions_validity_in_ms.\n# permissions_update_interval_in_ms: 2000\n\n# Validity period for credentials cache. This cache is tightly coupled to\n# the provided PasswordAuthenticator implementation of IAuthenticator. If\n# another IAuthenticator implementation is configured, this cache will not\n# be automatically used and so the following settings will have no effect.\n# Please note, credentials are cached in their encrypted form, so while\n# activating this cache may reduce the number of queries made to the\n# underlying table, it may not  bring a significant reduction in the\n# latency of individual authentication attempts.\n# Defaults to 2000, set to 0 to disable credentials caching.\ncredentials_validity_in_ms: 2000\n\n# Refresh interval for credentials cache (if enabled).\n# After this interval, cache entries become eligible for refresh. Upon next\n# access, an async reload is scheduled and the old value returned until it\n# completes. If credentials_validity_in_ms is non-zero, then this must be\n# also.\n# Defaults to the same value as credentials_validity_in_ms.\n# credentials_update_interval_in_ms: 2000\n\n# The partitioner is responsible for distributing groups of rows (by\n# partition key) across nodes in the cluster.  You should leave this\n# alone for new clusters.  The partitioner can NOT be changed without\n# reloading all data, so when upgrading you should set this to the\n# same partitioner you were already using.\n#\n# Besides Murmur3Partitioner, partitioners included for backwards\n# compatibility include RandomPartitioner, ByteOrderedPartitioner, and\n# OrderPreservingPartitioner.\n#\npartitioner: org.apache.cassandra.dht.Murmur3Partitioner\n\n# Directories where Cassandra should store data on disk.  Cassandra\n# will spread data evenly across them, subject to the granularity of\n# the configured compaction strategy.\n# If not set, the default directory is $CASSANDRA_HOME/data/data.\ndata_file_directories:\n    - /var/lib/cassandra/data\n\n# commit log.  when running on magnetic HDD, this should be a\n# separate spindle than the data directories.\n# If not set, the default directory is $CASSANDRA_HOME/data/commitlog.\ncommitlog_directory: /var/lib/cassandra/commitlog\n\n# Enable / disable CDC functionality on a per-node basis. This modifies the logic used\n# for write path allocation rejection (standard: never reject. cdc: reject Mutation\n# containing a CDC-enabled table if at space limit in cdc_raw_directory).\ncdc_enabled: false\n\n# CommitLogSegments are moved to this directory on flush if cdc_enabled: true and the\n# segment contains mutations for a CDC-enabled table. This should be placed on a\n# separate spindle than the data directories. If not set, the default directory is\n# $CASSANDRA_HOME/data/cdc_raw.\n# cdc_raw_directory: /var/lib/cassandra/cdc_raw\n\n# Policy for data disk failures:\n#\n# die\n#   shut down gossip and client transports and kill the JVM for any fs errors or\n#   single-sstable errors, so the node can be replaced.\n#\n# stop_paranoid\n#   shut down gossip and client transports even for single-sstable errors,\n#   kill the JVM for errors during startup.\n#\n# stop\n#   shut down gossip and client transports, leaving the node effectively dead, but\n#   can still be inspected via JMX, kill the JVM for errors during startup.\n#\n# best_effort\n#    stop using the failed disk and respond to requests based on\n#    remaining available sstables.  This means you WILL see obsolete\n#    data at CL.ONE!\n#\n# ignore\n#    ignore fatal errors and let requests fail, as in pre-1.2 Cassandra\ndisk_failure_policy: stop\n\n# Policy for commit disk failures:\n#\n# die\n#   shut down gossip and Thrift and kill the JVM, so the node can be replaced.\n#\n# stop\n#   shut down gossip and Thrift, leaving the node effectively dead, but\n#   can still be inspected via JMX.\n#\n# stop_commit\n#   shutdown the commit log, letting writes collect but\n#   continuing to service reads, as in pre-2.0.5 Cassandra\n#\n# ignore\n#   ignore fatal errors and let the batches fail\ncommit_failure_policy: stop\n\n# Maximum size of the native protocol prepared statement cache\n#\n# Valid values are either \"auto\" (omitting the value) or a value greater 0.\n#\n# Note that specifying a too large value will result in long running GCs and possibly\n# out-of-memory errors. Keep the value at a small fraction of the heap.\n#\n# If you constantly see \"prepared statements discarded in the last minute because\n# cache limit reached\" messages, the first step is to investigate the root cause\n# of these messages and check whether prepared statements are used correctly -\n# i.e. use bind markers for variable parts.\n#\n# Do only change the default value, if you really have more prepared statements than\n# fit in the cache. In most cases it is not necessary to change this value.\n# Constantly re-preparing statements is a performance penalty.\n#\n# Default value (\"auto\") is 1/256th of the heap or 10MB, whichever is greater\nprepared_statements_cache_size_mb:\n\n# Maximum size of the Thrift prepared statement cache\n#\n# If you do not use Thrift at all, it is safe to leave this value at \"auto\".\n#\n# See description of 'prepared_statements_cache_size_mb' above for more information.\n#\n# Default value (\"auto\") is 1/256th of the heap or 10MB, whichever is greater\nthrift_prepared_statements_cache_size_mb:\n\n# Maximum size of the key cache in memory.\n#\n# Each key cache hit saves 1 seek and each row cache hit saves 2 seeks at the\n# minimum, sometimes more. The key cache is fairly tiny for the amount of\n# time it saves, so it's worthwhile to use it at large numbers.\n# The row cache saves even more time, but must contain the entire row,\n# so it is extremely space-intensive. It's best to only use the\n# row cache if you have hot rows or static rows.\n#\n# NOTE: if you reduce the size, you may not get you hottest keys loaded on startup.\n#\n# Default value is empty to make it \"auto\" (min(5% of Heap (in MB), 100MB)). Set to 0 to disable key cache.\nkey_cache_size_in_mb:\n\n# Duration in seconds after which Cassandra should\n# save the key cache. Caches are saved to saved_caches_directory as\n# specified in this configuration file.\n#\n# Saved caches greatly improve cold-start speeds, and is relatively cheap in\n# terms of I/O for the key cache. Row cache saving is much more expensive and\n# has limited use.\n#\n# Default is 14400 or 4 hours.\nkey_cache_save_period: 14400\n\n# Number of keys from the key cache to save\n# Disabled by default, meaning all keys are going to be saved\n# key_cache_keys_to_save: 100\n\n# Row cache implementation class name. Available implementations:\n#\n# org.apache.cassandra.cache.OHCProvider\n#   Fully off-heap row cache implementation (default).\n#\n# org.apache.cassandra.cache.SerializingCacheProvider\n#   This is the row cache implementation available\n#   in previous releases of Cassandra.\n# row_cache_class_name: org.apache.cassandra.cache.OHCProvider\n\n# Maximum size of the row cache in memory.\n# Please note that OHC cache implementation requires some additional off-heap memory to manage\n# the map structures and some in-flight memory during operations before/after cache entries can be\n# accounted against the cache capacity. This overhead is usually small compared to the whole capacity.\n# Do not specify more memory that the system can afford in the worst usual situation and leave some\n# headroom for OS block level cache. Do never allow your system to swap.\n#\n# Default value is 0, to disable row caching.\nrow_cache_size_in_mb: 0\n\n# Duration in seconds after which Cassandra should save the row cache.\n# Caches are saved to saved_caches_directory as specified in this configuration file.\n#\n# Saved caches greatly improve cold-start speeds, and is relatively cheap in\n# terms of I/O for the key cache. Row cache saving is much more expensive and\n# has limited use.\n#\n# Default is 0 to disable saving the row cache.\nrow_cache_save_period: 0\n\n# Number of keys from the row cache to save.\n# Specify 0 (which is the default), meaning all keys are going to be saved\n# row_cache_keys_to_save: 100\n\n# Maximum size of the counter cache in memory.\n#\n# Counter cache helps to reduce counter locks' contention for hot counter cells.\n# In case of RF = 1 a counter cache hit will cause Cassandra to skip the read before\n# write entirely. With RF > 1 a counter cache hit will still help to reduce the duration\n# of the lock hold, helping with hot counter cell updates, but will not allow skipping\n# the read entirely. Only the local (clock, count) tuple of a counter cell is kept\n# in memory, not the whole counter, so it's relatively cheap.\n#\n# NOTE: if you reduce the size, you may not get you hottest keys loaded on startup.\n#\n# Default value is empty to make it \"auto\" (min(2.5% of Heap (in MB), 50MB)). Set to 0 to disable counter cache.\n# NOTE: if you perform counter deletes and rely on low gcgs, you should disable the counter cache.\ncounter_cache_size_in_mb:\n\n# Duration in seconds after which Cassandra should\n# save the counter cache (keys only). Caches are saved to saved_caches_directory as\n# specified in this configuration file.\n#\n# Default is 7200 or 2 hours.\ncounter_cache_save_period: 7200\n\n# Number of keys from the counter cache to save\n# Disabled by default, meaning all keys are going to be saved\n# counter_cache_keys_to_save: 100\n\n# saved caches\n# If not set, the default directory is $CASSANDRA_HOME/data/saved_caches.\nsaved_caches_directory: /var/lib/cassandra/saved_caches\n\n# commitlog_sync may be either \"periodic\" or \"batch.\"\n#\n# When in batch mode, Cassandra won't ack writes until the commit log\n# has been fsynced to disk.  It will wait\n# commitlog_sync_batch_window_in_ms milliseconds between fsyncs.\n# This window should be kept short because the writer threads will\n# be unable to do extra work while waiting.  (You may need to increase\n# concurrent_writes for the same reason.)\n#\n# commitlog_sync: batch\n# commitlog_sync_batch_window_in_ms: 2\n#\n# the other option is \"periodic\" where writes may be acked immediately\n# and the CommitLog is simply synced every commitlog_sync_period_in_ms\n# milliseconds.\ncommitlog_sync: periodic\ncommitlog_sync_period_in_ms: 10000\n\n# The size of the individual commitlog file segments.  A commitlog\n# segment may be archived, deleted, or recycled once all the data\n# in it (potentially from each columnfamily in the system) has been\n# flushed to sstables.\n#\n# The default size is 32, which is almost always fine, but if you are\n# archiving commitlog segments (see commitlog_archiving.properties),\n# then you probably want a finer granularity of archiving; 8 or 16 MB\n# is reasonable.\n# Max mutation size is also configurable via max_mutation_size_in_kb setting in\n# cassandra.yaml. The default is half the size commitlog_segment_size_in_mb * 1024.\n# This should be positive and less than 2048.\n#\n# NOTE: If max_mutation_size_in_kb is set explicitly then commitlog_segment_size_in_mb must\n# be set to at least twice the size of max_mutation_size_in_kb / 1024\n#\ncommitlog_segment_size_in_mb: 32\n\n# Compression to apply to the commit log. If omitted, the commit log\n# will be written uncompressed.  LZ4, Snappy, and Deflate compressors\n# are supported.\n# commitlog_compression:\n#   - class_name: LZ4Compressor\n#     parameters:\n#         -\n\n# any class that implements the SeedProvider interface and has a\n# constructor that takes a Map<String, String> of parameters will do.\nseed_provider:\n    # Addresses of hosts that are deemed contact points.\n    # Cassandra nodes use this list of hosts to find each other and learn\n    # the topology of the ring.  You must change this if you are running\n    # multiple nodes!\n    - class_name: org.apache.cassandra.locator.SimpleSeedProvider\n      parameters:\n          # seeds is actually a comma-delimited list of addresses.\n          # Ex: \"<ip1>,<ip2>,<ip3>\"\n          - seeds: \"172.17.0.2\"\n\n# For workloads with more data than can fit in memory, Cassandra's\n# bottleneck will be reads that need to fetch data from\n# disk. \"concurrent_reads\" should be set to (16 * number_of_drives) in\n# order to allow the operations to enqueue low enough in the stack\n# that the OS and drives can reorder them. Same applies to\n# \"concurrent_counter_writes\", since counter writes read the current\n# values before incrementing and writing them back.\n#\n# On the other hand, since writes are almost never IO bound, the ideal\n# number of \"concurrent_writes\" is dependent on the number of cores in\n# your system; (8 * number_of_cores) is a good rule of thumb.\nconcurrent_reads: 32\nconcurrent_writes: 32\nconcurrent_counter_writes: 32\n\n# For materialized view writes, as there is a read involved, so this should\n# be limited by the less of concurrent reads or concurrent writes.\nconcurrent_materialized_view_writes: 32\n\n# Maximum memory to use for sstable chunk cache and buffer pooling.\n# 32MB of this are reserved for pooling buffers, the rest is used as a\n# cache that holds uncompressed sstable chunks.\n# Defaults to the smaller of 1/4 of heap or 512MB. This pool is allocated off-heap,\n# so is in addition to the memory allocated for heap. The cache also has on-heap\n# overhead which is roughly 128 bytes per chunk (i.e. 0.2% of the reserved size\n# if the default 64k chunk size is used).\n# Memory is only allocated when needed.\n# file_cache_size_in_mb: 512\n\n# Flag indicating whether to allocate on or off heap when the sstable buffer\n# pool is exhausted, that is when it has exceeded the maximum memory\n# file_cache_size_in_mb, beyond which it will not cache buffers but allocate on request.\n\n# buffer_pool_use_heap_if_exhausted: true\n\n# The strategy for optimizing disk read\n# Possible values are:\n# ssd (for solid state disks, the default)\n# spinning (for spinning disks)\n# disk_optimization_strategy: ssd\n\n# Total permitted memory to use for memtables. Cassandra will stop\n# accepting writes when the limit is exceeded until a flush completes,\n# and will trigger a flush based on memtable_cleanup_threshold\n# If omitted, Cassandra will set both to 1/4 the size of the heap.\n# memtable_heap_space_in_mb: 2048\n# memtable_offheap_space_in_mb: 2048\n\n# memtable_cleanup_threshold is deprecated. The default calculation\n# is the only reasonable choice. See the comments on  memtable_flush_writers\n# for more information.\n#\n# Ratio of occupied non-flushing memtable size to total permitted size\n# that will trigger a flush of the largest memtable. Larger mct will\n# mean larger flushes and hence less compaction, but also less concurrent\n# flush activity which can make it difficult to keep your disks fed\n# under heavy write load.\n#\n# memtable_cleanup_threshold defaults to 1 / (memtable_flush_writers + 1)\n# memtable_cleanup_threshold: 0.11\n\n# Specify the way Cassandra allocates and manages memtable memory.\n# Options are:\n#\n# heap_buffers\n#   on heap nio buffers\n#\n# offheap_buffers\n#   off heap (direct) nio buffers\n#\n# offheap_objects\n#    off heap objects\nmemtable_allocation_type: heap_buffers\n\n# Total space to use for commit logs on disk.\n#\n# If space gets above this value, Cassandra will flush every dirty CF\n# in the oldest segment and remove it.  So a small total commitlog space\n# will tend to cause more flush activity on less-active columnfamilies.\n#\n# The default value is the smaller of 8192, and 1/4 of the total space\n# of the commitlog volume.\n#\n# commitlog_total_space_in_mb: 8192\n\n# This sets the number of memtable flush writer threads per disk\n# as well as the total number of memtables that can be flushed concurrently.\n# These are generally a combination of compute and IO bound.\n#\n# Memtable flushing is more CPU efficient than memtable ingest and a single thread\n# can keep up with the ingest rate of a whole server on a single fast disk\n# until it temporarily becomes IO bound under contention typically with compaction.\n# At that point you need multiple flush threads. At some point in the future\n# it may become CPU bound all the time.\n#\n# You can tell if flushing is falling behind using the MemtablePool.BlockedOnAllocation\n# metric which should be 0, but will be non-zero if threads are blocked waiting on flushing\n# to free memory.\n#\n# memtable_flush_writers defaults to two for a single data directory.\n# This means that two  memtables can be flushed concurrently to the single data directory.\n# If you have multiple data directories the default is one memtable flushing at a time\n# but the flush will use a thread per data directory so you will get two or more writers.\n#\n# Two is generally enough to flush on a fast disk [array] mounted as a single data directory.\n# Adding more flush writers will result in smaller more frequent flushes that introduce more\n# compaction overhead.\n#\n# There is a direct tradeoff between number of memtables that can be flushed concurrently\n# and flush size and frequency. More is not better you just need enough flush writers\n# to never stall waiting for flushing to free memory.\n#\n#memtable_flush_writers: 2\n\n# Total space to use for change-data-capture logs on disk.\n#\n# If space gets above this value, Cassandra will throw WriteTimeoutException\n# on Mutations including tables with CDC enabled. A CDCCompactor is responsible\n# for parsing the raw CDC logs and deleting them when parsing is completed.\n#\n# The default value is the min of 4096 mb and 1/8th of the total space\n# of the drive where cdc_raw_directory resides.\n# cdc_total_space_in_mb: 4096\n\n# When we hit our cdc_raw limit and the CDCCompactor is either running behind\n# or experiencing backpressure, we check at the following interval to see if any\n# new space for cdc-tracked tables has been made available. Default to 250ms\n# cdc_free_space_check_interval_ms: 250\n\n# A fixed memory pool size in MB for SSTable index summaries. If left\n# empty, this will default to 5% of the heap size. If the memory usage of\n# all index summaries exceeds this limit, SSTables with low read rates will\n# shrink their index summaries in order to meet this limit.  However, this\n# is a best-effort process. In extreme conditions Cassandra may need to use\n# more than this amount of memory.\nindex_summary_capacity_in_mb:\n\n# How frequently index summaries should be resampled.  This is done\n# periodically to redistribute memory from the fixed-size pool to sstables\n# proportional their recent read rates.  Setting to -1 will disable this\n# process, leaving existing index summaries at their current sampling level.\nindex_summary_resize_interval_in_minutes: 60\n\n# Whether to, when doing sequential writing, fsync() at intervals in\n# order to force the operating system to flush the dirty\n# buffers. Enable this to avoid sudden dirty buffer flushing from\n# impacting read latencies. Almost always a good idea on SSDs; not\n# necessarily on platters.\ntrickle_fsync: false\ntrickle_fsync_interval_in_kb: 10240\n\n# TCP port, for commands and data\n# For security reasons, you should not expose this port to the internet.  Firewall it if needed.\nstorage_port: 7000\n\n# SSL port, for encrypted communication.  Unused unless enabled in\n# encryption_options\n# For security reasons, you should not expose this port to the internet.  Firewall it if needed.\nssl_storage_port: 7001\n\n# Address or interface to bind to and tell other Cassandra nodes to connect to.\n# You _must_ change this if you want multiple nodes to be able to communicate!\n#\n# Set listen_address OR listen_interface, not both.\n#\n# Leaving it blank leaves it up to InetAddress.getLocalHost(). This\n# will always do the Right Thing _if_ the node is properly configured\n# (hostname, name resolution, etc), and the Right Thing is to use the\n# address associated with the hostname (it might not be).\n#\n# Setting listen_address to 0.0.0.0 is always wrong.\n#\nlisten_address: 172.17.0.2\n\n# Set listen_address OR listen_interface, not both. Interfaces must correspond\n# to a single address, IP aliasing is not supported.\n# listen_interface: eth0\n\n# If you choose to specify the interface by name and the interface has an ipv4 and an ipv6 address\n# you can specify which should be chosen using listen_interface_prefer_ipv6. If false the first ipv4\n# address will be used. If true the first ipv6 address will be used. Defaults to false preferring\n# ipv4. If there is only one address it will be selected regardless of ipv4/ipv6.\n# listen_interface_prefer_ipv6: false\n\n# Address to broadcast to other Cassandra nodes\n# Leaving this blank will set it to the same value as listen_address\nbroadcast_address: 172.17.0.2\n\n# When using multiple physical network interfaces, set this\n# to true to listen on broadcast_address in addition to\n# the listen_address, allowing nodes to communicate in both\n# interfaces.\n# Ignore this property if the network configuration automatically\n# routes  between the public and private networks such as EC2.\n# listen_on_broadcast_address: false\n\n# Internode authentication backend, implementing IInternodeAuthenticator;\n# used to allow/disallow connections from peer nodes.\n# internode_authenticator: org.apache.cassandra.auth.AllowAllInternodeAuthenticator\n\n# Whether to start the native transport server.\n# Please note that the address on which the native transport is bound is the\n# same as the rpc_address. The port however is different and specified below.\nstart_native_transport: true\n# port for the CQL native transport to listen for clients on\n# For security reasons, you should not expose this port to the internet.  Firewall it if needed.\nnative_transport_port: 9042\n# Enabling native transport encryption in client_encryption_options allows you to either use\n# encryption for the standard port or to use a dedicated, additional port along with the unencrypted\n# standard native_transport_port.\n# Enabling client encryption and keeping native_transport_port_ssl disabled will use encryption\n# for native_transport_port. Setting native_transport_port_ssl to a different value\n# from native_transport_port will use encryption for native_transport_port_ssl while\n# keeping native_transport_port unencrypted.\n# native_transport_port_ssl: 9142\n# The maximum threads for handling requests when the native transport is used.\n# This is similar to rpc_max_threads though the default differs slightly (and\n# there is no native_transport_min_threads, idle threads will always be stopped\n# after 30 seconds).\n# native_transport_max_threads: 128\n#\n# The maximum size of allowed frame. Frame (requests) larger than this will\n# be rejected as invalid. The default is 256MB. If you're changing this parameter,\n# you may want to adjust max_value_size_in_mb accordingly. This should be positive and less than 2048.\n# native_transport_max_frame_size_in_mb: 256\n\n# The maximum number of concurrent client connections.\n# The default is -1, which means unlimited.\n# native_transport_max_concurrent_connections: -1\n\n# The maximum number of concurrent client connections per source ip.\n# The default is -1, which means unlimited.\n# native_transport_max_concurrent_connections_per_ip: -1\n\n# Whether to start the thrift rpc server.\nstart_rpc: false\n\n# The address or interface to bind the Thrift RPC service and native transport\n# server to.\n#\n# Set rpc_address OR rpc_interface, not both.\n#\n# Leaving rpc_address blank has the same effect as on listen_address\n# (i.e. it will be based on the configured hostname of the node).\n#\n# Note that unlike listen_address, you can specify 0.0.0.0, but you must also\n# set broadcast_rpc_address to a value other than 0.0.0.0.\n#\n# For security reasons, you should not expose this port to the internet.  Firewall it if needed.\nrpc_address: 0.0.0.0\n\n# Set rpc_address OR rpc_interface, not both. Interfaces must correspond\n# to a single address, IP aliasing is not supported.\n# rpc_interface: eth1\n\n# If you choose to specify the interface by name and the interface has an ipv4 and an ipv6 address\n# you can specify which should be chosen using rpc_interface_prefer_ipv6. If false the first ipv4\n# address will be used. If true the first ipv6 address will be used. Defaults to false preferring\n# ipv4. If there is only one address it will be selected regardless of ipv4/ipv6.\n# rpc_interface_prefer_ipv6: false\n\n# port for Thrift to listen for clients on\nrpc_port: 9160\n\n# RPC address to broadcast to drivers and other Cassandra nodes. This cannot\n# be set to 0.0.0.0. If left blank, this will be set to the value of\n# rpc_address. If rpc_address is set to 0.0.0.0, broadcast_rpc_address must\n# be set.\nbroadcast_rpc_address: 172.17.0.2\n\n# enable or disable keepalive on rpc/native connections\nrpc_keepalive: true\n\n# Cassandra provides two out-of-the-box options for the RPC Server:\n#\n# sync\n#   One thread per thrift connection. For a very large number of clients, memory\n#   will be your limiting factor. On a 64 bit JVM, 180KB is the minimum stack size\n#   per thread, and that will correspond to your use of virtual memory (but physical memory\n#   may be limited depending on use of stack space).\n#\n# hsha\n#   Stands for \"half synchronous, half asynchronous.\" All thrift clients are handled\n#   asynchronously using a small number of threads that does not vary with the amount\n#   of thrift clients (and thus scales well to many clients). The rpc requests are still\n#   synchronous (one thread per active request). If hsha is selected then it is essential\n#   that rpc_max_threads is changed from the default value of unlimited.\n#\n# The default is sync because on Windows hsha is about 30% slower.  On Linux,\n# sync/hsha performance is about the same, with hsha of course using less memory.\n#\n# Alternatively,  can provide your own RPC server by providing the fully-qualified class name\n# of an o.a.c.t.TServerFactory that can create an instance of it.\nrpc_server_type: sync\n\n# Uncomment rpc_min|max_thread to set request pool size limits.\n#\n# Regardless of your choice of RPC server (see above), the number of maximum requests in the\n# RPC thread pool dictates how many concurrent requests are possible (but if you are using the sync\n# RPC server, it also dictates the number of clients that can be connected at all).\n#\n# The default is unlimited and thus provides no protection against clients overwhelming the server. You are\n# encouraged to set a maximum that makes sense for you in production, but do keep in mind that\n# rpc_max_threads represents the maximum number of client requests this server may execute concurrently.\n#\n# rpc_min_threads: 16\n# rpc_max_threads: 2048\n\n# uncomment to set socket buffer sizes on rpc connections\n# rpc_send_buff_size_in_bytes:\n# rpc_recv_buff_size_in_bytes:\n\n# Uncomment to set socket buffer size for internode communication\n# Note that when setting this, the buffer size is limited by net.core.wmem_max\n# and when not setting it it is defined by net.ipv4.tcp_wmem\n# See also:\n# /proc/sys/net/core/wmem_max\n# /proc/sys/net/core/rmem_max\n# /proc/sys/net/ipv4/tcp_wmem\n# /proc/sys/net/ipv4/tcp_wmem\n# and 'man tcp'\n# internode_send_buff_size_in_bytes:\n\n# Uncomment to set socket buffer size for internode communication\n# Note that when setting this, the buffer size is limited by net.core.wmem_max\n# and when not setting it it is defined by net.ipv4.tcp_wmem\n# internode_recv_buff_size_in_bytes:\n\n# Frame size for thrift (maximum message length).\nthrift_framed_transport_size_in_mb: 15\n\n# Set to true to have Cassandra create a hard link to each sstable\n# flushed or streamed locally in a backups/ subdirectory of the\n# keyspace data.  Removing these links is the operator's\n# responsibility.\nincremental_backups: false\n\n# Whether or not to take a snapshot before each compaction.  Be\n# careful using this option, since Cassandra won't clean up the\n# snapshots for you.  Mostly useful if you're paranoid when there\n# is a data format change.\nsnapshot_before_compaction: false\n\n# Whether or not a snapshot is taken of the data before keyspace truncation\n# or dropping of column families. The STRONGLY advised default of true\n# should be used to provide data safety. If you set this flag to false, you will\n# lose data on truncation or drop.\nauto_snapshot: true\n\n# Granularity of the collation index of rows within a partition.\n# Increase if your rows are large, or if you have a very large\n# number of rows per partition.  The competing goals are these:\n#\n# - a smaller granularity means more index entries are generated\n#   and looking up rows within the partition by collation column\n#   is faster\n# - but, Cassandra will keep the collation index in memory for hot\n#   rows (as part of the key cache), so a larger granularity means\n#   you can cache more hot rows\ncolumn_index_size_in_kb: 64\n\n# Per sstable indexed key cache entries (the collation index in memory\n# mentioned above) exceeding this size will not be held on heap.\n# This means that only partition information is held on heap and the\n# index entries are read from disk.\n#\n# Note that this size refers to the size of the\n# serialized index information and not the size of the partition.\ncolumn_index_cache_size_in_kb: 2\n\n# Number of simultaneous compactions to allow, NOT including\n# validation \"compactions\" for anti-entropy repair.  Simultaneous\n# compactions can help preserve read performance in a mixed read/write\n# workload, by mitigating the tendency of small sstables to accumulate\n# during a single long running compactions. The default is usually\n# fine and if you experience problems with compaction running too\n# slowly or too fast, you should look at\n# compaction_throughput_mb_per_sec first.\n#\n# concurrent_compactors defaults to the smaller of (number of disks,\n# number of cores), with a minimum of 2 and a maximum of 8.\n#\n# If your data directories are backed by SSD, you should increase this\n# to the number of cores.\n#concurrent_compactors: 1\n\n# Throttles compaction to the given total throughput across the entire\n# system. The faster you insert data, the faster you need to compact in\n# order to keep the sstable count down, but in general, setting this to\n# 16 to 32 times the rate you are inserting data is more than sufficient.\n# Setting this to 0 disables throttling. Note that this account for all types\n# of compaction, including validation compaction.\ncompaction_throughput_mb_per_sec: 16\n\n# When compacting, the replacement sstable(s) can be opened before they\n# are completely written, and used in place of the prior sstables for\n# any range that has been written. This helps to smoothly transfer reads\n# between the sstables, reducing page cache churn and keeping hot rows hot\nsstable_preemptive_open_interval_in_mb: 50\n\n# Throttles all outbound streaming file transfers on this node to the\n# given total throughput in Mbps. This is necessary because Cassandra does\n# mostly sequential IO when streaming data during bootstrap or repair, which\n# can lead to saturating the network connection and degrading rpc performance.\n# When unset, the default is 200 Mbps or 25 MB/s.\n# stream_throughput_outbound_megabits_per_sec: 200\n\n# Throttles all streaming file transfer between the datacenters,\n# this setting allows users to throttle inter dc stream throughput in addition\n# to throttling all network stream traffic as configured with\n# stream_throughput_outbound_megabits_per_sec\n# When unset, the default is 200 Mbps or 25 MB/s\n# inter_dc_stream_throughput_outbound_megabits_per_sec: 200\n\n# How long the coordinator should wait for read operations to complete\nread_request_timeout_in_ms: 5000\n# How long the coordinator should wait for seq or index scans to complete\nrange_request_timeout_in_ms: 10000\n# How long the coordinator should wait for writes to complete\nwrite_request_timeout_in_ms: 2000\n# How long the coordinator should wait for counter writes to complete\ncounter_write_request_timeout_in_ms: 5000\n# How long a coordinator should continue to retry a CAS operation\n# that contends with other proposals for the same row\ncas_contention_timeout_in_ms: 1000\n# How long the coordinator should wait for truncates to complete\n# (This can be much longer, because unless auto_snapshot is disabled\n# we need to flush first so we can snapshot before removing the data.)\ntruncate_request_timeout_in_ms: 60000\n# The default timeout for other, miscellaneous operations\nrequest_timeout_in_ms: 10000\n\n# How long before a node logs slow queries. Select queries that take longer than\n# this timeout to execute, will generate an aggregated log message, so that slow queries\n# can be identified. Set this value to zero to disable slow query logging.\nslow_query_log_timeout_in_ms: 500\n\n# Enable operation timeout information exchange between nodes to accurately\n# measure request timeouts.  If disabled, replicas will assume that requests\n# were forwarded to them instantly by the coordinator, which means that\n# under overload conditions we will waste that much extra time processing\n# already-timed-out requests.\n#\n# Warning: before enabling this property make sure to ntp is installed\n# and the times are synchronized between the nodes.\ncross_node_timeout: false\n\n# Set keep-alive period for streaming\n# This node will send a keep-alive message periodically with this period.\n# If the node does not receive a keep-alive message from the peer for\n# 2 keep-alive cycles the stream session times out and fail\n# Default value is 300s (5 minutes), which means stalled stream\n# times out in 10 minutes by default\n# streaming_keep_alive_period_in_secs: 300\n\n# phi value that must be reached for a host to be marked down.\n# most users should never need to adjust this.\n# phi_convict_threshold: 8\n\n# endpoint_snitch -- Set this to a class that implements\n# IEndpointSnitch.  The snitch has two functions:\n#\n# - it teaches Cassandra enough about your network topology to route\n#   requests efficiently\n# - it allows Cassandra to spread replicas around your cluster to avoid\n#   correlated failures. It does this by grouping machines into\n#   \"datacenters\" and \"racks.\"  Cassandra will do its best not to have\n#   more than one replica on the same \"rack\" (which may not actually\n#   be a physical location)\n#\n# CASSANDRA WILL NOT ALLOW YOU TO SWITCH TO AN INCOMPATIBLE SNITCH\n# ONCE DATA IS INSERTED INTO THE CLUSTER.  This would cause data loss.\n# This means that if you start with the default SimpleSnitch, which\n# locates every node on \"rack1\" in \"datacenter1\", your only options\n# if you need to add another datacenter are GossipingPropertyFileSnitch\n# (and the older PFS).  From there, if you want to migrate to an\n# incompatible snitch like Ec2Snitch you can do it by adding new nodes\n# under Ec2Snitch (which will locate them in a new \"datacenter\") and\n# decommissioning the old ones.\n#\n# Out of the box, Cassandra provides:\n#\n# SimpleSnitch:\n#    Treats Strategy order as proximity. This can improve cache\n#    locality when disabling read repair.  Only appropriate for\n#    single-datacenter deployments.\n#\n# GossipingPropertyFileSnitch\n#    This should be your go-to snitch for production use.  The rack\n#    and datacenter for the local node are defined in\n#    cassandra-rackdc.properties and propagated to other nodes via\n#    gossip.  If cassandra-topology.properties exists, it is used as a\n#    fallback, allowing migration from the PropertyFileSnitch.\n#\n# PropertyFileSnitch:\n#    Proximity is determined by rack and data center, which are\n#    explicitly configured in cassandra-topology.properties.\n#\n# Ec2Snitch:\n#    Appropriate for EC2 deployments in a single Region. Loads Region\n#    and Availability Zone information from the EC2 API. The Region is\n#    treated as the datacenter, and the Availability Zone as the rack.\n#    Only private IPs are used, so this will not work across multiple\n#    Regions.\n#\n# Ec2MultiRegionSnitch:\n#    Uses public IPs as broadcast_address to allow cross-region\n#    connectivity.  (Thus, you should set seed addresses to the public\n#    IP as well.) You will need to open the storage_port or\n#    ssl_storage_port on the public IP firewall.  (For intra-Region\n#    traffic, Cassandra will switch to the private IP after\n#    establishing a connection.)\n#\n# RackInferringSnitch:\n#    Proximity is determined by rack and data center, which are\n#    assumed to correspond to the 3rd and 2nd octet of each node's IP\n#    address, respectively.  Unless this happens to match your\n#    deployment conventions, this is best used as an example of\n#    writing a custom Snitch class and is provided in that spirit.\n#\n# You can use a custom Snitch by setting this to the full class name\n# of the snitch, which will be assumed to be on your classpath.\nendpoint_snitch: SimpleSnitch\n\n# controls how often to perform the more expensive part of host score\n# calculation\ndynamic_snitch_update_interval_in_ms: 100\n# controls how often to reset all host scores, allowing a bad host to\n# possibly recover\ndynamic_snitch_reset_interval_in_ms: 600000\n# if set greater than zero and read_repair_chance is < 1.0, this will allow\n# 'pinning' of replicas to hosts in order to increase cache capacity.\n# The badness threshold will control how much worse the pinned host has to be\n# before the dynamic snitch will prefer other replicas over it.  This is\n# expressed as a double which represents a percentage.  Thus, a value of\n# 0.2 means Cassandra would continue to prefer the static snitch values\n# until the pinned host was 20% worse than the fastest.\ndynamic_snitch_badness_threshold: 0.1\n\n# request_scheduler -- Set this to a class that implements\n# RequestScheduler, which will schedule incoming client requests\n# according to the specific policy. This is useful for multi-tenancy\n# with a single Cassandra cluster.\n# NOTE: This is specifically for requests from the client and does\n# not affect inter node communication.\n# org.apache.cassandra.scheduler.NoScheduler - No scheduling takes place\n# org.apache.cassandra.scheduler.RoundRobinScheduler - Round robin of\n# client requests to a node with a separate queue for each\n# request_scheduler_id. The scheduler is further customized by\n# request_scheduler_options as described below.\nrequest_scheduler: org.apache.cassandra.scheduler.NoScheduler\n\n# Scheduler Options vary based on the type of scheduler\n#\n# NoScheduler\n#   Has no options\n#\n# RoundRobin\n#   throttle_limit\n#     The throttle_limit is the number of in-flight\n#     requests per client.  Requests beyond\n#     that limit are queued up until\n#     running requests can complete.\n#     The value of 80 here is twice the number of\n#     concurrent_reads + concurrent_writes.\n#   default_weight\n#     default_weight is optional and allows for\n#     overriding the default which is 1.\n#   weights\n#     Weights are optional and will default to 1 or the\n#     overridden default_weight. The weight translates into how\n#     many requests are handled during each turn of the\n#     RoundRobin, based on the scheduler id.\n#\n# request_scheduler_options:\n#    throttle_limit: 80\n#    default_weight: 5\n#    weights:\n#      Keyspace1: 1\n#      Keyspace2: 5\n\n# request_scheduler_id -- An identifier based on which to perform\n# the request scheduling. Currently the only valid option is keyspace.\n# request_scheduler_id: keyspace\n\n# Enable or disable inter-node encryption\n# JVM defaults for supported SSL socket protocols and cipher suites can\n# be replaced using custom encryption options. This is not recommended\n# unless you have policies in place that dictate certain settings, or\n# need to disable vulnerable ciphers or protocols in case the JVM cannot\n# be updated.\n# FIPS compliant settings can be configured at JVM level and should not\n# involve changing encryption settings here:\n# https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/FIPS.html\n# *NOTE* No custom encryption options are enabled at the moment\n# The available internode options are : all, none, dc, rack\n#\n# If set to dc cassandra will encrypt the traffic between the DCs\n# If set to rack cassandra will encrypt the traffic between the racks\n#\n# The passwords used in these options must match the passwords used when generating\n# the keystore and truststore.  For instructions on generating these files, see:\n# http://download.oracle.com/javase/6/docs/technotes/guides/security/jsse/JSSERefGuide.html#CreateKeystore\n#\nserver_encryption_options:\n    internode_encryption: none\n    keystore: conf/.keystore\n    keystore_password: cassandra\n    truststore: conf/.truststore\n    truststore_password: cassandra\n    # More advanced defaults below:\n    # protocol: TLS\n    # algorithm: SunX509\n    # store_type: JKS\n    # cipher_suites: [TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA,TLS_DHE_RSA_WITH_AES_128_CBC_SHA,TLS_DHE_RSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA]\n    # require_client_auth: false\n    # require_endpoint_verification: false\n\n# enable or disable client/server encryption.\nclient_encryption_options:\n    enabled: false\n    # If enabled and optional is set to true encrypted and unencrypted connections are handled.\n    optional: false\n    keystore: conf/.keystore\n    keystore_password: cassandra\n    # require_client_auth: false\n    # Set trustore and truststore_password if require_client_auth is true\n    # truststore: conf/.truststore\n    # truststore_password: cassandra\n    # More advanced defaults below:\n    # protocol: TLS\n    # algorithm: SunX509\n    # store_type: JKS\n    # cipher_suites: [TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA,TLS_DHE_RSA_WITH_AES_128_CBC_SHA,TLS_DHE_RSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA]\n\n# internode_compression controls whether traffic between nodes is\n# compressed.\n# Can be:\n#\n# all\n#   all traffic is compressed\n#\n# dc\n#   traffic between different datacenters is compressed\n#\n# none\n#   nothing is compressed.\ninternode_compression: dc\n\n# Enable or disable tcp_nodelay for inter-dc communication.\n# Disabling it will result in larger (but fewer) network packets being sent,\n# reducing overhead from the TCP protocol itself, at the cost of increasing\n# latency if you block for cross-datacenter responses.\ninter_dc_tcp_nodelay: false\n\n# TTL for different trace types used during logging of the repair process.\ntracetype_query_ttl: 86400\ntracetype_repair_ttl: 604800\n\n# By default, Cassandra logs GC Pauses greater than 200 ms at INFO level\n# This threshold can be adjusted to minimize logging if necessary\n# gc_log_threshold_in_ms: 200\n\n# If unset, all GC Pauses greater than gc_log_threshold_in_ms will log at\n# INFO level\n# UDFs (user defined functions) are disabled by default.\n# As of Cassandra 3.0 there is a sandbox in place that should prevent execution of evil code.\nenable_user_defined_functions: false\n\n# Enables scripted UDFs (JavaScript UDFs).\n# Java UDFs are always enabled, if enable_user_defined_functions is true.\n# Enable this option to be able to use UDFs with \"language javascript\" or any custom JSR-223 provider.\n# This option has no effect, if enable_user_defined_functions is false.\nenable_scripted_user_defined_functions: false\n\n# The default Windows kernel timer and scheduling resolution is 15.6ms for power conservation.\n# Lowering this value on Windows can provide much tighter latency and better throughput, however\n# some virtualized environments may see a negative performance impact from changing this setting\n# below their system default. The sysinternals 'clockres' tool can confirm your system's default\n# setting.\nwindows_timer_interval: 1\n\n\n# Enables encrypting data at-rest (on disk). Different key providers can be plugged in, but the default reads from\n# a JCE-style keystore. A single keystore can hold multiple keys, but the one referenced by\n# the \"key_alias\" is the only key that will be used for encrypt operations; previously used keys\n# can still (and should!) be in the keystore and will be used on decrypt operations\n# (to handle the case of key rotation).\n#\n# It is strongly recommended to download and install Java Cryptography Extension (JCE)\n# Unlimited Strength Jurisdiction Policy Files for your version of the JDK.\n# (current link: http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html)\n#\n# Currently, only the following file types are supported for transparent data encryption, although\n# more are coming in future cassandra releases: commitlog, hints\ntransparent_data_encryption_options:\n    enabled: false\n    chunk_length_kb: 64\n    cipher: AES/CBC/PKCS5Padding\n    key_alias: testing:1\n    # CBC IV length for AES needs to be 16 bytes (which is also the default size)\n    # iv_length: 16\n    key_provider:\n      - class_name: org.apache.cassandra.security.JKSKeyProvider\n        parameters:\n          - keystore: conf/.keystore\n            keystore_password: cassandra\n            store_type: JCEKS\n            key_password: cassandra\n\n\n#####################\n# SAFETY THRESHOLDS #\n#####################\n\n# When executing a scan, within or across a partition, we need to keep the\n# tombstones seen in memory so we can return them to the coordinator, which\n# will use them to make sure other replicas also know about the deleted rows.\n# With workloads that generate a lot of tombstones, this can cause performance\n# problems and even exhaust the server heap.\n# (http://www.datastax.com/dev/blog/cassandra-anti-patterns-queues-and-queue-like-datasets)\n# Adjust the thresholds here if you understand the dangers and want to\n# scan more tombstones anyway.  These thresholds may also be adjusted at runtime\n# using the StorageService mbean.\ntombstone_warn_threshold: 1000\ntombstone_failure_threshold: 100000\n\n# Log WARN on any multiple-partition batch size exceeding this value. 5kb per batch by default.\n# Caution should be taken on increasing the size of this threshold as it can lead to node instability.\nbatch_size_warn_threshold_in_kb: 5\n\n# Fail any multiple-partition batch exceeding this value. 50kb (10x warn threshold) by default.\nbatch_size_fail_threshold_in_kb: 50\n\n# Log WARN on any batches not of type LOGGED than span across more partitions than this limit\nunlogged_batch_across_partitions_warn_threshold: 10\n\n# Log a warning when compacting partitions larger than this value\ncompaction_large_partition_warning_threshold_mb: 100\n\n# GC Pauses greater than gc_warn_threshold_in_ms will be logged at WARN level\n# Adjust the threshold based on your application throughput requirement\n# By default, Cassandra logs GC Pauses greater than 200 ms at INFO level\ngc_warn_threshold_in_ms: 1000\n\n# Maximum size of any value in SSTables. Safety measure to detect SSTable corruption\n# early. Any value size larger than this threshold will result into marking an SSTable\n# as corrupted. This should be positive and less than 2048.\n# max_value_size_in_mb: 256\n\n# Back-pressure settings #\n# If enabled, the coordinator will apply the back-pressure strategy specified below to each mutation\n# sent to replicas, with the aim of reducing pressure on overloaded replicas.\nback_pressure_enabled: false\n# The back-pressure strategy applied.\n# The default implementation, RateBasedBackPressure, takes three arguments:\n# high ratio, factor, and flow type, and uses the ratio between incoming mutation responses and outgoing mutation requests.\n# If below high ratio, outgoing mutations are rate limited according to the incoming rate decreased by the given factor;\n# if above high ratio, the rate limiting is increased by the given factor;\n# such factor is usually best configured between 1 and 10, use larger values for a faster recovery\n# at the expense of potentially more dropped mutations;\n# the rate limiting is applied according to the flow type: if FAST, it's rate limited at the speed of the fastest replica,\n# if SLOW at the speed of the slowest one.\n# New strategies can be added. Implementors need to implement org.apache.cassandra.net.BackpressureStrategy and\n# provide a public constructor accepting a Map<String, Object>.\nback_pressure_strategy:\n    - class_name: org.apache.cassandra.net.RateBasedBackPressure\n      parameters:\n        - high_ratio: 0.90\n          factor: 5\n          flow: FAST\n\n# Coalescing Strategies #\n# Coalescing multiples messages turns out to significantly boost message processing throughput (think doubling or more).\n# On bare metal, the floor for packet processing throughput is high enough that many applications won't notice, but in\n# virtualized environments, the point at which an application can be bound by network packet processing can be\n# surprisingly low compared to the throughput of task processing that is possible inside a VM. It's not that bare metal\n# doesn't benefit from coalescing messages, it's that the number of packets a bare metal network interface can process\n# is sufficient for many applications such that no load starvation is experienced even without coalescing.\n# There are other benefits to coalescing network messages that are harder to isolate with a simple metric like messages\n# per second. By coalescing multiple tasks together, a network thread can process multiple messages for the cost of one\n# trip to read from a socket, and all the task submission work can be done at the same time reducing context switching\n# and increasing cache friendliness of network message processing.\n# See CASSANDRA-8692 for details.\n\n# Strategy to use for coalescing messages in OutboundTcpConnection.\n# Can be fixed, movingaverage, timehorizon, disabled (default).\n# You can also specify a subclass of CoalescingStrategies.CoalescingStrategy by name.\n# otc_coalescing_strategy: DISABLED\n\n# How many microseconds to wait for coalescing. For fixed strategy this is the amount of time after the first\n# message is received before it will be sent with any accompanying messages. For moving average this is the\n# maximum amount of time that will be waited as well as the interval at which messages must arrive on average\n# for coalescing to be enabled.\n# otc_coalescing_window_us: 200\n\n# Do not try to coalesce messages if we already got that many messages. This should be more than 2 and less than 128.\n# otc_coalescing_enough_coalesced_messages: 8\n\n# How many milliseconds to wait between two expiration runs on the backlog (queue) of the OutboundTcpConnection.\n# Expiration is done if messages are piling up in the backlog. Droppable messages are expired to free the memory\n# taken by expired messages. The interval should be between 0 and 1000, and in most installations the default value\n# will be appropriate. A smaller value could potentially expire messages slightly sooner at the expense of more CPU\n# time and queue contention while iterating the backlog of messages.\n# An interval of 0 disables any wait time, which is the behavior of former Cassandra versions.\n#\n# otc_backlog_expiration_interval_ms: 200\n"
  },
  {
    "path": "modules/cassandra/src/test/resources/cassandra-ssl-configuration/cassandra.yaml",
    "content": "# Cassandra storage config YAML\n\n# NOTE:\n#   See http://wiki.apache.org/cassandra/StorageConfiguration for\n#   full explanations of configuration directives\n# /NOTE\n\n# The name of the cluster. This is mainly used to prevent machines in\n# one logical cluster from joining another.\ncluster_name: 'Test Cluster Integration Test'\n\n# This defines the number of tokens randomly assigned to this node on the ring\n# The more tokens, relative to other nodes, the larger the proportion of data\n# that this node will store. You probably want all nodes to have the same number\n# of tokens assuming they have equal hardware capability.\n#\n# If you leave this unspecified, Cassandra will use the default of 1 token for legacy compatibility,\n# and will use the initial_token as described below.\n#\n# Specifying initial_token will override this setting on the node's initial start,\n# on subsequent starts, this setting will apply even if initial token is set.\n#\n# If you already have a cluster with 1 token per node, and wish to migrate to\n# multiple tokens per node, see http://wiki.apache.org/cassandra/Operations\nnum_tokens: 256\n\n# Triggers automatic allocation of num_tokens tokens for this node. The allocation\n# algorithm attempts to choose tokens in a way that optimizes replicated load over\n# the nodes in the datacenter for the replication strategy used by the specified\n# keyspace.\n#\n# The load assigned to each node will be close to proportional to its number of\n# vnodes.\n#\n# Only supported with the Murmur3Partitioner.\n# allocate_tokens_for_keyspace: KEYSPACE\n\n# initial_token allows you to specify tokens manually.  While you can use it with\n# vnodes (num_tokens > 1, above) -- in which case you should provide a\n# comma-separated list -- it's primarily used when adding nodes to legacy clusters\n# that do not have vnodes enabled.\n# initial_token:\n\n# See http://wiki.apache.org/cassandra/HintedHandoff\n# May either be \"true\" or \"false\" to enable globally\nhinted_handoff_enabled: true\n\n# When hinted_handoff_enabled is true, a black list of data centers that will not\n# perform hinted handoff\n# hinted_handoff_disabled_datacenters:\n#    - DC1\n#    - DC2\n\n# this defines the maximum amount of time a dead host will have hints\n# generated.  After it has been dead this long, new hints for it will not be\n# created until it has been seen alive and gone down again.\nmax_hint_window_in_ms: 10800000 # 3 hours\n\n# Maximum throttle in KBs per second, per delivery thread.  This will be\n# reduced proportionally to the number of nodes in the cluster.  (If there\n# are two nodes in the cluster, each delivery thread will use the maximum\n# rate; if there are three, each will throttle to half of the maximum,\n# since we expect two nodes to be delivering hints simultaneously.)\nhinted_handoff_throttle_in_kb: 1024\n\n# Number of threads with which to deliver hints;\n# Consider increasing this number when you have multi-dc deployments, since\n# cross-dc handoff tends to be slower\nmax_hints_delivery_threads: 2\n\n# Directory where Cassandra should store hints.\n# If not set, the default directory is $CASSANDRA_HOME/data/hints.\n# hints_directory: /var/lib/cassandra/hints\n\n# How often hints should be flushed from the internal buffers to disk.\n# Will *not* trigger fsync.\nhints_flush_period_in_ms: 10000\n\n# Maximum size for a single hints file, in megabytes.\nmax_hints_file_size_in_mb: 128\n\n# Compression to apply to the hint files. If omitted, hints files\n# will be written uncompressed. LZ4, Snappy, and Deflate compressors\n# are supported.\n#hints_compression:\n#   - class_name: LZ4Compressor\n#     parameters:\n#         -\n\n# Maximum throttle in KBs per second, total. This will be\n# reduced proportionally to the number of nodes in the cluster.\nbatchlog_replay_throttle_in_kb: 1024\n\n# Authentication backend, implementing IAuthenticator; used to identify users\n# Out of the box, Cassandra provides org.apache.cassandra.auth.{AllowAllAuthenticator,\n# PasswordAuthenticator}.\n#\n# - AllowAllAuthenticator performs no checks - set it to disable authentication.\n# - PasswordAuthenticator relies on username/password pairs to authenticate\n#   users. It keeps usernames and hashed passwords in system_auth.roles table.\n#   Please increase system_auth keyspace replication factor if you use this authenticator.\n#   If using PasswordAuthenticator, CassandraRoleManager must also be used (see below)\nauthenticator: AllowAllAuthenticator\n\n# Authorization backend, implementing IAuthorizer; used to limit access/provide permissions\n# Out of the box, Cassandra provides org.apache.cassandra.auth.{AllowAllAuthorizer,\n# CassandraAuthorizer}.\n#\n# - AllowAllAuthorizer allows any action to any user - set it to disable authorization.\n# - CassandraAuthorizer stores permissions in system_auth.role_permissions table. Please\n#   increase system_auth keyspace replication factor if you use this authorizer.\nauthorizer: AllowAllAuthorizer\n\n# Part of the Authentication & Authorization backend, implementing IRoleManager; used\n# to maintain grants and memberships between roles.\n# Out of the box, Cassandra provides org.apache.cassandra.auth.CassandraRoleManager,\n# which stores role information in the system_auth keyspace. Most functions of the\n# IRoleManager require an authenticated login, so unless the configured IAuthenticator\n# actually implements authentication, most of this functionality will be unavailable.\n#\n# - CassandraRoleManager stores role data in the system_auth keyspace. Please\n#   increase system_auth keyspace replication factor if you use this role manager.\nrole_manager: CassandraRoleManager\n\n# Validity period for roles cache (fetching granted roles can be an expensive\n# operation depending on the role manager, CassandraRoleManager is one example)\n# Granted roles are cached for authenticated sessions in AuthenticatedUser and\n# after the period specified here, become eligible for (async) reload.\n# Defaults to 2000, set to 0 to disable caching entirely.\n# Will be disabled automatically for AllowAllAuthenticator.\nroles_validity_in_ms: 2000\n\n# Refresh interval for roles cache (if enabled).\n# After this interval, cache entries become eligible for refresh. Upon next\n# access, an async reload is scheduled and the old value returned until it\n# completes. If roles_validity_in_ms is non-zero, then this must be\n# also.\n# Defaults to the same value as roles_validity_in_ms.\n# roles_update_interval_in_ms: 2000\n\n# Validity period for permissions cache (fetching permissions can be an\n# expensive operation depending on the authorizer, CassandraAuthorizer is\n# one example). Defaults to 2000, set to 0 to disable.\n# Will be disabled automatically for AllowAllAuthorizer.\npermissions_validity_in_ms: 2000\n\n# Refresh interval for permissions cache (if enabled).\n# After this interval, cache entries become eligible for refresh. Upon next\n# access, an async reload is scheduled and the old value returned until it\n# completes. If permissions_validity_in_ms is non-zero, then this must be\n# also.\n# Defaults to the same value as permissions_validity_in_ms.\n# permissions_update_interval_in_ms: 2000\n\n# Validity period for credentials cache. This cache is tightly coupled to\n# the provided PasswordAuthenticator implementation of IAuthenticator. If\n# another IAuthenticator implementation is configured, this cache will not\n# be automatically used and so the following settings will have no effect.\n# Please note, credentials are cached in their encrypted form, so while\n# activating this cache may reduce the number of queries made to the\n# underlying table, it may not  bring a significant reduction in the\n# latency of individual authentication attempts.\n# Defaults to 2000, set to 0 to disable credentials caching.\ncredentials_validity_in_ms: 2000\n\n# Refresh interval for credentials cache (if enabled).\n# After this interval, cache entries become eligible for refresh. Upon next\n# access, an async reload is scheduled and the old value returned until it\n# completes. If credentials_validity_in_ms is non-zero, then this must be\n# also.\n# Defaults to the same value as credentials_validity_in_ms.\n# credentials_update_interval_in_ms: 2000\n\n# The partitioner is responsible for distributing groups of rows (by\n# partition key) across nodes in the cluster.  You should leave this\n# alone for new clusters.  The partitioner can NOT be changed without\n# reloading all data, so when upgrading you should set this to the\n# same partitioner you were already using.\n#\n# Besides Murmur3Partitioner, partitioners included for backwards\n# compatibility include RandomPartitioner, ByteOrderedPartitioner, and\n# OrderPreservingPartitioner.\n#\npartitioner: org.apache.cassandra.dht.Murmur3Partitioner\n\n# Directories where Cassandra should store data on disk.  Cassandra\n# will spread data evenly across them, subject to the granularity of\n# the configured compaction strategy.\n# If not set, the default directory is $CASSANDRA_HOME/data/data.\ndata_file_directories:\n    - /var/lib/cassandra/data\n\n# commit log.  when running on magnetic HDD, this should be a\n# separate spindle than the data directories.\n# If not set, the default directory is $CASSANDRA_HOME/data/commitlog.\ncommitlog_directory: /var/lib/cassandra/commitlog\n\n# Enable / disable CDC functionality on a per-node basis. This modifies the logic used\n# for write path allocation rejection (standard: never reject. cdc: reject Mutation\n# containing a CDC-enabled table if at space limit in cdc_raw_directory).\ncdc_enabled: false\n\n# CommitLogSegments are moved to this directory on flush if cdc_enabled: true and the\n# segment contains mutations for a CDC-enabled table. This should be placed on a\n# separate spindle than the data directories. If not set, the default directory is\n# $CASSANDRA_HOME/data/cdc_raw.\n# cdc_raw_directory: /var/lib/cassandra/cdc_raw\n\n# Policy for data disk failures:\n#\n# die\n#   shut down gossip and client transports and kill the JVM for any fs errors or\n#   single-sstable errors, so the node can be replaced.\n#\n# stop_paranoid\n#   shut down gossip and client transports even for single-sstable errors,\n#   kill the JVM for errors during startup.\n#\n# stop\n#   shut down gossip and client transports, leaving the node effectively dead, but\n#   can still be inspected via JMX, kill the JVM for errors during startup.\n#\n# best_effort\n#    stop using the failed disk and respond to requests based on\n#    remaining available sstables.  This means you WILL see obsolete\n#    data at CL.ONE!\n#\n# ignore\n#    ignore fatal errors and let requests fail, as in pre-1.2 Cassandra\ndisk_failure_policy: stop\n\n# Policy for commit disk failures:\n#\n# die\n#   shut down gossip and Thrift and kill the JVM, so the node can be replaced.\n#\n# stop\n#   shut down gossip and Thrift, leaving the node effectively dead, but\n#   can still be inspected via JMX.\n#\n# stop_commit\n#   shutdown the commit log, letting writes collect but\n#   continuing to service reads, as in pre-2.0.5 Cassandra\n#\n# ignore\n#   ignore fatal errors and let the batches fail\ncommit_failure_policy: stop\n\n# Maximum size of the native protocol prepared statement cache\n#\n# Valid values are either \"auto\" (omitting the value) or a value greater 0.\n#\n# Note that specifying a too large value will result in long running GCs and possbily\n# out-of-memory errors. Keep the value at a small fraction of the heap.\n#\n# If you constantly see \"prepared statements discarded in the last minute because\n# cache limit reached\" messages, the first step is to investigate the root cause\n# of these messages and check whether prepared statements are used correctly -\n# i.e. use bind markers for variable parts.\n#\n# Do only change the default value, if you really have more prepared statements than\n# fit in the cache. In most cases it is not neccessary to change this value.\n# Constantly re-preparing statements is a performance penalty.\n#\n# Default value (\"auto\") is 1/256th of the heap or 10MB, whichever is greater\nprepared_statements_cache_size_mb:\n\n# Maximum size of the Thrift prepared statement cache\n#\n# If you do not use Thrift at all, it is safe to leave this value at \"auto\".\n#\n# See description of 'prepared_statements_cache_size_mb' above for more information.\n#\n# Default value (\"auto\") is 1/256th of the heap or 10MB, whichever is greater\nthrift_prepared_statements_cache_size_mb:\n\n# Maximum size of the key cache in memory.\n#\n# Each key cache hit saves 1 seek and each row cache hit saves 2 seeks at the\n# minimum, sometimes more. The key cache is fairly tiny for the amount of\n# time it saves, so it's worthwhile to use it at large numbers.\n# The row cache saves even more time, but must contain the entire row,\n# so it is extremely space-intensive. It's best to only use the\n# row cache if you have hot rows or static rows.\n#\n# NOTE: if you reduce the size, you may not get you hottest keys loaded on startup.\n#\n# Default value is empty to make it \"auto\" (min(5% of Heap (in MB), 100MB)). Set to 0 to disable key cache.\nkey_cache_size_in_mb:\n\n# Duration in seconds after which Cassandra should\n# save the key cache. Caches are saved to saved_caches_directory as\n# specified in this configuration file.\n#\n# Saved caches greatly improve cold-start speeds, and is relatively cheap in\n# terms of I/O for the key cache. Row cache saving is much more expensive and\n# has limited use.\n#\n# Default is 14400 or 4 hours.\nkey_cache_save_period: 14400\n\n# Number of keys from the key cache to save\n# Disabled by default, meaning all keys are going to be saved\n# key_cache_keys_to_save: 100\n\n# Row cache implementation class name. Available implementations:\n#\n# org.apache.cassandra.cache.OHCProvider\n#   Fully off-heap row cache implementation (default).\n#\n# org.apache.cassandra.cache.SerializingCacheProvider\n#   This is the row cache implementation availabile\n#   in previous releases of Cassandra.\n# row_cache_class_name: org.apache.cassandra.cache.OHCProvider\n\n# Maximum size of the row cache in memory.\n# Please note that OHC cache implementation requires some additional off-heap memory to manage\n# the map structures and some in-flight memory during operations before/after cache entries can be\n# accounted against the cache capacity. This overhead is usually small compared to the whole capacity.\n# Do not specify more memory that the system can afford in the worst usual situation and leave some\n# headroom for OS block level cache. Do never allow your system to swap.\n#\n# Default value is 0, to disable row caching.\nrow_cache_size_in_mb: 0\n\n# Duration in seconds after which Cassandra should save the row cache.\n# Caches are saved to saved_caches_directory as specified in this configuration file.\n#\n# Saved caches greatly improve cold-start speeds, and is relatively cheap in\n# terms of I/O for the key cache. Row cache saving is much more expensive and\n# has limited use.\n#\n# Default is 0 to disable saving the row cache.\nrow_cache_save_period: 0\n\n# Number of keys from the row cache to save.\n# Specify 0 (which is the default), meaning all keys are going to be saved\n# row_cache_keys_to_save: 100\n\n# Maximum size of the counter cache in memory.\n#\n# Counter cache helps to reduce counter locks' contention for hot counter cells.\n# In case of RF = 1 a counter cache hit will cause Cassandra to skip the read before\n# write entirely. With RF > 1 a counter cache hit will still help to reduce the duration\n# of the lock hold, helping with hot counter cell updates, but will not allow skipping\n# the read entirely. Only the local (clock, count) tuple of a counter cell is kept\n# in memory, not the whole counter, so it's relatively cheap.\n#\n# NOTE: if you reduce the size, you may not get you hottest keys loaded on startup.\n#\n# Default value is empty to make it \"auto\" (min(2.5% of Heap (in MB), 50MB)). Set to 0 to disable counter cache.\n# NOTE: if you perform counter deletes and rely on low gcgs, you should disable the counter cache.\ncounter_cache_size_in_mb:\n\n# Duration in seconds after which Cassandra should\n# save the counter cache (keys only). Caches are saved to saved_caches_directory as\n# specified in this configuration file.\n#\n# Default is 7200 or 2 hours.\ncounter_cache_save_period: 7200\n\n# Number of keys from the counter cache to save\n# Disabled by default, meaning all keys are going to be saved\n# counter_cache_keys_to_save: 100\n\n# saved caches\n# If not set, the default directory is $CASSANDRA_HOME/data/saved_caches.\nsaved_caches_directory: /var/lib/cassandra/saved_caches\n\n# commitlog_sync may be either \"periodic\" or \"batch.\"\n#\n# When in batch mode, Cassandra won't ack writes until the commit log\n# has been fsynced to disk.  It will wait\n# commitlog_sync_batch_window_in_ms milliseconds between fsyncs.\n# This window should be kept short because the writer threads will\n# be unable to do extra work while waiting.  (You may need to increase\n# concurrent_writes for the same reason.)\n#\n# commitlog_sync: batch\n# commitlog_sync_batch_window_in_ms: 2\n#\n# the other option is \"periodic\" where writes may be acked immediately\n# and the CommitLog is simply synced every commitlog_sync_period_in_ms\n# milliseconds.\ncommitlog_sync: periodic\ncommitlog_sync_period_in_ms: 10000\n\n# The size of the individual commitlog file segments.  A commitlog\n# segment may be archived, deleted, or recycled once all the data\n# in it (potentially from each columnfamily in the system) has been\n# flushed to sstables.\n#\n# The default size is 32, which is almost always fine, but if you are\n# archiving commitlog segments (see commitlog_archiving.properties),\n# then you probably want a finer granularity of archiving; 8 or 16 MB\n# is reasonable.\n# Max mutation size is also configurable via max_mutation_size_in_kb setting in\n# cassandra.yaml. The default is half the size commitlog_segment_size_in_mb * 1024.\n# This should be positive and less than 2048.\n#\n# NOTE: If max_mutation_size_in_kb is set explicitly then commitlog_segment_size_in_mb must\n# be set to at least twice the size of max_mutation_size_in_kb / 1024\n#\ncommitlog_segment_size_in_mb: 32\n\n# Compression to apply to the commit log. If omitted, the commit log\n# will be written uncompressed.  LZ4, Snappy, and Deflate compressors\n# are supported.\n# commitlog_compression:\n#   - class_name: LZ4Compressor\n#     parameters:\n#         -\n\n# any class that implements the SeedProvider interface and has a\n# constructor that takes a Map<String, String> of parameters will do.\nseed_provider:\n    # Addresses of hosts that are deemed contact points.\n    # Cassandra nodes use this list of hosts to find each other and learn\n    # the topology of the ring.  You must change this if you are running\n    # multiple nodes!\n    - class_name: org.apache.cassandra.locator.SimpleSeedProvider\n      parameters:\n          # seeds is actually a comma-delimited list of addresses.\n          # Ex: \"<ip1>,<ip2>,<ip3>\"\n          - seeds: \"172.17.0.2\"\n\n# For workloads with more data than can fit in memory, Cassandra's\n# bottleneck will be reads that need to fetch data from\n# disk. \"concurrent_reads\" should be set to (16 * number_of_drives) in\n# order to allow the operations to enqueue low enough in the stack\n# that the OS and drives can reorder them. Same applies to\n# \"concurrent_counter_writes\", since counter writes read the current\n# values before incrementing and writing them back.\n#\n# On the other hand, since writes are almost never IO bound, the ideal\n# number of \"concurrent_writes\" is dependent on the number of cores in\n# your system; (8 * number_of_cores) is a good rule of thumb.\nconcurrent_reads: 32\nconcurrent_writes: 32\nconcurrent_counter_writes: 32\n\n# For materialized view writes, as there is a read involved, so this should\n# be limited by the less of concurrent reads or concurrent writes.\nconcurrent_materialized_view_writes: 32\n\n# Maximum memory to use for sstable chunk cache and buffer pooling.\n# 32MB of this are reserved for pooling buffers, the rest is used as an\n# cache that holds uncompressed sstable chunks.\n# Defaults to the smaller of 1/4 of heap or 512MB. This pool is allocated off-heap,\n# so is in addition to the memory allocated for heap. The cache also has on-heap\n# overhead which is roughly 128 bytes per chunk (i.e. 0.2% of the reserved size\n# if the default 64k chunk size is used).\n# Memory is only allocated when needed.\n# file_cache_size_in_mb: 512\n\n# Flag indicating whether to allocate on or off heap when the sstable buffer\n# pool is exhausted, that is when it has exceeded the maximum memory\n# file_cache_size_in_mb, beyond which it will not cache buffers but allocate on request.\n\n# buffer_pool_use_heap_if_exhausted: true\n\n# The strategy for optimizing disk read\n# Possible values are:\n# ssd (for solid state disks, the default)\n# spinning (for spinning disks)\n# disk_optimization_strategy: ssd\n\n# Total permitted memory to use for memtables. Cassandra will stop\n# accepting writes when the limit is exceeded until a flush completes,\n# and will trigger a flush based on memtable_cleanup_threshold\n# If omitted, Cassandra will set both to 1/4 the size of the heap.\n# memtable_heap_space_in_mb: 2048\n# memtable_offheap_space_in_mb: 2048\n\n# memtable_cleanup_threshold is deprecated. The default calculation\n# is the only reasonable choice. See the comments on  memtable_flush_writers\n# for more information.\n#\n# Ratio of occupied non-flushing memtable size to total permitted size\n# that will trigger a flush of the largest memtable. Larger mct will\n# mean larger flushes and hence less compaction, but also less concurrent\n# flush activity which can make it difficult to keep your disks fed\n# under heavy write load.\n#\n# memtable_cleanup_threshold defaults to 1 / (memtable_flush_writers + 1)\n# memtable_cleanup_threshold: 0.11\n\n# Specify the way Cassandra allocates and manages memtable memory.\n# Options are:\n#\n# heap_buffers\n#   on heap nio buffers\n#\n# offheap_buffers\n#   off heap (direct) nio buffers\n#\n# offheap_objects\n#    off heap objects\nmemtable_allocation_type: heap_buffers\n\n# Total space to use for commit logs on disk.\n#\n# If space gets above this value, Cassandra will flush every dirty CF\n# in the oldest segment and remove it.  So a small total commitlog space\n# will tend to cause more flush activity on less-active columnfamilies.\n#\n# The default value is the smaller of 8192, and 1/4 of the total space\n# of the commitlog volume.\n#\n# commitlog_total_space_in_mb: 8192\n\n# This sets the number of memtable flush writer threads per disk\n# as well as the total number of memtables that can be flushed concurrently.\n# These are generally a combination of compute and IO bound.\n#\n# Memtable flushing is more CPU efficient than memtable ingest and a single thread\n# can keep up with the ingest rate of a whole server on a single fast disk\n# until it temporarily becomes IO bound under contention typically with compaction.\n# At that point you need multiple flush threads. At some point in the future\n# it may become CPU bound all the time.\n#\n# You can tell if flushing is falling behind using the MemtablePool.BlockedOnAllocation\n# metric which should be 0, but will be non-zero if threads are blocked waiting on flushing\n# to free memory.\n#\n# memtable_flush_writers defaults to two for a single data directory.\n# This means that two  memtables can be flushed concurrently to the single data directory.\n# If you have multiple data directories the default is one memtable flushing at a time\n# but the flush will use a thread per data directory so you will get two or more writers.\n#\n# Two is generally enough to flush on a fast disk [array] mounted as a single data directory.\n# Adding more flush writers will result in smaller more frequent flushes that introduce more\n# compaction overhead.\n#\n# There is a direct tradeoff between number of memtables that can be flushed concurrently\n# and flush size and frequency. More is not better you just need enough flush writers\n# to never stall waiting for flushing to free memory.\n#\n#memtable_flush_writers: 2\n\n# Total space to use for change-data-capture logs on disk.\n#\n# If space gets above this value, Cassandra will throw WriteTimeoutException\n# on Mutations including tables with CDC enabled. A CDCCompactor is responsible\n# for parsing the raw CDC logs and deleting them when parsing is completed.\n#\n# The default value is the min of 4096 mb and 1/8th of the total space\n# of the drive where cdc_raw_directory resides.\n# cdc_total_space_in_mb: 4096\n\n# When we hit our cdc_raw limit and the CDCCompactor is either running behind\n# or experiencing backpressure, we check at the following interval to see if any\n# new space for cdc-tracked tables has been made available. Default to 250ms\n# cdc_free_space_check_interval_ms: 250\n\n# A fixed memory pool size in MB for for SSTable index summaries. If left\n# empty, this will default to 5% of the heap size. If the memory usage of\n# all index summaries exceeds this limit, SSTables with low read rates will\n# shrink their index summaries in order to meet this limit.  However, this\n# is a best-effort process. In extreme conditions Cassandra may need to use\n# more than this amount of memory.\nindex_summary_capacity_in_mb:\n\n# How frequently index summaries should be resampled.  This is done\n# periodically to redistribute memory from the fixed-size pool to sstables\n# proportional their recent read rates.  Setting to -1 will disable this\n# process, leaving existing index summaries at their current sampling level.\nindex_summary_resize_interval_in_minutes: 60\n\n# Whether to, when doing sequential writing, fsync() at intervals in\n# order to force the operating system to flush the dirty\n# buffers. Enable this to avoid sudden dirty buffer flushing from\n# impacting read latencies. Almost always a good idea on SSDs; not\n# necessarily on platters.\ntrickle_fsync: false\ntrickle_fsync_interval_in_kb: 10240\n\n# TCP port, for commands and data\n# For security reasons, you should not expose this port to the internet.  Firewall it if needed.\nstorage_port: 7000\n\n# SSL port, for encrypted communication.  Unused unless enabled in\n# encryption_options\n# For security reasons, you should not expose this port to the internet.  Firewall it if needed.\nssl_storage_port: 7001\n\n# Address or interface to bind to and tell other Cassandra nodes to connect to.\n# You _must_ change this if you want multiple nodes to be able to communicate!\n#\n# Set listen_address OR listen_interface, not both.\n#\n# Leaving it blank leaves it up to InetAddress.getLocalHost(). This\n# will always do the Right Thing _if_ the node is properly configured\n# (hostname, name resolution, etc), and the Right Thing is to use the\n# address associated with the hostname (it might not be).\n#\n# Setting listen_address to 0.0.0.0 is always wrong.\n#\nlisten_address: 172.17.0.2\n\n# Set listen_address OR listen_interface, not both. Interfaces must correspond\n# to a single address, IP aliasing is not supported.\n# listen_interface: eth0\n\n# If you choose to specify the interface by name and the interface has an ipv4 and an ipv6 address\n# you can specify which should be chosen using listen_interface_prefer_ipv6. If false the first ipv4\n# address will be used. If true the first ipv6 address will be used. Defaults to false preferring\n# ipv4. If there is only one address it will be selected regardless of ipv4/ipv6.\n# listen_interface_prefer_ipv6: false\n\n# Address to broadcast to other Cassandra nodes\n# Leaving this blank will set it to the same value as listen_address\nbroadcast_address: 172.17.0.2\n\n# When using multiple physical network interfaces, set this\n# to true to listen on broadcast_address in addition to\n# the listen_address, allowing nodes to communicate in both\n# interfaces.\n# Ignore this property if the network configuration automatically\n# routes  between the public and private networks such as EC2.\n# listen_on_broadcast_address: false\n\n# Internode authentication backend, implementing IInternodeAuthenticator;\n# used to allow/disallow connections from peer nodes.\n# internode_authenticator: org.apache.cassandra.auth.AllowAllInternodeAuthenticator\n\n# Whether to start the native transport server.\n# Please note that the address on which the native transport is bound is the\n# same as the rpc_address. The port however is different and specified below.\nstart_native_transport: true\n# port for the CQL native transport to listen for clients on\n# For security reasons, you should not expose this port to the internet.  Firewall it if needed.\nnative_transport_port: 9042\n# Enabling native transport encryption in client_encryption_options allows you to either use\n# encryption for the standard port or to use a dedicated, additional port along with the unencrypted\n# standard native_transport_port.\n# Enabling client encryption and keeping native_transport_port_ssl disabled will use encryption\n# for native_transport_port. Setting native_transport_port_ssl to a different value\n# from native_transport_port will use encryption for native_transport_port_ssl while\n# keeping native_transport_port unencrypted.\n# native_transport_port_ssl: 9142\n# The maximum threads for handling requests when the native transport is used.\n# This is similar to rpc_max_threads though the default differs slightly (and\n# there is no native_transport_min_threads, idle threads will always be stopped\n# after 30 seconds).\n# native_transport_max_threads: 128\n#\n# The maximum size of allowed frame. Frame (requests) larger than this will\n# be rejected as invalid. The default is 256MB. If you're changing this parameter,\n# you may want to adjust max_value_size_in_mb accordingly. This should be positive and less than 2048.\n# native_transport_max_frame_size_in_mb: 256\n\n# The maximum number of concurrent client connections.\n# The default is -1, which means unlimited.\n# native_transport_max_concurrent_connections: -1\n\n# The maximum number of concurrent client connections per source ip.\n# The default is -1, which means unlimited.\n# native_transport_max_concurrent_connections_per_ip: -1\n\n# Whether to start the thrift rpc server.\nstart_rpc: false\n\n# The address or interface to bind the Thrift RPC service and native transport\n# server to.\n#\n# Set rpc_address OR rpc_interface, not both.\n#\n# Leaving rpc_address blank has the same effect as on listen_address\n# (i.e. it will be based on the configured hostname of the node).\n#\n# Note that unlike listen_address, you can specify 0.0.0.0, but you must also\n# set broadcast_rpc_address to a value other than 0.0.0.0.\n#\n# For security reasons, you should not expose this port to the internet.  Firewall it if needed.\nrpc_address: 0.0.0.0\n\n# Set rpc_address OR rpc_interface, not both. Interfaces must correspond\n# to a single address, IP aliasing is not supported.\n# rpc_interface: eth1\n\n# If you choose to specify the interface by name and the interface has an ipv4 and an ipv6 address\n# you can specify which should be chosen using rpc_interface_prefer_ipv6. If false the first ipv4\n# address will be used. If true the first ipv6 address will be used. Defaults to false preferring\n# ipv4. If there is only one address it will be selected regardless of ipv4/ipv6.\n# rpc_interface_prefer_ipv6: false\n\n# port for Thrift to listen for clients on\nrpc_port: 9160\n\n# RPC address to broadcast to drivers and other Cassandra nodes. This cannot\n# be set to 0.0.0.0. If left blank, this will be set to the value of\n# rpc_address. If rpc_address is set to 0.0.0.0, broadcast_rpc_address must\n# be set.\nbroadcast_rpc_address: 172.17.0.2\n\n# enable or disable keepalive on rpc/native connections\nrpc_keepalive: true\n\n# Cassandra provides two out-of-the-box options for the RPC Server:\n#\n# sync\n#   One thread per thrift connection. For a very large number of clients, memory\n#   will be your limiting factor. On a 64 bit JVM, 180KB is the minimum stack size\n#   per thread, and that will correspond to your use of virtual memory (but physical memory\n#   may be limited depending on use of stack space).\n#\n# hsha\n#   Stands for \"half synchronous, half asynchronous.\" All thrift clients are handled\n#   asynchronously using a small number of threads that does not vary with the amount\n#   of thrift clients (and thus scales well to many clients). The rpc requests are still\n#   synchronous (one thread per active request). If hsha is selected then it is essential\n#   that rpc_max_threads is changed from the default value of unlimited.\n#\n# The default is sync because on Windows hsha is about 30% slower.  On Linux,\n# sync/hsha performance is about the same, with hsha of course using less memory.\n#\n# Alternatively,  can provide your own RPC server by providing the fully-qualified class name\n# of an o.a.c.t.TServerFactory that can create an instance of it.\nrpc_server_type: sync\n\n# Uncomment rpc_min|max_thread to set request pool size limits.\n#\n# Regardless of your choice of RPC server (see above), the number of maximum requests in the\n# RPC thread pool dictates how many concurrent requests are possible (but if you are using the sync\n# RPC server, it also dictates the number of clients that can be connected at all).\n#\n# The default is unlimited and thus provides no protection against clients overwhelming the server. You are\n# encouraged to set a maximum that makes sense for you in production, but do keep in mind that\n# rpc_max_threads represents the maximum number of client requests this server may execute concurrently.\n#\n# rpc_min_threads: 16\n# rpc_max_threads: 2048\n\n# uncomment to set socket buffer sizes on rpc connections\n# rpc_send_buff_size_in_bytes:\n# rpc_recv_buff_size_in_bytes:\n\n# Uncomment to set socket buffer size for internode communication\n# Note that when setting this, the buffer size is limited by net.core.wmem_max\n# and when not setting it it is defined by net.ipv4.tcp_wmem\n# See also:\n# /proc/sys/net/core/wmem_max\n# /proc/sys/net/core/rmem_max\n# /proc/sys/net/ipv4/tcp_wmem\n# /proc/sys/net/ipv4/tcp_wmem\n# and 'man tcp'\n# internode_send_buff_size_in_bytes:\n\n# Uncomment to set socket buffer size for internode communication\n# Note that when setting this, the buffer size is limited by net.core.wmem_max\n# and when not setting it it is defined by net.ipv4.tcp_wmem\n# internode_recv_buff_size_in_bytes:\n\n# Frame size for thrift (maximum message length).\nthrift_framed_transport_size_in_mb: 15\n\n# Set to true to have Cassandra create a hard link to each sstable\n# flushed or streamed locally in a backups/ subdirectory of the\n# keyspace data.  Removing these links is the operator's\n# responsibility.\nincremental_backups: false\n\n# Whether or not to take a snapshot before each compaction.  Be\n# careful using this option, since Cassandra won't clean up the\n# snapshots for you.  Mostly useful if you're paranoid when there\n# is a data format change.\nsnapshot_before_compaction: false\n\n# Whether or not a snapshot is taken of the data before keyspace truncation\n# or dropping of column families. The STRONGLY advised default of true\n# should be used to provide data safety. If you set this flag to false, you will\n# lose data on truncation or drop.\nauto_snapshot: true\n\n# Granularity of the collation index of rows within a partition.\n# Increase if your rows are large, or if you have a very large\n# number of rows per partition.  The competing goals are these:\n#\n# - a smaller granularity means more index entries are generated\n#   and looking up rows withing the partition by collation column\n#   is faster\n# - but, Cassandra will keep the collation index in memory for hot\n#   rows (as part of the key cache), so a larger granularity means\n#   you can cache more hot rows\ncolumn_index_size_in_kb: 64\n\n# Per sstable indexed key cache entries (the collation index in memory\n# mentioned above) exceeding this size will not be held on heap.\n# This means that only partition information is held on heap and the\n# index entries are read from disk.\n#\n# Note that this size refers to the size of the\n# serialized index information and not the size of the partition.\ncolumn_index_cache_size_in_kb: 2\n\n# Number of simultaneous compactions to allow, NOT including\n# validation \"compactions\" for anti-entropy repair.  Simultaneous\n# compactions can help preserve read performance in a mixed read/write\n# workload, by mitigating the tendency of small sstables to accumulate\n# during a single long running compactions. The default is usually\n# fine and if you experience problems with compaction running too\n# slowly or too fast, you should look at\n# compaction_throughput_mb_per_sec first.\n#\n# concurrent_compactors defaults to the smaller of (number of disks,\n# number of cores), with a minimum of 2 and a maximum of 8.\n#\n# If your data directories are backed by SSD, you should increase this\n# to the number of cores.\n#concurrent_compactors: 1\n\n# Throttles compaction to the given total throughput across the entire\n# system. The faster you insert data, the faster you need to compact in\n# order to keep the sstable count down, but in general, setting this to\n# 16 to 32 times the rate you are inserting data is more than sufficient.\n# Setting this to 0 disables throttling. Note that this account for all types\n# of compaction, including validation compaction.\ncompaction_throughput_mb_per_sec: 16\n\n# When compacting, the replacement sstable(s) can be opened before they\n# are completely written, and used in place of the prior sstables for\n# any range that has been written. This helps to smoothly transfer reads\n# between the sstables, reducing page cache churn and keeping hot rows hot\nsstable_preemptive_open_interval_in_mb: 50\n\n# Throttles all outbound streaming file transfers on this node to the\n# given total throughput in Mbps. This is necessary because Cassandra does\n# mostly sequential IO when streaming data during bootstrap or repair, which\n# can lead to saturating the network connection and degrading rpc performance.\n# When unset, the default is 200 Mbps or 25 MB/s.\n# stream_throughput_outbound_megabits_per_sec: 200\n\n# Throttles all streaming file transfer between the datacenters,\n# this setting allows users to throttle inter dc stream throughput in addition\n# to throttling all network stream traffic as configured with\n# stream_throughput_outbound_megabits_per_sec\n# When unset, the default is 200 Mbps or 25 MB/s\n# inter_dc_stream_throughput_outbound_megabits_per_sec: 200\n\n# How long the coordinator should wait for read operations to complete\nread_request_timeout_in_ms: 5000\n# How long the coordinator should wait for seq or index scans to complete\nrange_request_timeout_in_ms: 10000\n# How long the coordinator should wait for writes to complete\nwrite_request_timeout_in_ms: 2000\n# How long the coordinator should wait for counter writes to complete\ncounter_write_request_timeout_in_ms: 5000\n# How long a coordinator should continue to retry a CAS operation\n# that contends with other proposals for the same row\ncas_contention_timeout_in_ms: 1000\n# How long the coordinator should wait for truncates to complete\n# (This can be much longer, because unless auto_snapshot is disabled\n# we need to flush first so we can snapshot before removing the data.)\ntruncate_request_timeout_in_ms: 60000\n# The default timeout for other, miscellaneous operations\nrequest_timeout_in_ms: 10000\n\n# How long before a node logs slow queries. Select queries that take longer than\n# this timeout to execute, will generate an aggregated log message, so that slow queries\n# can be identified. Set this value to zero to disable slow query logging.\nslow_query_log_timeout_in_ms: 500\n\n# Enable operation timeout information exchange between nodes to accurately\n# measure request timeouts.  If disabled, replicas will assume that requests\n# were forwarded to them instantly by the coordinator, which means that\n# under overload conditions we will waste that much extra time processing\n# already-timed-out requests.\n#\n# Warning: before enabling this property make sure to ntp is installed\n# and the times are synchronized between the nodes.\ncross_node_timeout: false\n\n# Set keep-alive period for streaming\n# This node will send a keep-alive message periodically with this period.\n# If the node does not receive a keep-alive message from the peer for\n# 2 keep-alive cycles the stream session times out and fail\n# Default value is 300s (5 minutes), which means stalled stream\n# times out in 10 minutes by default\n# streaming_keep_alive_period_in_secs: 300\n\n# phi value that must be reached for a host to be marked down.\n# most users should never need to adjust this.\n# phi_convict_threshold: 8\n\n# endpoint_snitch -- Set this to a class that implements\n# IEndpointSnitch.  The snitch has two functions:\n#\n# - it teaches Cassandra enough about your network topology to route\n#   requests efficiently\n# - it allows Cassandra to spread replicas around your cluster to avoid\n#   correlated failures. It does this by grouping machines into\n#   \"datacenters\" and \"racks.\"  Cassandra will do its best not to have\n#   more than one replica on the same \"rack\" (which may not actually\n#   be a physical location)\n#\n# CASSANDRA WILL NOT ALLOW YOU TO SWITCH TO AN INCOMPATIBLE SNITCH\n# ONCE DATA IS INSERTED INTO THE CLUSTER.  This would cause data loss.\n# This means that if you start with the default SimpleSnitch, which\n# locates every node on \"rack1\" in \"datacenter1\", your only options\n# if you need to add another datacenter are GossipingPropertyFileSnitch\n# (and the older PFS).  From there, if you want to migrate to an\n# incompatible snitch like Ec2Snitch you can do it by adding new nodes\n# under Ec2Snitch (which will locate them in a new \"datacenter\") and\n# decommissioning the old ones.\n#\n# Out of the box, Cassandra provides:\n#\n# SimpleSnitch:\n#    Treats Strategy order as proximity. This can improve cache\n#    locality when disabling read repair.  Only appropriate for\n#    single-datacenter deployments.\n#\n# GossipingPropertyFileSnitch\n#    This should be your go-to snitch for production use.  The rack\n#    and datacenter for the local node are defined in\n#    cassandra-rackdc.properties and propagated to other nodes via\n#    gossip.  If cassandra-topology.properties exists, it is used as a\n#    fallback, allowing migration from the PropertyFileSnitch.\n#\n# PropertyFileSnitch:\n#    Proximity is determined by rack and data center, which are\n#    explicitly configured in cassandra-topology.properties.\n#\n# Ec2Snitch:\n#    Appropriate for EC2 deployments in a single Region. Loads Region\n#    and Availability Zone information from the EC2 API. The Region is\n#    treated as the datacenter, and the Availability Zone as the rack.\n#    Only private IPs are used, so this will not work across multiple\n#    Regions.\n#\n# Ec2MultiRegionSnitch:\n#    Uses public IPs as broadcast_address to allow cross-region\n#    connectivity.  (Thus, you should set seed addresses to the public\n#    IP as well.) You will need to open the storage_port or\n#    ssl_storage_port on the public IP firewall.  (For intra-Region\n#    traffic, Cassandra will switch to the private IP after\n#    establishing a connection.)\n#\n# RackInferringSnitch:\n#    Proximity is determined by rack and data center, which are\n#    assumed to correspond to the 3rd and 2nd octet of each node's IP\n#    address, respectively.  Unless this happens to match your\n#    deployment conventions, this is best used as an example of\n#    writing a custom Snitch class and is provided in that spirit.\n#\n# You can use a custom Snitch by setting this to the full class name\n# of the snitch, which will be assumed to be on your classpath.\nendpoint_snitch: SimpleSnitch\n\n# controls how often to perform the more expensive part of host score\n# calculation\ndynamic_snitch_update_interval_in_ms: 100\n# controls how often to reset all host scores, allowing a bad host to\n# possibly recover\ndynamic_snitch_reset_interval_in_ms: 600000\n# if set greater than zero and read_repair_chance is < 1.0, this will allow\n# 'pinning' of replicas to hosts in order to increase cache capacity.\n# The badness threshold will control how much worse the pinned host has to be\n# before the dynamic snitch will prefer other replicas over it.  This is\n# expressed as a double which represents a percentage.  Thus, a value of\n# 0.2 means Cassandra would continue to prefer the static snitch values\n# until the pinned host was 20% worse than the fastest.\ndynamic_snitch_badness_threshold: 0.1\n\n# request_scheduler -- Set this to a class that implements\n# RequestScheduler, which will schedule incoming client requests\n# according to the specific policy. This is useful for multi-tenancy\n# with a single Cassandra cluster.\n# NOTE: This is specifically for requests from the client and does\n# not affect inter node communication.\n# org.apache.cassandra.scheduler.NoScheduler - No scheduling takes place\n# org.apache.cassandra.scheduler.RoundRobinScheduler - Round robin of\n# client requests to a node with a separate queue for each\n# request_scheduler_id. The scheduler is further customized by\n# request_scheduler_options as described below.\nrequest_scheduler: org.apache.cassandra.scheduler.NoScheduler\n\n# Scheduler Options vary based on the type of scheduler\n#\n# NoScheduler\n#   Has no options\n#\n# RoundRobin\n#   throttle_limit\n#     The throttle_limit is the number of in-flight\n#     requests per client.  Requests beyond\n#     that limit are queued up until\n#     running requests can complete.\n#     The value of 80 here is twice the number of\n#     concurrent_reads + concurrent_writes.\n#   default_weight\n#     default_weight is optional and allows for\n#     overriding the default which is 1.\n#   weights\n#     Weights are optional and will default to 1 or the\n#     overridden default_weight. The weight translates into how\n#     many requests are handled during each turn of the\n#     RoundRobin, based on the scheduler id.\n#\n# request_scheduler_options:\n#    throttle_limit: 80\n#    default_weight: 5\n#    weights:\n#      Keyspace1: 1\n#      Keyspace2: 5\n\n# request_scheduler_id -- An identifier based on which to perform\n# the request scheduling. Currently the only valid option is keyspace.\n# request_scheduler_id: keyspace\n\n# Enable or disable inter-node encryption\n# JVM defaults for supported SSL socket protocols and cipher suites can\n# be replaced using custom encryption options. This is not recommended\n# unless you have policies in place that dictate certain settings, or\n# need to disable vulnerable ciphers or protocols in case the JVM cannot\n# be updated.\n# FIPS compliant settings can be configured at JVM level and should not\n# involve changing encryption settings here:\n# https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/FIPS.html\n# *NOTE* No custom encryption options are enabled at the moment\n# The available internode options are : all, none, dc, rack\n#\n# If set to dc cassandra will encrypt the traffic between the DCs\n# If set to rack cassandra will encrypt the traffic between the racks\n#\n# The passwords used in these options must match the passwords used when generating\n# the keystore and truststore.  For instructions on generating these files, see:\n# http://download.oracle.com/javase/6/docs/technotes/guides/security/jsse/JSSERefGuide.html#CreateKeystore\n#\nserver_encryption_options:\n    internode_encryption: none\n    keystore: conf/keystore\n    keystore_password: cassandra\n    truststore: conf/.truststore\n    truststore_password: cassandra\n    # More advanced defaults below:\n    # protocol: TLS\n    # algorithm: SunX509\n    # store_type: JKS\n    # cipher_suites: [TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA,TLS_DHE_RSA_WITH_AES_128_CBC_SHA,TLS_DHE_RSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA]\n    # require_client_auth: false\n    # require_endpoint_verification: false\n\n# enable or disable client/server encryption.\nclient_encryption_options:\n    enabled: true\n    # If enabled and optional is set to true encrypted and unencrypted connections are handled.\n    optional: false\n    keystore: /etc/cassandra/keystore.p12\n    keystore_password: \"cassandra\"\n    require_client_auth: true\n    truststore: /etc/cassandra/truststore.p12\n    truststore_password: \"cassandra\"\n    store_type: PKCS12\n    # More advanced defaults below:\n    # protocol: TLS\n    # algorithm: SunX509\n    # store_type: JKS\n    # cipher_suites: [TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA,TLS_DHE_RSA_WITH_AES_128_CBC_SHA,TLS_DHE_RSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA]\n\n# internode_compression controls whether traffic between nodes is\n# compressed.\n# Can be:\n#\n# all\n#   all traffic is compressed\n#\n# dc\n#   traffic between different datacenters is compressed\n#\n# none\n#   nothing is compressed.\ninternode_compression: dc\n\n# Enable or disable tcp_nodelay for inter-dc communication.\n# Disabling it will result in larger (but fewer) network packets being sent,\n# reducing overhead from the TCP protocol itself, at the cost of increasing\n# latency if you block for cross-datacenter responses.\ninter_dc_tcp_nodelay: false\n\n# TTL for different trace types used during logging of the repair process.\ntracetype_query_ttl: 86400\ntracetype_repair_ttl: 604800\n\n# By default, Cassandra logs GC Pauses greater than 200 ms at INFO level\n# This threshold can be adjusted to minimize logging if necessary\n# gc_log_threshold_in_ms: 200\n\n# If unset, all GC Pauses greater than gc_log_threshold_in_ms will log at\n# INFO level\n# UDFs (user defined functions) are disabled by default.\n# As of Cassandra 3.0 there is a sandbox in place that should prevent execution of evil code.\nenable_user_defined_functions: false\n\n# Enables scripted UDFs (JavaScript UDFs).\n# Java UDFs are always enabled, if enable_user_defined_functions is true.\n# Enable this option to be able to use UDFs with \"language javascript\" or any custom JSR-223 provider.\n# This option has no effect, if enable_user_defined_functions is false.\nenable_scripted_user_defined_functions: false\n\n# The default Windows kernel timer and scheduling resolution is 15.6ms for power conservation.\n# Lowering this value on Windows can provide much tighter latency and better throughput, however\n# some virtualized environments may see a negative performance impact from changing this setting\n# below their system default. The sysinternals 'clockres' tool can confirm your system's default\n# setting.\nwindows_timer_interval: 1\n\n\n# Enables encrypting data at-rest (on disk). Different key providers can be plugged in, but the default reads from\n# a JCE-style keystore. A single keystore can hold multiple keys, but the one referenced by\n# the \"key_alias\" is the only key that will be used for encrypt opertaions; previously used keys\n# can still (and should!) be in the keystore and will be used on decrypt operations\n# (to handle the case of key rotation).\n#\n# It is strongly recommended to download and install Java Cryptography Extension (JCE)\n# Unlimited Strength Jurisdiction Policy Files for your version of the JDK.\n# (current link: http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html)\n#\n# Currently, only the following file types are supported for transparent data encryption, although\n# more are coming in future cassandra releases: commitlog, hints\ntransparent_data_encryption_options:\n    enabled: false\n    chunk_length_kb: 64\n    cipher: AES/CBC/PKCS5Padding\n    key_alias: testing:1\n    # CBC IV length for AES needs to be 16 bytes (which is also the default size)\n    # iv_length: 16\n    key_provider:\n      - class_name: org.apache.cassandra.security.JKSKeyProvider\n        parameters:\n          - keystore: conf/keystore\n            keystore_password: cassandra\n            store_type: JCEKS\n            key_password: cassandra\n\n\n#####################\n# SAFETY THRESHOLDS #\n#####################\n\n# When executing a scan, within or across a partition, we need to keep the\n# tombstones seen in memory so we can return them to the coordinator, which\n# will use them to make sure other replicas also know about the deleted rows.\n# With workloads that generate a lot of tombstones, this can cause performance\n# problems and even exaust the server heap.\n# (http://www.datastax.com/dev/blog/cassandra-anti-patterns-queues-and-queue-like-datasets)\n# Adjust the thresholds here if you understand the dangers and want to\n# scan more tombstones anyway.  These thresholds may also be adjusted at runtime\n# using the StorageService mbean.\ntombstone_warn_threshold: 1000\ntombstone_failure_threshold: 100000\n\n# Log WARN on any multiple-partition batch size exceeding this value. 5kb per batch by default.\n# Caution should be taken on increasing the size of this threshold as it can lead to node instability.\nbatch_size_warn_threshold_in_kb: 5\n\n# Fail any multiple-partition batch exceeding this value. 50kb (10x warn threshold) by default.\nbatch_size_fail_threshold_in_kb: 50\n\n# Log WARN on any batches not of type LOGGED than span across more partitions than this limit\nunlogged_batch_across_partitions_warn_threshold: 10\n\n# Log a warning when compacting partitions larger than this value\ncompaction_large_partition_warning_threshold_mb: 100\n\n# GC Pauses greater than gc_warn_threshold_in_ms will be logged at WARN level\n# Adjust the threshold based on your application throughput requirement\n# By default, Cassandra logs GC Pauses greater than 200 ms at INFO level\ngc_warn_threshold_in_ms: 1000\n\n# Maximum size of any value in SSTables. Safety measure to detect SSTable corruption\n# early. Any value size larger than this threshold will result into marking an SSTable\n# as corrupted. This should be positive and less than 2048.\n# max_value_size_in_mb: 256\n\n# Back-pressure settings #\n# If enabled, the coordinator will apply the back-pressure strategy specified below to each mutation\n# sent to replicas, with the aim of reducing pressure on overloaded replicas.\nback_pressure_enabled: false\n# The back-pressure strategy applied.\n# The default implementation, RateBasedBackPressure, takes three arguments:\n# high ratio, factor, and flow type, and uses the ratio between incoming mutation responses and outgoing mutation requests.\n# If below high ratio, outgoing mutations are rate limited according to the incoming rate decreased by the given factor;\n# if above high ratio, the rate limiting is increased by the given factor;\n# such factor is usually best configured between 1 and 10, use larger values for a faster recovery\n# at the expense of potentially more dropped mutations;\n# the rate limiting is applied according to the flow type: if FAST, it's rate limited at the speed of the fastest replica,\n# if SLOW at the speed of the slowest one.\n# New strategies can be added. Implementors need to implement org.apache.cassandra.net.BackpressureStrategy and\n# provide a public constructor accepting a Map<String, Object>.\nback_pressure_strategy:\n    - class_name: org.apache.cassandra.net.RateBasedBackPressure\n      parameters:\n        - high_ratio: 0.90\n          factor: 5\n          flow: FAST\n\n# Coalescing Strategies #\n# Coalescing multiples messages turns out to significantly boost message processing throughput (think doubling or more).\n# On bare metal, the floor for packet processing throughput is high enough that many applications won't notice, but in\n# virtualized environments, the point at which an application can be bound by network packet processing can be\n# surprisingly low compared to the throughput of task processing that is possible inside a VM. It's not that bare metal\n# doesn't benefit from coalescing messages, it's that the number of packets a bare metal network interface can process\n# is sufficient for many applications such that no load starvation is experienced even without coalescing.\n# There are other benefits to coalescing network messages that are harder to isolate with a simple metric like messages\n# per second. By coalescing multiple tasks together, a network thread can process multiple messages for the cost of one\n# trip to read from a socket, and all the task submission work can be done at the same time reducing context switching\n# and increasing cache friendliness of network message processing.\n# See CASSANDRA-8692 for details.\n\n# Strategy to use for coalescing messages in OutboundTcpConnection.\n# Can be fixed, movingaverage, timehorizon, disabled (default).\n# You can also specify a subclass of CoalescingStrategies.CoalescingStrategy by name.\n# otc_coalescing_strategy: DISABLED\n\n# How many microseconds to wait for coalescing. For fixed strategy this is the amount of time after the first\n# message is received before it will be sent with any accompanying messages. For moving average this is the\n# maximum amount of time that will be waited as well as the interval at which messages must arrive on average\n# for coalescing to be enabled.\n# otc_coalescing_window_us: 200\n\n# Do not try to coalesce messages if we already got that many messages. This should be more than 2 and less than 128.\n# otc_coalescing_enough_coalesced_messages: 8\n\n# How many milliseconds to wait between two expiration runs on the backlog (queue) of the OutboundTcpConnection.\n# Expiration is done if messages are piling up in the backlog. Droppable messages are expired to free the memory\n# taken by expired messages. The interval should be between 0 and 1000, and in most installations the default value\n# will be appropriate. A smaller value could potentially expire messages slightly sooner at the expense of more CPU\n# time and queue contention while iterating the backlog of messages.\n# An interval of 0 disables any wait time, which is the behavior of former Cassandra versions.\n#\n# otc_backlog_expiration_interval_ms: 200\n"
  },
  {
    "path": "modules/cassandra/src/test/resources/cassandra-test-configuration-example/cassandra.yaml",
    "content": "# Cassandra storage config YAML\n\n# NOTE:\n#   See http://wiki.apache.org/cassandra/StorageConfiguration for\n#   full explanations of configuration directives\n# /NOTE\n\n# The name of the cluster. This is mainly used to prevent machines in\n# one logical cluster from joining another.\ncluster_name: 'Test Cluster Integration Test'\n\n# This defines the number of tokens randomly assigned to this node on the ring\n# The more tokens, relative to other nodes, the larger the proportion of data\n# that this node will store. You probably want all nodes to have the same number\n# of tokens assuming they have equal hardware capability.\n#\n# If you leave this unspecified, Cassandra will use the default of 1 token for legacy compatibility,\n# and will use the initial_token as described below.\n#\n# Specifying initial_token will override this setting on the node's initial start,\n# on subsequent starts, this setting will apply even if initial token is set.\n#\n# If you already have a cluster with 1 token per node, and wish to migrate to\n# multiple tokens per node, see http://wiki.apache.org/cassandra/Operations\nnum_tokens: 256\n\n# Triggers automatic allocation of num_tokens tokens for this node. The allocation\n# algorithm attempts to choose tokens in a way that optimizes replicated load over\n# the nodes in the datacenter for the replication strategy used by the specified\n# keyspace.\n#\n# The load assigned to each node will be close to proportional to its number of\n# vnodes.\n#\n# Only supported with the Murmur3Partitioner.\n# allocate_tokens_for_keyspace: KEYSPACE\n\n# initial_token allows you to specify tokens manually.  While you can use it with\n# vnodes (num_tokens > 1, above) -- in which case you should provide a\n# comma-separated list -- it's primarily used when adding nodes to legacy clusters\n# that do not have vnodes enabled.\n# initial_token:\n\n# See http://wiki.apache.org/cassandra/HintedHandoff\n# May either be \"true\" or \"false\" to enable globally\nhinted_handoff_enabled: true\n\n# When hinted_handoff_enabled is true, a black list of data centers that will not\n# perform hinted handoff\n# hinted_handoff_disabled_datacenters:\n#    - DC1\n#    - DC2\n\n# this defines the maximum amount of time a dead host will have hints\n# generated.  After it has been dead this long, new hints for it will not be\n# created until it has been seen alive and gone down again.\nmax_hint_window_in_ms: 10800000 # 3 hours\n\n# Maximum throttle in KBs per second, per delivery thread.  This will be\n# reduced proportionally to the number of nodes in the cluster.  (If there\n# are two nodes in the cluster, each delivery thread will use the maximum\n# rate; if there are three, each will throttle to half of the maximum,\n# since we expect two nodes to be delivering hints simultaneously.)\nhinted_handoff_throttle_in_kb: 1024\n\n# Number of threads with which to deliver hints;\n# Consider increasing this number when you have multi-dc deployments, since\n# cross-dc handoff tends to be slower\nmax_hints_delivery_threads: 2\n\n# Directory where Cassandra should store hints.\n# If not set, the default directory is $CASSANDRA_HOME/data/hints.\n# hints_directory: /var/lib/cassandra/hints\n\n# How often hints should be flushed from the internal buffers to disk.\n# Will *not* trigger fsync.\nhints_flush_period_in_ms: 10000\n\n# Maximum size for a single hints file, in megabytes.\nmax_hints_file_size_in_mb: 128\n\n# Compression to apply to the hint files. If omitted, hints files\n# will be written uncompressed. LZ4, Snappy, and Deflate compressors\n# are supported.\n#hints_compression:\n#   - class_name: LZ4Compressor\n#     parameters:\n#         -\n\n# Maximum throttle in KBs per second, total. This will be\n# reduced proportionally to the number of nodes in the cluster.\nbatchlog_replay_throttle_in_kb: 1024\n\n# Authentication backend, implementing IAuthenticator; used to identify users\n# Out of the box, Cassandra provides org.apache.cassandra.auth.{AllowAllAuthenticator,\n# PasswordAuthenticator}.\n#\n# - AllowAllAuthenticator performs no checks - set it to disable authentication.\n# - PasswordAuthenticator relies on username/password pairs to authenticate\n#   users. It keeps usernames and hashed passwords in system_auth.roles table.\n#   Please increase system_auth keyspace replication factor if you use this authenticator.\n#   If using PasswordAuthenticator, CassandraRoleManager must also be used (see below)\nauthenticator: AllowAllAuthenticator\n\n# Authorization backend, implementing IAuthorizer; used to limit access/provide permissions\n# Out of the box, Cassandra provides org.apache.cassandra.auth.{AllowAllAuthorizer,\n# CassandraAuthorizer}.\n#\n# - AllowAllAuthorizer allows any action to any user - set it to disable authorization.\n# - CassandraAuthorizer stores permissions in system_auth.role_permissions table. Please\n#   increase system_auth keyspace replication factor if you use this authorizer.\nauthorizer: AllowAllAuthorizer\n\n# Part of the Authentication & Authorization backend, implementing IRoleManager; used\n# to maintain grants and memberships between roles.\n# Out of the box, Cassandra provides org.apache.cassandra.auth.CassandraRoleManager,\n# which stores role information in the system_auth keyspace. Most functions of the\n# IRoleManager require an authenticated login, so unless the configured IAuthenticator\n# actually implements authentication, most of this functionality will be unavailable.\n#\n# - CassandraRoleManager stores role data in the system_auth keyspace. Please\n#   increase system_auth keyspace replication factor if you use this role manager.\nrole_manager: CassandraRoleManager\n\n# Validity period for roles cache (fetching granted roles can be an expensive\n# operation depending on the role manager, CassandraRoleManager is one example)\n# Granted roles are cached for authenticated sessions in AuthenticatedUser and\n# after the period specified here, become eligible for (async) reload.\n# Defaults to 2000, set to 0 to disable caching entirely.\n# Will be disabled automatically for AllowAllAuthenticator.\nroles_validity_in_ms: 2000\n\n# Refresh interval for roles cache (if enabled).\n# After this interval, cache entries become eligible for refresh. Upon next\n# access, an async reload is scheduled and the old value returned until it\n# completes. If roles_validity_in_ms is non-zero, then this must be\n# also.\n# Defaults to the same value as roles_validity_in_ms.\n# roles_update_interval_in_ms: 2000\n\n# Validity period for permissions cache (fetching permissions can be an\n# expensive operation depending on the authorizer, CassandraAuthorizer is\n# one example). Defaults to 2000, set to 0 to disable.\n# Will be disabled automatically for AllowAllAuthorizer.\npermissions_validity_in_ms: 2000\n\n# Refresh interval for permissions cache (if enabled).\n# After this interval, cache entries become eligible for refresh. Upon next\n# access, an async reload is scheduled and the old value returned until it\n# completes. If permissions_validity_in_ms is non-zero, then this must be\n# also.\n# Defaults to the same value as permissions_validity_in_ms.\n# permissions_update_interval_in_ms: 2000\n\n# Validity period for credentials cache. This cache is tightly coupled to\n# the provided PasswordAuthenticator implementation of IAuthenticator. If\n# another IAuthenticator implementation is configured, this cache will not\n# be automatically used and so the following settings will have no effect.\n# Please note, credentials are cached in their encrypted form, so while\n# activating this cache may reduce the number of queries made to the\n# underlying table, it may not  bring a significant reduction in the\n# latency of individual authentication attempts.\n# Defaults to 2000, set to 0 to disable credentials caching.\ncredentials_validity_in_ms: 2000\n\n# Refresh interval for credentials cache (if enabled).\n# After this interval, cache entries become eligible for refresh. Upon next\n# access, an async reload is scheduled and the old value returned until it\n# completes. If credentials_validity_in_ms is non-zero, then this must be\n# also.\n# Defaults to the same value as credentials_validity_in_ms.\n# credentials_update_interval_in_ms: 2000\n\n# The partitioner is responsible for distributing groups of rows (by\n# partition key) across nodes in the cluster.  You should leave this\n# alone for new clusters.  The partitioner can NOT be changed without\n# reloading all data, so when upgrading you should set this to the\n# same partitioner you were already using.\n#\n# Besides Murmur3Partitioner, partitioners included for backwards\n# compatibility include RandomPartitioner, ByteOrderedPartitioner, and\n# OrderPreservingPartitioner.\n#\npartitioner: org.apache.cassandra.dht.Murmur3Partitioner\n\n# Directories where Cassandra should store data on disk.  Cassandra\n# will spread data evenly across them, subject to the granularity of\n# the configured compaction strategy.\n# If not set, the default directory is $CASSANDRA_HOME/data/data.\ndata_file_directories:\n    - /var/lib/cassandra/data\n\n# commit log.  when running on magnetic HDD, this should be a\n# separate spindle than the data directories.\n# If not set, the default directory is $CASSANDRA_HOME/data/commitlog.\ncommitlog_directory: /var/lib/cassandra/commitlog\n\n# Enable / disable CDC functionality on a per-node basis. This modifies the logic used\n# for write path allocation rejection (standard: never reject. cdc: reject Mutation\n# containing a CDC-enabled table if at space limit in cdc_raw_directory).\ncdc_enabled: false\n\n# CommitLogSegments are moved to this directory on flush if cdc_enabled: true and the\n# segment contains mutations for a CDC-enabled table. This should be placed on a\n# separate spindle than the data directories. If not set, the default directory is\n# $CASSANDRA_HOME/data/cdc_raw.\n# cdc_raw_directory: /var/lib/cassandra/cdc_raw\n\n# Policy for data disk failures:\n#\n# die\n#   shut down gossip and client transports and kill the JVM for any fs errors or\n#   single-sstable errors, so the node can be replaced.\n#\n# stop_paranoid\n#   shut down gossip and client transports even for single-sstable errors,\n#   kill the JVM for errors during startup.\n#\n# stop\n#   shut down gossip and client transports, leaving the node effectively dead, but\n#   can still be inspected via JMX, kill the JVM for errors during startup.\n#\n# best_effort\n#    stop using the failed disk and respond to requests based on\n#    remaining available sstables.  This means you WILL see obsolete\n#    data at CL.ONE!\n#\n# ignore\n#    ignore fatal errors and let requests fail, as in pre-1.2 Cassandra\ndisk_failure_policy: stop\n\n# Policy for commit disk failures:\n#\n# die\n#   shut down gossip and Thrift and kill the JVM, so the node can be replaced.\n#\n# stop\n#   shut down gossip and Thrift, leaving the node effectively dead, but\n#   can still be inspected via JMX.\n#\n# stop_commit\n#   shutdown the commit log, letting writes collect but\n#   continuing to service reads, as in pre-2.0.5 Cassandra\n#\n# ignore\n#   ignore fatal errors and let the batches fail\ncommit_failure_policy: stop\n\n# Maximum size of the native protocol prepared statement cache\n#\n# Valid values are either \"auto\" (omitting the value) or a value greater 0.\n#\n# Note that specifying a too large value will result in long running GCs and possibly\n# out-of-memory errors. Keep the value at a small fraction of the heap.\n#\n# If you constantly see \"prepared statements discarded in the last minute because\n# cache limit reached\" messages, the first step is to investigate the root cause\n# of these messages and check whether prepared statements are used correctly -\n# i.e. use bind markers for variable parts.\n#\n# Do only change the default value, if you really have more prepared statements than\n# fit in the cache. In most cases it is not necessary to change this value.\n# Constantly re-preparing statements is a performance penalty.\n#\n# Default value (\"auto\") is 1/256th of the heap or 10MB, whichever is greater\nprepared_statements_cache_size_mb:\n\n# Maximum size of the Thrift prepared statement cache\n#\n# If you do not use Thrift at all, it is safe to leave this value at \"auto\".\n#\n# See description of 'prepared_statements_cache_size_mb' above for more information.\n#\n# Default value (\"auto\") is 1/256th of the heap or 10MB, whichever is greater\nthrift_prepared_statements_cache_size_mb:\n\n# Maximum size of the key cache in memory.\n#\n# Each key cache hit saves 1 seek and each row cache hit saves 2 seeks at the\n# minimum, sometimes more. The key cache is fairly tiny for the amount of\n# time it saves, so it's worthwhile to use it at large numbers.\n# The row cache saves even more time, but must contain the entire row,\n# so it is extremely space-intensive. It's best to only use the\n# row cache if you have hot rows or static rows.\n#\n# NOTE: if you reduce the size, you may not get you hottest keys loaded on startup.\n#\n# Default value is empty to make it \"auto\" (min(5% of Heap (in MB), 100MB)). Set to 0 to disable key cache.\nkey_cache_size_in_mb:\n\n# Duration in seconds after which Cassandra should\n# save the key cache. Caches are saved to saved_caches_directory as\n# specified in this configuration file.\n#\n# Saved caches greatly improve cold-start speeds, and is relatively cheap in\n# terms of I/O for the key cache. Row cache saving is much more expensive and\n# has limited use.\n#\n# Default is 14400 or 4 hours.\nkey_cache_save_period: 14400\n\n# Number of keys from the key cache to save\n# Disabled by default, meaning all keys are going to be saved\n# key_cache_keys_to_save: 100\n\n# Row cache implementation class name. Available implementations:\n#\n# org.apache.cassandra.cache.OHCProvider\n#   Fully off-heap row cache implementation (default).\n#\n# org.apache.cassandra.cache.SerializingCacheProvider\n#   This is the row cache implementation available\n#   in previous releases of Cassandra.\n# row_cache_class_name: org.apache.cassandra.cache.OHCProvider\n\n# Maximum size of the row cache in memory.\n# Please note that OHC cache implementation requires some additional off-heap memory to manage\n# the map structures and some in-flight memory during operations before/after cache entries can be\n# accounted against the cache capacity. This overhead is usually small compared to the whole capacity.\n# Do not specify more memory that the system can afford in the worst usual situation and leave some\n# headroom for OS block level cache. Do never allow your system to swap.\n#\n# Default value is 0, to disable row caching.\nrow_cache_size_in_mb: 0\n\n# Duration in seconds after which Cassandra should save the row cache.\n# Caches are saved to saved_caches_directory as specified in this configuration file.\n#\n# Saved caches greatly improve cold-start speeds, and is relatively cheap in\n# terms of I/O for the key cache. Row cache saving is much more expensive and\n# has limited use.\n#\n# Default is 0 to disable saving the row cache.\nrow_cache_save_period: 0\n\n# Number of keys from the row cache to save.\n# Specify 0 (which is the default), meaning all keys are going to be saved\n# row_cache_keys_to_save: 100\n\n# Maximum size of the counter cache in memory.\n#\n# Counter cache helps to reduce counter locks' contention for hot counter cells.\n# In case of RF = 1 a counter cache hit will cause Cassandra to skip the read before\n# write entirely. With RF > 1 a counter cache hit will still help to reduce the duration\n# of the lock hold, helping with hot counter cell updates, but will not allow skipping\n# the read entirely. Only the local (clock, count) tuple of a counter cell is kept\n# in memory, not the whole counter, so it's relatively cheap.\n#\n# NOTE: if you reduce the size, you may not get you hottest keys loaded on startup.\n#\n# Default value is empty to make it \"auto\" (min(2.5% of Heap (in MB), 50MB)). Set to 0 to disable counter cache.\n# NOTE: if you perform counter deletes and rely on low gcgs, you should disable the counter cache.\ncounter_cache_size_in_mb:\n\n# Duration in seconds after which Cassandra should\n# save the counter cache (keys only). Caches are saved to saved_caches_directory as\n# specified in this configuration file.\n#\n# Default is 7200 or 2 hours.\ncounter_cache_save_period: 7200\n\n# Number of keys from the counter cache to save\n# Disabled by default, meaning all keys are going to be saved\n# counter_cache_keys_to_save: 100\n\n# saved caches\n# If not set, the default directory is $CASSANDRA_HOME/data/saved_caches.\nsaved_caches_directory: /var/lib/cassandra/saved_caches\n\n# commitlog_sync may be either \"periodic\" or \"batch.\"\n#\n# When in batch mode, Cassandra won't ack writes until the commit log\n# has been fsynced to disk.  It will wait\n# commitlog_sync_batch_window_in_ms milliseconds between fsyncs.\n# This window should be kept short because the writer threads will\n# be unable to do extra work while waiting.  (You may need to increase\n# concurrent_writes for the same reason.)\n#\n# commitlog_sync: batch\n# commitlog_sync_batch_window_in_ms: 2\n#\n# the other option is \"periodic\" where writes may be acked immediately\n# and the CommitLog is simply synced every commitlog_sync_period_in_ms\n# milliseconds.\ncommitlog_sync: periodic\ncommitlog_sync_period_in_ms: 10000\n\n# The size of the individual commitlog file segments.  A commitlog\n# segment may be archived, deleted, or recycled once all the data\n# in it (potentially from each columnfamily in the system) has been\n# flushed to sstables.\n#\n# The default size is 32, which is almost always fine, but if you are\n# archiving commitlog segments (see commitlog_archiving.properties),\n# then you probably want a finer granularity of archiving; 8 or 16 MB\n# is reasonable.\n# Max mutation size is also configurable via max_mutation_size_in_kb setting in\n# cassandra.yaml. The default is half the size commitlog_segment_size_in_mb * 1024.\n# This should be positive and less than 2048.\n#\n# NOTE: If max_mutation_size_in_kb is set explicitly then commitlog_segment_size_in_mb must\n# be set to at least twice the size of max_mutation_size_in_kb / 1024\n#\ncommitlog_segment_size_in_mb: 32\n\n# Compression to apply to the commit log. If omitted, the commit log\n# will be written uncompressed.  LZ4, Snappy, and Deflate compressors\n# are supported.\n# commitlog_compression:\n#   - class_name: LZ4Compressor\n#     parameters:\n#         -\n\n# any class that implements the SeedProvider interface and has a\n# constructor that takes a Map<String, String> of parameters will do.\nseed_provider:\n    # Addresses of hosts that are deemed contact points.\n    # Cassandra nodes use this list of hosts to find each other and learn\n    # the topology of the ring.  You must change this if you are running\n    # multiple nodes!\n    - class_name: org.apache.cassandra.locator.SimpleSeedProvider\n      parameters:\n          # seeds is actually a comma-delimited list of addresses.\n          # Ex: \"<ip1>,<ip2>,<ip3>\"\n          - seeds: \"172.17.0.2\"\n\n# For workloads with more data than can fit in memory, Cassandra's\n# bottleneck will be reads that need to fetch data from\n# disk. \"concurrent_reads\" should be set to (16 * number_of_drives) in\n# order to allow the operations to enqueue low enough in the stack\n# that the OS and drives can reorder them. Same applies to\n# \"concurrent_counter_writes\", since counter writes read the current\n# values before incrementing and writing them back.\n#\n# On the other hand, since writes are almost never IO bound, the ideal\n# number of \"concurrent_writes\" is dependent on the number of cores in\n# your system; (8 * number_of_cores) is a good rule of thumb.\nconcurrent_reads: 32\nconcurrent_writes: 32\nconcurrent_counter_writes: 32\n\n# For materialized view writes, as there is a read involved, so this should\n# be limited by the less of concurrent reads or concurrent writes.\nconcurrent_materialized_view_writes: 32\n\n# Maximum memory to use for sstable chunk cache and buffer pooling.\n# 32MB of this are reserved for pooling buffers, the rest is used as a\n# cache that holds uncompressed sstable chunks.\n# Defaults to the smaller of 1/4 of heap or 512MB. This pool is allocated off-heap,\n# so is in addition to the memory allocated for heap. The cache also has on-heap\n# overhead which is roughly 128 bytes per chunk (i.e. 0.2% of the reserved size\n# if the default 64k chunk size is used).\n# Memory is only allocated when needed.\n# file_cache_size_in_mb: 512\n\n# Flag indicating whether to allocate on or off heap when the sstable buffer\n# pool is exhausted, that is when it has exceeded the maximum memory\n# file_cache_size_in_mb, beyond which it will not cache buffers but allocate on request.\n\n# buffer_pool_use_heap_if_exhausted: true\n\n# The strategy for optimizing disk read\n# Possible values are:\n# ssd (for solid state disks, the default)\n# spinning (for spinning disks)\n# disk_optimization_strategy: ssd\n\n# Total permitted memory to use for memtables. Cassandra will stop\n# accepting writes when the limit is exceeded until a flush completes,\n# and will trigger a flush based on memtable_cleanup_threshold\n# If omitted, Cassandra will set both to 1/4 the size of the heap.\n# memtable_heap_space_in_mb: 2048\n# memtable_offheap_space_in_mb: 2048\n\n# memtable_cleanup_threshold is deprecated. The default calculation\n# is the only reasonable choice. See the comments on  memtable_flush_writers\n# for more information.\n#\n# Ratio of occupied non-flushing memtable size to total permitted size\n# that will trigger a flush of the largest memtable. Larger mct will\n# mean larger flushes and hence less compaction, but also less concurrent\n# flush activity which can make it difficult to keep your disks fed\n# under heavy write load.\n#\n# memtable_cleanup_threshold defaults to 1 / (memtable_flush_writers + 1)\n# memtable_cleanup_threshold: 0.11\n\n# Specify the way Cassandra allocates and manages memtable memory.\n# Options are:\n#\n# heap_buffers\n#   on heap nio buffers\n#\n# offheap_buffers\n#   off heap (direct) nio buffers\n#\n# offheap_objects\n#    off heap objects\nmemtable_allocation_type: heap_buffers\n\n# Total space to use for commit logs on disk.\n#\n# If space gets above this value, Cassandra will flush every dirty CF\n# in the oldest segment and remove it.  So a small total commitlog space\n# will tend to cause more flush activity on less-active columnfamilies.\n#\n# The default value is the smaller of 8192, and 1/4 of the total space\n# of the commitlog volume.\n#\n# commitlog_total_space_in_mb: 8192\n\n# This sets the number of memtable flush writer threads per disk\n# as well as the total number of memtables that can be flushed concurrently.\n# These are generally a combination of compute and IO bound.\n#\n# Memtable flushing is more CPU efficient than memtable ingest and a single thread\n# can keep up with the ingest rate of a whole server on a single fast disk\n# until it temporarily becomes IO bound under contention typically with compaction.\n# At that point you need multiple flush threads. At some point in the future\n# it may become CPU bound all the time.\n#\n# You can tell if flushing is falling behind using the MemtablePool.BlockedOnAllocation\n# metric which should be 0, but will be non-zero if threads are blocked waiting on flushing\n# to free memory.\n#\n# memtable_flush_writers defaults to two for a single data directory.\n# This means that two  memtables can be flushed concurrently to the single data directory.\n# If you have multiple data directories the default is one memtable flushing at a time\n# but the flush will use a thread per data directory so you will get two or more writers.\n#\n# Two is generally enough to flush on a fast disk [array] mounted as a single data directory.\n# Adding more flush writers will result in smaller more frequent flushes that introduce more\n# compaction overhead.\n#\n# There is a direct tradeoff between number of memtables that can be flushed concurrently\n# and flush size and frequency. More is not better you just need enough flush writers\n# to never stall waiting for flushing to free memory.\n#\n#memtable_flush_writers: 2\n\n# Total space to use for change-data-capture logs on disk.\n#\n# If space gets above this value, Cassandra will throw WriteTimeoutException\n# on Mutations including tables with CDC enabled. A CDCCompactor is responsible\n# for parsing the raw CDC logs and deleting them when parsing is completed.\n#\n# The default value is the min of 4096 mb and 1/8th of the total space\n# of the drive where cdc_raw_directory resides.\n# cdc_total_space_in_mb: 4096\n\n# When we hit our cdc_raw limit and the CDCCompactor is either running behind\n# or experiencing backpressure, we check at the following interval to see if any\n# new space for cdc-tracked tables has been made available. Default to 250ms\n# cdc_free_space_check_interval_ms: 250\n\n# A fixed memory pool size in MB for SSTable index summaries. If left\n# empty, this will default to 5% of the heap size. If the memory usage of\n# all index summaries exceeds this limit, SSTables with low read rates will\n# shrink their index summaries in order to meet this limit.  However, this\n# is a best-effort process. In extreme conditions Cassandra may need to use\n# more than this amount of memory.\nindex_summary_capacity_in_mb:\n\n# How frequently index summaries should be resampled.  This is done\n# periodically to redistribute memory from the fixed-size pool to sstables\n# proportional their recent read rates.  Setting to -1 will disable this\n# process, leaving existing index summaries at their current sampling level.\nindex_summary_resize_interval_in_minutes: 60\n\n# Whether to, when doing sequential writing, fsync() at intervals in\n# order to force the operating system to flush the dirty\n# buffers. Enable this to avoid sudden dirty buffer flushing from\n# impacting read latencies. Almost always a good idea on SSDs; not\n# necessarily on platters.\ntrickle_fsync: false\ntrickle_fsync_interval_in_kb: 10240\n\n# TCP port, for commands and data\n# For security reasons, you should not expose this port to the internet.  Firewall it if needed.\nstorage_port: 7000\n\n# SSL port, for encrypted communication.  Unused unless enabled in\n# encryption_options\n# For security reasons, you should not expose this port to the internet.  Firewall it if needed.\nssl_storage_port: 7001\n\n# Address or interface to bind to and tell other Cassandra nodes to connect to.\n# You _must_ change this if you want multiple nodes to be able to communicate!\n#\n# Set listen_address OR listen_interface, not both.\n#\n# Leaving it blank leaves it up to InetAddress.getLocalHost(). This\n# will always do the Right Thing _if_ the node is properly configured\n# (hostname, name resolution, etc), and the Right Thing is to use the\n# address associated with the hostname (it might not be).\n#\n# Setting listen_address to 0.0.0.0 is always wrong.\n#\nlisten_address: 172.17.0.2\n\n# Set listen_address OR listen_interface, not both. Interfaces must correspond\n# to a single address, IP aliasing is not supported.\n# listen_interface: eth0\n\n# If you choose to specify the interface by name and the interface has an ipv4 and an ipv6 address\n# you can specify which should be chosen using listen_interface_prefer_ipv6. If false the first ipv4\n# address will be used. If true the first ipv6 address will be used. Defaults to false preferring\n# ipv4. If there is only one address it will be selected regardless of ipv4/ipv6.\n# listen_interface_prefer_ipv6: false\n\n# Address to broadcast to other Cassandra nodes\n# Leaving this blank will set it to the same value as listen_address\nbroadcast_address: 172.17.0.2\n\n# When using multiple physical network interfaces, set this\n# to true to listen on broadcast_address in addition to\n# the listen_address, allowing nodes to communicate in both\n# interfaces.\n# Ignore this property if the network configuration automatically\n# routes  between the public and private networks such as EC2.\n# listen_on_broadcast_address: false\n\n# Internode authentication backend, implementing IInternodeAuthenticator;\n# used to allow/disallow connections from peer nodes.\n# internode_authenticator: org.apache.cassandra.auth.AllowAllInternodeAuthenticator\n\n# Whether to start the native transport server.\n# Please note that the address on which the native transport is bound is the\n# same as the rpc_address. The port however is different and specified below.\nstart_native_transport: true\n# port for the CQL native transport to listen for clients on\n# For security reasons, you should not expose this port to the internet.  Firewall it if needed.\nnative_transport_port: 9042\n# Enabling native transport encryption in client_encryption_options allows you to either use\n# encryption for the standard port or to use a dedicated, additional port along with the unencrypted\n# standard native_transport_port.\n# Enabling client encryption and keeping native_transport_port_ssl disabled will use encryption\n# for native_transport_port. Setting native_transport_port_ssl to a different value\n# from native_transport_port will use encryption for native_transport_port_ssl while\n# keeping native_transport_port unencrypted.\n# native_transport_port_ssl: 9142\n# The maximum threads for handling requests when the native transport is used.\n# This is similar to rpc_max_threads though the default differs slightly (and\n# there is no native_transport_min_threads, idle threads will always be stopped\n# after 30 seconds).\n# native_transport_max_threads: 128\n#\n# The maximum size of allowed frame. Frame (requests) larger than this will\n# be rejected as invalid. The default is 256MB. If you're changing this parameter,\n# you may want to adjust max_value_size_in_mb accordingly. This should be positive and less than 2048.\n# native_transport_max_frame_size_in_mb: 256\n\n# The maximum number of concurrent client connections.\n# The default is -1, which means unlimited.\n# native_transport_max_concurrent_connections: -1\n\n# The maximum number of concurrent client connections per source ip.\n# The default is -1, which means unlimited.\n# native_transport_max_concurrent_connections_per_ip: -1\n\n# Whether to start the thrift rpc server.\nstart_rpc: false\n\n# The address or interface to bind the Thrift RPC service and native transport\n# server to.\n#\n# Set rpc_address OR rpc_interface, not both.\n#\n# Leaving rpc_address blank has the same effect as on listen_address\n# (i.e. it will be based on the configured hostname of the node).\n#\n# Note that unlike listen_address, you can specify 0.0.0.0, but you must also\n# set broadcast_rpc_address to a value other than 0.0.0.0.\n#\n# For security reasons, you should not expose this port to the internet.  Firewall it if needed.\nrpc_address: 0.0.0.0\n\n# Set rpc_address OR rpc_interface, not both. Interfaces must correspond\n# to a single address, IP aliasing is not supported.\n# rpc_interface: eth1\n\n# If you choose to specify the interface by name and the interface has an ipv4 and an ipv6 address\n# you can specify which should be chosen using rpc_interface_prefer_ipv6. If false the first ipv4\n# address will be used. If true the first ipv6 address will be used. Defaults to false preferring\n# ipv4. If there is only one address it will be selected regardless of ipv4/ipv6.\n# rpc_interface_prefer_ipv6: false\n\n# port for Thrift to listen for clients on\nrpc_port: 9160\n\n# RPC address to broadcast to drivers and other Cassandra nodes. This cannot\n# be set to 0.0.0.0. If left blank, this will be set to the value of\n# rpc_address. If rpc_address is set to 0.0.0.0, broadcast_rpc_address must\n# be set.\nbroadcast_rpc_address: 172.17.0.2\n\n# enable or disable keepalive on rpc/native connections\nrpc_keepalive: true\n\n# Cassandra provides two out-of-the-box options for the RPC Server:\n#\n# sync\n#   One thread per thrift connection. For a very large number of clients, memory\n#   will be your limiting factor. On a 64 bit JVM, 180KB is the minimum stack size\n#   per thread, and that will correspond to your use of virtual memory (but physical memory\n#   may be limited depending on use of stack space).\n#\n# hsha\n#   Stands for \"half synchronous, half asynchronous.\" All thrift clients are handled\n#   asynchronously using a small number of threads that does not vary with the amount\n#   of thrift clients (and thus scales well to many clients). The rpc requests are still\n#   synchronous (one thread per active request). If hsha is selected then it is essential\n#   that rpc_max_threads is changed from the default value of unlimited.\n#\n# The default is sync because on Windows hsha is about 30% slower.  On Linux,\n# sync/hsha performance is about the same, with hsha of course using less memory.\n#\n# Alternatively,  can provide your own RPC server by providing the fully-qualified class name\n# of an o.a.c.t.TServerFactory that can create an instance of it.\nrpc_server_type: sync\n\n# Uncomment rpc_min|max_thread to set request pool size limits.\n#\n# Regardless of your choice of RPC server (see above), the number of maximum requests in the\n# RPC thread pool dictates how many concurrent requests are possible (but if you are using the sync\n# RPC server, it also dictates the number of clients that can be connected at all).\n#\n# The default is unlimited and thus provides no protection against clients overwhelming the server. You are\n# encouraged to set a maximum that makes sense for you in production, but do keep in mind that\n# rpc_max_threads represents the maximum number of client requests this server may execute concurrently.\n#\n# rpc_min_threads: 16\n# rpc_max_threads: 2048\n\n# uncomment to set socket buffer sizes on rpc connections\n# rpc_send_buff_size_in_bytes:\n# rpc_recv_buff_size_in_bytes:\n\n# Uncomment to set socket buffer size for internode communication\n# Note that when setting this, the buffer size is limited by net.core.wmem_max\n# and when not setting it it is defined by net.ipv4.tcp_wmem\n# See also:\n# /proc/sys/net/core/wmem_max\n# /proc/sys/net/core/rmem_max\n# /proc/sys/net/ipv4/tcp_wmem\n# /proc/sys/net/ipv4/tcp_wmem\n# and 'man tcp'\n# internode_send_buff_size_in_bytes:\n\n# Uncomment to set socket buffer size for internode communication\n# Note that when setting this, the buffer size is limited by net.core.wmem_max\n# and when not setting it it is defined by net.ipv4.tcp_wmem\n# internode_recv_buff_size_in_bytes:\n\n# Frame size for thrift (maximum message length).\nthrift_framed_transport_size_in_mb: 15\n\n# Set to true to have Cassandra create a hard link to each sstable\n# flushed or streamed locally in a backups/ subdirectory of the\n# keyspace data.  Removing these links is the operator's\n# responsibility.\nincremental_backups: false\n\n# Whether or not to take a snapshot before each compaction.  Be\n# careful using this option, since Cassandra won't clean up the\n# snapshots for you.  Mostly useful if you're paranoid when there\n# is a data format change.\nsnapshot_before_compaction: false\n\n# Whether or not a snapshot is taken of the data before keyspace truncation\n# or dropping of column families. The STRONGLY advised default of true\n# should be used to provide data safety. If you set this flag to false, you will\n# lose data on truncation or drop.\nauto_snapshot: true\n\n# Granularity of the collation index of rows within a partition.\n# Increase if your rows are large, or if you have a very large\n# number of rows per partition.  The competing goals are these:\n#\n# - a smaller granularity means more index entries are generated\n#   and looking up rows within the partition by collation column\n#   is faster\n# - but, Cassandra will keep the collation index in memory for hot\n#   rows (as part of the key cache), so a larger granularity means\n#   you can cache more hot rows\ncolumn_index_size_in_kb: 64\n\n# Per sstable indexed key cache entries (the collation index in memory\n# mentioned above) exceeding this size will not be held on heap.\n# This means that only partition information is held on heap and the\n# index entries are read from disk.\n#\n# Note that this size refers to the size of the\n# serialized index information and not the size of the partition.\ncolumn_index_cache_size_in_kb: 2\n\n# Number of simultaneous compactions to allow, NOT including\n# validation \"compactions\" for anti-entropy repair.  Simultaneous\n# compactions can help preserve read performance in a mixed read/write\n# workload, by mitigating the tendency of small sstables to accumulate\n# during a single long running compactions. The default is usually\n# fine and if you experience problems with compaction running too\n# slowly or too fast, you should look at\n# compaction_throughput_mb_per_sec first.\n#\n# concurrent_compactors defaults to the smaller of (number of disks,\n# number of cores), with a minimum of 2 and a maximum of 8.\n#\n# If your data directories are backed by SSD, you should increase this\n# to the number of cores.\n#concurrent_compactors: 1\n\n# Throttles compaction to the given total throughput across the entire\n# system. The faster you insert data, the faster you need to compact in\n# order to keep the sstable count down, but in general, setting this to\n# 16 to 32 times the rate you are inserting data is more than sufficient.\n# Setting this to 0 disables throttling. Note that this account for all types\n# of compaction, including validation compaction.\ncompaction_throughput_mb_per_sec: 16\n\n# When compacting, the replacement sstable(s) can be opened before they\n# are completely written, and used in place of the prior sstables for\n# any range that has been written. This helps to smoothly transfer reads\n# between the sstables, reducing page cache churn and keeping hot rows hot\nsstable_preemptive_open_interval_in_mb: 50\n\n# Throttles all outbound streaming file transfers on this node to the\n# given total throughput in Mbps. This is necessary because Cassandra does\n# mostly sequential IO when streaming data during bootstrap or repair, which\n# can lead to saturating the network connection and degrading rpc performance.\n# When unset, the default is 200 Mbps or 25 MB/s.\n# stream_throughput_outbound_megabits_per_sec: 200\n\n# Throttles all streaming file transfer between the datacenters,\n# this setting allows users to throttle inter dc stream throughput in addition\n# to throttling all network stream traffic as configured with\n# stream_throughput_outbound_megabits_per_sec\n# When unset, the default is 200 Mbps or 25 MB/s\n# inter_dc_stream_throughput_outbound_megabits_per_sec: 200\n\n# How long the coordinator should wait for read operations to complete\nread_request_timeout_in_ms: 5000\n# How long the coordinator should wait for seq or index scans to complete\nrange_request_timeout_in_ms: 10000\n# How long the coordinator should wait for writes to complete\nwrite_request_timeout_in_ms: 2000\n# How long the coordinator should wait for counter writes to complete\ncounter_write_request_timeout_in_ms: 5000\n# How long a coordinator should continue to retry a CAS operation\n# that contends with other proposals for the same row\ncas_contention_timeout_in_ms: 1000\n# How long the coordinator should wait for truncates to complete\n# (This can be much longer, because unless auto_snapshot is disabled\n# we need to flush first so we can snapshot before removing the data.)\ntruncate_request_timeout_in_ms: 60000\n# The default timeout for other, miscellaneous operations\nrequest_timeout_in_ms: 10000\n\n# How long before a node logs slow queries. Select queries that take longer than\n# this timeout to execute, will generate an aggregated log message, so that slow queries\n# can be identified. Set this value to zero to disable slow query logging.\nslow_query_log_timeout_in_ms: 500\n\n# Enable operation timeout information exchange between nodes to accurately\n# measure request timeouts.  If disabled, replicas will assume that requests\n# were forwarded to them instantly by the coordinator, which means that\n# under overload conditions we will waste that much extra time processing\n# already-timed-out requests.\n#\n# Warning: before enabling this property make sure to ntp is installed\n# and the times are synchronized between the nodes.\ncross_node_timeout: false\n\n# Set keep-alive period for streaming\n# This node will send a keep-alive message periodically with this period.\n# If the node does not receive a keep-alive message from the peer for\n# 2 keep-alive cycles the stream session times out and fail\n# Default value is 300s (5 minutes), which means stalled stream\n# times out in 10 minutes by default\n# streaming_keep_alive_period_in_secs: 300\n\n# phi value that must be reached for a host to be marked down.\n# most users should never need to adjust this.\n# phi_convict_threshold: 8\n\n# endpoint_snitch -- Set this to a class that implements\n# IEndpointSnitch.  The snitch has two functions:\n#\n# - it teaches Cassandra enough about your network topology to route\n#   requests efficiently\n# - it allows Cassandra to spread replicas around your cluster to avoid\n#   correlated failures. It does this by grouping machines into\n#   \"datacenters\" and \"racks.\"  Cassandra will do its best not to have\n#   more than one replica on the same \"rack\" (which may not actually\n#   be a physical location)\n#\n# CASSANDRA WILL NOT ALLOW YOU TO SWITCH TO AN INCOMPATIBLE SNITCH\n# ONCE DATA IS INSERTED INTO THE CLUSTER.  This would cause data loss.\n# This means that if you start with the default SimpleSnitch, which\n# locates every node on \"rack1\" in \"datacenter1\", your only options\n# if you need to add another datacenter are GossipingPropertyFileSnitch\n# (and the older PFS).  From there, if you want to migrate to an\n# incompatible snitch like Ec2Snitch you can do it by adding new nodes\n# under Ec2Snitch (which will locate them in a new \"datacenter\") and\n# decommissioning the old ones.\n#\n# Out of the box, Cassandra provides:\n#\n# SimpleSnitch:\n#    Treats Strategy order as proximity. This can improve cache\n#    locality when disabling read repair.  Only appropriate for\n#    single-datacenter deployments.\n#\n# GossipingPropertyFileSnitch\n#    This should be your go-to snitch for production use.  The rack\n#    and datacenter for the local node are defined in\n#    cassandra-rackdc.properties and propagated to other nodes via\n#    gossip.  If cassandra-topology.properties exists, it is used as a\n#    fallback, allowing migration from the PropertyFileSnitch.\n#\n# PropertyFileSnitch:\n#    Proximity is determined by rack and data center, which are\n#    explicitly configured in cassandra-topology.properties.\n#\n# Ec2Snitch:\n#    Appropriate for EC2 deployments in a single Region. Loads Region\n#    and Availability Zone information from the EC2 API. The Region is\n#    treated as the datacenter, and the Availability Zone as the rack.\n#    Only private IPs are used, so this will not work across multiple\n#    Regions.\n#\n# Ec2MultiRegionSnitch:\n#    Uses public IPs as broadcast_address to allow cross-region\n#    connectivity.  (Thus, you should set seed addresses to the public\n#    IP as well.) You will need to open the storage_port or\n#    ssl_storage_port on the public IP firewall.  (For intra-Region\n#    traffic, Cassandra will switch to the private IP after\n#    establishing a connection.)\n#\n# RackInferringSnitch:\n#    Proximity is determined by rack and data center, which are\n#    assumed to correspond to the 3rd and 2nd octet of each node's IP\n#    address, respectively.  Unless this happens to match your\n#    deployment conventions, this is best used as an example of\n#    writing a custom Snitch class and is provided in that spirit.\n#\n# You can use a custom Snitch by setting this to the full class name\n# of the snitch, which will be assumed to be on your classpath.\nendpoint_snitch: SimpleSnitch\n\n# controls how often to perform the more expensive part of host score\n# calculation\ndynamic_snitch_update_interval_in_ms: 100\n# controls how often to reset all host scores, allowing a bad host to\n# possibly recover\ndynamic_snitch_reset_interval_in_ms: 600000\n# if set greater than zero and read_repair_chance is < 1.0, this will allow\n# 'pinning' of replicas to hosts in order to increase cache capacity.\n# The badness threshold will control how much worse the pinned host has to be\n# before the dynamic snitch will prefer other replicas over it.  This is\n# expressed as a double which represents a percentage.  Thus, a value of\n# 0.2 means Cassandra would continue to prefer the static snitch values\n# until the pinned host was 20% worse than the fastest.\ndynamic_snitch_badness_threshold: 0.1\n\n# request_scheduler -- Set this to a class that implements\n# RequestScheduler, which will schedule incoming client requests\n# according to the specific policy. This is useful for multi-tenancy\n# with a single Cassandra cluster.\n# NOTE: This is specifically for requests from the client and does\n# not affect inter node communication.\n# org.apache.cassandra.scheduler.NoScheduler - No scheduling takes place\n# org.apache.cassandra.scheduler.RoundRobinScheduler - Round robin of\n# client requests to a node with a separate queue for each\n# request_scheduler_id. The scheduler is further customized by\n# request_scheduler_options as described below.\nrequest_scheduler: org.apache.cassandra.scheduler.NoScheduler\n\n# Scheduler Options vary based on the type of scheduler\n#\n# NoScheduler\n#   Has no options\n#\n# RoundRobin\n#   throttle_limit\n#     The throttle_limit is the number of in-flight\n#     requests per client.  Requests beyond\n#     that limit are queued up until\n#     running requests can complete.\n#     The value of 80 here is twice the number of\n#     concurrent_reads + concurrent_writes.\n#   default_weight\n#     default_weight is optional and allows for\n#     overriding the default which is 1.\n#   weights\n#     Weights are optional and will default to 1 or the\n#     overridden default_weight. The weight translates into how\n#     many requests are handled during each turn of the\n#     RoundRobin, based on the scheduler id.\n#\n# request_scheduler_options:\n#    throttle_limit: 80\n#    default_weight: 5\n#    weights:\n#      Keyspace1: 1\n#      Keyspace2: 5\n\n# request_scheduler_id -- An identifier based on which to perform\n# the request scheduling. Currently the only valid option is keyspace.\n# request_scheduler_id: keyspace\n\n# Enable or disable inter-node encryption\n# JVM defaults for supported SSL socket protocols and cipher suites can\n# be replaced using custom encryption options. This is not recommended\n# unless you have policies in place that dictate certain settings, or\n# need to disable vulnerable ciphers or protocols in case the JVM cannot\n# be updated.\n# FIPS compliant settings can be configured at JVM level and should not\n# involve changing encryption settings here:\n# https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/FIPS.html\n# *NOTE* No custom encryption options are enabled at the moment\n# The available internode options are : all, none, dc, rack\n#\n# If set to dc cassandra will encrypt the traffic between the DCs\n# If set to rack cassandra will encrypt the traffic between the racks\n#\n# The passwords used in these options must match the passwords used when generating\n# the keystore and truststore.  For instructions on generating these files, see:\n# http://download.oracle.com/javase/6/docs/technotes/guides/security/jsse/JSSERefGuide.html#CreateKeystore\n#\nserver_encryption_options:\n    internode_encryption: none\n    keystore: conf/.keystore\n    keystore_password: cassandra\n    truststore: conf/.truststore\n    truststore_password: cassandra\n    # More advanced defaults below:\n    # protocol: TLS\n    # algorithm: SunX509\n    # store_type: JKS\n    # cipher_suites: [TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA,TLS_DHE_RSA_WITH_AES_128_CBC_SHA,TLS_DHE_RSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA]\n    # require_client_auth: false\n    # require_endpoint_verification: false\n\n# enable or disable client/server encryption.\nclient_encryption_options:\n    enabled: false\n    # If enabled and optional is set to true encrypted and unencrypted connections are handled.\n    optional: false\n    keystore: conf/.keystore\n    keystore_password: cassandra\n    # require_client_auth: false\n    # Set trustore and truststore_password if require_client_auth is true\n    # truststore: conf/.truststore\n    # truststore_password: cassandra\n    # More advanced defaults below:\n    # protocol: TLS\n    # algorithm: SunX509\n    # store_type: JKS\n    # cipher_suites: [TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA,TLS_DHE_RSA_WITH_AES_128_CBC_SHA,TLS_DHE_RSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA]\n\n# internode_compression controls whether traffic between nodes is\n# compressed.\n# Can be:\n#\n# all\n#   all traffic is compressed\n#\n# dc\n#   traffic between different datacenters is compressed\n#\n# none\n#   nothing is compressed.\ninternode_compression: dc\n\n# Enable or disable tcp_nodelay for inter-dc communication.\n# Disabling it will result in larger (but fewer) network packets being sent,\n# reducing overhead from the TCP protocol itself, at the cost of increasing\n# latency if you block for cross-datacenter responses.\ninter_dc_tcp_nodelay: false\n\n# TTL for different trace types used during logging of the repair process.\ntracetype_query_ttl: 86400\ntracetype_repair_ttl: 604800\n\n# By default, Cassandra logs GC Pauses greater than 200 ms at INFO level\n# This threshold can be adjusted to minimize logging if necessary\n# gc_log_threshold_in_ms: 200\n\n# If unset, all GC Pauses greater than gc_log_threshold_in_ms will log at\n# INFO level\n# UDFs (user defined functions) are disabled by default.\n# As of Cassandra 3.0 there is a sandbox in place that should prevent execution of evil code.\nenable_user_defined_functions: false\n\n# Enables scripted UDFs (JavaScript UDFs).\n# Java UDFs are always enabled, if enable_user_defined_functions is true.\n# Enable this option to be able to use UDFs with \"language javascript\" or any custom JSR-223 provider.\n# This option has no effect, if enable_user_defined_functions is false.\nenable_scripted_user_defined_functions: false\n\n# The default Windows kernel timer and scheduling resolution is 15.6ms for power conservation.\n# Lowering this value on Windows can provide much tighter latency and better throughput, however\n# some virtualized environments may see a negative performance impact from changing this setting\n# below their system default. The sysinternals 'clockres' tool can confirm your system's default\n# setting.\nwindows_timer_interval: 1\n\n\n# Enables encrypting data at-rest (on disk). Different key providers can be plugged in, but the default reads from\n# a JCE-style keystore. A single keystore can hold multiple keys, but the one referenced by\n# the \"key_alias\" is the only key that will be used for encrypt operations; previously used keys\n# can still (and should!) be in the keystore and will be used on decrypt operations\n# (to handle the case of key rotation).\n#\n# It is strongly recommended to download and install Java Cryptography Extension (JCE)\n# Unlimited Strength Jurisdiction Policy Files for your version of the JDK.\n# (current link: http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html)\n#\n# Currently, only the following file types are supported for transparent data encryption, although\n# more are coming in future cassandra releases: commitlog, hints\ntransparent_data_encryption_options:\n    enabled: false\n    chunk_length_kb: 64\n    cipher: AES/CBC/PKCS5Padding\n    key_alias: testing:1\n    # CBC IV length for AES needs to be 16 bytes (which is also the default size)\n    # iv_length: 16\n    key_provider:\n      - class_name: org.apache.cassandra.security.JKSKeyProvider\n        parameters:\n          - keystore: conf/.keystore\n            keystore_password: cassandra\n            store_type: JCEKS\n            key_password: cassandra\n\n\n#####################\n# SAFETY THRESHOLDS #\n#####################\n\n# When executing a scan, within or across a partition, we need to keep the\n# tombstones seen in memory so we can return them to the coordinator, which\n# will use them to make sure other replicas also know about the deleted rows.\n# With workloads that generate a lot of tombstones, this can cause performance\n# problems and even exhaust the server heap.\n# (http://www.datastax.com/dev/blog/cassandra-anti-patterns-queues-and-queue-like-datasets)\n# Adjust the thresholds here if you understand the dangers and want to\n# scan more tombstones anyway.  These thresholds may also be adjusted at runtime\n# using the StorageService mbean.\ntombstone_warn_threshold: 1000\ntombstone_failure_threshold: 100000\n\n# Log WARN on any multiple-partition batch size exceeding this value. 5kb per batch by default.\n# Caution should be taken on increasing the size of this threshold as it can lead to node instability.\nbatch_size_warn_threshold_in_kb: 5\n\n# Fail any multiple-partition batch exceeding this value. 50kb (10x warn threshold) by default.\nbatch_size_fail_threshold_in_kb: 50\n\n# Log WARN on any batches not of type LOGGED than span across more partitions than this limit\nunlogged_batch_across_partitions_warn_threshold: 10\n\n# Log a warning when compacting partitions larger than this value\ncompaction_large_partition_warning_threshold_mb: 100\n\n# GC Pauses greater than gc_warn_threshold_in_ms will be logged at WARN level\n# Adjust the threshold based on your application throughput requirement\n# By default, Cassandra logs GC Pauses greater than 200 ms at INFO level\ngc_warn_threshold_in_ms: 1000\n\n# Maximum size of any value in SSTables. Safety measure to detect SSTable corruption\n# early. Any value size larger than this threshold will result into marking an SSTable\n# as corrupted. This should be positive and less than 2048.\n# max_value_size_in_mb: 256\n\n# Back-pressure settings #\n# If enabled, the coordinator will apply the back-pressure strategy specified below to each mutation\n# sent to replicas, with the aim of reducing pressure on overloaded replicas.\nback_pressure_enabled: false\n# The back-pressure strategy applied.\n# The default implementation, RateBasedBackPressure, takes three arguments:\n# high ratio, factor, and flow type, and uses the ratio between incoming mutation responses and outgoing mutation requests.\n# If below high ratio, outgoing mutations are rate limited according to the incoming rate decreased by the given factor;\n# if above high ratio, the rate limiting is increased by the given factor;\n# such factor is usually best configured between 1 and 10, use larger values for a faster recovery\n# at the expense of potentially more dropped mutations;\n# the rate limiting is applied according to the flow type: if FAST, it's rate limited at the speed of the fastest replica,\n# if SLOW at the speed of the slowest one.\n# New strategies can be added. Implementors need to implement org.apache.cassandra.net.BackpressureStrategy and\n# provide a public constructor accepting a Map<String, Object>.\nback_pressure_strategy:\n    - class_name: org.apache.cassandra.net.RateBasedBackPressure\n      parameters:\n        - high_ratio: 0.90\n          factor: 5\n          flow: FAST\n\n# Coalescing Strategies #\n# Coalescing multiples messages turns out to significantly boost message processing throughput (think doubling or more).\n# On bare metal, the floor for packet processing throughput is high enough that many applications won't notice, but in\n# virtualized environments, the point at which an application can be bound by network packet processing can be\n# surprisingly low compared to the throughput of task processing that is possible inside a VM. It's not that bare metal\n# doesn't benefit from coalescing messages, it's that the number of packets a bare metal network interface can process\n# is sufficient for many applications such that no load starvation is experienced even without coalescing.\n# There are other benefits to coalescing network messages that are harder to isolate with a simple metric like messages\n# per second. By coalescing multiple tasks together, a network thread can process multiple messages for the cost of one\n# trip to read from a socket, and all the task submission work can be done at the same time reducing context switching\n# and increasing cache friendliness of network message processing.\n# See CASSANDRA-8692 for details.\n\n# Strategy to use for coalescing messages in OutboundTcpConnection.\n# Can be fixed, movingaverage, timehorizon, disabled (default).\n# You can also specify a subclass of CoalescingStrategies.CoalescingStrategy by name.\n# otc_coalescing_strategy: DISABLED\n\n# How many microseconds to wait for coalescing. For fixed strategy this is the amount of time after the first\n# message is received before it will be sent with any accompanying messages. For moving average this is the\n# maximum amount of time that will be waited as well as the interval at which messages must arrive on average\n# for coalescing to be enabled.\n# otc_coalescing_window_us: 200\n\n# Do not try to coalesce messages if we already got that many messages. This should be more than 2 and less than 128.\n# otc_coalescing_enough_coalesced_messages: 8\n\n# How many milliseconds to wait between two expiration runs on the backlog (queue) of the OutboundTcpConnection.\n# Expiration is done if messages are piling up in the backlog. Droppable messages are expired to free the memory\n# taken by expired messages. The interval should be between 0 and 1000, and in most installations the default value\n# will be appropriate. A smaller value could potentially expire messages slightly sooner at the expense of more CPU\n# time and queue contention while iterating the backlog of messages.\n# An interval of 0 disables any wait time, which is the behavior of former Cassandra versions.\n#\n# otc_backlog_expiration_interval_ms: 200"
  },
  {
    "path": "modules/cassandra/src/test/resources/client-ssl/cassandra.cer.pem",
    "content": "Bag Attributes\n    friendlyName: localhost\n    localKeyID: 54 69 6D 65 20 31 37 32 39 33 34 38 39 36 38 31 31 39 \nsubject=C = None, L = None, O = Testcontainers, OU = Testcontainers, CN = localhost\nissuer=C = None, L = None, O = Testcontainers, OU = Testcontainers, CN = localhost\n-----BEGIN CERTIFICATE-----\nMIIDbjCCAlagAwIBAgIJAKCVIipuH03/MA0GCSqGSIb3DQEBCwUAMGQxDTALBgNV\nBAYTBE5vbmUxDTALBgNVBAcTBE5vbmUxFzAVBgNVBAoTDlRlc3Rjb250YWluZXJz\nMRcwFQYDVQQLEw5UZXN0Y29udGFpbmVyczESMBAGA1UEAxMJbG9jYWxob3N0MCAX\nDTI0MTAxOTE0NDIwOFoYDzIxMjQwOTI1MTQ0MjA4WjBkMQ0wCwYDVQQGEwROb25l\nMQ0wCwYDVQQHEwROb25lMRcwFQYDVQQKEw5UZXN0Y29udGFpbmVyczEXMBUGA1UE\nCxMOVGVzdGNvbnRhaW5lcnMxEjAQBgNVBAMTCWxvY2FsaG9zdDCCASIwDQYJKoZI\nhvcNAQEBBQADggEPADCCAQoCggEBALocrhrM1gYB/pF/qlDY+eFQZ9L8SMgCmn+I\nmgx/UbKqJwLp5wYuoW/PA4RwraFPkimf5CAE2kpBGcJu/Qzyp0fJZlBXpmkDJVrG\npRbYz5mN4CrXNliYfAC1RzxvTT1tOjiDkk9kHVfs5nMVb9e2kq6tQEItflhlPzdD\nFOe0pY2XBX2stcQ6URRkK5buyPeLhnTrKMfLWEWKKKzSQGen+lbtBURZzkpmK88q\nqjLqqaZusXP6QlRVLqMADjQf7aXLi0A/fIhVrq1amqqiApJbijT0LP48DvS8DQQL\njNKkQ17vMClMmXusU5IgJMlXfGEzeTNUI56wHGYUdE69FTGFvZECAwEAAaMhMB8w\nHQYDVR0OBBYEFNsvIE+IgkE0aTc+1MI7hpPQL2ZEMA0GCSqGSIb3DQEBCwUAA4IB\nAQC4U/tGPuRS3m/r1p3aAq0D88UGg6oKHwqe3re3xrFAv9y+Y3M+FXyh5w/yMCAr\nPcVo6Pef3hEjwc9wDuQoIcQ9eRZtYI1RnhkkuC8TZRk1KGKg9Lj4Zzbse7FfK92Z\nDUYgIVyhC/YkeEDwTiZI8WxhbglozNg5Ygw+qLK4rYmk+X/NgdfdQHocuJ3Jwqqx\neYz0m2RUMhzxEI2z9jQr3DgjNkYrphLzaVXmO4MovzXx3DNeC8ADot9PGmaz24rl\nRDeSWynxbgqzdXGHxtyR0LY1k+Y+5wqU28L90D0o3ZtaMBnK+Ft2AP2zpbtgr8rR\nsf12uPyRUPzJQ46KNpjy4HN6\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "modules/cassandra/src/test/resources/client-ssl/cassandra.key.pem",
    "content": "Bag Attributes\n    friendlyName: localhost\n    localKeyID: 54 69 6D 65 20 31 37 32 39 33 34 38 39 36 38 31 31 39 \nKey Attributes: <No Attributes>\n-----BEGIN PRIVATE KEY-----\nMIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC6HK4azNYGAf6R\nf6pQ2PnhUGfS/EjIApp/iJoMf1GyqicC6ecGLqFvzwOEcK2hT5Ipn+QgBNpKQRnC\nbv0M8qdHyWZQV6ZpAyVaxqUW2M+ZjeAq1zZYmHwAtUc8b009bTo4g5JPZB1X7OZz\nFW/XtpKurUBCLX5YZT83QxTntKWNlwV9rLXEOlEUZCuW7sj3i4Z06yjHy1hFiiis\n0kBnp/pW7QVEWc5KZivPKqoy6qmmbrFz+kJUVS6jAA40H+2ly4tAP3yIVa6tWpqq\nogKSW4o09Cz+PA70vA0EC4zSpENe7zApTJl7rFOSICTJV3xhM3kzVCOesBxmFHRO\nvRUxhb2RAgMBAAECggEACqD5e7C+Rr8oz+jS/z8FAxsmcsqgFXW6NEjG4EPWx89a\nRWfthVFDov3XNsizzp/OulXWH2xnhkOyU7cm+Ia7JI+Z9w8Qz+dM5AkVA8Y23o9X\nTnSjNx57DODnEP21eZAzxpp50DlPFU02pzsbYhE2AbsFp0HirB9MI70CN24xR9hP\ni1zPgO7FVnLvn4INqVKgcV4vXlxvgDEvO4Myc1WoJXkyPCCObvEflBBWr2QwQfKT\nT2qjCJWv/P2PJGFaZbOrEvOHZjprSid4/n9gbQrodGoChhjiZT//l19Ay+7eJkUc\nyiiSK4u3fF4YPH9+CVpRQ94PHFe+0kQVvf+VGX/iqQKBgQDp5umTcZoho/KQzOd/\npA8fgnzbipEl5ep2MHB98cqEQ93eFv4l/bBehDQ1WvmFJY8SIzU4EQotpnCZd4Vb\nKH9PE8tsTRvw2cYbBuD751boLVaBn8wxtlTkFrN/CyAtV7w7AG0dXnDKushJx1NN\n8AgvSr0X4hf0AKIWGtVteoX7qwKBgQDLse7Ze5dNbG48CpBGqQS4wFkTSEs5QKI5\n68JXEQoCmJb06O7rxj4f5CALv3pReP3nrl5+kLmT8O+yU5C5dXf06k/z4GDJ6Esm\n8XTEfB2Ca+kI2RLyMRRXPA2nEunbSsyk1AVo2GeRJxG4TaDbu2zTkUBAEyCuwarW\nOMsuYodPswKBgQCZc/kB1qH8OAdHoGawgv24+m7Xycz4RCLSb20d86edprjEn+kV\nG56+I5Xs+0aAZ+e5Sof7xJIc6Pkudg9zgtojEyV+ZAhUt0sVKCoqmdeWc0gxupjI\ndIq1KX+RdccieFDxlJIBlpgBKRGF9dNdaoC0JiBwrtBwMIomXmxvatbECQKBgQCS\nX1xZn/xLwJ0+PAENJauk72OS/aJAk/d/U7ElS7M7xlbDyxbVCnHeDNoSVxgYr68U\n6zIwFOOmMb6tEGuxOX5n2nB1uUkUDf7jDyNvhhjWfaDJoOOCck5BmX/eDTNLR+bi\nkxEIFGnn3oFXRUFQZNCA/6GB6bzUl4qhwdIPlPHTDQKBgQCgztlUF5IOJFKMjVlY\nyoA/7+b5zwrh8Y2+SLzF/HLah85AuHxsgdTuQh+HLKSwJejKCT95BSRJO/kV0XCR\nKZGStqETpEH/2AJkxpjt0FZxtIQdnyTargbiipe4JzI3iCTLtfN5C9Pn3ZJ9giap\nB5uQm4762aH2jw1kKFegHlIgJg==\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "modules/cassandra/src/test/resources/initial-with-error.cql",
    "content": "CREATE KEYSPACE keySpaceTest WITH replication = {'class': 'SimpleStrategy', 'replication_factor' : 1};\n\nUSE keySpaceTest;\n\n/* The following statement contains an error (missing primary key) on purpose, do not fix it! */\nCREATE TABLE catalog_category (id bigint);\n"
  },
  {
    "path": "modules/cassandra/src/test/resources/initial.cql",
    "content": "CREATE KEYSPACE keySpaceTest WITH replication = {'class': 'SimpleStrategy', 'replication_factor' : 1};\n\nUSE keySpaceTest;\n\nCREATE TABLE catalog_category (id bigint primary key, name text);\n\nINSERT INTO catalog_category (id, name) VALUES (1, 'test_category');"
  },
  {
    "path": "modules/cassandra/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/chromadb/build.gradle",
    "content": "description = \"Testcontainers :: ChromaDB\"\n\ndependencies {\n    api project(':testcontainers')\n\n    testImplementation 'io.rest-assured:rest-assured:5.5.6'\n}\n"
  },
  {
    "path": "modules/chromadb/src/main/java/org/testcontainers/chromadb/ChromaDBContainer.java",
    "content": "package org.testcontainers.chromadb;\n\nimport lombok.extern.slf4j.Slf4j;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.utility.ComparableVersion;\nimport org.testcontainers.utility.DockerImageName;\n\n/**\n * Testcontainers implementation of ChromaDB.\n * <p>\n * Supported images: {@code chromadb/chroma}, {@code ghcr.io/chroma-core/chroma}\n * <p>\n * Exposed ports: 8000\n */\n@Slf4j\npublic class ChromaDBContainer extends GenericContainer<ChromaDBContainer> {\n\n    private static final DockerImageName DEFAULT_DOCKER_IMAGE = DockerImageName.parse(\"chromadb/chroma\");\n\n    private static final DockerImageName GHCR_DOCKER_IMAGE = DockerImageName.parse(\"ghcr.io/chroma-core/chroma\");\n\n    public ChromaDBContainer(String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    public ChromaDBContainer(DockerImageName dockerImageName) {\n        this(dockerImageName, isVersion2(dockerImageName.getVersionPart()));\n    }\n\n    public ChromaDBContainer(DockerImageName dockerImageName, boolean isVersion2) {\n        super(dockerImageName);\n        String apiPath = isVersion2 ? \"/api/v2/heartbeat\" : \"/api/v1/heartbeat\";\n        dockerImageName.assertCompatibleWith(DEFAULT_DOCKER_IMAGE, GHCR_DOCKER_IMAGE);\n        withExposedPorts(8000);\n        waitingFor(Wait.forHttp(apiPath));\n    }\n\n    public String getEndpoint() {\n        return \"http://\" + getHost() + \":\" + getFirstMappedPort();\n    }\n\n    private static boolean isVersion2(String version) {\n        if (version.equals(\"latest\")) {\n            return true;\n        }\n\n        ComparableVersion comparableVersion = new ComparableVersion(version);\n        if (comparableVersion.isGreaterThanOrEqualTo(\"1.0.0\")) {\n            return true;\n        }\n\n        log.warn(\"Version {} is less than 1.0.0 or not a semantic version.\", version);\n        return false;\n    }\n}\n"
  },
  {
    "path": "modules/chromadb/src/test/java/org/testcontainers/chromadb/ChromaDBContainerTest.java",
    "content": "package org.testcontainers.chromadb;\n\nimport io.restassured.http.ContentType;\nimport org.junit.jupiter.api.Test;\n\nimport static io.restassured.RestAssured.given;\n\nclass ChromaDBContainerTest {\n\n    @Test\n    void test() {\n        try ( // container {\n            ChromaDBContainer chroma = new ChromaDBContainer(\"chromadb/chroma:0.4.23\")\n            // }\n        ) {\n            chroma.start();\n\n            given()\n                .baseUri(chroma.getEndpoint())\n                .when()\n                .body(\"{\\\"name\\\": \\\"test\\\"}\")\n                .contentType(ContentType.JSON)\n                .post(\"/api/v1/databases\")\n                .then()\n                .statusCode(200);\n\n            given().baseUri(chroma.getEndpoint()).when().get(\"/api/v1/databases/test\").then().statusCode(200);\n        }\n    }\n\n    @Test\n    void testVersion2() {\n        try (ChromaDBContainer chroma = new ChromaDBContainer(\"chromadb/chroma:1.0.0\")) {\n            chroma.start();\n\n            given()\n                .baseUri(chroma.getEndpoint())\n                .when()\n                .body(\"{\\\"name\\\": \\\"test\\\"}\")\n                .contentType(ContentType.JSON)\n                .post(\"/api/v2/tenants\")\n                .then()\n                .statusCode(200);\n\n            given().baseUri(chroma.getEndpoint()).when().get(\"/api/v2/tenants/test\").then().statusCode(200);\n        }\n    }\n}\n"
  },
  {
    "path": "modules/chromadb/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/clickhouse/build.gradle",
    "content": "description = \"Testcontainers :: JDBC :: ClickHouse\"\n\ndependencies {\n    api project(':testcontainers')\n    api project(':testcontainers-jdbc')\n\n    compileOnly project(':testcontainers-r2dbc')\n    compileOnly(group: 'com.clickhouse', name: 'clickhouse-r2dbc', version: '0.9.4', classifier: 'http')\n\n    testImplementation project(':testcontainers-jdbc-test')\n    testRuntimeOnly(group: 'com.clickhouse', name: 'clickhouse-jdbc', version: '0.9.4', classifier: 'all')\n\n    testImplementation 'com.clickhouse:client-v2:0.9.4'\n    testImplementation testFixtures(project(':testcontainers-r2dbc'))\n    testRuntimeOnly(group: 'com.clickhouse', name: 'clickhouse-r2dbc', version: '0.9.4', classifier: 'http')\n}\n"
  },
  {
    "path": "modules/clickhouse/src/main/java/org/testcontainers/clickhouse/ClickHouseContainer.java",
    "content": "package org.testcontainers.clickhouse;\n\nimport org.testcontainers.containers.JdbcDatabaseContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.time.Duration;\nimport java.util.HashSet;\nimport java.util.Set;\n\n/**\n * Testcontainers implementation for ClickHouse.\n * <p>\n * Supported image: {@code clickhouse/clickhouse-server}\n * <p>\n * Exposed ports:\n * <ul>\n *     <li>Database: 8123</li>\n *     <li>Console: 9000</li>\n * </ul>\n */\npublic class ClickHouseContainer extends JdbcDatabaseContainer<ClickHouseContainer> {\n\n    static final String CLICKHOUSE_CLICKHOUSE_SERVER = \"clickhouse/clickhouse-server\";\n\n    private static final DockerImageName CLICKHOUSE_IMAGE_NAME = DockerImageName.parse(CLICKHOUSE_CLICKHOUSE_SERVER);\n\n    static final Integer HTTP_PORT = 8123;\n\n    static final Integer NATIVE_PORT = 9000;\n\n    private static final String LEGACY_V1_DRIVER_CLASS_NAME = \"com.clickhouse.jdbc.ClickHouseDriver\";\n\n    private static final String DRIVER_CLASS_NAME = \"com.clickhouse.jdbc.Driver\";\n\n    private static final String JDBC_URL_PREFIX = \"jdbc:clickhouse://\";\n\n    private static final String TEST_QUERY = \"SELECT 1\";\n\n    static final String DEFAULT_USER = \"test\";\n\n    static final String DEFAULT_PASSWORD = \"test\";\n\n    private String databaseName = \"default\";\n\n    private String username = DEFAULT_USER;\n\n    private String password = DEFAULT_PASSWORD;\n\n    public ClickHouseContainer(String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    public ClickHouseContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(CLICKHOUSE_IMAGE_NAME);\n\n        addExposedPorts(HTTP_PORT, NATIVE_PORT);\n        waitingFor(\n            Wait\n                .forHttp(\"/\")\n                .forPort(HTTP_PORT)\n                .forStatusCode(200)\n                .forResponsePredicate(\"Ok.\"::equals)\n                .withStartupTimeout(Duration.ofMinutes(1))\n        );\n    }\n\n    @Override\n    protected void configure() {\n        withEnv(\"CLICKHOUSE_DB\", this.databaseName);\n        withEnv(\"CLICKHOUSE_USER\", this.username);\n        withEnv(\"CLICKHOUSE_PASSWORD\", this.password);\n    }\n\n    @Override\n    public Set<Integer> getLivenessCheckPortNumbers() {\n        return new HashSet<>(getMappedPort(HTTP_PORT));\n    }\n\n    @Override\n    public String getDriverClassName() {\n        try {\n            Class.forName(DRIVER_CLASS_NAME);\n            return DRIVER_CLASS_NAME;\n        } catch (ClassNotFoundException e) {\n            return LEGACY_V1_DRIVER_CLASS_NAME;\n        }\n    }\n\n    @Override\n    public String getJdbcUrl() {\n        return (\n            JDBC_URL_PREFIX +\n            getHost() +\n            \":\" +\n            getMappedPort(HTTP_PORT) +\n            \"/\" +\n            this.databaseName +\n            constructUrlParameters(\"?\", \"&\")\n        );\n    }\n\n    public String getHttpUrl() {\n        return \"http://\" + getHost() + \":\" + getMappedPort(HTTP_PORT);\n    }\n\n    @Override\n    public String getUsername() {\n        return username;\n    }\n\n    @Override\n    public String getPassword() {\n        return password;\n    }\n\n    @Override\n    public String getDatabaseName() {\n        return databaseName;\n    }\n\n    @Override\n    public String getTestQueryString() {\n        return TEST_QUERY;\n    }\n\n    @Override\n    public ClickHouseContainer withUsername(String username) {\n        this.username = username;\n        return this;\n    }\n\n    @Override\n    public ClickHouseContainer withPassword(String password) {\n        this.password = password;\n        return this;\n    }\n\n    @Override\n    public ClickHouseContainer withDatabaseName(String databaseName) {\n        this.databaseName = databaseName;\n        return this;\n    }\n\n    @Override\n    protected void waitUntilContainerStarted() {\n        getWaitStrategy().waitUntilReady(this);\n    }\n}\n"
  },
  {
    "path": "modules/clickhouse/src/main/java/org/testcontainers/clickhouse/ClickHouseR2DBCDatabaseContainer.java",
    "content": "package org.testcontainers.clickhouse;\n\nimport io.r2dbc.spi.ConnectionFactoryOptions;\nimport org.testcontainers.r2dbc.R2DBCDatabaseContainer;\n\n/**\n * ClickHouse R2DBC support\n */\npublic class ClickHouseR2DBCDatabaseContainer implements R2DBCDatabaseContainer {\n\n    private final ClickHouseContainer container;\n\n    public ClickHouseR2DBCDatabaseContainer(ClickHouseContainer container) {\n        this.container = container;\n    }\n\n    public static ConnectionFactoryOptions getOptions(ClickHouseContainer container) {\n        ConnectionFactoryOptions options = ConnectionFactoryOptions\n            .builder()\n            .option(ConnectionFactoryOptions.DRIVER, ClickHouseR2DBCDatabaseContainerProvider.DRIVER)\n            .build();\n\n        return new ClickHouseR2DBCDatabaseContainer(container).configure(options);\n    }\n\n    @Override\n    public void start() {\n        this.container.start();\n    }\n\n    @Override\n    public void stop() {\n        this.container.stop();\n    }\n\n    @Override\n    public ConnectionFactoryOptions configure(ConnectionFactoryOptions options) {\n        return options\n            .mutate()\n            .option(ConnectionFactoryOptions.HOST, container.getHost())\n            .option(ConnectionFactoryOptions.PORT, container.getMappedPort(ClickHouseContainer.HTTP_PORT))\n            .option(ConnectionFactoryOptions.DATABASE, container.getDatabaseName())\n            .option(ConnectionFactoryOptions.USER, container.getUsername())\n            .option(ConnectionFactoryOptions.PASSWORD, container.getPassword())\n            .option(ConnectionFactoryOptions.PROTOCOL, \"http\")\n            .build();\n    }\n}\n"
  },
  {
    "path": "modules/clickhouse/src/main/java/org/testcontainers/clickhouse/ClickHouseR2DBCDatabaseContainerProvider.java",
    "content": "package org.testcontainers.clickhouse;\n\nimport com.clickhouse.r2dbc.connection.ClickHouseConnectionFactoryProvider;\nimport io.r2dbc.spi.ConnectionFactoryMetadata;\nimport io.r2dbc.spi.ConnectionFactoryOptions;\nimport org.testcontainers.r2dbc.R2DBCDatabaseContainer;\nimport org.testcontainers.r2dbc.R2DBCDatabaseContainerProvider;\n\nimport javax.annotation.Nullable;\n\npublic class ClickHouseR2DBCDatabaseContainerProvider implements R2DBCDatabaseContainerProvider {\n\n    static final String DRIVER = ClickHouseConnectionFactoryProvider.CLICKHOUSE_DRIVER;\n\n    @Override\n    public boolean supports(ConnectionFactoryOptions options) {\n        return DRIVER.equals(options.getRequiredValue(ConnectionFactoryOptions.DRIVER));\n    }\n\n    @Override\n    public R2DBCDatabaseContainer createContainer(ConnectionFactoryOptions options) {\n        String image =\n            ClickHouseContainer.CLICKHOUSE_CLICKHOUSE_SERVER + \":\" + options.getRequiredValue(IMAGE_TAG_OPTION);\n        ClickHouseContainer container = new ClickHouseContainer(image)\n            .withDatabaseName((String) options.getRequiredValue(ConnectionFactoryOptions.DATABASE));\n\n        if (Boolean.TRUE.equals(options.getValue(REUSABLE_OPTION))) {\n            container.withReuse(true);\n        }\n        return new ClickHouseR2DBCDatabaseContainer(container);\n    }\n\n    @Nullable\n    @Override\n    public ConnectionFactoryMetadata getMetadata(ConnectionFactoryOptions options) {\n        ConnectionFactoryOptions.Builder builder = options.mutate();\n        if (!options.hasOption(ConnectionFactoryOptions.USER)) {\n            builder.option(ConnectionFactoryOptions.USER, ClickHouseContainer.DEFAULT_USER);\n        }\n        if (!options.hasOption(ConnectionFactoryOptions.PASSWORD)) {\n            builder.option(ConnectionFactoryOptions.PASSWORD, ClickHouseContainer.DEFAULT_PASSWORD);\n        }\n        builder.option(ConnectionFactoryOptions.PROTOCOL, \"http\");\n        return R2DBCDatabaseContainerProvider.super.getMetadata(builder.build());\n    }\n}\n"
  },
  {
    "path": "modules/clickhouse/src/main/java/org/testcontainers/containers/ClickHouseContainer.java",
    "content": "package org.testcontainers.containers;\n\nimport org.testcontainers.containers.wait.strategy.HttpWaitStrategy;\nimport org.testcontainers.utility.ComparableVersion;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.time.Duration;\nimport java.util.HashSet;\nimport java.util.Set;\n\n/**\n * Testcontainers implementation for ClickHouse.\n *\n * @deprecated use {@link org.testcontainers.clickhouse.ClickHouseContainer} instead\n */\npublic class ClickHouseContainer extends JdbcDatabaseContainer<ClickHouseContainer> {\n\n    public static final String NAME = \"clickhouse\";\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"yandex/clickhouse-server\");\n\n    private static final DockerImageName CLICKHOUSE_IMAGE_NAME = DockerImageName.parse(\"clickhouse/clickhouse-server\");\n\n    @Deprecated\n    public static final String IMAGE = DEFAULT_IMAGE_NAME.getUnversionedPart();\n\n    @Deprecated\n    public static final String DEFAULT_TAG = \"18.10.3\";\n\n    public static final Integer HTTP_PORT = 8123;\n\n    public static final Integer NATIVE_PORT = 9000;\n\n    private static final String LEGACY_DRIVER_CLASS_NAME = \"ru.yandex.clickhouse.ClickHouseDriver\";\n\n    private static final String DRIVER_CLASS_NAME = \"com.clickhouse.jdbc.ClickHouseDriver\";\n\n    private static final String JDBC_URL_PREFIX = \"jdbc:\" + NAME + \"://\";\n\n    private static final String TEST_QUERY = \"SELECT 1\";\n\n    private String databaseName = \"default\";\n\n    private String username = \"default\";\n\n    private String password = \"\";\n\n    private boolean supportsNewDriver;\n\n    /**\n     * @deprecated use {@link #ClickHouseContainer(DockerImageName)} instead\n     */\n    @Deprecated\n    public ClickHouseContainer() {\n        this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG));\n    }\n\n    public ClickHouseContainer(String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    public ClickHouseContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME, CLICKHOUSE_IMAGE_NAME);\n        supportsNewDriver = isNewDriverSupported(dockerImageName);\n\n        addExposedPorts(HTTP_PORT, NATIVE_PORT);\n        this.waitStrategy =\n            new HttpWaitStrategy()\n                .forStatusCode(200)\n                .forResponsePredicate(\"Ok.\"::equals)\n                .withStartupTimeout(Duration.ofMinutes(1));\n    }\n\n    @Override\n    public Set<Integer> getLivenessCheckPortNumbers() {\n        return new HashSet<>(getMappedPort(HTTP_PORT));\n    }\n\n    @Override\n    public String getDriverClassName() {\n        try {\n            if (supportsNewDriver) {\n                Class.forName(DRIVER_CLASS_NAME);\n                return DRIVER_CLASS_NAME;\n            } else {\n                return LEGACY_DRIVER_CLASS_NAME;\n            }\n        } catch (ClassNotFoundException e) {\n            return LEGACY_DRIVER_CLASS_NAME;\n        }\n    }\n\n    private static boolean isNewDriverSupported(DockerImageName dockerImageName) {\n        // New driver supports versions 20.7+. Check the version part of the tag\n        return new ComparableVersion(dockerImageName.getVersionPart()).isGreaterThanOrEqualTo(\"20.7\");\n    }\n\n    @Override\n    public String getJdbcUrl() {\n        return JDBC_URL_PREFIX + getHost() + \":\" + getMappedPort(HTTP_PORT) + \"/\" + databaseName;\n    }\n\n    @Override\n    public String getUsername() {\n        return username;\n    }\n\n    @Override\n    public String getPassword() {\n        return password;\n    }\n\n    @Override\n    public String getTestQueryString() {\n        return TEST_QUERY;\n    }\n\n    @Override\n    public ClickHouseContainer withUrlParam(String paramName, String paramValue) {\n        throw new UnsupportedOperationException(\"The ClickHouse does not support this\");\n    }\n}\n"
  },
  {
    "path": "modules/clickhouse/src/main/java/org/testcontainers/containers/ClickHouseProvider.java",
    "content": "package org.testcontainers.containers;\n\nimport org.testcontainers.clickhouse.ClickHouseContainer;\nimport org.testcontainers.utility.DockerImageName;\n\npublic class ClickHouseProvider extends JdbcDatabaseContainerProvider {\n\n    private static final String DEFAULT_TAG = \"24.12-alpine\";\n\n    @Override\n    public boolean supports(String databaseType) {\n        return databaseType.equals(\"clickhouse\");\n    }\n\n    @Override\n    public JdbcDatabaseContainer<?> newInstance() {\n        return newInstance(DEFAULT_TAG);\n    }\n\n    @Override\n    public JdbcDatabaseContainer<?> newInstance(String tag) {\n        return new ClickHouseContainer(DockerImageName.parse(\"clickhouse/clickhouse-server\").withTag(tag));\n    }\n}\n"
  },
  {
    "path": "modules/clickhouse/src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider",
    "content": "org.testcontainers.containers.ClickHouseProvider"
  },
  {
    "path": "modules/clickhouse/src/main/resources/META-INF/services/org.testcontainers.r2dbc.R2DBCDatabaseContainerProvider",
    "content": "org.testcontainers.clickhouse.ClickHouseR2DBCDatabaseContainerProvider\n"
  },
  {
    "path": "modules/clickhouse/src/test/java/org/testcontainers/ClickhouseTestImages.java",
    "content": "package org.testcontainers;\n\nimport org.testcontainers.utility.DockerImageName;\n\npublic interface ClickhouseTestImages {\n    DockerImageName CLICKHOUSE_IMAGE = DockerImageName.parse(\"clickhouse/clickhouse-server:21.11.11-alpine\");\n\n    DockerImageName CLICKHOUSE_24_12_IMAGE = DockerImageName.parse(\"clickhouse/clickhouse-server:24.12-alpine\");\n}\n"
  },
  {
    "path": "modules/clickhouse/src/test/java/org/testcontainers/clickhouse/ClickHouseContainerTest.java",
    "content": "package org.testcontainers.clickhouse;\n\nimport com.clickhouse.client.api.Client;\nimport com.clickhouse.client.api.data_formats.ClickHouseBinaryFormatReader;\nimport com.clickhouse.client.api.query.QueryResponse;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.ClickhouseTestImages;\nimport org.testcontainers.db.AbstractContainerDatabaseTest;\n\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.fail;\n\nclass ClickHouseContainerTest extends AbstractContainerDatabaseTest {\n\n    @Test\n    void testSimple() throws SQLException {\n        try ( // container {\n            ClickHouseContainer clickhouse = new ClickHouseContainer(\"clickhouse/clickhouse-server:21.11-alpine\")\n            // }\n        ) {\n            clickhouse.start();\n\n            ResultSet resultSet = performQuery(clickhouse, \"SELECT 1\");\n\n            int resultSetInt = resultSet.getInt(1);\n            assertThat(resultSetInt).isEqualTo(1);\n        }\n    }\n\n    @Test\n    void customCredentialsWithUrlParams() throws SQLException {\n        try (\n            ClickHouseContainer clickhouse = new ClickHouseContainer(\"clickhouse/clickhouse-server:21.11.2-alpine\")\n                .withUsername(\"default\")\n                .withPassword(\"\")\n                .withDatabaseName(\"test\")\n                // The new driver uses the prefix `clickhouse_setting_` for session settings\n                .withUrlParam(\"clickhouse_setting_max_result_rows\", \"5\")\n        ) {\n            clickhouse.start();\n\n            ResultSet resultSet = performQuery(\n                clickhouse,\n                \"SELECT value FROM system.settings where name='max_result_rows'\"\n            );\n\n            int resultSetInt = resultSet.getInt(1);\n            assertThat(resultSetInt).isEqualTo(5);\n        }\n    }\n\n    @Test\n    void testNewAuth() throws SQLException {\n        try (ClickHouseContainer clickhouse = new ClickHouseContainer(ClickhouseTestImages.CLICKHOUSE_24_12_IMAGE)) {\n            clickhouse.start();\n\n            ResultSet resultSet = performQuery(clickhouse, \"SELECT 1\");\n\n            int resultSetInt = resultSet.getInt(1);\n            assertThat(resultSetInt).isEqualTo(1);\n        }\n    }\n\n    @Test\n    void testGetHttpMethodWithHttpClient() {\n        ClickHouseContainer clickhouse = new ClickHouseContainer(ClickhouseTestImages.CLICKHOUSE_24_12_IMAGE);\n        clickhouse.start();\n        Client client = new Client.Builder()\n            .addEndpoint(clickhouse.getHttpUrl())\n            .setUsername(clickhouse.getUsername())\n            .setPassword(clickhouse.getPassword())\n            .build();\n        try {\n            QueryResponse queryResponse = client.query(\"SELECT 1\").get(1, TimeUnit.MINUTES);\n            ClickHouseBinaryFormatReader reader = client.newBinaryFormatReader(queryResponse);\n            reader.next();\n            int result = reader.getInteger(1);\n            assertThat(result).isEqualTo(1);\n        } catch (ExecutionException | InterruptedException | TimeoutException e) {\n            fail(\"Cannot get sql result:\" + e);\n        } finally {\n            clickhouse.close();\n            client.close();\n        }\n    }\n}\n"
  },
  {
    "path": "modules/clickhouse/src/test/java/org/testcontainers/clickhouse/ClickHouseR2DBCDatabaseContainerTest.java",
    "content": "package org.testcontainers.clickhouse;\n\nimport io.r2dbc.spi.ConnectionFactoryOptions;\nimport org.testcontainers.r2dbc.AbstractR2DBCDatabaseContainerTest;\n\npublic class ClickHouseR2DBCDatabaseContainerTest extends AbstractR2DBCDatabaseContainerTest<ClickHouseContainer> {\n\n    @Override\n    protected ConnectionFactoryOptions getOptions(ClickHouseContainer container) {\n        return ClickHouseR2DBCDatabaseContainer.getOptions(container);\n    }\n\n    @Override\n    protected String createR2DBCUrl() {\n        return \"r2dbc:tc:clickhouse:///db?TC_IMAGE_TAG=21.11.11-alpine\";\n    }\n\n    @Override\n    protected ClickHouseContainer createContainer() {\n        return new ClickHouseContainer(\"clickhouse/clickhouse-server:21.11.11-alpine\");\n    }\n}\n"
  },
  {
    "path": "modules/clickhouse/src/test/java/org/testcontainers/jdbc/clickhouse/ClickhouseJDBCDriverTest.java",
    "content": "package org.testcontainers.jdbc.clickhouse;\n\nimport org.testcontainers.jdbc.AbstractJDBCDriverTest;\n\nimport java.util.Arrays;\nimport java.util.EnumSet;\n\nclass ClickhouseJDBCDriverTest extends AbstractJDBCDriverTest {\n\n    public static Iterable<Object[]> data() {\n        return Arrays.asList(\n            new Object[][] { //\n                { \"jdbc:tc:clickhouse://hostname/databasename\", EnumSet.of(Options.PmdKnownBroken) },\n            }\n        );\n    }\n}\n"
  },
  {
    "path": "modules/clickhouse/src/test/java/org/testcontainers/junit/clickhouse/SimpleClickhouseTest.java",
    "content": "package org.testcontainers.junit.clickhouse;\n\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.ClickhouseTestImages;\nimport org.testcontainers.containers.ClickHouseContainer;\nimport org.testcontainers.db.AbstractContainerDatabaseTest;\n\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass SimpleClickhouseTest extends AbstractContainerDatabaseTest {\n\n    @Test\n    public void testSimple() throws SQLException {\n        try (ClickHouseContainer clickhouse = new ClickHouseContainer(ClickhouseTestImages.CLICKHOUSE_IMAGE)) {\n            clickhouse.start();\n\n            ResultSet resultSet = performQuery(clickhouse, \"SELECT 1\");\n\n            int resultSetInt = resultSet.getInt(1);\n            assertThat(resultSetInt).as(\"A basic SELECT query succeeds\").isEqualTo(1);\n        }\n    }\n}\n"
  },
  {
    "path": "modules/clickhouse/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/cockroachdb/build.gradle",
    "content": "description = \"Testcontainers :: JDBC :: CockroachDB\"\n\ndependencies {\n    api project(':testcontainers-jdbc')\n\n    testRuntimeOnly 'org.postgresql:postgresql:42.7.8'\n\n    testImplementation project(':testcontainers-jdbc-test')\n}\n"
  },
  {
    "path": "modules/cockroachdb/src/main/java/org/testcontainers/cockroachdb/CockroachContainer.java",
    "content": "package org.testcontainers.cockroachdb;\n\nimport org.testcontainers.containers.JdbcDatabaseContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.containers.wait.strategy.WaitAllStrategy;\nimport org.testcontainers.utility.ComparableVersion;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.time.Duration;\n\n/**\n * Testcontainers implementation for CockroachDB.\n * <p>\n * Supported image: {@code cockroachdb/cockroach}\n * <p>\n * Exposed ports:\n * <ul>\n *     <li>Database: 26257</li>\n *     <li>Console: 8080</li>\n * </ul>\n */\npublic class CockroachContainer extends JdbcDatabaseContainer<CockroachContainer> {\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"cockroachdb/cockroach\");\n\n    public static final String NAME = \"cockroach\";\n\n    private static final String JDBC_DRIVER_CLASS_NAME = \"org.postgresql.Driver\";\n\n    private static final String JDBC_URL_PREFIX = \"jdbc:postgresql\";\n\n    private static final String TEST_QUERY_STRING = \"SELECT 1\";\n\n    private static final int REST_API_PORT = 8080;\n\n    private static final int DB_PORT = 26257;\n\n    private static final String FIRST_VERSION_WITH_ENV_VARS_SUPPORT = \"22.1.0\";\n\n    private String databaseName = \"postgres\";\n\n    private String username = \"root\";\n\n    private String password = \"\";\n\n    private boolean isVersionGreaterThanOrEqualTo221;\n\n    public CockroachContainer(final String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    public CockroachContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n        this.isVersionGreaterThanOrEqualTo221 = isVersionGreaterThanOrEqualTo221(dockerImageName);\n\n        WaitAllStrategy waitStrategy = new WaitAllStrategy();\n        waitStrategy.withStrategy(\n            Wait.forHttp(\"/health\").forPort(REST_API_PORT).forStatusCode(200).withStartupTimeout(Duration.ofMinutes(1))\n        );\n        if (this.isVersionGreaterThanOrEqualTo221) {\n            waitStrategy.withStrategy(Wait.forSuccessfulCommand(\"[ -f ./init_success ] || { exit 1; }\"));\n        }\n\n        withExposedPorts(REST_API_PORT, DB_PORT);\n        waitingFor(waitStrategy);\n        withCommand(\"start-single-node --insecure\");\n    }\n\n    @Override\n    protected void configure() {\n        withEnv(\"COCKROACH_USER\", this.username);\n        withEnv(\"COCKROACH_PASSWORD\", this.password);\n        if (this.password != null && !this.password.isEmpty()) {\n            withCommand(\"start-single-node\");\n        }\n        withEnv(\"COCKROACH_DATABASE\", this.databaseName);\n    }\n\n    @Override\n    public String getDriverClassName() {\n        return JDBC_DRIVER_CLASS_NAME;\n    }\n\n    @Override\n    public String getJdbcUrl() {\n        String additionalUrlParams = constructUrlParameters(\"?\", \"&\");\n        return (\n            JDBC_URL_PREFIX +\n            \"://\" +\n            getHost() +\n            \":\" +\n            getMappedPort(DB_PORT) +\n            \"/\" +\n            databaseName +\n            additionalUrlParams\n        );\n    }\n\n    @Override\n    public final String getDatabaseName() {\n        return databaseName;\n    }\n\n    @Override\n    public String getUsername() {\n        return username;\n    }\n\n    @Override\n    public String getPassword() {\n        return password;\n    }\n\n    @Override\n    public String getTestQueryString() {\n        return TEST_QUERY_STRING;\n    }\n\n    @Override\n    public CockroachContainer withUsername(String username) {\n        validateIfVersionSupportsUsernameOrPasswordOrDatabase(\"username\");\n        this.username = username;\n        return this;\n    }\n\n    @Override\n    public CockroachContainer withPassword(String password) {\n        validateIfVersionSupportsUsernameOrPasswordOrDatabase(\"password\");\n        this.password = password;\n        return this;\n    }\n\n    @Override\n    public CockroachContainer withDatabaseName(final String databaseName) {\n        validateIfVersionSupportsUsernameOrPasswordOrDatabase(\"databaseName\");\n        this.databaseName = databaseName;\n        return this;\n    }\n\n    private boolean isVersionGreaterThanOrEqualTo221(DockerImageName dockerImageName) {\n        ComparableVersion version = new ComparableVersion(dockerImageName.getVersionPart().replaceFirst(\"v\", \"\"));\n        return version.isGreaterThanOrEqualTo(FIRST_VERSION_WITH_ENV_VARS_SUPPORT);\n    }\n\n    private void validateIfVersionSupportsUsernameOrPasswordOrDatabase(String parameter) {\n        if (!isVersionGreaterThanOrEqualTo221) {\n            throw new UnsupportedOperationException(\n                String.format(\"Setting a %s in not supported in the versions below 22.1.0\", parameter)\n            );\n        }\n    }\n\n    @Override\n    protected void waitUntilContainerStarted() {\n        getWaitStrategy().waitUntilReady(this);\n    }\n}\n"
  },
  {
    "path": "modules/cockroachdb/src/main/java/org/testcontainers/containers/CockroachContainer.java",
    "content": "package org.testcontainers.containers;\n\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.containers.wait.strategy.WaitAllStrategy;\nimport org.testcontainers.utility.ComparableVersion;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.time.Duration;\n\n/**\n * Testcontainers implementation for CockroachDB.\n * <p>\n * Supported image: {@code cockroachdb/cockroach}\n * <p>\n * Exposed ports:\n * <ul>\n *     <li>Database: 26257</li>\n *     <li>Console: 8080</li>\n * </ul>\n *\n * @deprecated use {@link org.testcontainers.cockroachdb.CockroachContainer} instead\n */\n@Deprecated\npublic class CockroachContainer extends JdbcDatabaseContainer<CockroachContainer> {\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"cockroachdb/cockroach\");\n\n    private static final String DEFAULT_TAG = \"v19.2.11\";\n\n    public static final String NAME = \"cockroach\";\n\n    @Deprecated\n    public static final String IMAGE = DEFAULT_IMAGE_NAME.getUnversionedPart();\n\n    @Deprecated\n    public static final String IMAGE_TAG = DEFAULT_TAG;\n\n    private static final String JDBC_DRIVER_CLASS_NAME = \"org.postgresql.Driver\";\n\n    private static final String JDBC_URL_PREFIX = \"jdbc:postgresql\";\n\n    private static final String TEST_QUERY_STRING = \"SELECT 1\";\n\n    private static final int REST_API_PORT = 8080;\n\n    private static final int DB_PORT = 26257;\n\n    private static final String FIRST_VERSION_WITH_ENV_VARS_SUPPORT = \"22.1.0\";\n\n    private String databaseName = \"postgres\";\n\n    private String username = \"root\";\n\n    private String password = \"\";\n\n    private boolean isVersionGreaterThanOrEqualTo221;\n\n    /**\n     * @deprecated use {@link #CockroachContainer(DockerImageName)} instead\n     */\n    @Deprecated\n    public CockroachContainer() {\n        this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG));\n    }\n\n    public CockroachContainer(final String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    public CockroachContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n        this.isVersionGreaterThanOrEqualTo221 = isVersionGreaterThanOrEqualTo221(dockerImageName);\n\n        WaitAllStrategy waitStrategy = new WaitAllStrategy();\n        waitStrategy.withStrategy(\n            Wait.forHttp(\"/health\").forPort(REST_API_PORT).forStatusCode(200).withStartupTimeout(Duration.ofMinutes(1))\n        );\n        if (this.isVersionGreaterThanOrEqualTo221) {\n            waitStrategy.withStrategy(Wait.forSuccessfulCommand(\"[ -f ./init_success ] || { exit 1; }\"));\n        }\n\n        withExposedPorts(REST_API_PORT, DB_PORT);\n        waitingFor(waitStrategy);\n        withCommand(\"start-single-node --insecure\");\n    }\n\n    @Override\n    protected void configure() {\n        withEnv(\"COCKROACH_USER\", this.username);\n        withEnv(\"COCKROACH_PASSWORD\", this.password);\n        if (this.password != null && !this.password.isEmpty()) {\n            withCommand(\"start-single-node\");\n        }\n        withEnv(\"COCKROACH_DATABASE\", this.databaseName);\n    }\n\n    @Override\n    public String getDriverClassName() {\n        return JDBC_DRIVER_CLASS_NAME;\n    }\n\n    @Override\n    public String getJdbcUrl() {\n        String additionalUrlParams = constructUrlParameters(\"?\", \"&\");\n        return (\n            JDBC_URL_PREFIX +\n            \"://\" +\n            getHost() +\n            \":\" +\n            getMappedPort(DB_PORT) +\n            \"/\" +\n            databaseName +\n            additionalUrlParams\n        );\n    }\n\n    @Override\n    public final String getDatabaseName() {\n        return databaseName;\n    }\n\n    @Override\n    public String getUsername() {\n        return username;\n    }\n\n    @Override\n    public String getPassword() {\n        return password;\n    }\n\n    @Override\n    public String getTestQueryString() {\n        return TEST_QUERY_STRING;\n    }\n\n    @Override\n    public CockroachContainer withUsername(String username) {\n        validateIfVersionSupportsUsernameOrPasswordOrDatabase(\"username\");\n        this.username = username;\n        return this;\n    }\n\n    @Override\n    public CockroachContainer withPassword(String password) {\n        validateIfVersionSupportsUsernameOrPasswordOrDatabase(\"password\");\n        this.password = password;\n        return this;\n    }\n\n    @Override\n    public CockroachContainer withDatabaseName(final String databaseName) {\n        validateIfVersionSupportsUsernameOrPasswordOrDatabase(\"databaseName\");\n        this.databaseName = databaseName;\n        return this;\n    }\n\n    private boolean isVersionGreaterThanOrEqualTo221(DockerImageName dockerImageName) {\n        ComparableVersion version = new ComparableVersion(dockerImageName.getVersionPart().replaceFirst(\"v\", \"\"));\n        return version.isGreaterThanOrEqualTo(FIRST_VERSION_WITH_ENV_VARS_SUPPORT);\n    }\n\n    private void validateIfVersionSupportsUsernameOrPasswordOrDatabase(String parameter) {\n        if (!isVersionGreaterThanOrEqualTo221) {\n            throw new UnsupportedOperationException(\n                String.format(\"Setting a %s in not supported in the versions below 22.1.0\", parameter)\n            );\n        }\n    }\n\n    @Override\n    protected void waitUntilContainerStarted() {\n        getWaitStrategy().waitUntilReady(this);\n    }\n}\n"
  },
  {
    "path": "modules/cockroachdb/src/main/java/org/testcontainers/containers/CockroachContainerProvider.java",
    "content": "package org.testcontainers.containers;\n\nimport org.testcontainers.utility.DockerImageName;\n\npublic class CockroachContainerProvider extends JdbcDatabaseContainerProvider {\n\n    @Override\n    public boolean supports(String databaseType) {\n        return databaseType.equals(CockroachContainer.NAME);\n    }\n\n    @Override\n    public JdbcDatabaseContainer newInstance() {\n        return newInstance(CockroachContainer.IMAGE_TAG);\n    }\n\n    @Override\n    public JdbcDatabaseContainer newInstance(String tag) {\n        return new CockroachContainer(DockerImageName.parse(CockroachContainer.IMAGE).withTag(tag));\n    }\n}\n"
  },
  {
    "path": "modules/cockroachdb/src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider",
    "content": "org.testcontainers.containers.CockroachContainerProvider"
  },
  {
    "path": "modules/cockroachdb/src/test/java/org/testcontainers/CockroachDBTestImages.java",
    "content": "package org.testcontainers;\n\nimport org.testcontainers.utility.DockerImageName;\n\npublic interface CockroachDBTestImages {\n    DockerImageName COCKROACHDB_IMAGE = DockerImageName.parse(\"cockroachdb/cockroach:v22.2.3\");\n}\n"
  },
  {
    "path": "modules/cockroachdb/src/test/java/org/testcontainers/cockroachdb/CockroachContainerTest.java",
    "content": "package org.testcontainers.cockroachdb;\n\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.CockroachDBTestImages;\nimport org.testcontainers.db.AbstractContainerDatabaseTest;\nimport org.testcontainers.images.builder.Transferable;\n\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.util.logging.Level;\nimport java.util.logging.LogManager;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass CockroachContainerTest extends AbstractContainerDatabaseTest {\n    static {\n        // Postgres JDBC driver uses JUL; disable it to avoid annoying, irrelevant, stderr logs during connection testing\n        LogManager.getLogManager().getLogger(\"\").setLevel(Level.OFF);\n    }\n\n    @Test\n    void testSimple() throws SQLException {\n        try ( // container {\n            CockroachContainer cockroach = new CockroachContainer(\"cockroachdb/cockroach:v26.1.1\")\n            // }\n        ) {\n            cockroach.start();\n\n            ResultSet resultSet = performQuery(cockroach, \"SELECT 1\");\n\n            int resultSetInt = resultSet.getInt(1);\n            assertThat(resultSetInt).as(\"A basic SELECT query succeeds\").isEqualTo(1);\n        }\n    }\n\n    @Test\n    void testExplicitInitScript() throws SQLException {\n        try (\n            CockroachContainer cockroach = new CockroachContainer(CockroachDBTestImages.COCKROACHDB_IMAGE)\n                .withInitScript(\"somepath/init_postgresql.sql\")\n        ) { // CockroachDB is expected to be compatible with Postgres\n            cockroach.start();\n\n            ResultSet resultSet = performQuery(cockroach, \"SELECT foo FROM bar\");\n\n            String firstColumnValue = resultSet.getString(1);\n            assertThat(firstColumnValue).as(\"Value from init script should equal real value\").isEqualTo(\"hello world\");\n        }\n    }\n\n    @Test\n    void testWithAdditionalUrlParamInJdbcUrl() {\n        CockroachContainer cockroach = new CockroachContainer(CockroachDBTestImages.COCKROACHDB_IMAGE)\n            .withUrlParam(\"sslmode\", \"disable\")\n            .withUrlParam(\"application_name\", \"cockroach\");\n\n        try {\n            cockroach.start();\n            String jdbcUrl = cockroach.getJdbcUrl();\n            assertThat(jdbcUrl)\n                .contains(\"?\")\n                .contains(\"&\")\n                .contains(\"sslmode=disable\")\n                .contains(\"application_name=cockroach\");\n        } finally {\n            cockroach.stop();\n        }\n    }\n\n    @Test\n    void testWithUsernamePasswordDatabase() throws SQLException {\n        try (\n            CockroachContainer cockroach = new CockroachContainer(CockroachDBTestImages.COCKROACHDB_IMAGE)\n                .withUsername(\"test_user\")\n                .withPassword(\"test_password\")\n                .withDatabaseName(\"test_database\")\n        ) {\n            cockroach.start();\n\n            ResultSet resultSet = performQuery(cockroach, \"SELECT 1\");\n\n            int resultSetInt = resultSet.getInt(1);\n            assertThat(resultSetInt).as(\"A basic SELECT query succeeds\").isEqualTo(1);\n\n            String jdbcUrl = cockroach.getJdbcUrl();\n            assertThat(jdbcUrl).contains(\"/\" + \"test_database\");\n        }\n    }\n\n    @Test\n    void testInitializationScript() throws SQLException {\n        String sql =\n            \"USE postgres; \\n\" +\n            \"CREATE TABLE bar (foo VARCHAR(255)); \\n\" +\n            \"INSERT INTO bar (foo) VALUES ('hello world');\";\n\n        try (\n            CockroachContainer cockroach = new CockroachContainer(CockroachDBTestImages.COCKROACHDB_IMAGE)\n                .withCopyToContainer(Transferable.of(sql), \"/docker-entrypoint-initdb.d/init.sql\")\n                .withLogConsumer(outputFrame -> System.out.println(outputFrame.getUtf8String()))\n        ) { // CockroachDB is expected to be compatible with Postgres\n            cockroach.start();\n\n            ResultSet resultSet = performQuery(cockroach, \"SELECT foo FROM bar\");\n\n            String firstColumnValue = resultSet.getString(1);\n            assertThat(firstColumnValue).as(\"Value from init script should equal real value\").isEqualTo(\"hello world\");\n        }\n    }\n}\n"
  },
  {
    "path": "modules/cockroachdb/src/test/java/org/testcontainers/jdbc/cockroachdb/CockroachDBJDBCDriverTest.java",
    "content": "package org.testcontainers.jdbc.cockroachdb;\n\nimport org.testcontainers.jdbc.AbstractJDBCDriverTest;\n\nimport java.util.Arrays;\nimport java.util.EnumSet;\n\nclass CockroachDBJDBCDriverTest extends AbstractJDBCDriverTest {\n\n    public static Iterable<Object[]> data() {\n        return Arrays.asList(\n            new Object[][] { //\n                { \"jdbc:tc:cockroach:v22.2.3://hostname/databasename\", EnumSet.noneOf(Options.class) },\n            }\n        );\n    }\n}\n"
  },
  {
    "path": "modules/cockroachdb/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/cockroachdb/src/test/resources/somepath/init_postgresql.sql",
    "content": "CREATE TABLE bar (\n  foo VARCHAR(255)\n);\n\nINSERT INTO bar (foo) VALUES ('hello world');"
  },
  {
    "path": "modules/consul/build.gradle",
    "content": "description = \"Testcontainers :: Consul\"\n\ndependencies {\n    api project(':testcontainers')\n\n    testImplementation 'com.ecwid.consul:consul-api:1.4.5'\n    testImplementation 'io.rest-assured:rest-assured:5.5.6'\n}\n"
  },
  {
    "path": "modules/consul/src/main/java/org/testcontainers/consul/ConsulContainer.java",
    "content": "package org.testcontainers.consul;\n\nimport com.github.dockerjava.api.command.InspectContainerResponse;\nimport com.github.dockerjava.api.model.Capability;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\n/**\n * Testcontainers implementation for Consul.\n * <p>\n * Supported images: {@code hashicorp/consul}, {@code consul}\n * <p>\n * Exposed ports:\n * <ul>\n *     <li>HTTP: 8500</li>\n *     <li>gRPC: 8502</li>\n * </ul>\n */\npublic class ConsulContainer extends GenericContainer<ConsulContainer> {\n\n    private static final DockerImageName DEFAULT_OLD_IMAGE_NAME = DockerImageName.parse(\"consul\");\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"hashicorp/consul\");\n\n    private static final int CONSUL_HTTP_PORT = 8500;\n\n    private static final int CONSUL_GRPC_PORT = 8502;\n\n    private List<String> initCommands = new ArrayList<>();\n\n    private String[] startConsulCmd = new String[] { \"agent\", \"-dev\", \"-client\", \"0.0.0.0\" };\n\n    public ConsulContainer(String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    public ConsulContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_OLD_IMAGE_NAME, DEFAULT_IMAGE_NAME);\n\n        // Use the status leader endpoint to verify if consul is running.\n        setWaitStrategy(Wait.forHttp(\"/v1/status/leader\").forPort(CONSUL_HTTP_PORT).forStatusCode(200));\n\n        withCreateContainerCmdModifier(cmd -> cmd.getHostConfig().withCapAdd(Capability.IPC_LOCK));\n        withEnv(\"CONSUL_ADDR\", \"http://0.0.0.0:\" + CONSUL_HTTP_PORT);\n        withCommand(startConsulCmd);\n        withExposedPorts(CONSUL_HTTP_PORT, CONSUL_GRPC_PORT);\n    }\n\n    @Override\n    protected void containerIsStarted(InspectContainerResponse containerInfo) {\n        runConsulCommands();\n    }\n\n    private void runConsulCommands() {\n        if (!initCommands.isEmpty()) {\n            String commands = initCommands\n                .stream()\n                .map(command -> \"consul \" + command)\n                .collect(Collectors.joining(\" && \"));\n            try {\n                ExecResult execResult = this.execInContainer(new String[] { \"/bin/sh\", \"-c\", commands });\n                if (execResult.getExitCode() != 0) {\n                    logger()\n                        .error(\n                            \"Failed to execute these init commands {}. Exit code {}. Stdout {}. Stderr {}\",\n                            initCommands,\n                            execResult.getExitCode(),\n                            execResult.getStdout(),\n                            execResult.getStderr()\n                        );\n                }\n            } catch (IOException | InterruptedException e) {\n                logger()\n                    .error(\n                        \"Failed to execute these init commands {}. Exception message: {}\",\n                        initCommands,\n                        e.getMessage()\n                    );\n            }\n        }\n    }\n\n    /**\n     * Run consul commands using the consul cli.\n     *\n     * Useful for enabling more secret engines like:\n     * <pre>\n     *     .withConsulCommand(\"secrets enable pki\")\n     *     .withConsulCommand(\"secrets enable transit\")\n     * </pre>\n     * or register specific K/V like:\n     * <pre>\n     *     .withConsulCommand(\"kv put config/testing1 value123\")\n     * </pre>\n     * @param commands The commands to send to the consul cli\n     * @return this\n     */\n    public ConsulContainer withConsulCommand(String... commands) {\n        initCommands.addAll(Arrays.asList(commands));\n        return self();\n    }\n}\n"
  },
  {
    "path": "modules/consul/src/test/java/org/testcontainers/consul/ConsulContainerTest.java",
    "content": "package org.testcontainers.consul;\n\nimport com.ecwid.consul.v1.ConsulClient;\nimport com.ecwid.consul.v1.Response;\nimport com.ecwid.consul.v1.kv.model.GetValue;\nimport io.restassured.RestAssured;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.GenericContainer;\n\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Base64;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass ConsulContainerTest {\n\n    private static ConsulContainer consul = new ConsulContainer(\"hashicorp/consul:1.15\")\n        .withConsulCommand(\"kv put config/testing1 value123\");\n\n    @BeforeAll\n    static void setup() {\n        consul.start();\n    }\n\n    @AfterAll\n    static void teardown() {\n        consul.stop();\n    }\n\n    @Test\n    void readFirstPropertyPathWithCli() throws IOException, InterruptedException {\n        GenericContainer.ExecResult result = consul.execInContainer(\"consul\", \"kv\", \"get\", \"config/testing1\");\n        final String output = result.getStdout().replaceAll(\"\\\\r?\\\\n\", \"\");\n        assertThat(output).contains(\"value123\");\n    }\n\n    @Test\n    void readFirstSecretPathOverHttpApi() {\n        io.restassured.response.Response response = RestAssured\n            .given()\n            .when()\n            .get(\"http://\" + getHostAndPort() + \"/v1/kv/config/testing1\")\n            .andReturn();\n\n        assertThat(response.body().jsonPath().getString(\"[0].Value\"))\n            .isEqualTo(Base64.getEncoder().encodeToString(\"value123\".getBytes(StandardCharsets.UTF_8)));\n    }\n\n    @Test\n    void writeAndReadMultipleValuesUsingClient() {\n        final ConsulClient consulClient = new ConsulClient(consul.getHost(), consul.getFirstMappedPort());\n\n        final Map<String, String> properties = new HashMap<>();\n        properties.put(\"value\", \"world\");\n        properties.put(\"other_value\", \"another world\");\n\n        // Write operation\n        properties.forEach((key, value) -> {\n            Response<Boolean> writeResponse = consulClient.setKVValue(key, value);\n            assertThat(writeResponse.getValue()).isTrue();\n        });\n\n        // Read operation\n        properties.forEach((key, value) -> {\n            Response<GetValue> readResponse = consulClient.getKVValue(key);\n            assertThat(readResponse.getValue().getDecodedValue()).isEqualTo(value);\n        });\n    }\n\n    private String getHostAndPort() {\n        return consul.getHost() + \":\" + consul.getMappedPort(8500);\n    }\n}\n"
  },
  {
    "path": "modules/consul/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/couchbase/AUTHORS",
    "content": "﻿Tayeb Chlyah <tayebchlyah@gmail.com>\nTobias Happ <tobias.happ@gmx.de>\n"
  },
  {
    "path": "modules/couchbase/build.gradle",
    "content": "description = \"Testcontainers :: Couchbase\"\n\ndependencies {\n    api project(':testcontainers')\n    // TODO use JDK's HTTP client and/or Apache HttpClient5\n    shaded 'com.squareup.okhttp3:okhttp:5.3.2'\n\n    testImplementation 'com.couchbase.client:java-client:3.10.0'\n    testImplementation 'org.awaitility:awaitility:4.3.0'\n}\n"
  },
  {
    "path": "modules/couchbase/src/main/java/org/testcontainers/couchbase/BucketDefinition.java",
    "content": "/*\n * Copyright (c) 2020 Couchbase, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.testcontainers.couchbase;\n\n/**\n * Allows to configure the properties of a bucket that should be created.\n */\npublic class BucketDefinition {\n\n    private final String name;\n\n    private boolean flushEnabled = false;\n\n    private boolean queryPrimaryIndex = true;\n\n    private int quota = 100;\n\n    private int numReplicas = 0;\n\n    public BucketDefinition(final String name) {\n        this.name = name;\n    }\n\n    /**\n     * Allows to configure the number of replicas on a bucket (defaults to 0).\n     * <p>\n     * By default the bucket is initialized with 0 replicas since only a single container is launched. Modifying\n     * this value can still be useful in some test scenarios (i.e. to test failures with the wrong number of replicas\n     * and durability requirements on operations).\n     * <p>\n     * Couchbase buckets can have a maximum of three replicas configured.\n     *\n     * @param numReplicas the number of replicas to configure.\n     * @return this {@link BucketDefinition} for chaining purposes.\n     */\n    public BucketDefinition withReplicas(final int numReplicas) {\n        if (numReplicas < 0 || numReplicas > 3) {\n            throw new IllegalArgumentException(\"The number of replicas must be between 0 and 3 (inclusive)\");\n        }\n        this.numReplicas = numReplicas;\n        return this;\n    }\n\n    /**\n     * Enables flush for this bucket (disabled by default).\n     *\n     * @param flushEnabled if true, the bucket can be flushed.\n     * @return this {@link BucketDefinition} for chaining purposes.\n     */\n    public BucketDefinition withFlushEnabled(final boolean flushEnabled) {\n        this.flushEnabled = flushEnabled;\n        return this;\n    }\n\n    /**\n     * Sets a custom bucket quota (100MiB by default).\n     *\n     * @param quota the quota to set for the bucket in mebibytes.\n     * @return this {@link BucketDefinition} for chaining purposes.\n     */\n    public BucketDefinition withQuota(final int quota) {\n        if (quota < 100) {\n            throw new IllegalArgumentException(\"Bucket quota cannot be less than 100MB!\");\n        }\n        this.quota = quota;\n        return this;\n    }\n\n    /**\n     * Allows to disable creating a primary index for this bucket (enabled by default).\n     *\n     * @param create if false, a primary index will not be created.\n     * @return this {@link BucketDefinition} for chaining purposes.\n     */\n    public BucketDefinition withPrimaryIndex(final boolean create) {\n        this.queryPrimaryIndex = create;\n        return this;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public boolean hasFlushEnabled() {\n        return flushEnabled;\n    }\n\n    public boolean hasPrimaryIndex() {\n        return queryPrimaryIndex;\n    }\n\n    public int getQuota() {\n        return quota;\n    }\n\n    public int getNumReplicas() {\n        return numReplicas;\n    }\n}\n"
  },
  {
    "path": "modules/couchbase/src/main/java/org/testcontainers/couchbase/CouchbaseContainer.java",
    "content": "/*\n * Copyright (c) 2020 Couchbase, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.testcontainers.couchbase;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.github.dockerjava.api.command.InspectContainerResponse;\nimport com.github.dockerjava.api.model.ContainerNetwork;\nimport lombok.Cleanup;\nimport okhttp3.Credentials;\nimport okhttp3.FormBody;\nimport okhttp3.OkHttpClient;\nimport okhttp3.Request;\nimport okhttp3.RequestBody;\nimport okhttp3.Response;\nimport org.rnorth.ducttape.unreliables.Unreliables;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.wait.strategy.HttpWaitStrategy;\nimport org.testcontainers.containers.wait.strategy.WaitAllStrategy;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.EnumSet;\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.Predicate;\nimport java.util.stream.Collectors;\n\n/**\n * Testcontainers implementation for Couchbase.\n * <p>\n * Supported image: {@code couchbase/server}\n * <p>\n * Exposed ports:\n * <ul>\n *     <li>Console: 8091</li>\n * </ul>\n * <p>\n * Note that it does not depend on a specific couchbase SDK, so it can be used with both the Java SDK 2 and 3 as well\n * as the Scala SDK 1 or newer. We recommend using the latest and greatest SDKs for the best experience.\n */\npublic class CouchbaseContainer extends GenericContainer<CouchbaseContainer> {\n\n    private static final int MGMT_PORT = 8091;\n\n    private static final int MGMT_SSL_PORT = 18091;\n\n    private static final int VIEW_PORT = 8092;\n\n    private static final int VIEW_SSL_PORT = 18092;\n\n    private static final int QUERY_PORT = 8093;\n\n    private static final int QUERY_SSL_PORT = 18093;\n\n    private static final int SEARCH_PORT = 8094;\n\n    private static final int SEARCH_SSL_PORT = 18094;\n\n    private static final int ANALYTICS_PORT = 8095;\n\n    private static final int ANALYTICS_SSL_PORT = 18095;\n\n    private static final int EVENTING_PORT = 8096;\n\n    private static final int EVENTING_SSL_PORT = 18096;\n\n    private static final int KV_PORT = 11210;\n\n    private static final int KV_SSL_PORT = 11207;\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"couchbase/server\");\n\n    private static final ObjectMapper MAPPER = new ObjectMapper();\n\n    private static final OkHttpClient HTTP_CLIENT = new OkHttpClient();\n\n    private String username = \"Administrator\";\n\n    private String password = \"password\";\n\n    /**\n     * Enabled services does not include Analytics since most users likely do not need to test\n     * with it and is also a little heavy on memory and runtime requirements. Also, it is only\n     * available with the enterprise edition (EE).\n     */\n    private Set<CouchbaseService> enabledServices = EnumSet.of(\n        CouchbaseService.KV,\n        CouchbaseService.QUERY,\n        CouchbaseService.SEARCH,\n        CouchbaseService.INDEX\n    );\n\n    /**\n     * Holds the custom service quotas if configured by the user.\n     */\n    private final Map<CouchbaseService, Integer> customServiceQuotas = new HashMap<>();\n\n    private final List<BucketDefinition> buckets = new ArrayList<>();\n\n    private boolean isEnterprise = false;\n\n    private boolean hasTlsPorts = false;\n\n    /**\n     * Creates a new couchbase container with the specified image name.\n     *\n     * @param dockerImageName the image name that should be used.\n     */\n    public CouchbaseContainer(final String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    /**\n     * Create a new couchbase container with the specified image name.\n     * @param dockerImageName the image name that should be used.\n     */\n    public CouchbaseContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n    }\n\n    /**\n     * Set custom username and password for the admin user.\n     *\n     * @param username the admin username to use.\n     * @param password the password for the admin user.\n     * @return this {@link CouchbaseContainer} for chaining purposes.\n     */\n    public CouchbaseContainer withCredentials(final String username, final String password) {\n        checkNotRunning();\n        this.username = username;\n        this.password = password;\n        return this;\n    }\n\n    public CouchbaseContainer withBucket(final BucketDefinition bucketDefinition) {\n        checkNotRunning();\n        this.buckets.add(bucketDefinition);\n        return this;\n    }\n\n    public CouchbaseContainer withEnabledServices(final CouchbaseService... enabled) {\n        checkNotRunning();\n        this.enabledServices = EnumSet.copyOf(Arrays.asList(enabled));\n        return this;\n    }\n\n    /**\n     * Configures a custom memory quota for a given service.\n     *\n     * @param service the service to configure the quota for.\n     * @param quotaMb the memory quota in MB.\n     * @return this {@link CouchbaseContainer} for chaining purposes.\n     */\n    public CouchbaseContainer withServiceQuota(final CouchbaseService service, final int quotaMb) {\n        checkNotRunning();\n        if (!service.hasQuota()) {\n            throw new IllegalArgumentException(\"The provided service (\" + service + \") has no quota to configure\");\n        }\n        if (quotaMb < service.getMinimumQuotaMb()) {\n            throw new IllegalArgumentException(\n                \"The custom quota (\" +\n                quotaMb +\n                \") must not be smaller than the \" +\n                \"minimum quota for the service (\" +\n                service.getMinimumQuotaMb() +\n                \")\"\n            );\n        }\n        this.customServiceQuotas.put(service, quotaMb);\n        return this;\n    }\n\n    /**\n     * Enables the analytics service which is not enabled by default.\n     *\n     * @return this {@link CouchbaseContainer} for chaining purposes.\n     */\n    public CouchbaseContainer withAnalyticsService() {\n        checkNotRunning();\n        this.enabledServices.add(CouchbaseService.ANALYTICS);\n        return this;\n    }\n\n    /**\n     * Enables the eventing service which is not enabled by default.\n     *\n     * @return this {@link CouchbaseContainer} for chaining purposes.\n     */\n    public CouchbaseContainer withEventingService() {\n        checkNotRunning();\n        this.enabledServices.add(CouchbaseService.EVENTING);\n        return this;\n    }\n\n    public final String getUsername() {\n        return username;\n    }\n\n    public final String getPassword() {\n        return password;\n    }\n\n    public int getBootstrapCarrierDirectPort() {\n        return getMappedPort(KV_PORT);\n    }\n\n    public int getBootstrapHttpDirectPort() {\n        return getMappedPort(MGMT_PORT);\n    }\n\n    public String getConnectionString() {\n        return String.format(\"couchbase://%s:%d\", getHost(), getBootstrapCarrierDirectPort());\n    }\n\n    @Override\n    protected void configure() {\n        super.configure();\n\n        exposePorts();\n\n        WaitAllStrategy waitStrategy = new WaitAllStrategy();\n\n        // Makes sure that all nodes in the cluster are healthy.\n        waitStrategy =\n            waitStrategy.withStrategy(\n                new HttpWaitStrategy()\n                    .forPath(\"/pools/default\")\n                    .forPort(MGMT_PORT)\n                    .withBasicCredentials(username, password)\n                    .forStatusCode(200)\n                    .forResponsePredicate(response -> {\n                        try {\n                            return Optional\n                                .of(MAPPER.readTree(response))\n                                .map(n -> n.at(\"/nodes/0/status\"))\n                                .map(JsonNode::asText)\n                                .map(\"healthy\"::equals)\n                                .orElse(false);\n                        } catch (IOException e) {\n                            logger().error(\"Unable to parse response: {}\", response, e);\n                            return false;\n                        }\n                    })\n            );\n\n        if (enabledServices.contains(CouchbaseService.QUERY)) {\n            waitStrategy =\n                waitStrategy.withStrategy(\n                    new HttpWaitStrategy()\n                        .forPath(\"/admin/ping\")\n                        .forPort(QUERY_PORT)\n                        .withBasicCredentials(username, password)\n                        .forStatusCode(200)\n                );\n        }\n\n        if (enabledServices.contains(CouchbaseService.ANALYTICS)) {\n            waitStrategy =\n                waitStrategy.withStrategy(\n                    new HttpWaitStrategy()\n                        .forPath(\"/admin/ping\")\n                        .forPort(ANALYTICS_PORT)\n                        .withBasicCredentials(username, password)\n                        .forStatusCode(200)\n                );\n        }\n\n        if (enabledServices.contains(CouchbaseService.EVENTING)) {\n            waitStrategy =\n                waitStrategy.withStrategy(\n                    new HttpWaitStrategy()\n                        .forPath(\"/api/v1/config\")\n                        .forPort(EVENTING_PORT)\n                        .withBasicCredentials(username, password)\n                        .forStatusCode(200)\n                );\n        }\n\n        waitingFor(waitStrategy);\n    }\n\n    /**\n     * Configures the exposed ports based on the enabled services.\n     * <p>\n     * Note that the MGMT_PORTs are always enabled since there must always be a cluster\n     * manager. Also, the View engine ports are implicitly available on the same nodes\n     * where the KV service is enabled - it is not possible to configure them individually.\n     */\n    private void exposePorts() {\n        addExposedPorts(MGMT_PORT, MGMT_SSL_PORT);\n\n        if (enabledServices.contains(CouchbaseService.KV)) {\n            addExposedPorts(KV_PORT, KV_SSL_PORT);\n            addExposedPorts(VIEW_PORT, VIEW_SSL_PORT);\n        }\n        if (enabledServices.contains(CouchbaseService.ANALYTICS)) {\n            addExposedPorts(ANALYTICS_PORT, ANALYTICS_SSL_PORT);\n        }\n        if (enabledServices.contains(CouchbaseService.QUERY)) {\n            addExposedPorts(QUERY_PORT, QUERY_SSL_PORT);\n        }\n        if (enabledServices.contains(CouchbaseService.SEARCH)) {\n            addExposedPorts(SEARCH_PORT, SEARCH_SSL_PORT);\n        }\n        if (enabledServices.contains(CouchbaseService.EVENTING)) {\n            addExposedPorts(EVENTING_PORT, EVENTING_SSL_PORT);\n        }\n    }\n\n    @Override\n    protected void containerIsStarting(InspectContainerResponse containerInfo, boolean reused) {\n        if (!reused) {\n            containerIsStarting(containerInfo);\n        }\n    }\n\n    @Override\n    protected void containerIsStarting(final InspectContainerResponse containerInfo) {\n        logger().debug(\"Couchbase container is starting, performing configuration.\");\n\n        timePhase(\"waitUntilNodeIsOnline\", this::waitUntilNodeIsOnline);\n        timePhase(\"initializeIsEnterprise\", this::initializeIsEnterprise);\n        timePhase(\"initializeHasTlsPorts\", this::initializeHasTlsPorts);\n        timePhase(\"renameNode\", this::renameNode);\n        timePhase(\"initializeServices\", this::initializeServices);\n        timePhase(\"setMemoryQuotas\", this::setMemoryQuotas);\n        timePhase(\"configureAdminUser\", this::configureAdminUser);\n        timePhase(\"configureExternalPorts\", this::configureExternalPorts);\n\n        if (enabledServices.contains(CouchbaseService.INDEX)) {\n            timePhase(\"configureIndexer\", this::configureIndexer);\n        }\n    }\n\n    @Override\n    protected void containerIsStarted(InspectContainerResponse containerInfo, boolean reused) {\n        if (!reused) {\n            this.containerIsStarted(containerInfo);\n        }\n    }\n\n    @Override\n    protected void containerIsStarted(InspectContainerResponse containerInfo) {\n        timePhase(\"createBuckets\", this::createBuckets);\n\n        logger()\n            .info(\"Couchbase container is ready! UI available at http://{}:{}\", getHost(), getMappedPort(MGMT_PORT));\n    }\n\n    /**\n     * Before we can start configuring the host, we need to wait until the cluster manager is listening.\n     */\n    private void waitUntilNodeIsOnline() {\n        new HttpWaitStrategy().forPort(MGMT_PORT).forPath(\"/pools\").forStatusCode(200).waitUntilReady(this);\n    }\n\n    /**\n     * Fetches edition (enterprise or community) of started container.\n     */\n    private void initializeIsEnterprise() {\n        @Cleanup\n        Response response = doHttpRequest(MGMT_PORT, \"/pools\", \"GET\", null, true);\n\n        try {\n            isEnterprise = MAPPER.readTree(response.body().string()).get(\"isEnterprise\").asBoolean();\n        } catch (IOException e) {\n            throw new IllegalStateException(\"Couchbase /pools did not return valid JSON\");\n        }\n\n        if (!isEnterprise) {\n            if (enabledServices.contains(CouchbaseService.ANALYTICS)) {\n                throw new IllegalStateException(\"The Analytics Service is only supported with the Enterprise version\");\n            }\n            if (enabledServices.contains(CouchbaseService.EVENTING)) {\n                throw new IllegalStateException(\"The Eventing Service is only supported with the Enterprise version\");\n            }\n        }\n    }\n\n    /**\n     * Initializes the {@link #hasTlsPorts} flag.\n     * <p>\n     * Community Edition might support TLS one happy day, so use a \"supports TLS\" flag separate from\n     * the \"enterprise edition\" flag.\n     */\n    private void initializeHasTlsPorts() {\n        @Cleanup\n        Response response = doHttpRequest(MGMT_PORT, \"/pools/default/nodeServices\", \"GET\", null, true);\n\n        try {\n            String clusterTopology = response.body().string();\n            hasTlsPorts =\n                !MAPPER\n                    .readTree(clusterTopology)\n                    .path(\"nodesExt\")\n                    .path(0)\n                    .path(\"services\")\n                    .path(\"mgmtSSL\")\n                    .isMissingNode();\n        } catch (IOException e) {\n            throw new IllegalStateException(\"Couchbase /pools/default/nodeServices did not return valid JSON\");\n        }\n    }\n\n    /**\n     * Rebinds/renames the internal hostname.\n     * <p>\n     * To make sure the internal hostname is different from the external (alternate) address and the SDK can pick it\n     * up automatically, we bind the internal hostname to the internal IP address.\n     */\n    private void renameNode() {\n        logger().debug(\"Renaming Couchbase Node from localhost to {}\", getHost());\n\n        @Cleanup\n        Response response = doHttpRequest(\n            MGMT_PORT,\n            \"/node/controller/rename\",\n            \"POST\",\n            new FormBody.Builder().add(\"hostname\", getInternalIpAddress()).build(),\n            false\n        );\n\n        checkSuccessfulResponse(response, \"Could not rename couchbase node\");\n    }\n\n    /**\n     * Initializes services based on the configured enabled services.\n     */\n    private void initializeServices() {\n        logger().debug(\"Initializing couchbase services on host: {}\", enabledServices);\n\n        final String services = enabledServices\n            .stream()\n            .map(CouchbaseService::getIdentifier)\n            .collect(Collectors.joining(\",\"));\n\n        @Cleanup\n        Response response = doHttpRequest(\n            MGMT_PORT,\n            \"/node/controller/setupServices\",\n            \"POST\",\n            new FormBody.Builder().add(\"services\", services).build(),\n            false\n        );\n\n        checkSuccessfulResponse(response, \"Could not enable couchbase services\");\n    }\n\n    /**\n     * Sets the memory quotas for each enabled service.\n     * <p>\n     * If there is no explicit custom quota defined, the default minimum quota will be used.\n     */\n    private void setMemoryQuotas() {\n        logger().debug(\"Custom service memory quotas: {}\", customServiceQuotas);\n\n        final FormBody.Builder quotaBuilder = new FormBody.Builder();\n        for (CouchbaseService service : enabledServices) {\n            if (!service.hasQuota()) {\n                continue;\n            }\n\n            int quota = customServiceQuotas.getOrDefault(service, service.getMinimumQuotaMb());\n            if (CouchbaseService.KV.equals(service)) {\n                quotaBuilder.add(\"memoryQuota\", Integer.toString(quota));\n            } else {\n                quotaBuilder.add(service.getIdentifier() + \"MemoryQuota\", Integer.toString(quota));\n            }\n        }\n\n        @Cleanup\n        Response response = doHttpRequest(MGMT_PORT, \"/pools/default\", \"POST\", quotaBuilder.build(), false);\n\n        checkSuccessfulResponse(response, \"Could not configure service memory quotas\");\n    }\n\n    /**\n     * Configures the admin user on the couchbase node.\n     * <p>\n     * After this stage, all subsequent API calls need to have the basic auth header set.\n     */\n    private void configureAdminUser() {\n        logger().debug(\"Configuring couchbase admin user with username: \\\"{}\\\"\", username);\n\n        @Cleanup\n        Response response = doHttpRequest(\n            MGMT_PORT,\n            \"/settings/web\",\n            \"POST\",\n            new FormBody.Builder()\n                .add(\"username\", username)\n                .add(\"password\", password)\n                .add(\"port\", Integer.toString(MGMT_PORT))\n                .build(),\n            false\n        );\n\n        checkSuccessfulResponse(response, \"Could not configure couchbase admin user\");\n    }\n\n    /**\n     * Configures the external ports for SDK access.\n     * <p>\n     * Since the internal ports are not accessible from outside the container, this code configures the \"external\"\n     * hostname and services to align with the mapped ports. The SDK will pick it up and then automatically connect\n     * to those ports. Note that for all services non-ssl and ssl ports are configured.\n     */\n    private void configureExternalPorts() {\n        logger().debug(\"Mapping external ports to the alternate address configuration\");\n\n        final FormBody.Builder builder = new FormBody.Builder();\n        builder.add(\"hostname\", getHost());\n        builder.add(\"mgmt\", Integer.toString(getMappedPort(MGMT_PORT)));\n        if (hasTlsPorts) {\n            builder.add(\"mgmtSSL\", Integer.toString(getMappedPort(MGMT_SSL_PORT)));\n        }\n\n        if (enabledServices.contains(CouchbaseService.KV)) {\n            builder.add(\"kv\", Integer.toString(getMappedPort(KV_PORT)));\n            builder.add(\"capi\", Integer.toString(getMappedPort(VIEW_PORT)));\n            if (hasTlsPorts) {\n                builder.add(\"kvSSL\", Integer.toString(getMappedPort(KV_SSL_PORT)));\n                builder.add(\"capiSSL\", Integer.toString(getMappedPort(VIEW_SSL_PORT)));\n            }\n        }\n\n        if (enabledServices.contains(CouchbaseService.QUERY)) {\n            builder.add(\"n1ql\", Integer.toString(getMappedPort(QUERY_PORT)));\n            if (hasTlsPorts) {\n                builder.add(\"n1qlSSL\", Integer.toString(getMappedPort(QUERY_SSL_PORT)));\n            }\n        }\n\n        if (enabledServices.contains(CouchbaseService.SEARCH)) {\n            builder.add(\"fts\", Integer.toString(getMappedPort(SEARCH_PORT)));\n            if (hasTlsPorts) {\n                builder.add(\"ftsSSL\", Integer.toString(getMappedPort(SEARCH_SSL_PORT)));\n            }\n        }\n\n        if (enabledServices.contains(CouchbaseService.ANALYTICS)) {\n            builder.add(\"cbas\", Integer.toString(getMappedPort(ANALYTICS_PORT)));\n            if (hasTlsPorts) {\n                builder.add(\"cbasSSL\", Integer.toString(getMappedPort(ANALYTICS_SSL_PORT)));\n            }\n        }\n\n        if (enabledServices.contains(CouchbaseService.EVENTING)) {\n            builder.add(\"eventingAdminPort\", Integer.toString(getMappedPort(EVENTING_PORT)));\n            if (hasTlsPorts) {\n                builder.add(\"eventingSSL\", Integer.toString(getMappedPort(EVENTING_SSL_PORT)));\n            }\n        }\n\n        @Cleanup\n        Response response = doHttpRequest(\n            MGMT_PORT,\n            \"/node/controller/setupAlternateAddresses/external\",\n            \"PUT\",\n            builder.build(),\n            true\n        );\n\n        checkSuccessfulResponse(response, \"Could not configure external ports\");\n    }\n\n    /**\n     * Configures the indexer service so that indexes can be created later on the bucket.\n     */\n    private void configureIndexer() {\n        logger().debug(\"Configuring the indexer service\");\n\n        @Cleanup\n        Response response = doHttpRequest(\n            MGMT_PORT,\n            \"/settings/indexes\",\n            \"POST\",\n            new FormBody.Builder().add(\"storageMode\", isEnterprise ? \"memory_optimized\" : \"forestdb\").build(),\n            true\n        );\n\n        checkSuccessfulResponse(response, \"Could not configure the indexing service\");\n    }\n\n    /**\n     * Based on the user-configured bucket definitions, creating buckets and corresponding indexes if needed.\n     */\n    private void createBuckets() {\n        logger().debug(\"Creating {} buckets (and corresponding indexes).\", buckets.size());\n\n        for (BucketDefinition bucket : buckets) {\n            logger().debug(\"Creating bucket \\\"{}\\\"\", bucket.getName());\n\n            @Cleanup\n            Response response = doHttpRequest(\n                MGMT_PORT,\n                \"/pools/default/buckets\",\n                \"POST\",\n                new FormBody.Builder()\n                    .add(\"name\", bucket.getName())\n                    .add(\"ramQuotaMB\", Integer.toString(bucket.getQuota()))\n                    .add(\"flushEnabled\", bucket.hasFlushEnabled() ? \"1\" : \"0\")\n                    .add(\"replicaNumber\", Integer.toString(bucket.getNumReplicas()))\n                    .build(),\n                true\n            );\n\n            checkSuccessfulResponse(response, \"Could not create bucket \" + bucket.getName());\n\n            timePhase(\n                \"createBucket:\" + bucket.getName() + \":waitForAllServicesEnabled\",\n                () -> {\n                    new HttpWaitStrategy()\n                        .forPath(\"/pools/default/b/\" + bucket.getName())\n                        .forPort(MGMT_PORT)\n                        .withBasicCredentials(username, password)\n                        .forStatusCode(200)\n                        .forResponsePredicate(new AllServicesEnabledPredicate())\n                        .waitUntilReady(this);\n                }\n            );\n\n            if (enabledServices.contains(CouchbaseService.QUERY)) {\n                // If the query service is enabled, make sure that we only proceed if the query engine also\n                // knows about the bucket in its metadata configuration.\n                timePhase(\n                    \"createBucket:\" + bucket.getName() + \":queryKeyspacePresent\",\n                    () -> {\n                        Unreliables.retryUntilTrue(\n                            1,\n                            TimeUnit.MINUTES,\n                            () -> {\n                                @Cleanup\n                                Response queryResponse = doHttpRequest(\n                                    QUERY_PORT,\n                                    \"/query/service\",\n                                    \"POST\",\n                                    new FormBody.Builder()\n                                        .add(\n                                            \"statement\",\n                                            \"SELECT COUNT(*) > 0 as present FROM system:keyspaces WHERE name = \\\"\" +\n                                            bucket.getName() +\n                                            \"\\\"\"\n                                        )\n                                        .build(),\n                                    true\n                                );\n\n                                String body = queryResponse.body() != null ? queryResponse.body().string() : null;\n                                checkSuccessfulResponse(\n                                    queryResponse,\n                                    \"Could not poll query service state for bucket: \" + bucket.getName()\n                                );\n\n                                return Optional\n                                    .of(MAPPER.readTree(body))\n                                    .map(n -> n.at(\"/results/0/present\"))\n                                    .map(JsonNode::asBoolean)\n                                    .orElse(false);\n                            }\n                        );\n                    }\n                );\n            }\n\n            if (bucket.hasPrimaryIndex()) {\n                if (enabledServices.contains(CouchbaseService.QUERY)) {\n                    @Cleanup\n                    Response queryResponse = doHttpRequest(\n                        QUERY_PORT,\n                        \"/query/service\",\n                        \"POST\",\n                        new FormBody.Builder()\n                            .add(\"statement\", \"CREATE PRIMARY INDEX on `\" + bucket.getName() + \"`\")\n                            .build(),\n                        true\n                    );\n\n                    try {\n                        checkSuccessfulResponse(\n                            queryResponse,\n                            \"Could not create primary index for bucket \" + bucket.getName()\n                        );\n                    } catch (IllegalStateException ex) {\n                        // potentially ignore the error, the index will be eventually built.\n                        if (!ex.getMessage().contains(\"Index creation will be retried in background\")) {\n                            throw ex;\n                        }\n                    }\n\n                    timePhase(\n                        \"createBucket:\" + bucket.getName() + \":primaryIndexOnline\",\n                        () -> {\n                            Unreliables.retryUntilTrue(\n                                1,\n                                TimeUnit.MINUTES,\n                                () -> {\n                                    @Cleanup\n                                    Response stateResponse = doHttpRequest(\n                                        QUERY_PORT,\n                                        \"/query/service\",\n                                        \"POST\",\n                                        new FormBody.Builder()\n                                            .add(\n                                                \"statement\",\n                                                \"SELECT count(*) > 0 AS online FROM system:indexes where keyspace_id = \\\"\" +\n                                                bucket.getName() +\n                                                \"\\\" and is_primary = true and state = \\\"online\\\"\"\n                                            )\n                                            .build(),\n                                        true\n                                    );\n\n                                    String body = stateResponse.body() != null ? stateResponse.body().string() : null;\n                                    checkSuccessfulResponse(\n                                        stateResponse,\n                                        \"Could not poll primary index state for bucket: \" + bucket.getName()\n                                    );\n\n                                    return Optional\n                                        .of(MAPPER.readTree(body))\n                                        .map(n -> n.at(\"/results/0/online\"))\n                                        .map(JsonNode::asBoolean)\n                                        .orElse(false);\n                                }\n                            );\n                        }\n                    );\n                } else {\n                    logger()\n                        .info(\n                            \"Primary index creation for bucket {} ignored, since QUERY service is not present.\",\n                            bucket.getName()\n                        );\n                }\n            }\n        }\n    }\n\n    /**\n     * Helper method to extract the internal IP address based on the network configuration.\n     */\n    private String getInternalIpAddress() {\n        return getContainerInfo()\n            .getNetworkSettings()\n            .getNetworks()\n            .values()\n            .stream()\n            .findFirst()\n            .map(ContainerNetwork::getIpAddress)\n            .orElseThrow(() -> new IllegalStateException(\"No network available to extract the internal IP from!\"));\n    }\n\n    /**\n     * Helper method to check if the response is successful and release the body if needed.\n     *\n     * @param response the response to check.\n     * @param message the message that should be part of the exception of not successful.\n     */\n    private void checkSuccessfulResponse(final Response response, final String message) {\n        if (!response.isSuccessful()) {\n            String body = null;\n            if (response.body() != null) {\n                try {\n                    body = response.body().string();\n                } catch (IOException e) {\n                    logger().debug(\"Unable to read body of response: {}\", response, e);\n                }\n            }\n\n            throw new IllegalStateException(message + \": \" + response + \", body=\" + (body == null ? \"<null>\" : body));\n        }\n    }\n\n    /**\n     * Checks if already running and if so raises an exception to prevent too-late setters.\n     */\n    private void checkNotRunning() {\n        if (isRunning()) {\n            throw new IllegalStateException(\"Setter can only be called before the container is running\");\n        }\n    }\n\n    /**\n     * Helper method to perform a request against a couchbase server HTTP endpoint.\n     *\n     * @param port the (unmapped) original port that should be used.\n     * @param path the relative http path.\n     * @param method the http method to use.\n     * @param body if present, will be part of the payload.\n     * @param auth if authentication with the admin user and password should be used.\n     * @return the response of the request.\n     */\n    private Response doHttpRequest(\n        final int port,\n        final String path,\n        final String method,\n        final RequestBody body,\n        final boolean auth\n    ) {\n        try {\n            Request.Builder requestBuilder = new Request.Builder()\n                .url(\"http://\" + getHost() + \":\" + getMappedPort(port) + path);\n\n            if (auth) {\n                requestBuilder = requestBuilder.header(\"Authorization\", Credentials.basic(username, password));\n            }\n\n            if (body == null) {\n                requestBuilder = requestBuilder.get();\n            } else {\n                requestBuilder = requestBuilder.method(method, body);\n            }\n\n            return HTTP_CLIENT.newCall(requestBuilder.build()).execute();\n        } catch (Exception ex) {\n            throw new RuntimeException(\"Could not perform request against couchbase HTTP endpoint \", ex);\n        }\n    }\n\n    /**\n     * Helper method which times an individual phase and logs it for debugging and optimization purposes.\n     *\n     * @param name the name of the phase.\n     * @param toTime the runnable that should be timed.\n     */\n    private void timePhase(final String name, final Runnable toTime) {\n        long start = System.nanoTime();\n        toTime.run();\n        long end = System.nanoTime();\n\n        logger().debug(\"Phase {} took {}ms\", name, TimeUnit.NANOSECONDS.toMillis(end - start));\n    }\n\n    /**\n     * In addition to getting a 200, we need to make sure that all services we need are enabled and available on\n     * the bucket.\n     * <p>\n     *  Fixes the issue observed in https://github.com/testcontainers/testcontainers-java/issues/2993\n     */\n    private class AllServicesEnabledPredicate implements Predicate<String> {\n\n        @Override\n        public boolean test(final String rawConfig) {\n            try {\n                for (JsonNode node : MAPPER.readTree(rawConfig).at(\"/nodesExt\")) {\n                    for (CouchbaseService enabledService : enabledServices) {\n                        boolean found = false;\n                        Iterator<String> fieldNames = node.get(\"services\").fieldNames();\n                        while (fieldNames.hasNext()) {\n                            if (fieldNames.next().startsWith(enabledService.getIdentifier())) {\n                                found = true;\n                            }\n                        }\n                        if (!found) {\n                            logger().trace(\"Service {} not yet part of config, retrying.\", enabledService);\n                            return false;\n                        }\n                    }\n                }\n                return true;\n            } catch (IOException ex) {\n                logger().error(\"Unable to parse response: {}\", rawConfig, ex);\n                return false;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "modules/couchbase/src/main/java/org/testcontainers/couchbase/CouchbaseService.java",
    "content": "/*\n * Copyright (c) 2020 Couchbase, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.testcontainers.couchbase;\n\n/**\n * Describes the different services that should be exposed on the container.\n */\npublic enum CouchbaseService {\n    /**\n     * Key-Value service.\n     */\n    KV(\"kv\", 256),\n\n    /**\n     * Query (N1QL) service.\n     * <p>\n     * Note that the query service has no memory quota, so it is set to 0.\n     */\n    QUERY(\"n1ql\", 0),\n\n    /**\n     * Search (FTS) service.\n     */\n    SEARCH(\"fts\", 256),\n\n    /**\n     * Indexing service (needed if QUERY is also used!).\n     */\n    INDEX(\"index\", 256),\n\n    /**\n     * Analytics service.\n     */\n    ANALYTICS(\"cbas\", 1024),\n\n    /**\n     * Eventing service.\n     */\n    EVENTING(\"eventing\", 256);\n\n    private final String identifier;\n\n    private final int minimumQuotaMb;\n\n    CouchbaseService(final String identifier, final int minimumQuotaMb) {\n        this.identifier = identifier;\n        this.minimumQuotaMb = minimumQuotaMb;\n    }\n\n    /**\n     * Returns the internal service identifier.\n     *\n     * @return the internal service identifier.\n     */\n    String getIdentifier() {\n        return identifier;\n    }\n\n    /**\n     * Returns the minimum quota for the service in MB.\n     *\n     * @return the minimum quota in MB.\n     */\n    int getMinimumQuotaMb() {\n        return minimumQuotaMb;\n    }\n\n    /**\n     * Returns true if the service has a quota that needs to be applied.\n     *\n     * @return true if its quota needs to be applied.\n     */\n    boolean hasQuota() {\n        return minimumQuotaMb > 0;\n    }\n}\n"
  },
  {
    "path": "modules/couchbase/src/test/java/org/testcontainers/couchbase/CouchbaseContainerTest.java",
    "content": "/*\n * Copyright (c) 2020 Couchbase, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *    http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.testcontainers.couchbase;\n\nimport com.couchbase.client.java.Bucket;\nimport com.couchbase.client.java.Cluster;\nimport com.couchbase.client.java.Collection;\nimport com.couchbase.client.java.json.JsonObject;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.ContainerLaunchException;\n\nimport java.time.Duration;\nimport java.util.function.Consumer;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\nimport static org.awaitility.Awaitility.await;\n\nclass CouchbaseContainerTest {\n\n    private static final String COUCHBASE_IMAGE_ENTERPRISE = \"couchbase/server:enterprise-7.0.3\";\n\n    private static final String COUCHBASE_IMAGE_ENTERPRISE_RECENT = \"couchbase/server:enterprise-7.6.2\";\n\n    private static final String COUCHBASE_IMAGE_COMMUNITY = \"couchbase/server:community-7.0.2\";\n\n    private static final String COUCHBASE_IMAGE_COMMUNITY_RECENT = \"couchbase/server:community-7.6.2\";\n\n    @Test\n    void testBasicContainerUsageForEnterpriseContainer() {\n        testBasicContainerUsage(COUCHBASE_IMAGE_ENTERPRISE);\n    }\n\n    @Test\n    void testBasicContainerUsageForEnterpriseContainerRecent() {\n        testBasicContainerUsage(COUCHBASE_IMAGE_ENTERPRISE_RECENT);\n    }\n\n    @Test\n    void testBasicContainerUsageForCommunityContainer() {\n        testBasicContainerUsage(COUCHBASE_IMAGE_COMMUNITY);\n    }\n\n    @Test\n    void testBasicContainerUsageForCommunityContainerRecent() {\n        testBasicContainerUsage(COUCHBASE_IMAGE_COMMUNITY_RECENT);\n    }\n\n    private void testBasicContainerUsage(String couchbaseImage) {\n        // bucket_definition {\n        BucketDefinition bucketDefinition = new BucketDefinition(\"mybucket\");\n        // }\n\n        try (\n            // container_definition {\n            CouchbaseContainer container = new CouchbaseContainer(couchbaseImage).withBucket(bucketDefinition)\n            // }\n        ) {\n            setUpClient(\n                container,\n                cluster -> {\n                    Bucket bucket = cluster.bucket(bucketDefinition.getName());\n                    bucket.waitUntilReady(Duration.ofSeconds(10L));\n\n                    Collection collection = bucket.defaultCollection();\n\n                    collection.upsert(\"foo\", JsonObject.create().put(\"key\", \"value\"));\n\n                    JsonObject fooObject = collection.get(\"foo\").contentAsObject();\n\n                    assertThat(fooObject.getString(\"key\")).isEqualTo(\"value\");\n                }\n            );\n        }\n    }\n\n    @Test\n    void testBucketIsFlushableIfEnabled() {\n        BucketDefinition bucketDefinition = new BucketDefinition(\"mybucket\").withFlushEnabled(true);\n\n        try (\n            CouchbaseContainer container = new CouchbaseContainer(COUCHBASE_IMAGE_ENTERPRISE)\n                .withBucket(bucketDefinition)\n        ) {\n            setUpClient(\n                container,\n                cluster -> {\n                    Bucket bucket = cluster.bucket(bucketDefinition.getName());\n                    bucket.waitUntilReady(Duration.ofSeconds(10L));\n\n                    Collection collection = bucket.defaultCollection();\n\n                    collection.upsert(\"foo\", JsonObject.create().put(\"key\", \"value\"));\n\n                    cluster.buckets().flushBucket(bucketDefinition.getName());\n\n                    await().untilAsserted(() -> assertThat(collection.exists(\"foo\").exists()).isFalse());\n                }\n            );\n        }\n    }\n\n    /**\n     * Make sure that the code fails fast if the Analytics service is enabled on the community\n     * edition which is not supported.\n     */\n    @Test\n    void testFailureIfCommunityUsedWithAnalytics() {\n        try (\n            CouchbaseContainer container = new CouchbaseContainer(COUCHBASE_IMAGE_COMMUNITY)\n                .withEnabledServices(CouchbaseService.KV, CouchbaseService.ANALYTICS)\n        ) {\n            assertThatThrownBy(() -> {\n                    setUpClient(container, cluster -> {});\n                })\n                .isInstanceOf(ContainerLaunchException.class);\n        }\n    }\n\n    /**\n     * Make sure that the code fails fast if the Eventing service is enabled on the community\n     * edition which is not supported.\n     */\n    @Test\n    void testFailureIfCommunityUsedWithEventing() {\n        try (\n            CouchbaseContainer container = new CouchbaseContainer(COUCHBASE_IMAGE_COMMUNITY)\n                .withEnabledServices(CouchbaseService.KV, CouchbaseService.EVENTING)\n        ) {\n            assertThatThrownBy(() -> {\n                    setUpClient(container, cluster -> {});\n                })\n                .isInstanceOf(ContainerLaunchException.class);\n        }\n    }\n\n    private void setUpClient(CouchbaseContainer container, Consumer<Cluster> consumer) {\n        container.start();\n\n        // cluster_creation {\n        Cluster cluster = Cluster.connect(\n            container.getConnectionString(),\n            container.getUsername(),\n            container.getPassword()\n        );\n        // }\n\n        try {\n            consumer.accept(cluster);\n        } finally {\n            cluster.disconnect();\n        }\n    }\n}\n"
  },
  {
    "path": "modules/couchbase/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/cratedb/build.gradle",
    "content": "description = \"Testcontainers :: JDBC :: CrateDB\"\n\ndependencies {\n    api project(':testcontainers-jdbc')\n\n    testRuntimeOnly 'org.postgresql:postgresql:42.7.8'\n\n    testImplementation project(':testcontainers-jdbc-test')\n\n    compileOnly 'org.jetbrains:annotations:26.0.2-1'\n}\n"
  },
  {
    "path": "modules/cratedb/src/main/java/org/testcontainers/cratedb/CrateDBContainer.java",
    "content": "package org.testcontainers.cratedb;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.testcontainers.containers.JdbcDatabaseContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.util.Set;\n\n/**\n * Testcontainers implementation for CrateDB.\n * <p>\n * Supported image: {@code crate}\n * <p>\n * Exposed ports:\n * <ul>\n *     <li>Database: 5432</li>\n *     <li>Console: 4200</li>\n * </ul>\n */\npublic class CrateDBContainer extends JdbcDatabaseContainer<CrateDBContainer> {\n\n    static final String NAME = \"cratedb\";\n\n    static final String IMAGE = \"crate\";\n\n    static final String DEFAULT_TAG = \"5.3.1\";\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"crate\");\n\n    static final Integer CRATEDB_PG_PORT = 5432;\n\n    static final Integer CRATEDB_HTTP_PORT = 4200;\n\n    private String databaseName = \"crate\";\n\n    private String username = \"crate\";\n\n    private String password = \"crate\";\n\n    public CrateDBContainer(final String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    public CrateDBContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n        withCommand(\"crate -C discovery.type=single-node\");\n\n        waitingFor(Wait.forHttp(\"/\").forPort(CRATEDB_HTTP_PORT).forStatusCode(200));\n\n        addExposedPort(CRATEDB_PG_PORT);\n        addExposedPort(CRATEDB_HTTP_PORT);\n    }\n\n    /**\n     * @return the ports on which to check if the container is ready\n     * @deprecated use {@link #getLivenessCheckPortNumbers()} instead\n     */\n    @NotNull\n    @Override\n    @Deprecated\n    protected Set<Integer> getLivenessCheckPorts() {\n        return super.getLivenessCheckPorts();\n    }\n\n    @Override\n    public String getDriverClassName() {\n        return \"org.postgresql.Driver\";\n    }\n\n    @Override\n    public String getJdbcUrl() {\n        String additionalUrlParams = constructUrlParameters(\"?\", \"&\");\n        return (\n            \"jdbc:postgresql://\" +\n            getHost() +\n            \":\" +\n            getMappedPort(CRATEDB_PG_PORT) +\n            \"/\" +\n            databaseName +\n            additionalUrlParams\n        );\n    }\n\n    @Override\n    public String getDatabaseName() {\n        return databaseName;\n    }\n\n    @Override\n    public String getUsername() {\n        return username;\n    }\n\n    @Override\n    public String getPassword() {\n        return password;\n    }\n\n    @Override\n    public String getTestQueryString() {\n        return \"SELECT 1\";\n    }\n\n    @Override\n    public CrateDBContainer withDatabaseName(final String databaseName) {\n        this.databaseName = databaseName;\n        return self();\n    }\n\n    @Override\n    public CrateDBContainer withUsername(final String username) {\n        this.username = username;\n        return self();\n    }\n\n    @Override\n    public CrateDBContainer withPassword(final String password) {\n        this.password = password;\n        return self();\n    }\n\n    @Override\n    protected void waitUntilContainerStarted() {\n        getWaitStrategy().waitUntilReady(this);\n    }\n}\n"
  },
  {
    "path": "modules/cratedb/src/main/java/org/testcontainers/cratedb/CrateDBContainerProvider.java",
    "content": "package org.testcontainers.cratedb;\n\nimport org.testcontainers.containers.JdbcDatabaseContainer;\nimport org.testcontainers.containers.JdbcDatabaseContainerProvider;\nimport org.testcontainers.jdbc.ConnectionUrl;\nimport org.testcontainers.utility.DockerImageName;\n\n/**\n * Factory for CrateDB containers using PostgreSQL JDBC driver.\n */\npublic class CrateDBContainerProvider extends JdbcDatabaseContainerProvider {\n\n    public static final String USER_PARAM = \"user\";\n\n    public static final String PASSWORD_PARAM = \"password\";\n\n    @Override\n    public boolean supports(String databaseType) {\n        return databaseType.equals(CrateDBContainer.NAME);\n    }\n\n    @Override\n    public JdbcDatabaseContainer newInstance() {\n        return newInstance(CrateDBContainer.DEFAULT_TAG);\n    }\n\n    @Override\n    public JdbcDatabaseContainer newInstance(String tag) {\n        return new CrateDBContainer(DockerImageName.parse(CrateDBContainer.IMAGE).withTag(tag));\n    }\n\n    @Override\n    public JdbcDatabaseContainer newInstance(ConnectionUrl connectionUrl) {\n        return newInstanceFromConnectionUrl(connectionUrl, USER_PARAM, PASSWORD_PARAM);\n    }\n}\n"
  },
  {
    "path": "modules/cratedb/src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider",
    "content": "org.testcontainers.cratedb.CrateDBContainerProvider\n"
  },
  {
    "path": "modules/cratedb/src/test/java/org/testcontainers/CrateDBTestImages.java",
    "content": "package org.testcontainers;\n\nimport org.testcontainers.utility.DockerImageName;\n\npublic interface CrateDBTestImages {\n    DockerImageName CRATEDB_TEST_IMAGE = DockerImageName.parse(\"crate:5.2.5\");\n}\n"
  },
  {
    "path": "modules/cratedb/src/test/java/org/testcontainers/jdbc/cratedb/CrateDBJDBCDriverTest.java",
    "content": "package org.testcontainers.jdbc.cratedb;\n\nimport org.testcontainers.jdbc.AbstractJDBCDriverTest;\n\nimport java.util.Arrays;\nimport java.util.EnumSet;\n\nclass CrateDBJDBCDriverTest extends AbstractJDBCDriverTest {\n\n    public static Iterable<Object[]> data() {\n        return Arrays.asList(\n            new Object[][] {\n                { \"jdbc:tc:cratedb:5.2.3://hostname/crate?user=crate&password=somepwd\", EnumSet.noneOf(Options.class) },\n            }\n        );\n    }\n}\n"
  },
  {
    "path": "modules/cratedb/src/test/java/org/testcontainers/junit/cratedb/SimpleCrateDBTest.java",
    "content": "package org.testcontainers.junit.cratedb;\n\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.CrateDBTestImages;\nimport org.testcontainers.cratedb.CrateDBContainer;\nimport org.testcontainers.db.AbstractContainerDatabaseTest;\n\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.util.logging.Level;\nimport java.util.logging.LogManager;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass SimpleCrateDBTest extends AbstractContainerDatabaseTest {\n    static {\n        // Postgres JDBC driver uses JUL; disable it to avoid annoying, irrelevant, stderr logs during connection testing\n        LogManager.getLogManager().getLogger(\"\").setLevel(Level.OFF);\n    }\n\n    @Test\n    void testSimple() throws SQLException {\n        try ( // container {\n            CrateDBContainer cratedb = new CrateDBContainer(\"crate:5.2.5\")\n            // }\n        ) {\n            cratedb.start();\n\n            ResultSet resultSet = performQuery(cratedb, \"SELECT 1\");\n            int resultSetInt = resultSet.getInt(1);\n            assertThat(resultSetInt).as(\"A basic SELECT query succeeds\").isEqualTo(1);\n            assertHasCorrectExposedAndLivenessCheckPorts(cratedb);\n        }\n    }\n\n    @Test\n    void testCommandOverride() throws SQLException {\n        try (\n            CrateDBContainer cratedb = new CrateDBContainer(CrateDBTestImages.CRATEDB_TEST_IMAGE)\n                .withCommand(\"crate -C discovery.type=single-node -C cluster.name=testcontainers\")\n        ) {\n            cratedb.start();\n\n            ResultSet resultSet = performQuery(cratedb, \"select name from sys.cluster\");\n            String result = resultSet.getString(1);\n            assertThat(result).as(\"cluster name should be overridden\").isEqualTo(\"testcontainers\");\n        }\n    }\n\n    @Test\n    void testExplicitInitScript() throws SQLException {\n        try (\n            CrateDBContainer cratedb = new CrateDBContainer(CrateDBTestImages.CRATEDB_TEST_IMAGE)\n                .withInitScript(\"somepath/init_cratedb.sql\")\n        ) {\n            cratedb.start();\n\n            ResultSet resultSet = performQuery(cratedb, \"SELECT foo FROM bar\");\n\n            String firstColumnValue = resultSet.getString(1);\n            assertThat(firstColumnValue).as(\"Value from init script should equal real value\").isEqualTo(\"hello world\");\n        }\n    }\n\n    private void assertHasCorrectExposedAndLivenessCheckPorts(CrateDBContainer cratedb) {\n        assertThat(cratedb.getExposedPorts()).containsExactlyInAnyOrder(5432, 4200);\n        assertThat(cratedb.getLivenessCheckPortNumbers())\n            .containsExactlyInAnyOrder(cratedb.getMappedPort(5432), cratedb.getMappedPort(4200));\n    }\n}\n"
  },
  {
    "path": "modules/cratedb/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/cratedb/src/test/resources/somepath/init_cratedb.sql",
    "content": "CREATE TABLE bar (\n  foo STRING\n);\n\nINSERT INTO bar (foo) VALUES ('hello world');\nREFRESH TABLE bar;\n"
  },
  {
    "path": "modules/database-commons/build.gradle",
    "content": "description = \"Testcontainers :: Database-Commons\"\n\ndependencies {\n    api project(':testcontainers')\n}\n"
  },
  {
    "path": "modules/database-commons/src/main/java/org/testcontainers/delegate/AbstractDatabaseDelegate.java",
    "content": "package org.testcontainers.delegate;\n\nimport java.util.Collection;\n\n/**\n * @param <CONNECTION> connection to the database\n */\npublic abstract class AbstractDatabaseDelegate<CONNECTION> implements DatabaseDelegate {\n\n    /**\n     * Database connection\n     */\n    private CONNECTION connection;\n\n    private boolean isConnectionStarted = false;\n\n    /**\n     * Get or create new connection to the database\n     */\n    protected CONNECTION getConnection() {\n        if (!isConnectionStarted) {\n            connection = createNewConnection();\n            isConnectionStarted = true;\n        }\n        return connection;\n    }\n\n    @Override\n    public void execute(\n        Collection<String> statements,\n        String scriptPath,\n        boolean continueOnError,\n        boolean ignoreFailedDrops\n    ) {\n        int lineNumber = 0;\n        for (String statement : statements) {\n            lineNumber++;\n            execute(statement, scriptPath, lineNumber, continueOnError, ignoreFailedDrops);\n        }\n    }\n\n    @Override\n    public void close() {\n        if (isConnectionStarted) {\n            closeConnectionQuietly(connection);\n            isConnectionStarted = false;\n        }\n    }\n\n    /**\n     * Quietly close the connection\n     */\n    protected abstract void closeConnectionQuietly(CONNECTION connection);\n\n    /**\n     * Template method for creating new connections to the database\n     */\n    protected abstract CONNECTION createNewConnection();\n}\n"
  },
  {
    "path": "modules/database-commons/src/main/java/org/testcontainers/delegate/DatabaseDelegate.java",
    "content": "package org.testcontainers.delegate;\n\nimport java.util.Collection;\n\n/**\n * Database delegate\n *\n * Gives an abstraction from concrete database\n */\npublic interface DatabaseDelegate extends AutoCloseable {\n    /**\n     * Execute statement by the implementation of the delegate\n     */\n    void execute(\n        String statement,\n        String scriptPath,\n        int lineNumber,\n        boolean continueOnError,\n        boolean ignoreFailedDrops\n    );\n\n    /**\n     * Execute collection of statements\n     */\n    void execute(Collection<String> statements, String scriptPath, boolean continueOnError, boolean ignoreFailedDrops);\n\n    /**\n     * Close connection to the database\n     *\n     * Overridden to suppress throwing Exception\n     */\n    @Override\n    void close();\n}\n"
  },
  {
    "path": "modules/database-commons/src/main/java/org/testcontainers/exception/ConnectionCreationException.java",
    "content": "package org.testcontainers.exception;\n\n/**\n * Inability to create connection to the database\n */\npublic class ConnectionCreationException extends RuntimeException {\n\n    public ConnectionCreationException(String message, Throwable cause) {\n        super(message, cause);\n    }\n}\n"
  },
  {
    "path": "modules/database-commons/src/main/java/org/testcontainers/ext/ScriptScanner.java",
    "content": "package org.testcontainers.ext;\n\nimport lombok.Getter;\nimport lombok.RequiredArgsConstructor;\n\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/**\n * Rough lexical parser for SQL scripts.\n */\n@RequiredArgsConstructor\nclass ScriptScanner {\n\n    private final String resource;\n\n    private final String script;\n\n    private final String separator;\n\n    private final String commentPrefix;\n\n    private final String blockCommentStartDelimiter;\n\n    private final String blockCommentEndDelimiter;\n\n    private final Pattern eol = Pattern.compile(\"[\\n\\r]+\");\n\n    private final Pattern whitespace = Pattern.compile(\"\\\\s+\");\n\n    private final Pattern identifier = Pattern.compile(\"[a-z][a-z0-9_$]*\", Pattern.CASE_INSENSITIVE);\n\n    private final Pattern dollarQuotedStringDelimiter = Pattern.compile(\"\\\\$\\\\w*\\\\$\");\n\n    private int offset;\n\n    @Getter\n    private String currentMatch;\n\n    private boolean matches(String substring) {\n        if (script.startsWith(substring, offset)) {\n            currentMatch = substring;\n            offset += currentMatch.length();\n            return true;\n        } else {\n            currentMatch = \"\";\n            return false;\n        }\n    }\n\n    private boolean matches(Pattern regexp) {\n        Matcher m = regexp.matcher(script);\n        m.region(offset, script.length());\n        if (m.lookingAt()) {\n            currentMatch = m.group();\n            offset = m.end();\n            return true;\n        } else {\n            currentMatch = \"\";\n            return false;\n        }\n    }\n\n    private boolean matchesSingleLineComment() {\n        /* Matches from commentPrefix to the EOL or end of script */\n        if (matches(commentPrefix)) {\n            Matcher m = eol.matcher(script);\n            if (m.find(offset)) {\n                currentMatch = commentPrefix + script.substring(offset, m.end());\n                offset = m.end();\n            } else {\n                currentMatch = commentPrefix + script.substring(offset);\n                offset = script.length();\n            }\n            return true;\n        }\n        return false;\n    }\n\n    private boolean matchesMultilineComment() {\n        /* Matches from blockCommentStartDelimiter to the next blockCommentEndDelimiter.\n         * Error, if blockCommentEndDelimiter is not found. */\n        if (matches(blockCommentStartDelimiter)) {\n            int end = script.indexOf(blockCommentEndDelimiter, offset);\n            if (end < 0) {\n                throw new ScriptUtils.ScriptParseException(\n                    String.format(\"Missing block comment end delimiter [%s].\", blockCommentEndDelimiter),\n                    resource\n                );\n            }\n            end += blockCommentEndDelimiter.length();\n            currentMatch = blockCommentStartDelimiter + script.substring(offset, end);\n            offset = end;\n            return true;\n        }\n        return false;\n    }\n\n    private boolean matchesQuotedString(final char quote) {\n        if (script.charAt(offset) == quote) {\n            boolean escaped = false;\n            for (int i = offset + 1; i < script.length(); i++) {\n                char c = script.charAt(i);\n                if (escaped) {\n                    //just skip the escaped character and drop the flag\n                    escaped = false;\n                } else if (c == '\\\\') {\n                    escaped = true;\n                } else if (c == quote) {\n                    currentMatch = script.substring(offset, i + 1);\n                    offset = i + 1;\n                    return true;\n                }\n            }\n        }\n        return false;\n    }\n\n    private boolean matchesDollarQuotedString() {\n        //Matches $<tag>$ .... $<tag>$\n        if (matches(dollarQuotedStringDelimiter)) {\n            String delimiter = currentMatch;\n            int end = script.indexOf(delimiter, offset);\n            if (end < 0) {\n                throw new ScriptUtils.ScriptParseException(\n                    String.format(\"Unclosed dollar quoted string [%s].\", delimiter),\n                    resource\n                );\n            }\n            end += delimiter.length();\n            currentMatch = delimiter + script.substring(offset, end);\n            offset = end;\n            return true;\n        }\n        return false;\n    }\n\n    Lexem next() {\n        if (offset < script.length()) {\n            if (matches(separator)) {\n                return Lexem.SEPARATOR;\n            } else if (matchesSingleLineComment() || matchesMultilineComment()) {\n                return Lexem.COMMENT;\n            } else if (\n                matchesQuotedString('\\'') ||\n                matchesQuotedString('\"') ||\n                matchesQuotedString('`') ||\n                matchesDollarQuotedString()\n            ) {\n                return Lexem.QUOTED_STRING;\n            } else if (matches(identifier)) {\n                return Lexem.IDENTIFIER;\n            } else if (matches(whitespace)) {\n                return Lexem.WHITESPACE;\n            } else {\n                currentMatch = String.valueOf(script.charAt(offset++));\n                return Lexem.OTHER;\n            }\n        } else {\n            return Lexem.EOF;\n        }\n    }\n\n    enum Lexem {\n        SEPARATOR,\n        COMMENT,\n        QUOTED_STRING,\n        WHITESPACE,\n        IDENTIFIER,\n        OTHER,\n        EOF,\n    }\n}\n"
  },
  {
    "path": "modules/database-commons/src/main/java/org/testcontainers/ext/ScriptSplitter.java",
    "content": "package org.testcontainers.ext;\n\nimport lombok.RequiredArgsConstructor;\nimport org.apache.commons.lang3.StringUtils;\nimport org.testcontainers.ext.ScriptScanner.Lexem;\n\nimport java.util.List;\n\n/**\n * Performs splitting of an SQL script into statements including\n * basic clean-up.\n */\n@RequiredArgsConstructor\nclass ScriptSplitter {\n\n    private final ScriptScanner scanner;\n\n    private final List<String> statements;\n\n    private final StringBuilder sb = new StringBuilder();\n\n    /**\n     * Standard parsing:\n     * 1. Remove comments\n     * 2. Shrink whitespace and eols\n     * 3. Split on separator\n     */\n    void split() {\n        Lexem l;\n        while ((l = scanner.next()) != Lexem.EOF) {\n            switch (l) {\n                case SEPARATOR:\n                    flushStringBuilder();\n                    break;\n                case COMMENT:\n                    //skip\n                    break;\n                case WHITESPACE:\n                    if (sb.length() == 0 || sb.charAt(sb.length() - 1) != ' ') {\n                        sb.append(' ');\n                    }\n                    break;\n                case IDENTIFIER:\n                    appendMatch();\n                    if (\"begin\".equalsIgnoreCase(scanner.getCurrentMatch())) {\n                        compoundStatement(false);\n                        flushStringBuilder();\n                    }\n                    break;\n                default:\n                    appendMatch();\n            }\n        }\n        flushStringBuilder();\n    }\n\n    /**\n     * Compound statement ('create procedure') mode:\n     * 1. Do not remove comments\n     * 2. Do not shrink whitespace\n     * 3. Do not split on separators\n     * 3. This mode can be recursive\n     */\n    private void compoundStatement(boolean recursive) {\n        Lexem l;\n        while ((l = scanner.next()) != Lexem.EOF) {\n            appendMatch();\n            if (Lexem.IDENTIFIER.equals(l)) {\n                if (\"begin\".equalsIgnoreCase(scanner.getCurrentMatch())) {\n                    compoundStatement(true);\n                } else if (\"end\".equalsIgnoreCase(scanner.getCurrentMatch())) {\n                    if (endOfBlock(recursive)) {\n                        return;\n                    }\n                }\n            }\n        }\n        flushStringBuilder();\n    }\n\n    private boolean endOfBlock(boolean recursive) {\n        Lexem l;\n        StringBuilder temporary = new StringBuilder();\n        while ((l = scanner.next()) != Lexem.EOF) {\n            switch (l) {\n                case COMMENT:\n                case WHITESPACE:\n                    temporary.append(scanner.getCurrentMatch());\n                    break;\n                case SEPARATOR:\n                    //Only whitespace and comments preceded the separator: true end of block\n                    //If it's an internal block, append everything\n                    if (recursive) {\n                        sb.append(temporary);\n                        appendMatch();\n                    }\n                    return true;\n                default:\n                    // Semicolon is not recognized as separator: this means that a custom\n                    // separator is used. Still, 'END;' should be a valid end of block\n                    if (\";\".equals(scanner.getCurrentMatch())) {\n                        if (recursive) {\n                            sb.append(temporary);\n                        }\n                        appendMatch();\n                        return true;\n                    }\n                    sb.append(temporary);\n                    appendMatch();\n                    return false;\n            }\n        }\n        return true;\n    }\n\n    private void appendMatch() {\n        sb.append(scanner.getCurrentMatch());\n    }\n\n    private void flushStringBuilder() {\n        final String s = sb.toString().trim();\n        if (StringUtils.isNotEmpty(s)) {\n            statements.add(s);\n        }\n        sb.setLength(0);\n    }\n}\n"
  },
  {
    "path": "modules/database-commons/src/main/java/org/testcontainers/ext/ScriptUtils.java",
    "content": "/*\n * Copyright 2002-2014 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.testcontainers.ext;\n\nimport org.apache.commons.io.IOUtils;\nimport org.apache.commons.lang3.StringUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.testcontainers.delegate.DatabaseDelegate;\n\nimport java.io.IOException;\nimport java.net.URL;\nimport java.nio.charset.StandardCharsets;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.concurrent.TimeUnit;\n\nimport javax.script.ScriptException;\n\n/**\n * This is a modified version of the Spring-JDBC ScriptUtils class, adapted to reduce\n * dependencies and slightly alter the API.\n *\n * Generic utility methods for working with SQL scripts. Mainly for internal use\n * within the framework.\n */\npublic abstract class ScriptUtils {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(ScriptUtils.class);\n\n    /**\n     * Default statement separator within SQL scripts.\n     */\n    public static final String DEFAULT_STATEMENT_SEPARATOR = \";\";\n\n    /**\n     * Fallback statement separator within SQL scripts.\n     * <p>Used if neither a custom defined separator nor the\n     * {@link #DEFAULT_STATEMENT_SEPARATOR} is present in a given script.\n     */\n    public static final String FALLBACK_STATEMENT_SEPARATOR = \"\\n\";\n\n    /**\n     * Default prefix for line comments within SQL scripts.\n     */\n    public static final String DEFAULT_COMMENT_PREFIX = \"--\";\n\n    /**\n     * Default start delimiter for block comments within SQL scripts.\n     */\n    public static final String DEFAULT_BLOCK_COMMENT_START_DELIMITER = \"/*\";\n\n    /**\n     * Default end delimiter for block comments within SQL scripts.\n     */\n    public static final String DEFAULT_BLOCK_COMMENT_END_DELIMITER = \"*/\";\n\n    /**\n     * Prevent instantiation of this utility class.\n     */\n    private ScriptUtils() {\n        /* no-op */\n    }\n\n    /**\n     * Split an SQL script into separate statements delimited by the provided\n     * separator string. Each individual statement will be added to the provided\n     * {@code List}.\n     * <p>Within the script, the provided {@code commentPrefix} will be honored:\n     * any text beginning with the comment prefix and extending to the end of the\n     * line will be omitted from the output. Similarly, the provided\n     * {@code blockCommentStartDelimiter} and {@code blockCommentEndDelimiter}\n     * delimiters will be honored: any text enclosed in a block comment will be\n     * omitted from the output. In addition, multiple adjacent whitespace characters\n     * will be collapsed into a single space.\n     * @param resource the resource from which the script was read\n     * @param script the SQL script; never {@code null} or empty\n     * @param separator text separating each statement &mdash; typically a ';' or\n     * newline character; never {@code null}\n     * @param commentPrefix the prefix that identifies SQL line comments &mdash;\n     * typically \"--\"; never {@code null} or empty\n     * @param blockCommentStartDelimiter the <em>start</em> block comment delimiter;\n     * never {@code null} or empty\n     * @param blockCommentEndDelimiter the <em>end</em> block comment delimiter;\n     * never {@code null} or empty\n     * @param statements the list that will contain the individual statements\n     */\n    public static void splitSqlScript(\n        String resource,\n        String script,\n        String separator,\n        String commentPrefix,\n        String blockCommentStartDelimiter,\n        String blockCommentEndDelimiter,\n        List<String> statements\n    ) {\n        checkArgument(StringUtils.isNotEmpty(script), \"script must not be null or empty\");\n        checkArgument(separator != null, \"separator must not be null\");\n        checkArgument(StringUtils.isNotEmpty(commentPrefix), \"commentPrefix must not be null or empty\");\n        checkArgument(\n            StringUtils.isNotEmpty(blockCommentStartDelimiter),\n            \"blockCommentStartDelimiter must not be null or empty\"\n        );\n        checkArgument(\n            StringUtils.isNotEmpty(blockCommentEndDelimiter),\n            \"blockCommentEndDelimiter must not be null or empty\"\n        );\n\n        new ScriptSplitter(\n            new ScriptScanner(\n                resource,\n                script,\n                separator,\n                commentPrefix,\n                blockCommentStartDelimiter,\n                blockCommentEndDelimiter\n            ),\n            statements\n        )\n            .split();\n    }\n\n    private static void checkArgument(boolean expression, String errorMessage) {\n        if (!expression) {\n            throw new IllegalArgumentException(errorMessage);\n        }\n    }\n\n    /**\n     * Does the provided SQL script contain the specified delimiter?\n     * @param script the SQL script\n     * @param delim String delimiting each statement - typically a ';' character\n     */\n    public static boolean containsSqlScriptDelimiters(String script, String delim) {\n        return containsSqlScriptDelimiters(\n            \"\",\n            script,\n            DEFAULT_COMMENT_PREFIX,\n            delim,\n            DEFAULT_BLOCK_COMMENT_START_DELIMITER,\n            DEFAULT_BLOCK_COMMENT_END_DELIMITER\n        );\n    }\n\n    /**\n     * Does the provided SQL script contain the specified delimiter?\n     *\n     * @param script                     the SQL script\n     * @param delim                      String delimiting each statement - typically a ';' character\n     * @param commentPrefix              the prefix that identifies comments in the SQL script,\n     *                                   typically \"--\"\n     * @param blockCommentStartDelimiter block comment start delimiter\n     * @param blockCommentEndDelimiter   block comment end delimiter\n     */\n    public static boolean containsSqlScriptDelimiters(\n        String scriptPath,\n        String script,\n        String commentPrefix,\n        String delim,\n        String blockCommentStartDelimiter,\n        String blockCommentEndDelimiter\n    ) {\n        ScriptScanner scanner = new ScriptScanner(\n            scriptPath,\n            script,\n            delim,\n            commentPrefix,\n            blockCommentStartDelimiter,\n            blockCommentEndDelimiter\n        );\n        ScriptScanner.Lexem l;\n        while ((l = scanner.next()) != ScriptScanner.Lexem.EOF) {\n            if (ScriptScanner.Lexem.SEPARATOR.equals(l)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    /**\n     * Load script from classpath and apply it to the given database\n     *\n     * @param databaseDelegate database delegate for script execution\n     * @param initScriptPath   the resource to load the init script from\n     */\n    public static void runInitScript(DatabaseDelegate databaseDelegate, String initScriptPath) {\n        try {\n            URL resource = Thread.currentThread().getContextClassLoader().getResource(initScriptPath);\n            if (resource == null) {\n                resource = ScriptUtils.class.getClassLoader().getResource(initScriptPath);\n                if (resource == null) {\n                    LOGGER.warn(\"Could not load classpath init script: {}\", initScriptPath);\n                    throw new ScriptLoadException(\n                        \"Could not load classpath init script: \" + initScriptPath + \". Resource not found.\"\n                    );\n                }\n            }\n            String scripts = IOUtils.toString(resource, StandardCharsets.UTF_8);\n            executeDatabaseScript(databaseDelegate, initScriptPath, scripts);\n        } catch (IOException e) {\n            LOGGER.warn(\"Could not load classpath init script: {}\", initScriptPath);\n            throw new ScriptLoadException(\"Could not load classpath init script: \" + initScriptPath, e);\n        } catch (ScriptException e) {\n            LOGGER.error(\"Error while executing init script: {}\", initScriptPath, e);\n            throw new UncategorizedScriptException(\"Error while executing init script: \" + initScriptPath, e);\n        }\n    }\n\n    public static void executeDatabaseScript(DatabaseDelegate databaseDelegate, String scriptPath, String script)\n        throws ScriptException {\n        executeDatabaseScript(\n            databaseDelegate,\n            scriptPath,\n            script,\n            false,\n            false,\n            DEFAULT_COMMENT_PREFIX,\n            DEFAULT_STATEMENT_SEPARATOR,\n            DEFAULT_BLOCK_COMMENT_START_DELIMITER,\n            DEFAULT_BLOCK_COMMENT_END_DELIMITER\n        );\n    }\n\n    /**\n     * Execute the given database script.\n     * <p>Statement separators and comments will be removed before executing\n     * individual statements within the supplied script.\n     * <p><b>Do not use this method to execute DDL if you expect rollback.</b>\n     * @param databaseDelegate database delegate for script execution\n     * @param scriptPath the resource (potentially associated with a specific encoding)\n     * to load the SQL script from\n     * @param script the raw script content\n     *@param continueOnError whether or not to continue without throwing an exception\n     * in the event of an error\n     * @param ignoreFailedDrops whether or not to continue in the event of specifically\n     * an error on a {@code DROP} statement\n     * @param commentPrefix the prefix that identifies comments in the SQL script &mdash;\n     * typically \"--\"\n     * @param separator the script statement separator; defaults to\n     * {@value #DEFAULT_STATEMENT_SEPARATOR} if not specified and falls back to\n     * {@value #FALLBACK_STATEMENT_SEPARATOR} as a last resort\n     * @param blockCommentStartDelimiter the <em>start</em> block comment delimiter; never\n     * {@code null} or empty\n     * @param blockCommentEndDelimiter the <em>end</em> block comment delimiter; never\n     * {@code null} or empty       @throws ScriptException if an error occurred while executing the SQL script\n     */\n    public static void executeDatabaseScript(\n        DatabaseDelegate databaseDelegate,\n        String scriptPath,\n        String script,\n        boolean continueOnError,\n        boolean ignoreFailedDrops,\n        String commentPrefix,\n        String separator,\n        String blockCommentStartDelimiter,\n        String blockCommentEndDelimiter\n    ) throws ScriptException {\n        try {\n            if (LOGGER.isInfoEnabled()) {\n                LOGGER.info(\"Executing database script from \" + scriptPath);\n            }\n\n            long startTime = System.nanoTime();\n            List<String> statements = new LinkedList<>();\n\n            if (separator == null) {\n                separator = DEFAULT_STATEMENT_SEPARATOR;\n            }\n            if (\n                !containsSqlScriptDelimiters(\n                    scriptPath,\n                    script,\n                    commentPrefix,\n                    separator,\n                    blockCommentStartDelimiter,\n                    blockCommentEndDelimiter\n                )\n            ) {\n                separator = FALLBACK_STATEMENT_SEPARATOR;\n            }\n\n            splitSqlScript(\n                scriptPath,\n                script,\n                separator,\n                commentPrefix,\n                blockCommentStartDelimiter,\n                blockCommentEndDelimiter,\n                statements\n            );\n\n            try (DatabaseDelegate closeableDelegate = databaseDelegate) {\n                closeableDelegate.execute(statements, scriptPath, continueOnError, ignoreFailedDrops);\n            }\n\n            long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime);\n            if (LOGGER.isInfoEnabled()) {\n                LOGGER.info(\"Executed database script from \" + scriptPath + \" in \" + elapsedTime + \" ms.\");\n            }\n        } catch (Exception ex) {\n            if (ex instanceof ScriptException) {\n                throw (ScriptException) ex;\n            }\n\n            throw new UncategorizedScriptException(\n                \"Failed to execute database script from resource [\" + script + \"]\",\n                ex\n            );\n        }\n    }\n\n    public static class ScriptLoadException extends RuntimeException {\n\n        public ScriptLoadException(String message) {\n            super(message);\n        }\n\n        public ScriptLoadException(String message, Throwable cause) {\n            super(message, cause);\n        }\n    }\n\n    public static class ScriptParseException extends RuntimeException {\n\n        public ScriptParseException(String format, String scriptPath) {\n            super(String.format(format, scriptPath));\n        }\n    }\n\n    public static class ScriptStatementFailedException extends RuntimeException {\n\n        public ScriptStatementFailedException(String statement, int lineNumber, String scriptPath) {\n            this(statement, lineNumber, scriptPath, null);\n        }\n\n        public ScriptStatementFailedException(String statement, int lineNumber, String scriptPath, Exception ex) {\n            super(String.format(\"Script execution failed (%s:%d): %s\", scriptPath, lineNumber, statement), ex);\n        }\n    }\n\n    public static class UncategorizedScriptException extends RuntimeException {\n\n        public UncategorizedScriptException(String s, Exception ex) {\n            super(s, ex);\n        }\n    }\n}\n"
  },
  {
    "path": "modules/database-commons/src/test/java/org/testcontainers/ext/ScriptScannerTest.java",
    "content": "package org.testcontainers.ext;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.junit.jupiter.api.Test;\n\nimport java.util.regex.Pattern;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass ScriptScannerTest {\n\n    @Test\n    void testHugeStringLiteral() {\n        String script = \"/* a comment */    \\\"\" + StringUtils.repeat('~', 10000) + \"\\\";\";\n        ScriptScanner scanner = scanner(script);\n        assertThat(scanner.next()).isEqualTo(ScriptScanner.Lexem.COMMENT);\n        assertThat(scanner.next()).isEqualTo(ScriptScanner.Lexem.WHITESPACE);\n        assertThat(scanner.next()).isEqualTo(ScriptScanner.Lexem.QUOTED_STRING);\n        assertThat(scanner.getCurrentMatch()).matches(Pattern.compile(\"\\\"~+\\\"\"));\n    }\n\n    @Test\n    void testPgIdentifierWithDollarSigns() {\n        ScriptScanner scanner = scanner(\n            \"this$is$a$valid$postgreSQL$identifier  \" +\n            \"$a$While this is a quoted string$a$$ --just followed by a dollar sign\"\n        );\n        assertThat(scanner.next()).isEqualTo(ScriptScanner.Lexem.IDENTIFIER);\n        assertThat(scanner.next()).isEqualTo(ScriptScanner.Lexem.WHITESPACE);\n        assertThat(scanner.next()).isEqualTo(ScriptScanner.Lexem.QUOTED_STRING);\n        assertThat(scanner.next()).isEqualTo(ScriptScanner.Lexem.OTHER);\n    }\n\n    @Test\n    void testQuotedLiterals() {\n        ScriptScanner scanner = scanner(\"'this \\\\'is a literal' \\\"this \\\\\\\" is a literal\\\"\");\n        assertThat(scanner.next()).isEqualTo(ScriptScanner.Lexem.QUOTED_STRING);\n        assertThat(scanner.getCurrentMatch()).isEqualTo(\"'this \\\\'is a literal'\");\n        assertThat(scanner.next()).isEqualTo(ScriptScanner.Lexem.WHITESPACE);\n        assertThat(scanner.next()).isEqualTo(ScriptScanner.Lexem.QUOTED_STRING);\n        assertThat(scanner.getCurrentMatch()).isEqualTo(\"\\\"this \\\\\\\" is a literal\\\"\");\n    }\n\n    private static ScriptScanner scanner(String script) {\n        return new ScriptScanner(\n            \"dummy\",\n            script,\n            ScriptUtils.DEFAULT_STATEMENT_SEPARATOR,\n            ScriptUtils.DEFAULT_COMMENT_PREFIX,\n            ScriptUtils.DEFAULT_BLOCK_COMMENT_START_DELIMITER,\n            ScriptUtils.DEFAULT_BLOCK_COMMENT_END_DELIMITER\n        );\n    }\n}\n"
  },
  {
    "path": "modules/database-commons/src/test/java/org/testcontainers/ext/ScriptSplittingTest.java",
    "content": "package org.testcontainers.ext;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\n\nclass ScriptSplittingTest {\n\n    @Test\n    void testStringDemarcation() {\n        String script = \"SELECT 'foo `bar`'; SELECT 'foo -- `bar`'; SELECT 'foo /* `bar`';\";\n\n        List<String> expected = Arrays.asList(\"SELECT 'foo `bar`'\", \"SELECT 'foo -- `bar`'\", \"SELECT 'foo /* `bar`'\");\n\n        splitAndCompare(script, expected);\n    }\n\n    @Test\n    void testIssue1547Case1() {\n        String script =\n            \"create database if not exists ttt;\\n\" +\n            \"\\n\" +\n            \"use ttt;\\n\" +\n            \"\\n\" +\n            \"create table aaa\\n\" +\n            \"(\\n\" +\n            \"    id                  bigint auto_increment   primary key,\\n\" +\n            \"    end_time            datetime     null       COMMENT 'end_time',\\n\" +\n            \"    data_status         varchar(16)  not null\\n\" +\n            \") comment 'aaa';\\n\" +\n            \"\\n\" +\n            \"create table bbb\\n\" +\n            \"(\\n\" +\n            \"    id                  bigint auto_increment   primary key\\n\" +\n            \") comment 'bbb';\";\n\n        List<String> expected = Arrays.asList(\n            \"create database if not exists ttt\",\n            \"use ttt\",\n            \"create table aaa ( id bigint auto_increment primary key, end_time datetime null COMMENT 'end_time', data_status varchar(16) not null ) comment 'aaa'\",\n            \"create table bbb ( id bigint auto_increment primary key ) comment 'bbb'\"\n        );\n\n        splitAndCompare(script, expected);\n    }\n\n    @Test\n    void testIssue1547Case2() {\n        String script =\n            \"CREATE TABLE bar (\\n\" +\n            \"  end_time VARCHAR(255)\\n\" +\n            \");\\n\" +\n            \"CREATE TABLE bar (\\n\" +\n            \"  end_time VARCHAR(255)\\n\" +\n            \");\";\n\n        List<String> expected = Arrays.asList(\n            \"CREATE TABLE bar ( end_time VARCHAR(255) )\",\n            \"CREATE TABLE bar ( end_time VARCHAR(255) )\"\n        );\n\n        splitAndCompare(script, expected);\n    }\n\n    @Test\n    void testSplittingEnquotedSemicolon() {\n        String script = \"CREATE TABLE `bar;bar` (\\n\" + \"  end_time VARCHAR(255)\\n\" + \");\";\n\n        List<String> expected = Arrays.asList(\"CREATE TABLE `bar;bar` ( end_time VARCHAR(255) )\");\n\n        splitAndCompare(script, expected);\n    }\n\n    @Test\n    void testUnusualSemicolonPlacement() {\n        String script = \"SELECT 1;;;;;SELECT 2;\\n;SELECT 3\\n; SELECT 4;\\n SELECT 5\";\n\n        List<String> expected = Arrays.asList(\"SELECT 1\", \"SELECT 2\", \"SELECT 3\", \"SELECT 4\", \"SELECT 5\");\n\n        splitAndCompare(script, expected);\n    }\n\n    @Test\n    void testCommentedSemicolon() {\n        String script =\n            \"CREATE TABLE bar (\\n\" + \"  foo VARCHAR(255)\\n\" + \"); \\nDROP PROCEDURE IF EXISTS -- ;\\n\" + \"    count_foo\";\n\n        List<String> expected = Arrays.asList(\n            \"CREATE TABLE bar ( foo VARCHAR(255) )\",\n            \"DROP PROCEDURE IF EXISTS count_foo\"\n        );\n\n        splitAndCompare(script, expected);\n    }\n\n    @Test\n    void testStringEscaping() {\n        String script =\n            \"SELECT \\\"a /* string literal containing comment characters like -- here\\\";\\n\" +\n            \"SELECT \\\"a 'quoting' \\\\\\\"scenario ` involving BEGIN keyword\\\\\\\" here\\\";\\n\" +\n            \"SELECT * from `bar`;\";\n\n        List<String> expected = Arrays.asList(\n            \"SELECT \\\"a /* string literal containing comment characters like -- here\\\"\",\n            \"SELECT \\\"a 'quoting' \\\\\\\"scenario ` involving BEGIN keyword\\\\\\\" here\\\"\",\n            \"SELECT * from `bar`\"\n        );\n\n        splitAndCompare(script, expected);\n    }\n\n    @Test\n    void testBlockCommentExclusion() {\n        String script = \"INSERT INTO bar (foo) /* ; */ VALUES ('hello world');\";\n\n        List<String> expected = Arrays.asList(\"INSERT INTO bar (foo) VALUES ('hello world')\");\n\n        splitAndCompare(script, expected);\n    }\n\n    @Test\n    void testBeginEndKeywordCorrectDetection() {\n        String script =\n            \"INSERT INTO something_end (begin_with_the_token, another_field) /*end*/ VALUES /* end */ (' begin ', `end`)-- begin\\n;\";\n\n        List<String> expected = Arrays.asList(\n            \"INSERT INTO something_end (begin_with_the_token, another_field) VALUES (' begin ', `end`)\"\n        );\n\n        splitAndCompare(script, expected);\n    }\n\n    @Test\n    void testCommentInStrings() {\n        String script =\n            \"CREATE TABLE bar (foo VARCHAR(255));\\n\" +\n            \"\\n\" +\n            \"/* Insert Values */\\n\" +\n            \"INSERT INTO bar (foo) values ('--1');\\n\" +\n            \"INSERT INTO bar (foo) values ('--2');\\n\" +\n            \"INSERT INTO bar (foo) values ('/* something */');\\n\" +\n            \"/* INSERT INTO bar (foo) values (' */'); -- '*/;\\n\" + // purposefully broken, to see if it breaks our splitting\n            \"INSERT INTO bar (foo) values ('foo');\";\n\n        List<String> expected = Arrays.asList(\n            \"CREATE TABLE bar (foo VARCHAR(255))\",\n            \"INSERT INTO bar (foo) values ('--1')\",\n            \"INSERT INTO bar (foo) values ('--2')\",\n            \"INSERT INTO bar (foo) values ('/* something */')\",\n            \"'); -- '*/\",\n            \"INSERT INTO bar (foo) values ('foo')\"\n        );\n\n        splitAndCompare(script, expected);\n    }\n\n    @Test\n    void testMultipleBeginEndDetection() {\n        String script =\n            \"CREATE TABLE bar (foo VARCHAR(255));\\n\" +\n            \"\\n\" +\n            \"CREATE TABLE gender (gender VARCHAR(255));\\n\" +\n            \"CREATE TABLE ending (ending VARCHAR(255));\\n\" +\n            \"CREATE TABLE end2 (end2 VARCHAR(255));\\n\" +\n            \"CREATE TABLE end_2 (end2 VARCHAR(255));\\n\" +\n            \"\\n\" +\n            \"BEGIN\\n\" +\n            \"  INSERT INTO ending values ('ending');\\n\" +\n            \"END;\\n\" +\n            \"\\n\" +\n            \"BEGIN\\n\" +\n            \"  INSERT INTO ending values ('ending');\\n\" +\n            \"END/*hello*/;\\n\" +\n            \"\\n\" +\n            \"BEGIN--Hello\\n\" +\n            \"  INSERT INTO ending values ('ending');\\n\" +\n            \"END;\\n\" +\n            \"\\n\" +\n            \"/*Hello*/BEGIN\\n\" +\n            \"  INSERT INTO ending values ('ending');\\n\" +\n            \"END;\\n\" +\n            \"\\n\" +\n            \"CREATE TABLE foo (bar VARCHAR(255));\";\n\n        List<String> expected = Arrays.asList(\n            \"CREATE TABLE bar (foo VARCHAR(255))\",\n            \"CREATE TABLE gender (gender VARCHAR(255))\",\n            \"CREATE TABLE ending (ending VARCHAR(255))\",\n            \"CREATE TABLE end2 (end2 VARCHAR(255))\",\n            \"CREATE TABLE end_2 (end2 VARCHAR(255))\",\n            \"BEGIN\\n\" + \"  INSERT INTO ending values ('ending');\\n\" + \"END\",\n            \"BEGIN\\n\" + \"  INSERT INTO ending values ('ending');\\n\" + \"END\",\n            \"BEGIN--Hello\\n\" + \"  INSERT INTO ending values ('ending');\\n\" + \"END\",\n            \"BEGIN\\n\" + \"  INSERT INTO ending values ('ending');\\n\" + \"END\",\n            \"CREATE TABLE foo (bar VARCHAR(255))\"\n        );\n\n        splitAndCompare(script, expected);\n    }\n\n    @Test\n    void testProcedureBlock() {\n        String script =\n            \"CREATE PROCEDURE count_foo()\\n\" +\n            \"  BEGIN\\n\" +\n            \"\\n\" +\n            \"    BEGIN\\n\" +\n            \"      SELECT *\\n\" +\n            \"      FROM bar;\\n\" +\n            \"      SELECT 1\\n\" +\n            \"      FROM dual;\\n\" +\n            \"    END;\\n\" +\n            \"\\n\" +\n            \"    BEGIN\\n\" +\n            \"      select * from bar;\\n\" +\n            \"    END;\\n\" +\n            \"\\n\" +\n            \"    -- we can do comments\\n\" +\n            \"\\n\" +\n            \"    /* including block\\n\" +\n            \"       comments\\n\" +\n            \"     */\\n\" +\n            \"\\n\" +\n            \"    /* what if BEGIN appears inside a comment? */\\n\" +\n            \"\\n\" +\n            \"    select \\\"or what if BEGIN appears inside a literal?\\\";\\n\" +\n            \"\\n\" +\n            \"  END /*; */;\";\n\n        List<String> expected = Arrays.asList(\n            \"CREATE PROCEDURE count_foo() BEGIN\\n\" +\n            \"\\n\" +\n            \"    BEGIN\\n\" +\n            \"      SELECT *\\n\" +\n            \"      FROM bar;\\n\" +\n            \"      SELECT 1\\n\" +\n            \"      FROM dual;\\n\" +\n            \"    END;\\n\" +\n            \"\\n\" +\n            \"    BEGIN\\n\" +\n            \"      select * from bar;\\n\" +\n            \"    END;\\n\" +\n            \"\\n\" +\n            \"    -- we can do comments\\n\" +\n            \"\\n\" +\n            \"    /* including block\\n\" +\n            \"       comments\\n\" +\n            \"     */\\n\" +\n            \"\\n\" +\n            \"    /* what if BEGIN appears inside a comment? */\\n\" +\n            \"\\n\" +\n            \"    select \\\"or what if BEGIN appears inside a literal?\\\";\\n\" +\n            \"\\n\" +\n            \"  END\"\n        );\n\n        splitAndCompare(script, expected);\n    }\n\n    @Test\n    void testUnclosedBlockComment() {\n        String script = \"SELECT 'foo `bar`'; /*\";\n        assertThatThrownBy(() -> doSplit(script, ScriptUtils.DEFAULT_STATEMENT_SEPARATOR))\n            .isInstanceOf(ScriptUtils.ScriptParseException.class)\n            .hasMessageContaining(\"*/\");\n    }\n\n    @Test\n    void testIssue1452Case() {\n        String script =\n            \"create table test (text VARCHAR(255));\\n\" +\n            \"\\n\" +\n            \"/* some comment */\\n\" +\n            \"insert into `test` (`text`) values ('a     b');\";\n\n        List<String> expected = Arrays.asList(\n            \"create table test (text VARCHAR(255))\",\n            \"insert into `test` (`text`) values ('a     b')\"\n        );\n\n        splitAndCompare(script, expected);\n    }\n\n    @Test\n    void testIfLoopBlocks() {\n        String script =\n            \"BEGIN\\n\" +\n            \"    rec_loop: LOOP\\n\" +\n            \"        FETCH blah;\\n\" +\n            \"        IF something_wrong THEN LEAVE rec_loop; END IF;\\n\" +\n            \"        do_something_else;\\n\" +\n            \"    END LOOP;\\n\" +\n            \"END /* final comment */;\";\n        List<String> expected = Collections.singletonList(\n            \"BEGIN\\n\" +\n            \"    rec_loop: LOOP\\n\" +\n            \"        FETCH blah;\\n\" +\n            \"        IF something_wrong THEN LEAVE rec_loop; END IF;\\n\" +\n            \"        do_something_else;\\n\" +\n            \"    END LOOP;\\n\" +\n            \"END\"\n        );\n        splitAndCompare(script, expected);\n    }\n\n    @Test\n    void testIfLoopBlocksSpecificSeparator() {\n        String script =\n            \"BEGIN\\n\" +\n            \"    rec_loop: LOOP\\n\" +\n            \"        FETCH blah;\\n\" +\n            \"        IF something_wrong THEN LEAVE rec_loop; END IF;\\n\" +\n            \"        do_something_else;\\n\" +\n            \"    END LOOP;\\n\" +\n            \"END;\\n\" +\n            \"@\\n\" +\n            \"CALL something();\\n\" +\n            \"@\\n\";\n        List<String> expected = Arrays.asList(\n            \"BEGIN\\n\" +\n            \"    rec_loop: LOOP\\n\" +\n            \"        FETCH blah;\\n\" +\n            \"        IF something_wrong THEN LEAVE rec_loop; END IF;\\n\" +\n            \"        do_something_else;\\n\" +\n            \"    END LOOP;\\n\" +\n            \"END;\",\n            \"CALL something();\"\n        );\n        splitAndCompare(script, expected, \"@\");\n    }\n\n    @Test\n    void oracleStyleBlocks() {\n        String script = \"BEGIN END; /\\n\" + \"BEGIN END;\";\n        List<String> expected = Arrays.asList(\"BEGIN END;\", \"BEGIN END;\");\n        splitAndCompare(script, expected, \"/\");\n    }\n\n    @Test\n    void testMultiProcedureMySQLScript() {\n        String script =\n            \"CREATE PROCEDURE doiterate(p1 INT)\\n\" +\n            \"  BEGIN\\n\" +\n            \"    label1: LOOP\\n\" +\n            \"      SET p1 = p1 + 1;\\n\" +\n            \"      IF p1 < 10 THEN\\n\" +\n            \"        ITERATE label1;\\n\" +\n            \"      END IF;\\n\" +\n            \"      LEAVE label1;\\n\" +\n            \"    END LOOP label1;\\n\" +\n            \"  END;\\n\" +\n            \"\\n\" +\n            \"CREATE PROCEDURE dowhile()\\n\" +\n            \"  BEGIN\\n\" +\n            \"    DECLARE v1 INT DEFAULT 5;\\n\" +\n            \"    WHILE v1 > 0 DO\\n\" +\n            \"      SET v1 = v1 - 1;\\n\" +\n            \"    END WHILE;\\n\" +\n            \"  END;\\n\" +\n            \"\\n\" +\n            \"CREATE PROCEDURE dorepeat(p1 INT)\\n\" +\n            \"  BEGIN\\n\" +\n            \"    SET @x = 0;\\n\" +\n            \"    REPEAT\\n\" +\n            \"      SET @x = @x + 1;\\n\" +\n            \"    UNTIL @x > p1 END REPEAT;\\n\" +\n            \"  END;\";\n        List<String> expected = Arrays.asList(\n            \"CREATE PROCEDURE doiterate(p1 INT) BEGIN\\n\" +\n            \"    label1: LOOP\\n\" +\n            \"      SET p1 = p1 + 1;\\n\" +\n            \"      IF p1 < 10 THEN\\n\" +\n            \"        ITERATE label1;\\n\" +\n            \"      END IF;\\n\" +\n            \"      LEAVE label1;\\n\" +\n            \"    END LOOP label1;\\n\" +\n            \"  END\",\n            \"CREATE PROCEDURE dowhile() BEGIN\\n\" +\n            \"    DECLARE v1 INT DEFAULT 5;\\n\" +\n            \"    WHILE v1 > 0 DO\\n\" +\n            \"      SET v1 = v1 - 1;\\n\" +\n            \"    END WHILE;\\n\" +\n            \"  END\",\n            \"CREATE PROCEDURE dorepeat(p1 INT) BEGIN\\n\" +\n            \"    SET @x = 0;\\n\" +\n            \"    REPEAT\\n\" +\n            \"      SET @x = @x + 1;\\n\" +\n            \"    UNTIL @x > p1 END REPEAT;\\n\" +\n            \"  END\"\n        );\n        splitAndCompare(script, expected);\n    }\n\n    @Test\n    void testDollarQuotedStrings() {\n        String script =\n            \"CREATE FUNCTION f ()\\n\" +\n            \"RETURNS INT\\n\" +\n            \"AS $$\\n\" +\n            \"BEGIN\\n\" +\n            \"    RETURN 1;\\n\" +\n            \"END;\\n\" +\n            \"$$ LANGUAGE plpgsql;\";\n        List<String> expected = Collections.singletonList(\n            \"CREATE FUNCTION f () RETURNS INT AS $$\\n\" +\n            \"BEGIN\\n\" +\n            \"    RETURN 1;\\n\" +\n            \"END;\\n\" +\n            \"$$ LANGUAGE plpgsql\"\n        );\n        splitAndCompare(script, expected);\n    }\n\n    @Test\n    void testNestedDollarQuotedString() {\n        //see https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-DOLLAR-QUOTING\n        String script =\n            \"CREATE FUNCTION f() AS $function$\\n\" +\n            \"BEGIN\\n\" +\n            \"    RETURN ($1 ~ $q$[\\\\t\\\\r\\\\n\\\\v\\\\\\\\]$q$);\\n\" +\n            \"END;\\n\" +\n            \"$function$;\" +\n            \"create table foo ();\";\n        List<String> expected = Arrays.asList(\n            \"CREATE FUNCTION f() AS $function$\\n\" +\n            \"BEGIN\\n\" +\n            \"    RETURN ($1 ~ $q$[\\\\t\\\\r\\\\n\\\\v\\\\\\\\]$q$);\\n\" +\n            \"END;\\n\" +\n            \"$function$\",\n            \"create table foo ()\"\n        );\n        splitAndCompare(script, expected);\n    }\n\n    @Test\n    void testUnclosedDollarQuotedString() {\n        String script = \"SELECT $tag$ ..... $\";\n        assertThatThrownBy(() -> doSplit(script, ScriptUtils.DEFAULT_STATEMENT_SEPARATOR))\n            .isInstanceOf(ScriptUtils.ScriptParseException.class)\n            .hasMessageContaining(\"$tag$\");\n    }\n\n    private void splitAndCompare(String script, List<String> expected) {\n        splitAndCompare(script, expected, ScriptUtils.DEFAULT_STATEMENT_SEPARATOR);\n    }\n\n    private void splitAndCompare(String script, List<String> expected, String separator) {\n        final List<String> statements = doSplit(script, separator);\n        assertThat(statements).isEqualTo(expected);\n    }\n\n    private List<String> doSplit(String script, String separator) {\n        final List<String> statements = new ArrayList<>();\n        ScriptUtils.splitSqlScript(\n            \"ignored\",\n            script,\n            separator,\n            ScriptUtils.DEFAULT_COMMENT_PREFIX,\n            ScriptUtils.DEFAULT_BLOCK_COMMENT_START_DELIMITER,\n            ScriptUtils.DEFAULT_BLOCK_COMMENT_END_DELIMITER,\n            statements\n        );\n        return statements;\n    }\n\n    @Test\n    void testIgnoreDelimitersInLiteralsAndComments() {\n        assertThat(ScriptUtils.containsSqlScriptDelimiters(\"'@' /*@*/ \\\"@\\\" $tag$@$tag$ --@\", \"@\")).isFalse();\n    }\n\n    @Test\n    void testContainsDelimiters() {\n        assertThat(ScriptUtils.containsSqlScriptDelimiters(\"'@' /*@*/ @ \\\"@\\\" --@\", \"@\")).isTrue();\n    }\n}\n"
  },
  {
    "path": "modules/databend/build.gradle",
    "content": "description = \"Testcontainers :: JDBC :: Databend\"\n\ndependencies {\n    api project(':testcontainers-jdbc')\n\n    testImplementation project(':testcontainers-jdbc-test')\n    testRuntimeOnly 'com.databend:databend-jdbc:0.4.1'\n}\n"
  },
  {
    "path": "modules/databend/src/main/java/org/testcontainers/databend/DatabendContainer.java",
    "content": "package org.testcontainers.databend;\n\nimport org.testcontainers.containers.JdbcDatabaseContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.util.HashSet;\nimport java.util.Set;\n\n/**\n * Testcontainers implementation for Databend.\n * <p>\n * Supported image: {@code datafuselabs/databend}\n * <p>\n * Exposed ports:\n * <ul>\n *     <li>Database: 8000</li>\n * </ul>\n */\npublic class DatabendContainer extends JdbcDatabaseContainer<DatabendContainer> {\n\n    static final String NAME = \"databend\";\n\n    static final DockerImageName DOCKER_IMAGE_NAME = DockerImageName.parse(\"datafuselabs/databend\");\n\n    private static final Integer HTTP_PORT = 8000;\n\n    private static final String DRIVER_CLASS_NAME = \"com.databend.jdbc.DatabendDriver\";\n\n    private static final String JDBC_URL_PREFIX = \"jdbc:\" + NAME + \"://\";\n\n    private static final String TEST_QUERY = \"SELECT 1\";\n\n    private String databaseName = \"default\";\n\n    private String username = \"databend\";\n\n    private String password = \"databend\";\n\n    public DatabendContainer(String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    public DatabendContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DOCKER_IMAGE_NAME);\n\n        addExposedPorts(HTTP_PORT);\n        waitingFor(Wait.forHttp(\"/\").forResponsePredicate(response -> response.equals(\"Ok.\")));\n    }\n\n    @Override\n    protected void configure() {\n        withEnv(\"QUERY_DEFAULT_USER\", this.username);\n        withEnv(\"QUERY_DEFAULT_PASSWORD\", this.password);\n    }\n\n    @Override\n    public Set<Integer> getLivenessCheckPortNumbers() {\n        return new HashSet<>(getMappedPort(HTTP_PORT));\n    }\n\n    @Override\n    public String getDriverClassName() {\n        return DRIVER_CLASS_NAME;\n    }\n\n    @Override\n    public String getJdbcUrl() {\n        return (\n            JDBC_URL_PREFIX +\n            getHost() +\n            \":\" +\n            getMappedPort(HTTP_PORT) +\n            \"/\" +\n            this.databaseName +\n            constructUrlParameters(\"?\", \"&\")\n        );\n    }\n\n    @Override\n    public String getUsername() {\n        return this.username;\n    }\n\n    @Override\n    public String getPassword() {\n        return this.password;\n    }\n\n    @Override\n    public String getDatabaseName() {\n        return this.databaseName;\n    }\n\n    @Override\n    public String getTestQueryString() {\n        return TEST_QUERY;\n    }\n\n    @Override\n    public DatabendContainer withUsername(String username) {\n        this.username = username;\n        return this;\n    }\n\n    @Override\n    public DatabendContainer withPassword(String password) {\n        this.password = password;\n        return this;\n    }\n}\n"
  },
  {
    "path": "modules/databend/src/main/java/org/testcontainers/databend/DatabendContainerProvider.java",
    "content": "package org.testcontainers.databend;\n\nimport org.testcontainers.containers.JdbcDatabaseContainer;\nimport org.testcontainers.containers.JdbcDatabaseContainerProvider;\n\npublic class DatabendContainerProvider extends JdbcDatabaseContainerProvider {\n\n    private static final String DEFAULT_TAG = \"v1.2.615\";\n\n    @Override\n    public boolean supports(String databaseType) {\n        return databaseType.equals(DatabendContainer.NAME);\n    }\n\n    @Override\n    public JdbcDatabaseContainer newInstance() {\n        return newInstance(DEFAULT_TAG);\n    }\n\n    @Override\n    public JdbcDatabaseContainer newInstance(String tag) {\n        if (tag != null) {\n            return new DatabendContainer(DatabendContainer.DOCKER_IMAGE_NAME.withTag(tag));\n        } else {\n            return newInstance();\n        }\n    }\n}\n"
  },
  {
    "path": "modules/databend/src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider",
    "content": "org.testcontainers.databend.DatabendContainerProvider\n"
  },
  {
    "path": "modules/databend/src/test/java/org/testcontainers/databend/DatabendContainerTest.java",
    "content": "package org.testcontainers.databend;\n\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.db.AbstractContainerDatabaseTest;\n\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass DatabendContainerTest extends AbstractContainerDatabaseTest {\n\n    @Test\n    void testSimple() throws SQLException {\n        try ( // container {\n            DatabendContainer databend = new DatabendContainer(\"datafuselabs/databend:v1.2.615\")\n            // }\n        ) {\n            databend.start();\n\n            ResultSet resultSet = performQuery(databend, \"SELECT 1\");\n\n            int resultSetInt = resultSet.getInt(1);\n            assertThat(resultSetInt).isEqualTo(1);\n        }\n    }\n\n    @Test\n    void customCredentialsWithUrlParams() throws SQLException {\n        try (\n            DatabendContainer databend = new DatabendContainer(\"datafuselabs/databend:v1.2.615\")\n                .withUsername(\"test\")\n                .withPassword(\"test\")\n                .withUrlParam(\"ssl\", \"false\")\n        ) {\n            databend.start();\n\n            ResultSet resultSet = performQuery(databend, \"SELECT 1;\");\n\n            int resultSetInt = resultSet.getInt(1);\n            assertThat(resultSetInt).isEqualTo(1);\n        }\n    }\n}\n"
  },
  {
    "path": "modules/databend/src/test/java/org/testcontainers/databend/DatabendJDBCDriverTest.java",
    "content": "package org.testcontainers.databend;\n\nimport org.testcontainers.jdbc.AbstractJDBCDriverTest;\n\nimport java.util.Arrays;\nimport java.util.EnumSet;\n\nclass DatabendJDBCDriverTest extends AbstractJDBCDriverTest {\n\n    public static Iterable<Object[]> data() {\n        return Arrays.asList(\n            new Object[][] { //\n                { \"jdbc:tc:databend://hostname/databasename\", EnumSet.of(Options.PmdKnownBroken) },\n            }\n        );\n    }\n}\n"
  },
  {
    "path": "modules/databend/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/db2/build.gradle",
    "content": "description = \"Testcontainers :: JDBC :: DB2\"\n\ndependencies {\n    api project(':testcontainers-jdbc')\n\n    testImplementation project(':testcontainers-jdbc-test')\n    testRuntimeOnly 'com.ibm.db2:jcc:12.1.3.0'\n}\n"
  },
  {
    "path": "modules/db2/src/main/java/org/testcontainers/containers/Db2Container.java",
    "content": "package org.testcontainers.containers;\n\nimport com.github.dockerjava.api.model.Capability;\nimport org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.LicenseAcceptance;\n\nimport java.time.Duration;\nimport java.time.temporal.ChronoUnit;\nimport java.util.Set;\n\n/**\n * Testcontainers implementation for IBM DB2.\n * <p>\n * Supported images: {@code icr.io/db2_community/db2}, {@code ibmcom/db2}\n * <p>\n * Exposed ports:\n * <ul>\n *     <li>Database: 50000</li>\n * </ul>\n * @deprecated use {@link org.testcontainers.db2.Db2Container} instead.\n */\n@Deprecated\npublic class Db2Container extends JdbcDatabaseContainer<Db2Container> {\n\n    public static final String NAME = \"db2\";\n\n    @Deprecated\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"ibmcom/db2\");\n\n    private static final DockerImageName DEFAULT_NEW_IMAGE_NAME = DockerImageName.parse(\"icr.io/db2_community/db2\");\n\n    @Deprecated\n    public static final String DEFAULT_DB2_IMAGE_NAME = DEFAULT_IMAGE_NAME.getUnversionedPart();\n\n    @Deprecated\n    public static final String DEFAULT_TAG = \"11.5.0.0a\";\n\n    public static final int DB2_PORT = 50000;\n\n    private String databaseName = \"test\";\n\n    private String username = \"db2inst1\";\n\n    private String password = \"foobar1234\";\n\n    /**\n     * @deprecated use {@link #Db2Container(DockerImageName)} instead\n     */\n    @Deprecated\n    public Db2Container() {\n        this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG));\n    }\n\n    public Db2Container(String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    public Db2Container(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_NEW_IMAGE_NAME, DEFAULT_IMAGE_NAME);\n\n        withCreateContainerCmdModifier(cmd -> cmd.withCapAdd(Capability.IPC_LOCK).withCapAdd(Capability.IPC_OWNER));\n        this.waitStrategy =\n            new LogMessageWaitStrategy()\n                .withRegEx(\".*Setup has completed\\\\..*\")\n                .withStartupTimeout(Duration.of(10, ChronoUnit.MINUTES));\n\n        addExposedPort(DB2_PORT);\n    }\n\n    /**\n     * @return the ports on which to check if the container is ready\n     * @deprecated use {@link #getLivenessCheckPortNumbers()} instead\n     */\n    @Override\n    @Deprecated\n    protected Set<Integer> getLivenessCheckPorts() {\n        return super.getLivenessCheckPorts();\n    }\n\n    @Override\n    protected void configure() {\n        // If license was not accepted programmatically, check if it was accepted via resource file\n        if (!getEnvMap().containsKey(\"LICENSE\")) {\n            LicenseAcceptance.assertLicenseAccepted(this.getDockerImageName());\n            acceptLicense();\n        }\n\n        addEnv(\"DBNAME\", databaseName);\n        addEnv(\"DB2INSTANCE\", username);\n        addEnv(\"DB2INST1_PASSWORD\", password);\n\n        // These settings help the DB2 container start faster\n        if (!getEnvMap().containsKey(\"AUTOCONFIG\")) {\n            addEnv(\"AUTOCONFIG\", \"false\");\n        }\n        if (!getEnvMap().containsKey(\"ARCHIVE_LOGS\")) {\n            addEnv(\"ARCHIVE_LOGS\", \"false\");\n        }\n    }\n\n    /**\n     * Accepts the license for the DB2 container by setting the LICENSE=accept\n     * variable as described at <a href=\"https://hub.docker.com/r/ibmcom/db2\">https://hub.docker.com/r/ibmcom/db2</a>\n     */\n    public Db2Container acceptLicense() {\n        addEnv(\"LICENSE\", \"accept\");\n        return this;\n    }\n\n    @Override\n    public String getDriverClassName() {\n        return \"com.ibm.db2.jcc.DB2Driver\";\n    }\n\n    @Override\n    public String getJdbcUrl() {\n        String additionalUrlParams = constructUrlParameters(\":\", \";\", \";\");\n        return \"jdbc:db2://\" + getHost() + \":\" + getMappedPort(DB2_PORT) + \"/\" + databaseName + additionalUrlParams;\n    }\n\n    @Override\n    public String getUsername() {\n        return username;\n    }\n\n    @Override\n    public String getPassword() {\n        return password;\n    }\n\n    @Override\n    public String getDatabaseName() {\n        return databaseName;\n    }\n\n    @Override\n    public Db2Container withUsername(String username) {\n        this.username = username;\n        return this;\n    }\n\n    @Override\n    public Db2Container withPassword(String password) {\n        this.password = password;\n        return this;\n    }\n\n    @Override\n    public Db2Container withDatabaseName(String dbName) {\n        this.databaseName = dbName;\n        return this;\n    }\n\n    @Override\n    protected void waitUntilContainerStarted() {\n        getWaitStrategy().waitUntilReady(this);\n    }\n\n    @Override\n    protected String getTestQueryString() {\n        return \"SELECT 1 FROM SYSIBM.SYSDUMMY1\";\n    }\n}\n"
  },
  {
    "path": "modules/db2/src/main/java/org/testcontainers/containers/Db2ContainerProvider.java",
    "content": "package org.testcontainers.containers;\n\nimport org.testcontainers.utility.DockerImageName;\n\npublic class Db2ContainerProvider extends JdbcDatabaseContainerProvider {\n\n    @Override\n    public boolean supports(String databaseType) {\n        return databaseType.equals(Db2Container.NAME);\n    }\n\n    @Override\n    public JdbcDatabaseContainer newInstance() {\n        return newInstance(Db2Container.DEFAULT_TAG);\n    }\n\n    @Override\n    public JdbcDatabaseContainer newInstance(String tag) {\n        return new Db2Container(DockerImageName.parse(Db2Container.DEFAULT_DB2_IMAGE_NAME).withTag(tag));\n    }\n}\n"
  },
  {
    "path": "modules/db2/src/main/java/org/testcontainers/db2/Db2Container.java",
    "content": "package org.testcontainers.db2;\n\nimport com.github.dockerjava.api.model.Capability;\nimport org.testcontainers.containers.JdbcDatabaseContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.LicenseAcceptance;\n\nimport java.time.Duration;\nimport java.util.Set;\n\n/**\n * Testcontainers implementation for IBM DB2.\n * <p>\n * Supported images: {@code icr.io/db2_community/db2}, {@code ibmcom/db2}\n * <p>\n * Exposed ports:\n * <ul>\n *     <li>Database: 50000</li>\n * </ul>\n */\npublic class Db2Container extends JdbcDatabaseContainer<Db2Container> {\n\n    public static final String NAME = \"db2\";\n\n    private static final DockerImageName DEFAULT_NEW_IMAGE_NAME = DockerImageName.parse(\"icr.io/db2_community/db2\");\n\n    public static final int DB2_PORT = 50000;\n\n    private String databaseName = \"test\";\n\n    private String username = \"db2inst1\";\n\n    private String password = \"foobar1234\";\n\n    public Db2Container(String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    public Db2Container(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_NEW_IMAGE_NAME);\n\n        withCreateContainerCmdModifier(cmd -> cmd.withCapAdd(Capability.IPC_LOCK).withCapAdd(Capability.IPC_OWNER));\n        waitingFor(Wait.forLogMessage(\".*Setup has completed\\\\..*\", 1).withStartupTimeout(Duration.ofMinutes(10)));\n\n        addExposedPort(DB2_PORT);\n    }\n\n    /**\n     * @return the ports on which to check if the container is ready\n     * @deprecated use {@link #getLivenessCheckPortNumbers()} instead\n     */\n    @Override\n    @Deprecated\n    protected Set<Integer> getLivenessCheckPorts() {\n        return super.getLivenessCheckPorts();\n    }\n\n    @Override\n    protected void configure() {\n        // If license was not accepted programmatically, check if it was accepted via resource file\n        if (!getEnvMap().containsKey(\"LICENSE\")) {\n            LicenseAcceptance.assertLicenseAccepted(this.getDockerImageName());\n            acceptLicense();\n        }\n\n        addEnv(\"DBNAME\", databaseName);\n        addEnv(\"DB2INSTANCE\", username);\n        addEnv(\"DB2INST1_PASSWORD\", password);\n\n        // These settings help the DB2 container start faster\n        if (!getEnvMap().containsKey(\"AUTOCONFIG\")) {\n            addEnv(\"AUTOCONFIG\", \"false\");\n        }\n        if (!getEnvMap().containsKey(\"ARCHIVE_LOGS\")) {\n            addEnv(\"ARCHIVE_LOGS\", \"false\");\n        }\n    }\n\n    /**\n     * Accepts the license for the DB2 container by setting the LICENSE=accept\n     * variable as described at <a href=\"https://hub.docker.com/r/ibmcom/db2\">https://hub.docker.com/r/ibmcom/db2</a>\n     */\n    public Db2Container acceptLicense() {\n        addEnv(\"LICENSE\", \"accept\");\n        return this;\n    }\n\n    @Override\n    public String getDriverClassName() {\n        return \"com.ibm.db2.jcc.DB2Driver\";\n    }\n\n    @Override\n    public String getJdbcUrl() {\n        String additionalUrlParams = constructUrlParameters(\":\", \";\", \";\");\n        return \"jdbc:db2://\" + getHost() + \":\" + getMappedPort(DB2_PORT) + \"/\" + databaseName + additionalUrlParams;\n    }\n\n    @Override\n    public String getUsername() {\n        return username;\n    }\n\n    @Override\n    public String getPassword() {\n        return password;\n    }\n\n    @Override\n    public String getDatabaseName() {\n        return databaseName;\n    }\n\n    @Override\n    public Db2Container withUsername(String username) {\n        this.username = username;\n        return this;\n    }\n\n    @Override\n    public Db2Container withPassword(String password) {\n        this.password = password;\n        return this;\n    }\n\n    @Override\n    public Db2Container withDatabaseName(String dbName) {\n        this.databaseName = dbName;\n        return this;\n    }\n\n    @Override\n    protected void waitUntilContainerStarted() {\n        getWaitStrategy().waitUntilReady(this);\n    }\n\n    @Override\n    protected String getTestQueryString() {\n        return \"SELECT 1 FROM SYSIBM.SYSDUMMY1\";\n    }\n}\n"
  },
  {
    "path": "modules/db2/src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider",
    "content": "org.testcontainers.containers.Db2ContainerProvider\n"
  },
  {
    "path": "modules/db2/src/test/java/org/testcontainers/Db2TestImages.java",
    "content": "package org.testcontainers;\n\nimport org.testcontainers.utility.DockerImageName;\n\npublic interface Db2TestImages {\n    DockerImageName DB2_IMAGE = DockerImageName.parse(\"icr.io/db2_community/db2:11.5.8.0\");\n}\n"
  },
  {
    "path": "modules/db2/src/test/java/org/testcontainers/db2/Db2ContainerTest.java",
    "content": "package org.testcontainers.db2;\n\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.Db2TestImages;\nimport org.testcontainers.db.AbstractContainerDatabaseTest;\n\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass Db2ContainerTest extends AbstractContainerDatabaseTest {\n\n    @Test\n    void testSimple() throws SQLException {\n        try ( // container {\n            Db2Container db2 = new Db2Container(\"icr.io/db2_community/db2:11.5.8.0\").acceptLicense()\n            // }\n        ) {\n            db2.start();\n\n            ResultSet resultSet = performQuery(db2, \"SELECT 1 FROM SYSIBM.SYSDUMMY1\");\n\n            int resultSetInt = resultSet.getInt(1);\n            assertThat(resultSetInt).as(\"A basic SELECT query succeeds\").isEqualTo(1);\n            assertHasCorrectExposedAndLivenessCheckPorts(db2);\n        }\n    }\n\n    @Test\n    void testSimpleWithNewImage() throws SQLException {\n        try (Db2Container db2 = new Db2Container(\"icr.io/db2_community/db2:11.5.8.0\").acceptLicense()) {\n            db2.start();\n\n            ResultSet resultSet = performQuery(db2, \"SELECT 1 FROM SYSIBM.SYSDUMMY1\");\n\n            int resultSetInt = resultSet.getInt(1);\n            assertThat(resultSetInt).as(\"A basic SELECT query succeeds\").isEqualTo(1);\n            assertHasCorrectExposedAndLivenessCheckPorts(db2);\n        }\n    }\n\n    @Test\n    void testWithAdditionalUrlParamInJdbcUrl() {\n        try (\n            Db2Container db2 = new Db2Container(Db2TestImages.DB2_IMAGE)\n                .withUrlParam(\"sslConnection\", \"false\")\n                .acceptLicense()\n        ) {\n            db2.start();\n\n            String jdbcUrl = db2.getJdbcUrl();\n            assertThat(jdbcUrl).contains(\":sslConnection=false;\");\n        }\n    }\n\n    private void assertHasCorrectExposedAndLivenessCheckPorts(Db2Container db2) {\n        assertThat(db2.getExposedPorts()).containsExactly(Db2Container.DB2_PORT);\n        assertThat(db2.getLivenessCheckPortNumbers()).containsExactly(db2.getMappedPort(Db2Container.DB2_PORT));\n    }\n}\n"
  },
  {
    "path": "modules/db2/src/test/java/org/testcontainers/jdbc/db2/DB2JDBCDriverTest.java",
    "content": "package org.testcontainers.jdbc.db2;\n\nimport org.testcontainers.jdbc.AbstractJDBCDriverTest;\n\nimport java.util.Arrays;\nimport java.util.EnumSet;\n\nclass DB2JDBCDriverTest extends AbstractJDBCDriverTest {\n\n    public static Iterable<Object[]> data() {\n        return Arrays.asList(\n            new Object[][] { //\n                { \"jdbc:tc:db2://hostname/databasename\", EnumSet.noneOf(Options.class) },\n            }\n        );\n    }\n}\n"
  },
  {
    "path": "modules/db2/src/test/resources/container-license-acceptance.txt",
    "content": "ibmcom/db2:11.5.0.0a\n"
  },
  {
    "path": "modules/db2/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/elasticsearch/build.gradle",
    "content": "description = \"Testcontainers :: elasticsearch\"\n\ndependencies {\n    api project(':testcontainers')\n\n    testImplementation \"org.elasticsearch.client:elasticsearch-rest-client:9.2.2\"\n    testImplementation \"org.elasticsearch.client:transport:7.17.29\"\n}\n"
  },
  {
    "path": "modules/elasticsearch/src/main/java/org/testcontainers/elasticsearch/ElasticsearchContainer.java",
    "content": "package org.testcontainers.elasticsearch;\n\nimport com.github.dockerjava.api.exception.NotFoundException;\nimport lombok.extern.slf4j.Slf4j;\nimport org.apache.commons.io.IOUtils;\nimport org.apache.commons.lang3.StringUtils;\nimport org.testcontainers.containers.BindMode;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.utility.ComparableVersion;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.ByteArrayInputStream;\nimport java.net.InetSocketAddress;\nimport java.security.KeyStore;\nimport java.security.cert.Certificate;\nimport java.security.cert.CertificateFactory;\nimport java.util.Optional;\n\nimport javax.net.ssl.SSLContext;\nimport javax.net.ssl.TrustManagerFactory;\n\n/**\n * Testcontainers implementation for Elasticsearch.\n * <p>\n * Supported image: {@code docker.elastic.co/elasticsearch/elasticsearch}, {@code elasticsearch}\n * <p>\n * Exposed ports:\n * <ul>\n *     <li>HTTP: 9200</li>\n *     <li>TCP Transport: 9300</li>\n * </ul>\n */\n@Slf4j\npublic class ElasticsearchContainer extends GenericContainer<ElasticsearchContainer> {\n\n    /**\n     * Elasticsearch Default Password for Elasticsearch &gt;= 8\n     */\n    public static final String ELASTICSEARCH_DEFAULT_PASSWORD = \"changeme\";\n\n    /**\n     * Elasticsearch Default HTTP port\n     */\n    private static final int ELASTICSEARCH_DEFAULT_PORT = 9200;\n\n    /**\n     * Elasticsearch Default Transport port\n     * The TransportClient will be removed in Elasticsearch 8. No need to expose this port anymore in the future.\n     */\n    @Deprecated\n    private static final int ELASTICSEARCH_DEFAULT_TCP_PORT = 9300;\n\n    /**\n     * Elasticsearch Docker base image\n     */\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\n        \"docker.elastic.co/elasticsearch/elasticsearch\"\n    );\n\n    @Deprecated\n    private static final DockerImageName DEFAULT_OSS_IMAGE_NAME = DockerImageName.parse(\n        \"docker.elastic.co/elasticsearch/elasticsearch-oss\"\n    );\n\n    private static final DockerImageName ELASTICSEARCH_IMAGE_NAME = DockerImageName.parse(\"elasticsearch\");\n\n    // default location of the automatically generated self-signed HTTP cert for versions >= 8\n    private static final String DEFAULT_CERT_PATH = \"/usr/share/elasticsearch/config/certs/http_ca.crt\";\n\n    @Deprecated\n    private boolean isOss = false;\n\n    private final boolean isAtLeastMajorVersion8;\n\n    private String certPath = \"\";\n\n    /**\n     * Create an Elasticsearch Container by passing the full docker image name\n     *\n     * @param dockerImageName Full docker image name as a {@link String}, like: docker.elastic.co/elasticsearch/elasticsearch:7.9.2\n     */\n    public ElasticsearchContainer(String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    /**\n     * Create an Elasticsearch Container by passing the full docker image name\n     *\n     * @param dockerImageName Full docker image name as a {@link DockerImageName}, like: DockerImageName.parse(\"docker.elastic.co/elasticsearch/elasticsearch:7.9.2\")\n     */\n    public ElasticsearchContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME, DEFAULT_OSS_IMAGE_NAME, ELASTICSEARCH_IMAGE_NAME);\n\n        if (dockerImageName.isCompatibleWith(DEFAULT_OSS_IMAGE_NAME)) {\n            this.isOss = true;\n            log.warn(\n                \"{} is not supported anymore after 7.10.2. Please switch to {}\",\n                dockerImageName.getUnversionedPart(),\n                DEFAULT_IMAGE_NAME.getUnversionedPart()\n            );\n        }\n\n        withEnv(\"discovery.type\", \"single-node\");\n        // disable disk threshold checks\n        withEnv(\"cluster.routing.allocation.disk.threshold_enabled\", \"false\");\n        // Sets default memory of elasticsearch instance to 2GB\n        // Spaces are deliberate to allow user to define additional jvm options as elasticsearch resolves option files lexicographically\n        withClasspathResourceMapping(\n            \"elasticsearch-default-memory-vm.options\",\n            \"/usr/share/elasticsearch/config/jvm.options.d/ elasticsearch-default-memory-vm.options\",\n            BindMode.READ_ONLY\n        );\n        addExposedPorts(ELASTICSEARCH_DEFAULT_PORT, ELASTICSEARCH_DEFAULT_TCP_PORT);\n        this.isAtLeastMajorVersion8 =\n            new ComparableVersion(dockerImageName.getVersionPart()).isGreaterThanOrEqualTo(\"8.0.0\");\n        // regex that\n        //   matches 8.3 JSON logging with started message and some follow up content within the message field\n        //   matches 8.0 JSON logging with no whitespace between message field and content\n        //   matches 7.x JSON logging with whitespace between message field and content\n        //   matches 6.x text logging with node name in brackets and just a 'started' message till the end of the line\n        String regex = \".*(\\\"message\\\":\\\\s?\\\"started[\\\\s?|\\\"].*|] started\\n$)\";\n        setWaitStrategy(Wait.forLogMessage(regex, 1));\n        if (isAtLeastMajorVersion8) {\n            withPassword(ELASTICSEARCH_DEFAULT_PASSWORD);\n            withCertPath(DEFAULT_CERT_PATH);\n        }\n    }\n\n    /**\n     * If this is running above Elasticsearch 8, this will return the probably self-signed CA cert that has been extracted\n     *\n     * @return byte array optional containing the CA cert extracted from the docker container\n     */\n    public Optional<byte[]> caCertAsBytes() {\n        if (StringUtils.isBlank(certPath)) {\n            return Optional.empty();\n        }\n        try {\n            byte[] bytes = copyFileFromContainer(certPath, IOUtils::toByteArray);\n            if (bytes.length > 0) {\n                return Optional.of(bytes);\n            }\n        } catch (NotFoundException e) {\n            // just emit an error message, but do not throw an exception\n            // this might be ok, if the docker image is accidentally looking like version 8 or latest\n            // can happen if Elasticsearch is repackaged, i.e. with custom plugins\n            log.warn(\"CA cert under \" + certPath + \" not found.\");\n        }\n        return Optional.empty();\n    }\n\n    /**\n     * A SSL context based on the self-signed CA, so that using this SSL Context allows to connect to the Elasticsearch service\n     * @return a customized SSL Context\n     */\n    public SSLContext createSslContextFromCa() {\n        try {\n            CertificateFactory factory = CertificateFactory.getInstance(\"X.509\");\n            Certificate trustedCa = factory.generateCertificate(\n                new ByteArrayInputStream(\n                    caCertAsBytes()\n                        .orElseThrow(() -> new IllegalStateException(\"CA cert under \" + certPath + \" not found.\"))\n                )\n            );\n            KeyStore trustStore = KeyStore.getInstance(\"pkcs12\");\n            trustStore.load(null, null);\n            trustStore.setCertificateEntry(\"ca\", trustedCa);\n\n            final SSLContext sslContext = SSLContext.getInstance(\"TLSv1.3\");\n            TrustManagerFactory tmfactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());\n            tmfactory.init(trustStore);\n            sslContext.init(null, tmfactory.getTrustManagers(), null);\n            return sslContext;\n        } catch (Exception e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    /**\n     * Define the Elasticsearch password to set. It enables security behind the scene for major version below 8.0.0.\n     * It's not possible to use security with the oss image.\n     * @param password Password to set\n     * @return this\n     */\n    public ElasticsearchContainer withPassword(String password) {\n        if (isOss) {\n            throw new IllegalArgumentException(\n                \"You can not activate security on Elastic OSS Image. Please switch to the default distribution\"\n            );\n        }\n        withEnv(\"ELASTIC_PASSWORD\", password);\n        if (!isAtLeastMajorVersion8) {\n            // major version 8 is secure by default and does not need this to enable authentication\n            withEnv(\"xpack.security.enabled\", \"true\");\n        }\n        return this;\n    }\n\n    /**\n     * Configure a CA cert path that is not the default\n     *\n     * @param certPath Path to the CA certificate within the Docker container to extract it from after start up\n     * @return this\n     */\n    public ElasticsearchContainer withCertPath(String certPath) {\n        this.certPath = certPath;\n        return this;\n    }\n\n    public String getHttpHostAddress() {\n        return getHost() + \":\" + getMappedPort(ELASTICSEARCH_DEFAULT_PORT);\n    }\n\n    // The TransportClient will be removed in Elasticsearch 8. No need to expose this port anymore in the future.\n    @Deprecated\n    public InetSocketAddress getTcpHost() {\n        return new InetSocketAddress(getHost(), getMappedPort(ELASTICSEARCH_DEFAULT_TCP_PORT));\n    }\n}\n"
  },
  {
    "path": "modules/elasticsearch/src/main/resources/elasticsearch-default-memory-vm.options",
    "content": "-Xms2147483648\n-Xmx2147483648\n-Dingest.geoip.downloader.enabled.default=false\n"
  },
  {
    "path": "modules/elasticsearch/src/test/java/org/testcontainers/elasticsearch/ElasticsearchContainerTest.java",
    "content": "package org.testcontainers.elasticsearch;\n\nimport com.github.dockerjava.api.DockerClient;\nimport org.apache.http.HttpHost;\nimport org.apache.http.auth.AuthScope;\nimport org.apache.http.auth.UsernamePasswordCredentials;\nimport org.apache.http.client.CredentialsProvider;\nimport org.apache.http.impl.client.BasicCredentialsProvider;\nimport org.apache.http.util.EntityUtils;\nimport org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse;\nimport org.elasticsearch.client.Request;\nimport org.elasticsearch.client.Response;\nimport org.elasticsearch.client.ResponseException;\nimport org.elasticsearch.client.RestClient;\nimport org.elasticsearch.client.transport.TransportClient;\nimport org.elasticsearch.common.settings.Settings;\nimport org.elasticsearch.common.transport.TransportAddress;\nimport org.elasticsearch.transport.client.PreBuiltTransportClient;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.DockerClientFactory;\nimport org.testcontainers.containers.BindMode;\nimport org.testcontainers.containers.wait.strategy.HttpWaitStrategy;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.images.RemoteDockerImage;\nimport org.testcontainers.images.builder.Transferable;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.MountableFile;\n\nimport java.io.IOException;\n\nimport javax.net.ssl.SSLHandshakeException;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.catchThrowable;\n\nclass ElasticsearchContainerTest {\n\n    /**\n     * Elasticsearch version which should be used for the Tests\n     */\n    private static final String ELASTICSEARCH_VERSION = \"7.9.2\";\n\n    private static final DockerImageName ELASTICSEARCH_IMAGE = DockerImageName\n        .parse(\"docker.elastic.co/elasticsearch/elasticsearch\")\n        .withTag(ELASTICSEARCH_VERSION);\n\n    /**\n     * Elasticsearch default username, when secured\n     */\n    private static final String ELASTICSEARCH_USERNAME = \"elastic\";\n\n    /**\n     * From 6.8, we can optionally activate security with a default password.\n     */\n    private static final String ELASTICSEARCH_PASSWORD = \"changeme\";\n\n    private RestClient client = null;\n\n    private RestClient anonymousClient = null;\n\n    @AfterEach\n    public void stopRestClient() throws IOException {\n        if (client != null) {\n            client.close();\n            client = null;\n        }\n        if (anonymousClient != null) {\n            anonymousClient.close();\n            anonymousClient = null;\n        }\n    }\n\n    @SuppressWarnings(\"deprecation\") // Using deprecated constructor for verification of backwards compatibility\n    @Test\n    @Deprecated // We will remove this test in the future\n    void elasticsearchDeprecatedCtorTest() throws IOException {\n        // Create the elasticsearch container.\n        try (\n            ElasticsearchContainer container = new ElasticsearchContainer(ELASTICSEARCH_IMAGE).withEnv(\"foo\", \"bar\") // dummy env for compiler checking correct generics usage\n        ) {\n            // Start the container. This step might take some time...\n            container.start();\n\n            // Do whatever you want with the rest client ...\n            Response response = getClient(container).performRequest(new Request(\"GET\", \"/\"));\n            assertThat(response.getStatusLine().getStatusCode()).isEqualTo(200);\n            assertThat(EntityUtils.toString(response.getEntity())).contains(ELASTICSEARCH_VERSION);\n\n            // The default image is running with the features under Elastic License\n            response = getClient(container).performRequest(new Request(\"GET\", \"/_xpack/\"));\n            assertThat(response.getStatusLine().getStatusCode()).isEqualTo(200);\n            // For now we test that we have the monitoring feature available\n            assertThat(EntityUtils.toString(response.getEntity())).contains(\"monitoring\");\n        }\n    }\n\n    @Test\n    void elasticsearchDefaultTest() throws IOException {\n        // Create the elasticsearch container.\n        try (\n            ElasticsearchContainer container = new ElasticsearchContainer(ELASTICSEARCH_IMAGE).withEnv(\"foo\", \"bar\") // dummy env for compiler checking correct generics usage\n        ) {\n            // Start the container. This step might take some time...\n            container.start();\n\n            // Do whatever you want with the rest client ...\n            Response response = getClient(container).performRequest(new Request(\"GET\", \"/\"));\n            assertThat(response.getStatusLine().getStatusCode()).isEqualTo(200);\n            assertThat(EntityUtils.toString(response.getEntity())).contains(ELASTICSEARCH_VERSION);\n\n            // The default image is running with the features under Elastic License\n            response = getClient(container).performRequest(new Request(\"GET\", \"/_xpack/\"));\n            assertThat(response.getStatusLine().getStatusCode()).isEqualTo(200);\n            // For now we test that we have the monitoring feature available\n            assertThat(EntityUtils.toString(response.getEntity())).contains(\"monitoring\");\n        }\n    }\n\n    @Test\n    void elasticsearchSecuredTest() throws IOException {\n        try (\n            ElasticsearchContainer container = new ElasticsearchContainer(ELASTICSEARCH_IMAGE)\n                .withPassword(ELASTICSEARCH_PASSWORD)\n        ) {\n            container.start();\n\n            // The cluster should be secured so it must fail when we try to access / without credentials\n            assertThat(catchThrowable(() -> getAnonymousClient(container).performRequest(new Request(\"GET\", \"/\"))))\n                .as(\"We should not be able to access / URI with an anonymous client.\")\n                .isInstanceOf(ResponseException.class);\n\n            // But it should work when we try to access / with the proper login and password\n            Response response = getClient(container).performRequest(new Request(\"GET\", \"/\"));\n            assertThat(response.getStatusLine().getStatusCode()).isEqualTo(200);\n            assertThat(EntityUtils.toString(response.getEntity())).contains(ELASTICSEARCH_VERSION);\n        }\n    }\n\n    @Test\n    void elasticsearchVersion() throws IOException {\n        try (ElasticsearchContainer container = new ElasticsearchContainer(ELASTICSEARCH_IMAGE)) {\n            container.start();\n            Response response = getClient(container).performRequest(new Request(\"GET\", \"/\"));\n            assertThat(response.getStatusLine().getStatusCode()).isEqualTo(200);\n            String responseAsString = EntityUtils.toString(response.getEntity());\n            assertThat(responseAsString).contains(ELASTICSEARCH_VERSION);\n        }\n    }\n\n    @Test\n    void elasticsearchVersion83() throws IOException {\n        try (\n            ElasticsearchContainer container = new ElasticsearchContainer(\n                \"docker.elastic.co/elasticsearch/elasticsearch:8.3.0\"\n            )\n        ) {\n            container.start();\n            Response response = getClient(container).performRequest(new Request(\"GET\", \"/\"));\n            assertThat(response.getStatusLine().getStatusCode()).isEqualTo(200);\n            assertThat(EntityUtils.toString(response.getEntity())).contains(\"8.3.0\");\n        }\n    }\n\n    @Test\n    void elasticsearchOssImage() throws IOException {\n        try (\n            // ossContainer {\n            ElasticsearchContainer container = new ElasticsearchContainer(\n                \"docker.elastic.co/elasticsearch/elasticsearch-oss:7.10.2\"\n            )\n            // }\n        ) {\n            container.start();\n            Response response = getClient(container).performRequest(new Request(\"GET\", \"/\"));\n            assertThat(response.getStatusLine().getStatusCode()).isEqualTo(200);\n            // The OSS image does not have any feature under Elastic License\n            assertThat(catchThrowable(() -> getClient(container).performRequest(new Request(\"GET\", \"/_xpack/\"))))\n                .as(\"We should not have /_xpack endpoint with an OSS License\")\n                .isInstanceOf(ResponseException.class);\n        }\n    }\n\n    @Test\n    void restClientClusterHealth() throws IOException {\n        // httpClientContainer7 {\n        // Create the elasticsearch container.\n        try (ElasticsearchContainer container = new ElasticsearchContainer(ELASTICSEARCH_IMAGE)) {\n            // Start the container. This step might take some time...\n            container.start();\n\n            // Do whatever you want with the rest client ...\n            final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();\n            credentialsProvider.setCredentials(\n                AuthScope.ANY,\n                new UsernamePasswordCredentials(ELASTICSEARCH_USERNAME, ELASTICSEARCH_PASSWORD)\n            );\n\n            client =\n                RestClient\n                    .builder(HttpHost.create(container.getHttpHostAddress()))\n                    .setHttpClientConfigCallback(httpClientBuilder -> {\n                        return httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);\n                    })\n                    .build();\n\n            Response response = client.performRequest(new Request(\"GET\", \"/_cluster/health\"));\n            // }}\n            assertThat(response.getStatusLine().getStatusCode()).isEqualTo(200);\n            assertThat(EntityUtils.toString(response.getEntity())).contains(\"cluster_name\");\n            // httpClientContainer7 {{\n        }\n        // }\n    }\n\n    @Test\n    void restClientClusterHealthElasticsearch8() throws IOException {\n        // httpClientContainer8 {\n        // Create the elasticsearch container.\n        try (\n            ElasticsearchContainer container = new ElasticsearchContainer(\n                \"docker.elastic.co/elasticsearch/elasticsearch:8.1.2\"\n            )\n        ) {\n            // Start the container. This step might take some time...\n            container.start();\n\n            // Do whatever you want with the rest client ...\n            final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();\n            credentialsProvider.setCredentials(\n                AuthScope.ANY,\n                new UsernamePasswordCredentials(ELASTICSEARCH_USERNAME, ELASTICSEARCH_PASSWORD)\n            );\n\n            client =\n                RestClient\n                    // use HTTPS for Elasticsearch 8\n                    .builder(HttpHost.create(\"https://\" + container.getHttpHostAddress()))\n                    .setHttpClientConfigCallback(httpClientBuilder -> {\n                        httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);\n                        // SSL is activated by default in Elasticsearch 8\n                        httpClientBuilder.setSSLContext(container.createSslContextFromCa());\n                        return httpClientBuilder;\n                    })\n                    .build();\n\n            Response response = client.performRequest(new Request(\"GET\", \"/_cluster/health\"));\n            // }}\n            assertThat(response.getStatusLine().getStatusCode()).isEqualTo(200);\n            assertThat(EntityUtils.toString(response.getEntity())).contains(\"cluster_name\");\n            // httpClientContainer8 {{\n        }\n        // }\n    }\n\n    @Test\n    void restClientClusterHealthElasticsearch8WithoutSSL() throws IOException {\n        // httpClientContainerNoSSL8 {\n        // Create the elasticsearch container.\n        try (\n            ElasticsearchContainer container = new ElasticsearchContainer(\n                \"docker.elastic.co/elasticsearch/elasticsearch:8.1.2\"\n            )\n                // disable SSL\n                .withEnv(\"xpack.security.transport.ssl.enabled\", \"false\")\n                .withEnv(\"xpack.security.http.ssl.enabled\", \"false\")\n        ) {\n            // Start the container. This step might take some time...\n            container.start();\n\n            // Do whatever you want with the rest client ...\n            final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();\n            credentialsProvider.setCredentials(\n                AuthScope.ANY,\n                new UsernamePasswordCredentials(ELASTICSEARCH_USERNAME, ELASTICSEARCH_PASSWORD)\n            );\n\n            client =\n                RestClient\n                    .builder(HttpHost.create(container.getHttpHostAddress()))\n                    .setHttpClientConfigCallback(httpClientBuilder -> {\n                        return httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);\n                    })\n                    .build();\n\n            Response response = client.performRequest(new Request(\"GET\", \"/_cluster/health\"));\n            // }}\n            assertThat(response.getStatusLine().getStatusCode()).isEqualTo(200);\n            assertThat(EntityUtils.toString(response.getEntity())).contains(\"cluster_name\");\n            // httpClientContainerNoSSL8 {{\n        }\n        // }\n    }\n\n    @Test\n    void restClientSecuredClusterHealth() throws IOException {\n        // httpClientSecuredContainer {\n        // Create the elasticsearch container.\n        try (\n            ElasticsearchContainer container = new ElasticsearchContainer(ELASTICSEARCH_IMAGE)\n                // With a password\n                .withPassword(ELASTICSEARCH_PASSWORD)\n        ) {\n            // Start the container. This step might take some time...\n            container.start();\n\n            // Create the secured client.\n            final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();\n            credentialsProvider.setCredentials(\n                AuthScope.ANY,\n                new UsernamePasswordCredentials(ELASTICSEARCH_USERNAME, ELASTICSEARCH_PASSWORD)\n            );\n\n            client =\n                RestClient\n                    .builder(HttpHost.create(container.getHttpHostAddress()))\n                    .setHttpClientConfigCallback(httpClientBuilder -> {\n                        return httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);\n                    })\n                    .build();\n\n            Response response = client.performRequest(new Request(\"GET\", \"/_cluster/health\"));\n            // }}\n            assertThat(response.getStatusLine().getStatusCode()).isEqualTo(200);\n            assertThat(EntityUtils.toString(response.getEntity())).contains(\"cluster_name\");\n            // httpClientSecuredContainer {{\n        }\n        // }\n    }\n\n    @SuppressWarnings(\"deprecation\") // The TransportClient will be removed in Elasticsearch 8.\n    @Test\n    void transportClientClusterHealth() {\n        // transportClientContainer {\n        // Create the elasticsearch container.\n        try (ElasticsearchContainer container = new ElasticsearchContainer(ELASTICSEARCH_IMAGE)) {\n            // Start the container. This step might take some time...\n            container.start();\n\n            // Do whatever you want with the transport client\n            TransportAddress transportAddress = new TransportAddress(container.getTcpHost());\n            String expectedClusterName = \"docker-cluster\";\n            Settings settings = Settings.builder().put(\"cluster.name\", expectedClusterName).build();\n            try (\n                TransportClient transportClient = new PreBuiltTransportClient(settings)\n                    .addTransportAddress(transportAddress)\n            ) {\n                ClusterHealthResponse healths = transportClient.admin().cluster().prepareHealth().get();\n                String clusterName = healths.getClusterName();\n                // }}}\n                assertThat(clusterName).isEqualTo(expectedClusterName);\n                // transportClientContainer {{{\n            }\n        }\n        // }\n    }\n\n    @Test\n    void incompatibleSettingsTest() {\n        // The OSS image can not use security feature\n        assertThat(\n            catchThrowable(() -> {\n                new ElasticsearchContainer(\"docker.elastic.co/elasticsearch/elasticsearch-oss:7.10.2\")\n                    .withPassword(\"foo\");\n            })\n        )\n            .as(\"We should not be able to activate security with an OSS License\")\n            .isInstanceOf(IllegalArgumentException.class);\n    }\n\n    @Test\n    void testDockerHubElasticsearch8ImageSecureByDefault() throws Exception {\n        try (ElasticsearchContainer container = new ElasticsearchContainer(\"elasticsearch:8.1.2\")) {\n            container.start();\n\n            assertClusterHealthResponse(container);\n        }\n    }\n\n    @Test\n    void testElasticsearch8SecureByDefaultCustomCaCertFails() throws Exception {\n        final MountableFile mountableFile = MountableFile.forClasspathResource(\"http_ca.crt\");\n        String caPath = \"/tmp/http_ca.crt\";\n        try (\n            ElasticsearchContainer container = new ElasticsearchContainer(\n                \"docker.elastic.co/elasticsearch/elasticsearch:8.1.2\"\n            )\n                .withCopyToContainer(mountableFile, caPath)\n                .withCertPath(caPath)\n        ) {\n            container.start();\n\n            // this is expected, as a different cert is used for creating the SSL context\n            assertThat(catchThrowable(() -> getClusterHealth(container)))\n                .as(\n                    \"PKIX path validation failed: java.security.cert.CertPathValidatorException: Path does not chain with any of the trust anchors\"\n                )\n                .isInstanceOf(SSLHandshakeException.class);\n        }\n    }\n\n    @Test\n    void testElasticsearch8SecureByDefaultHttpWaitStrategy() throws Exception {\n        final HttpWaitStrategy httpsWaitStrategy = Wait\n            .forHttps(\"/\")\n            .forPort(9200)\n            .forStatusCode(200)\n            .withBasicCredentials(ELASTICSEARCH_USERNAME, ELASTICSEARCH_PASSWORD)\n            // trusting self-signed certificate\n            .allowInsecure();\n\n        try (\n            ElasticsearchContainer container = new ElasticsearchContainer(\n                \"docker.elastic.co/elasticsearch/elasticsearch:8.1.2\"\n            )\n                .waitingFor(httpsWaitStrategy)\n        ) {\n            // Start the container. This step might take some time...\n            container.start();\n\n            assertClusterHealthResponse(container);\n        }\n    }\n\n    @Test\n    void testElasticsearch8SecureByDefaultFailsSilentlyOnLatestImages() throws Exception {\n        // this test exists for custom images by users that use the `latest` tag\n        // even though the version might be older than version 8\n        // this tags an old 7.x version as :latest\n        tagImage(\"docker.elastic.co/elasticsearch/elasticsearch:7.9.2\", \"elasticsearch-tc-older-release\", \"latest\");\n        DockerImageName image = DockerImageName\n            .parse(\"elasticsearch-tc-older-release:latest\")\n            .asCompatibleSubstituteFor(\"docker.elastic.co/elasticsearch/elasticsearch\");\n\n        try (ElasticsearchContainer container = new ElasticsearchContainer(image)) {\n            container.start();\n\n            Response response = getClient(container).performRequest(new Request(\"GET\", \"/_cluster/health\"));\n            assertThat(response.getStatusLine().getStatusCode()).isEqualTo(200);\n            assertThat(EntityUtils.toString(response.getEntity())).contains(\"cluster_name\");\n        }\n    }\n\n    @Test\n    void testElasticsearch7CanHaveSecurityEnabledAndUseSslContext() throws Exception {\n        String customizedCertPath = \"/usr/share/elasticsearch/config/certs/http_ca_customized.crt\";\n        try (\n            ElasticsearchContainer container = new ElasticsearchContainer(\n                \"docker.elastic.co/elasticsearch/elasticsearch:7.17.15\"\n            )\n                .withPassword(ElasticsearchContainer.ELASTICSEARCH_DEFAULT_PASSWORD)\n                .withEnv(\"xpack.security.enabled\", \"true\")\n                .withEnv(\"xpack.security.http.ssl.enabled\", \"true\")\n                .withEnv(\"xpack.security.http.ssl.key\", \"/usr/share/elasticsearch/config/certs/elasticsearch.key\")\n                .withEnv(\n                    \"xpack.security.http.ssl.certificate\",\n                    \"/usr/share/elasticsearch/config/certs/elasticsearch.crt\"\n                )\n                .withEnv(\"xpack.security.http.ssl.certificate_authorities\", customizedCertPath)\n                // these lines show how certificates can be created self-made way\n                // obviously this shouldn't be done in prod environment, where proper and officially signed keys should be present\n                .withCopyToContainer(\n                    Transferable.of(\n                        \"#!/bin/bash\\n\" +\n                        \"mkdir -p /usr/share/elasticsearch/config/certs;\" +\n                        \"openssl req -x509 -newkey rsa:4096 -keyout /usr/share/elasticsearch/config/certs/elasticsearch.key -out /usr/share/elasticsearch/config/certs/elasticsearch.crt -days 365 -nodes -subj \\\"/CN=localhost\\\";\" +\n                        \"openssl x509 -outform der -in /usr/share/elasticsearch/config/certs/elasticsearch.crt -out \" +\n                        customizedCertPath +\n                        \"; chown -R elasticsearch /usr/share/elasticsearch/config/certs/\",\n                        555\n                    ),\n                    \"/usr/share/elasticsearch/generate-certs.sh\"\n                )\n                // because we need to generate the certificates before Elasticsearch starts, the entry command has to be tuned accordingly\n                .withCommand(\n                    \"sh\",\n                    \"-c\",\n                    \"/usr/share/elasticsearch/generate-certs.sh && /usr/local/bin/docker-entrypoint.sh\"\n                )\n                .withCertPath(customizedCertPath)\n        ) {\n            container.start();\n            assertClusterHealthResponse(container);\n        }\n    }\n\n    @Test\n    void testElasticsearchDefaultMaxHeapSize() throws Exception {\n        long defaultHeapSize = 2147483648L;\n\n        try (ElasticsearchContainer container = new ElasticsearchContainer(ELASTICSEARCH_IMAGE)) {\n            container.start();\n            assertElasticsearchContainerHasHeapSize(container, defaultHeapSize);\n        }\n    }\n\n    @Test\n    void testElasticsearchCustomMaxHeapSizeInEnvironmentVariable() throws Exception {\n        long customHeapSize = 1574961152;\n\n        try (\n            ElasticsearchContainer container = new ElasticsearchContainer(ELASTICSEARCH_IMAGE)\n                .withEnv(\"ES_JAVA_OPTS\", String.format(\"-Xms%d  -Xmx%d\", customHeapSize, customHeapSize))\n        ) {\n            container.start();\n            assertElasticsearchContainerHasHeapSize(container, customHeapSize);\n        }\n    }\n\n    @Test\n    void testElasticsearchCustomMaxHeapSizeInJvmOptionsFile() throws Exception {\n        long customHeapSize = 1574961152;\n\n        try (\n            ElasticsearchContainer container = new ElasticsearchContainer(ELASTICSEARCH_IMAGE)\n                .withClasspathResourceMapping(\n                    \"test-custom-memory-jvm.options\",\n                    \"/usr/share/elasticsearch/config/jvm.options.d/a-user-defined-jvm.options\",\n                    BindMode.READ_ONLY\n                );\n        ) {\n            container.start();\n            assertElasticsearchContainerHasHeapSize(container, customHeapSize);\n        }\n    }\n\n    private void tagImage(String sourceImage, String targetImage, String targetTag) throws InterruptedException {\n        DockerClient dockerClient = DockerClientFactory.instance().client();\n        dockerClient\n            .tagImageCmd(new RemoteDockerImage(DockerImageName.parse(sourceImage)).get(), targetImage, targetTag)\n            .exec();\n    }\n\n    private Response getClusterHealth(ElasticsearchContainer container) throws IOException {\n        // Create the secured client.\n        final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();\n        credentialsProvider.setCredentials(\n            AuthScope.ANY,\n            new UsernamePasswordCredentials(\n                ELASTICSEARCH_USERNAME,\n                ElasticsearchContainer.ELASTICSEARCH_DEFAULT_PASSWORD\n            )\n        );\n\n        client =\n            RestClient\n                .builder(HttpHost.create(\"https://\" + container.getHttpHostAddress()))\n                .setHttpClientConfigCallback(httpClientBuilder -> {\n                    httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);\n                    httpClientBuilder.setSSLContext(container.createSslContextFromCa());\n                    return httpClientBuilder;\n                })\n                .build();\n\n        return client.performRequest(new Request(\"GET\", \"/_cluster/health\"));\n    }\n\n    private RestClient getClient(ElasticsearchContainer container) {\n        if (client == null) {\n            final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();\n            credentialsProvider.setCredentials(\n                AuthScope.ANY,\n                new UsernamePasswordCredentials(ELASTICSEARCH_USERNAME, ELASTICSEARCH_PASSWORD)\n            );\n\n            String protocol = container.caCertAsBytes().isPresent() ? \"https://\" : \"http://\";\n\n            client =\n                RestClient\n                    .builder(HttpHost.create(protocol + container.getHttpHostAddress()))\n                    .setHttpClientConfigCallback(httpClientBuilder -> {\n                        if (container.caCertAsBytes().isPresent()) {\n                            httpClientBuilder.setSSLContext(container.createSslContextFromCa());\n                        }\n                        return httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);\n                    })\n                    .build();\n        }\n\n        return client;\n    }\n\n    private RestClient getAnonymousClient(ElasticsearchContainer container) {\n        if (anonymousClient == null) {\n            anonymousClient = RestClient.builder(HttpHost.create(container.getHttpHostAddress())).build();\n        }\n\n        return anonymousClient;\n    }\n\n    private void assertElasticsearchContainerHasHeapSize(ElasticsearchContainer container, long heapSizeInBytes)\n        throws Exception {\n        Response response = getClient(container).performRequest(new Request(\"GET\", \"/_nodes/_all/jvm\"));\n        String responseBody = EntityUtils.toString(response.getEntity());\n        assertThat(response.getStatusLine().getStatusCode()).isEqualTo(200);\n        assertThat(responseBody).contains(\"\\\"heap_init_in_bytes\\\":\" + heapSizeInBytes);\n        assertThat(responseBody).contains(\"\\\"heap_max_in_bytes\\\":\" + heapSizeInBytes);\n    }\n\n    private void assertClusterHealthResponse(ElasticsearchContainer container) throws IOException {\n        Response response = getClusterHealth(container);\n        assertThat(response.getStatusLine().getStatusCode()).isEqualTo(200);\n        assertThat(EntityUtils.toString(response.getEntity())).contains(\"cluster_name\");\n    }\n}\n"
  },
  {
    "path": "modules/elasticsearch/src/test/resources/http_ca.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIIFWjCCA0KgAwIBAgIVAPVLz0Dwvzl66ZVpyJY/ntos+qQZMA0GCSqGSIb3DQEB\nCwUAMDwxOjA4BgNVBAMTMUVsYXN0aWNzZWFyY2ggc2VjdXJpdHkgYXV0by1jb25m\naWd1cmF0aW9uIEhUVFAgQ0EwHhcNMjIwNDE0MDcyOTA4WhcNMjUwNDEzMDcyOTA4\nWjA8MTowOAYDVQQDEzFFbGFzdGljc2VhcmNoIHNlY3VyaXR5IGF1dG8tY29uZmln\ndXJhdGlvbiBIVFRQIENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA\nufHmvM04dPKNF6AulX3HrjNfcYy62pBO2oJIKhScetDzuRupv/qiXkO1gzGT3/jk\n8uk57rzylDFoCgFNUyWdYQAXD+qsy5vbAytGVNCHOGSuWlNm1bbDYwZNTXjZTK6C\nCKIY31lbFn9a4oM+Jp1kvIr8GSMQAYDisq+yrVloDLkRs1SPImnoaXsq8epxloBf\nEn0vo5V8PtOh+xQFpPP21pd5QohCSB+jMaxaizScWX+k7BijEaS1LCsc2w2PNvwn\n/bEvtW7w9w+HnzRGZW2nVlt8eji1PHMmfM5Zugn4HAxsSvdI9VRFGAeT2moNiSLN\nzxOWmEvQVl2MxRWiTkM1EM7CFDN40hLHtHej8UddeXTDDXyLoUjY3FmaB45rLKdE\nk/rszepc3lmptEMh74iUowKaYZTS5jRqT0yIDzevP5je3JP+pe4aNkx7lWMhTReQ\n6fs97nd71PfJBuMcHPEA14zwSzSRb/8mNqqaQLBb5H1DpDcZtzbBxb4wFSAa7Dd0\npVl4A04iB4PS3DaWg/im2C5a83nUTld7Lvy+I6cO1MTaXdkzn6EWXtxkHj7P4VXX\nsFTr+Z2A6g/novqderirQzq8aD87MBp2hLBgG59lVB3IXA+CJTzBRBZipU+FOSs2\n1enMlaEa84d8cb+GSpDkmsvamPMhhLeMw6QLmhQXoWMCAwEAAaNTMFEwHQYDVR0O\nBBYEFOt9JC+RiqfFdGf/UT2vfmnV8f+kMB8GA1UdIwQYMBaAFOt9JC+RiqfFdGf/\nUT2vfmnV8f+kMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIBALFX\nr2CKtu2DcLFQaZft9LeRK67xU0rjN8w1+0MN9otSkU9avd8atPOI9p6r67HZyoTv\nLDA47ajitzPo4zAXiy4GUXFVCjx3iPOQ4TTCm0YEf9IudLHLqGTbK+Pup6XlfAMb\nRejkztcXkxw3Cy6SjIRq99xU8J6Y1jAggB162hqnp3u42nA1PgOJgo/biUYeVtu7\nY01gKPdCEkiqmSFxfLiRPv5Z4WyYoKge6UyDYFHu0zyMY7zQ2hzrPMFsEhI4+g5D\nW7ihilcOijhvfeeWIxcP5lRn2pGbf2GtwqtA7Bt9YKp+NQIBPzK7D1ymS0v/CAWh\n3Bv1rqkqDK8TlBybZitTTB6MgGtXOBccouTPmBFBXSWydvW4GW6Dag/ogHHE2vG2\nxlXY6EC1QEzExcM5FZNJI6SOaK0nl+WKAv060U/1ZqcRIkhyctYdkrK4449n1JMy\nwjtwcDW7QxhQspHp8GEXztLctokqGjnuMcgPjVoFdiF3w/IV0UUvVeFK4Oms0YbH\nuFr3q44Fu/Fol68/1CUk1ytgLUS5anf0Q0WlJsmMUX156ATA29dVBfloJN63EYd7\n01uwbjoMJce7MiwTaLIetW75fxxZHlQK9TMNhaQwKUO8SRaNuE4wKURFoKPg/Dqu\nyPhx9adseStlJ3oV6ziEWMwjOK2JmJf0bmIqQ7KR\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "modules/elasticsearch/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/elasticsearch/src/test/resources/test-custom-memory-jvm.options",
    "content": "-Xms1574961152\n-Xmx1574961152\n"
  },
  {
    "path": "modules/gcloud/build.gradle",
    "content": "description = \"Testcontainers :: GCloud\"\n\ndependencies {\n    api project(':testcontainers')\n\n    testImplementation platform(\"com.google.cloud:libraries-bom:26.72.0\")\n    testImplementation 'com.google.cloud:google-cloud-bigquery'\n    testImplementation 'com.google.cloud:google-cloud-datastore'\n    testImplementation 'com.google.cloud:google-cloud-firestore'\n    testImplementation 'com.google.cloud:google-cloud-pubsub'\n    testImplementation 'com.google.cloud:google-cloud-spanner'\n    testImplementation 'com.google.cloud:google-cloud-bigtable'\n}\n"
  },
  {
    "path": "modules/gcloud/src/main/java/org/testcontainers/containers/BigQueryEmulatorContainer.java",
    "content": "package org.testcontainers.containers;\n\nimport org.testcontainers.utility.DockerImageName;\n\n/**\n * Testcontainers implementation for BigQuery.\n * <p>\n * Supported image: {@code ghcr.io/goccy/bigquery-emulator}\n * <p>\n *\n * @deprecated use {@link org.testcontainers.gcloud.BigQueryEmulatorContainer} instead.\n */\n@Deprecated\npublic class BigQueryEmulatorContainer extends GenericContainer<BigQueryEmulatorContainer> {\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"ghcr.io/goccy/bigquery-emulator\");\n\n    private static final int HTTP_PORT = 9050;\n\n    private static final int GRPC_PORT = 9060;\n\n    private static final String PROJECT_ID = \"test-project\";\n\n    public BigQueryEmulatorContainer(String image) {\n        this(DockerImageName.parse(image));\n    }\n\n    public BigQueryEmulatorContainer(DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n        addExposedPorts(HTTP_PORT, GRPC_PORT);\n        withCommand(\"--project\", PROJECT_ID);\n    }\n\n    public String getEmulatorHttpEndpoint() {\n        return String.format(\"http://%s:%d\", getHost(), getMappedPort(HTTP_PORT));\n    }\n\n    public Integer getEmulatorGrpcPort() {\n        return getMappedPort(GRPC_PORT);\n    }\n\n    public String getProjectId() {\n        return PROJECT_ID;\n    }\n}\n"
  },
  {
    "path": "modules/gcloud/src/main/java/org/testcontainers/containers/BigtableEmulatorContainer.java",
    "content": "package org.testcontainers.containers;\n\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.utility.DockerImageName;\n\n/**\n * A Bigtable container that relies in google cloud sdk.\n * <p>\n * Supported images: {@code gcr.io/google.com/cloudsdktool/google-cloud-cli}, {@code gcr.io/google.com/cloudsdktool/cloud-sdk}\n * <p>\n * Default port is 9000.\n *\n * @deprecated use {@link org.testcontainers.gcloud.BigtableEmulatorContainer} instead.\n */\n@Deprecated\npublic class BigtableEmulatorContainer extends GenericContainer<BigtableEmulatorContainer> {\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\n        \"gcr.io/google.com/cloudsdktool/google-cloud-cli\"\n    );\n\n    private static final DockerImageName CLOUD_SDK_IMAGE_NAME = DockerImageName.parse(\n        \"gcr.io/google.com/cloudsdktool/cloud-sdk\"\n    );\n\n    private static final String CMD = \"gcloud beta emulators bigtable start --host-port 0.0.0.0:9000\";\n\n    private static final int PORT = 9000;\n\n    public BigtableEmulatorContainer(String image) {\n        this(DockerImageName.parse(image));\n    }\n\n    public BigtableEmulatorContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME, CLOUD_SDK_IMAGE_NAME);\n\n        withExposedPorts(PORT);\n        setWaitStrategy(Wait.forLogMessage(\".*running.*$\", 1));\n        withCommand(\"/bin/sh\", \"-c\", CMD);\n    }\n\n    /**\n     * @return a <code>host:port</code> pair corresponding to the address on which the emulator is\n     * reachable from the test host machine. Directly usable as a parameter to the\n     * com.google.cloud.ServiceOptions.Builder#setHost(java.lang.String) method.\n     */\n    public String getEmulatorEndpoint() {\n        return getHost() + \":\" + getEmulatorPort();\n    }\n\n    public int getEmulatorPort() {\n        return getMappedPort(PORT);\n    }\n}\n"
  },
  {
    "path": "modules/gcloud/src/main/java/org/testcontainers/containers/DatastoreEmulatorContainer.java",
    "content": "package org.testcontainers.containers;\n\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.utility.DockerImageName;\n\n/**\n * A Datastore container that relies in google cloud sdk.\n * <p>\n * Supported images: {@code gcr.io/google.com/cloudsdktool/google-cloud-cli}, {@code gcr.io/google.com/cloudsdktool/cloud-sdk}\n * <p>\n * Default port is 8081.\n *\n * @deprecated use {@link org.testcontainers.gcloud.DatastoreEmulatorContainer} instead.\n */\n@Deprecated\npublic class DatastoreEmulatorContainer extends GenericContainer<DatastoreEmulatorContainer> {\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\n        \"gcr.io/google.com/cloudsdktool/google-cloud-cli\"\n    );\n\n    private static final DockerImageName CLOUD_SDK_IMAGE_NAME = DockerImageName.parse(\n        \"gcr.io/google.com/cloudsdktool/cloud-sdk\"\n    );\n\n    private static final String PROJECT_ID = \"test-project\";\n\n    private static final String CMD = String.format(\n        \"gcloud beta emulators datastore start --project %s --host-port 0.0.0.0:8081\",\n        PROJECT_ID\n    );\n\n    private static final int HTTP_PORT = 8081;\n\n    private String flags;\n\n    public DatastoreEmulatorContainer(final String image) {\n        this(DockerImageName.parse(image));\n    }\n\n    public DatastoreEmulatorContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME, CLOUD_SDK_IMAGE_NAME);\n\n        withExposedPorts(HTTP_PORT);\n        setWaitStrategy(Wait.forHttp(\"/\").forStatusCode(200));\n    }\n\n    @Override\n    protected void configure() {\n        String command = CMD;\n        if (this.flags != null && !this.flags.isEmpty()) {\n            command += \" \" + this.flags;\n        }\n        withCommand(\"/bin/sh\", \"-c\", command);\n    }\n\n    public DatastoreEmulatorContainer withFlags(String flags) {\n        this.flags = flags;\n        return this;\n    }\n\n    /**\n     * @return a <code>host:port</code> pair corresponding to the address on which the emulator is\n     * reachable from the test host machine. Directly usable as a parameter to the\n     * com.google.cloud.ServiceOptions.Builder#setHost(java.lang.String) method.\n     */\n    public String getEmulatorEndpoint() {\n        return getHost() + \":\" + getMappedPort(HTTP_PORT);\n    }\n\n    public String getProjectId() {\n        return PROJECT_ID;\n    }\n}\n"
  },
  {
    "path": "modules/gcloud/src/main/java/org/testcontainers/containers/FirestoreEmulatorContainer.java",
    "content": "package org.testcontainers.containers;\n\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.utility.DockerImageName;\n\n/**\n * A Firestore container that relies in google cloud sdk.\n * <p>\n * Supported images: {@code gcr.io/google.com/cloudsdktool/google-cloud-cli}, {@code gcr.io/google.com/cloudsdktool/cloud-sdk}\n * <p>\n * Default port is 8080.\n *\n * @deprecated use {@link org.testcontainers.gcloud.FirestoreEmulatorContainer} instead.\n */\n@Deprecated\npublic class FirestoreEmulatorContainer extends GenericContainer<FirestoreEmulatorContainer> {\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\n        \"gcr.io/google.com/cloudsdktool/google-cloud-cli\"\n    );\n\n    private static final DockerImageName CLOUD_SDK_IMAGE_NAME = DockerImageName.parse(\n        \"gcr.io/google.com/cloudsdktool/cloud-sdk\"\n    );\n\n    private static final String CMD = \"gcloud beta emulators firestore start --host-port 0.0.0.0:8080\";\n\n    private static final int PORT = 8080;\n\n    private String flags;\n\n    public FirestoreEmulatorContainer(String image) {\n        this(DockerImageName.parse(image));\n    }\n\n    public FirestoreEmulatorContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME, CLOUD_SDK_IMAGE_NAME);\n\n        withExposedPorts(PORT);\n        setWaitStrategy(Wait.forLogMessage(\".*running.*$\", 1));\n    }\n\n    @Override\n    protected void configure() {\n        String command = CMD;\n        if (this.flags != null && !this.flags.isEmpty()) {\n            command += \" \" + this.flags;\n        }\n        withCommand(\"/bin/sh\", \"-c\", command);\n    }\n\n    public FirestoreEmulatorContainer withFlags(String flags) {\n        this.flags = flags;\n        return this;\n    }\n\n    /**\n     * @return a <code>host:port</code> pair corresponding to the address on which the emulator is\n     * reachable from the test host machine. Directly usable as a parameter to the\n     * com.google.cloud.ServiceOptions.Builder#setHost(java.lang.String) method.\n     */\n    public String getEmulatorEndpoint() {\n        return getHost() + \":\" + getMappedPort(8080);\n    }\n}\n"
  },
  {
    "path": "modules/gcloud/src/main/java/org/testcontainers/containers/PubSubEmulatorContainer.java",
    "content": "package org.testcontainers.containers;\n\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.utility.DockerImageName;\n\n/**\n * A PubSub container that relies in google cloud sdk.\n * <p>\n * Supported images: {@code gcr.io/google.com/cloudsdktool/google-cloud-cli}, {@code gcr.io/google.com/cloudsdktool/cloud-sdk}\n * <p>\n * Default port is 8085.\n *\n * @deprecated use {@link org.testcontainers.gcloud.PubSubEmulatorContainer} instead.\n */\n@Deprecated\npublic class PubSubEmulatorContainer extends GenericContainer<PubSubEmulatorContainer> {\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\n        \"gcr.io/google.com/cloudsdktool/google-cloud-cli\"\n    );\n\n    private static final DockerImageName CLOUD_SDK_IMAGE_NAME = DockerImageName.parse(\n        \"gcr.io/google.com/cloudsdktool/cloud-sdk\"\n    );\n\n    private static final String CMD = \"gcloud beta emulators pubsub start --host-port 0.0.0.0:8085\";\n\n    private static final int PORT = 8085;\n\n    public PubSubEmulatorContainer(String image) {\n        this(DockerImageName.parse(image));\n    }\n\n    public PubSubEmulatorContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME, CLOUD_SDK_IMAGE_NAME);\n\n        withExposedPorts(8085);\n        setWaitStrategy(Wait.forLogMessage(\".*started.*$\", 1));\n        withCommand(\"/bin/sh\", \"-c\", CMD);\n    }\n\n    /**\n     * @return a <code>host:port</code> pair corresponding to the address on which the emulator is\n     * reachable from the test host machine. Directly usable as a parameter to the\n     * io.grpc.ManagedChannelBuilder#forTarget(java.lang.String) method.\n     */\n    public String getEmulatorEndpoint() {\n        return getHost() + \":\" + getMappedPort(PORT);\n    }\n}\n"
  },
  {
    "path": "modules/gcloud/src/main/java/org/testcontainers/containers/SpannerEmulatorContainer.java",
    "content": "package org.testcontainers.containers;\n\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.utility.DockerImageName;\n\n/**\n * A Spanner container. Default ports: 9010 for GRPC and 9020 for HTTP.\n * <p>\n * Supported image: {@code gcr.io/cloud-spanner-emulator/emulator}\n *\n * @deprecated use {@link org.testcontainers.gcloud.SpannerEmulatorContainer} instead.\n */\n@Deprecated\npublic class SpannerEmulatorContainer extends GenericContainer<SpannerEmulatorContainer> {\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\n        \"gcr.io/cloud-spanner-emulator/emulator\"\n    );\n\n    private static final int GRPC_PORT = 9010;\n\n    private static final int HTTP_PORT = 9020;\n\n    public SpannerEmulatorContainer(String image) {\n        this(DockerImageName.parse(image));\n    }\n\n    public SpannerEmulatorContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n\n        withExposedPorts(GRPC_PORT, HTTP_PORT);\n        setWaitStrategy(Wait.forLogMessage(\".*Cloud Spanner emulator running\\\\..*\", 1));\n    }\n\n    /**\n     * @return a <code>host:port</code> pair corresponding to the address on which the emulator's\n     * gRPC endpoint is reachable from the test host machine. Directly usable as a parameter to the\n     * com.google.cloud.spanner.SpannerOptions.Builder#setEmulatorHost(java.lang.String) method.\n     */\n    public String getEmulatorGrpcEndpoint() {\n        return getHost() + \":\" + getMappedPort(GRPC_PORT);\n    }\n\n    /**\n     * @return a <code>host:port</code> pair corresponding to the address on which the emulator's\n     * HTTP REST endpoint is reachable from the test host machine.\n     */\n    public String getEmulatorHttpEndpoint() {\n        return getHost() + \":\" + getMappedPort(HTTP_PORT);\n    }\n}\n"
  },
  {
    "path": "modules/gcloud/src/main/java/org/testcontainers/gcloud/BigQueryEmulatorContainer.java",
    "content": "package org.testcontainers.gcloud;\n\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.utility.DockerImageName;\n\n/**\n * Testcontainers implementation for BigQuery.\n * <p>\n * Supported image: {@code ghcr.io/goccy/bigquery-emulator}\n * <p>\n */\npublic class BigQueryEmulatorContainer extends GenericContainer<BigQueryEmulatorContainer> {\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"ghcr.io/goccy/bigquery-emulator\");\n\n    private static final int HTTP_PORT = 9050;\n\n    private static final int GRPC_PORT = 9060;\n\n    private static final String PROJECT_ID = \"test-project\";\n\n    public BigQueryEmulatorContainer(String image) {\n        this(DockerImageName.parse(image));\n    }\n\n    public BigQueryEmulatorContainer(DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n        addExposedPorts(HTTP_PORT, GRPC_PORT);\n        withCommand(\"--project\", PROJECT_ID);\n    }\n\n    public String getEmulatorHttpEndpoint() {\n        return String.format(\"http://%s:%d\", getHost(), getMappedPort(HTTP_PORT));\n    }\n\n    public Integer getEmulatorGrpcPort() {\n        return getMappedPort(GRPC_PORT);\n    }\n\n    public String getProjectId() {\n        return PROJECT_ID;\n    }\n}\n"
  },
  {
    "path": "modules/gcloud/src/main/java/org/testcontainers/gcloud/BigtableEmulatorContainer.java",
    "content": "package org.testcontainers.gcloud;\n\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.utility.DockerImageName;\n\n/**\n * A Bigtable container that relies in google cloud sdk.\n * <p>\n * Supported images: {@code gcr.io/google.com/cloudsdktool/google-cloud-cli}, {@code gcr.io/google.com/cloudsdktool/cloud-sdk}\n * <p>\n * Default port is 9000.\n */\npublic class BigtableEmulatorContainer extends GenericContainer<BigtableEmulatorContainer> {\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\n        \"gcr.io/google.com/cloudsdktool/google-cloud-cli\"\n    );\n\n    private static final DockerImageName CLOUD_SDK_IMAGE_NAME = DockerImageName.parse(\n        \"gcr.io/google.com/cloudsdktool/cloud-sdk\"\n    );\n\n    private static final String CMD = \"gcloud beta emulators bigtable start --host-port 0.0.0.0:9000\";\n\n    private static final int PORT = 9000;\n\n    public BigtableEmulatorContainer(String image) {\n        this(DockerImageName.parse(image));\n    }\n\n    public BigtableEmulatorContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME, CLOUD_SDK_IMAGE_NAME);\n\n        withExposedPorts(PORT);\n        setWaitStrategy(Wait.forLogMessage(\".*running.*$\", 1));\n        withCommand(\"/bin/sh\", \"-c\", CMD);\n    }\n\n    /**\n     * @return a <code>host:port</code> pair corresponding to the address on which the emulator is\n     * reachable from the test host machine. Directly usable as a parameter to the\n     * com.google.cloud.ServiceOptions.Builder#setHost(java.lang.String) method.\n     */\n    public String getEmulatorEndpoint() {\n        return getHost() + \":\" + getEmulatorPort();\n    }\n\n    public int getEmulatorPort() {\n        return getMappedPort(PORT);\n    }\n}\n"
  },
  {
    "path": "modules/gcloud/src/main/java/org/testcontainers/gcloud/DatastoreEmulatorContainer.java",
    "content": "package org.testcontainers.gcloud;\n\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.utility.DockerImageName;\n\n/**\n * A Datastore container that relies in google cloud sdk.\n * <p>\n * Supported images: {@code gcr.io/google.com/cloudsdktool/google-cloud-cli}, {@code gcr.io/google.com/cloudsdktool/cloud-sdk}\n * <p>\n * Default port is 8081.\n */\npublic class DatastoreEmulatorContainer extends GenericContainer<DatastoreEmulatorContainer> {\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\n        \"gcr.io/google.com/cloudsdktool/google-cloud-cli\"\n    );\n\n    private static final DockerImageName CLOUD_SDK_IMAGE_NAME = DockerImageName.parse(\n        \"gcr.io/google.com/cloudsdktool/cloud-sdk\"\n    );\n\n    private static final String PROJECT_ID = \"test-project\";\n\n    private static final String CMD = String.format(\n        \"gcloud beta emulators datastore start --project %s --host-port 0.0.0.0:8081\",\n        PROJECT_ID\n    );\n\n    private static final int HTTP_PORT = 8081;\n\n    private String flags;\n\n    public DatastoreEmulatorContainer(final String image) {\n        this(DockerImageName.parse(image));\n    }\n\n    public DatastoreEmulatorContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME, CLOUD_SDK_IMAGE_NAME);\n\n        withExposedPorts(HTTP_PORT);\n        setWaitStrategy(Wait.forHttp(\"/\").forStatusCode(200));\n    }\n\n    @Override\n    protected void configure() {\n        String command = CMD;\n        if (this.flags != null && !this.flags.isEmpty()) {\n            command += \" \" + this.flags;\n        }\n        withCommand(\"/bin/sh\", \"-c\", command);\n    }\n\n    public DatastoreEmulatorContainer withFlags(String flags) {\n        this.flags = flags;\n        return this;\n    }\n\n    /**\n     * @return a <code>host:port</code> pair corresponding to the address on which the emulator is\n     * reachable from the test host machine. Directly usable as a parameter to the\n     * com.google.cloud.ServiceOptions.Builder#setHost(java.lang.String) method.\n     */\n    public String getEmulatorEndpoint() {\n        return getHost() + \":\" + getMappedPort(HTTP_PORT);\n    }\n\n    public String getProjectId() {\n        return PROJECT_ID;\n    }\n}\n"
  },
  {
    "path": "modules/gcloud/src/main/java/org/testcontainers/gcloud/FirestoreEmulatorContainer.java",
    "content": "package org.testcontainers.gcloud;\n\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.utility.DockerImageName;\n\n/**\n * A Firestore container that relies in google cloud sdk.\n * <p>\n * Supported images: {@code gcr.io/google.com/cloudsdktool/google-cloud-cli}, {@code gcr.io/google.com/cloudsdktool/cloud-sdk}\n * <p>\n * Default port is 8080.\n */\npublic class FirestoreEmulatorContainer extends GenericContainer<FirestoreEmulatorContainer> {\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\n        \"gcr.io/google.com/cloudsdktool/google-cloud-cli\"\n    );\n\n    private static final DockerImageName CLOUD_SDK_IMAGE_NAME = DockerImageName.parse(\n        \"gcr.io/google.com/cloudsdktool/cloud-sdk\"\n    );\n\n    private static final String CMD = \"gcloud beta emulators firestore start --host-port 0.0.0.0:8080\";\n\n    private static final int PORT = 8080;\n\n    private String flags;\n\n    public FirestoreEmulatorContainer(String image) {\n        this(DockerImageName.parse(image));\n    }\n\n    public FirestoreEmulatorContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME, CLOUD_SDK_IMAGE_NAME);\n\n        withExposedPorts(PORT);\n        setWaitStrategy(Wait.forLogMessage(\".*running.*$\", 1));\n    }\n\n    @Override\n    protected void configure() {\n        String command = CMD;\n        if (this.flags != null && !this.flags.isEmpty()) {\n            command += \" \" + this.flags;\n        }\n        withCommand(\"/bin/sh\", \"-c\", command);\n    }\n\n    public FirestoreEmulatorContainer withFlags(String flags) {\n        this.flags = flags;\n        return this;\n    }\n\n    /**\n     * @return a <code>host:port</code> pair corresponding to the address on which the emulator is\n     * reachable from the test host machine. Directly usable as a parameter to the\n     * com.google.cloud.ServiceOptions.Builder#setHost(java.lang.String) method.\n     */\n    public String getEmulatorEndpoint() {\n        return getHost() + \":\" + getMappedPort(8080);\n    }\n}\n"
  },
  {
    "path": "modules/gcloud/src/main/java/org/testcontainers/gcloud/PubSubEmulatorContainer.java",
    "content": "package org.testcontainers.gcloud;\n\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.utility.DockerImageName;\n\n/**\n * A PubSub container that relies in google cloud sdk.\n * <p>\n * Supported images: {@code gcr.io/google.com/cloudsdktool/google-cloud-cli}, {@code gcr.io/google.com/cloudsdktool/cloud-sdk}\n * <p>\n * Default port is 8085.\n */\npublic class PubSubEmulatorContainer extends GenericContainer<PubSubEmulatorContainer> {\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\n        \"gcr.io/google.com/cloudsdktool/google-cloud-cli\"\n    );\n\n    private static final DockerImageName CLOUD_SDK_IMAGE_NAME = DockerImageName.parse(\n        \"gcr.io/google.com/cloudsdktool/cloud-sdk\"\n    );\n\n    private static final String CMD = \"gcloud beta emulators pubsub start --host-port 0.0.0.0:8085\";\n\n    private static final int PORT = 8085;\n\n    public PubSubEmulatorContainer(String image) {\n        this(DockerImageName.parse(image));\n    }\n\n    public PubSubEmulatorContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME, CLOUD_SDK_IMAGE_NAME);\n\n        withExposedPorts(8085);\n        setWaitStrategy(Wait.forLogMessage(\".*started.*$\", 1));\n        withCommand(\"/bin/sh\", \"-c\", CMD);\n    }\n\n    /**\n     * @return a <code>host:port</code> pair corresponding to the address on which the emulator is\n     * reachable from the test host machine. Directly usable as a parameter to the\n     * io.grpc.ManagedChannelBuilder#forTarget(java.lang.String) method.\n     */\n    public String getEmulatorEndpoint() {\n        return getHost() + \":\" + getMappedPort(PORT);\n    }\n}\n"
  },
  {
    "path": "modules/gcloud/src/main/java/org/testcontainers/gcloud/SpannerEmulatorContainer.java",
    "content": "package org.testcontainers.gcloud;\n\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.utility.DockerImageName;\n\n/**\n * A Spanner container. Default ports: 9010 for GRPC and 9020 for HTTP.\n * <p>\n * Supported image: {@code gcr.io/cloud-spanner-emulator/emulator}\n */\npublic class SpannerEmulatorContainer extends GenericContainer<SpannerEmulatorContainer> {\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\n        \"gcr.io/cloud-spanner-emulator/emulator\"\n    );\n\n    private static final int GRPC_PORT = 9010;\n\n    private static final int HTTP_PORT = 9020;\n\n    public SpannerEmulatorContainer(String image) {\n        this(DockerImageName.parse(image));\n    }\n\n    public SpannerEmulatorContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n\n        withExposedPorts(GRPC_PORT, HTTP_PORT);\n        setWaitStrategy(Wait.forLogMessage(\".*Cloud Spanner emulator running\\\\..*\", 1));\n    }\n\n    /**\n     * @return a <code>host:port</code> pair corresponding to the address on which the emulator's\n     * gRPC endpoint is reachable from the test host machine. Directly usable as a parameter to the\n     * com.google.cloud.spanner.SpannerOptions.Builder#setEmulatorHost(java.lang.String) method.\n     */\n    public String getEmulatorGrpcEndpoint() {\n        return getHost() + \":\" + getMappedPort(GRPC_PORT);\n    }\n\n    /**\n     * @return a <code>host:port</code> pair corresponding to the address on which the emulator's\n     * HTTP REST endpoint is reachable from the test host machine.\n     */\n    public String getEmulatorHttpEndpoint() {\n        return getHost() + \":\" + getMappedPort(HTTP_PORT);\n    }\n}\n"
  },
  {
    "path": "modules/gcloud/src/test/java/org/testcontainers/gcloud/BigQueryEmulatorContainerTest.java",
    "content": "package org.testcontainers.gcloud;\n\nimport com.google.api.core.ApiFuture;\nimport com.google.api.gax.core.NoCredentialsProvider;\nimport com.google.api.gax.grpc.GrpcTransportChannel;\nimport com.google.api.gax.rpc.FixedTransportChannelProvider;\nimport com.google.cloud.NoCredentials;\nimport com.google.cloud.bigquery.BigQuery;\nimport com.google.cloud.bigquery.BigQueryOptions;\nimport com.google.cloud.bigquery.DatasetId;\nimport com.google.cloud.bigquery.DatasetInfo;\nimport com.google.cloud.bigquery.Field;\nimport com.google.cloud.bigquery.QueryJobConfiguration;\nimport com.google.cloud.bigquery.Schema;\nimport com.google.cloud.bigquery.StandardSQLTypeName;\nimport com.google.cloud.bigquery.StandardTableDefinition;\nimport com.google.cloud.bigquery.TableDefinition;\nimport com.google.cloud.bigquery.TableId;\nimport com.google.cloud.bigquery.TableInfo;\nimport com.google.cloud.bigquery.TableResult;\nimport com.google.cloud.bigquery.storage.v1.AppendRowsResponse;\nimport com.google.cloud.bigquery.storage.v1.BatchCommitWriteStreamsRequest;\nimport com.google.cloud.bigquery.storage.v1.BatchCommitWriteStreamsResponse;\nimport com.google.cloud.bigquery.storage.v1.BigQueryWriteClient;\nimport com.google.cloud.bigquery.storage.v1.BigQueryWriteSettings;\nimport com.google.cloud.bigquery.storage.v1.CreateWriteStreamRequest;\nimport com.google.cloud.bigquery.storage.v1.FinalizeWriteStreamRequest;\nimport com.google.cloud.bigquery.storage.v1.FinalizeWriteStreamResponse;\nimport com.google.cloud.bigquery.storage.v1.JsonStreamWriter;\nimport com.google.cloud.bigquery.storage.v1.TableName;\nimport com.google.cloud.bigquery.storage.v1.WriteStream;\nimport io.grpc.ManagedChannelBuilder;\nimport org.json.JSONArray;\nimport org.json.JSONObject;\nimport org.junit.jupiter.api.Test;\nimport org.threeten.bp.Duration;\n\nimport java.math.BigDecimal;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass BigQueryEmulatorContainerTest {\n\n    @Test\n    void testHttpEndpoint() throws Exception {\n        try (\n            // emulatorContainer {\n            BigQueryEmulatorContainer container = new BigQueryEmulatorContainer(\"ghcr.io/goccy/bigquery-emulator:0.4.3\")\n            // }\n        ) {\n            container.start();\n\n            // bigQueryClient {\n            String url = container.getEmulatorHttpEndpoint();\n            BigQueryOptions options = BigQueryOptions\n                .newBuilder()\n                .setProjectId(container.getProjectId())\n                .setHost(url)\n                .setLocation(url)\n                .setCredentials(NoCredentials.getInstance())\n                .build();\n            BigQuery bigQuery = options.getService();\n            // }\n\n            String fn =\n                \"CREATE FUNCTION testr(arr ARRAY<STRUCT<name STRING, val INT64>>) AS ((SELECT SUM(IF(elem.name = \\\"foo\\\",elem.val,null)) FROM UNNEST(arr) AS elem))\";\n\n            bigQuery.query(QueryJobConfiguration.newBuilder(fn).build());\n\n            String sql =\n                \"SELECT testr([STRUCT<name STRING, val INT64>(\\\"foo\\\", 10), STRUCT<name STRING, val INT64>(\\\"bar\\\", 40), STRUCT<name STRING, val INT64>(\\\"foo\\\", 20)])\";\n            TableResult result = bigQuery.query(QueryJobConfiguration.newBuilder(sql).build());\n            List<BigDecimal> values = result\n                .streamValues()\n                .map(fieldValues -> fieldValues.get(0).getNumericValue())\n                .collect(Collectors.toList());\n            assertThat(values).containsOnly(BigDecimal.valueOf(30));\n        }\n    }\n\n    @Test\n    void testGrcpEndpoint() throws Exception {\n        try (\n            BigQueryEmulatorContainer container = new BigQueryEmulatorContainer(\"ghcr.io/goccy/bigquery-emulator:0.6.5\")\n        ) {\n            container.start();\n\n            BigQuery bigQuery = getBigQuery(container);\n            String tableName = \"test-table\";\n            String datasetName = \"test-dataset\";\n\n            bigQuery.create(DatasetInfo.of(DatasetId.of(container.getProjectId(), datasetName)));\n\n            Schema schema = Schema.of(Field.of(\"name\", StandardSQLTypeName.STRING));\n\n            TableId tableId = TableId.of(datasetName, tableName);\n            TableDefinition tableDefinition = StandardTableDefinition.of(schema);\n            TableInfo tableInfo = TableInfo.newBuilder(tableId, tableDefinition).build();\n\n            bigQuery.create(tableInfo);\n\n            BigQueryWriteSettings.Builder bigQueryWriteSettingsBuilder = BigQueryWriteSettings.newBuilder();\n\n            bigQueryWriteSettingsBuilder\n                .createWriteStreamSettings()\n                .setRetrySettings(\n                    bigQueryWriteSettingsBuilder\n                        .createWriteStreamSettings()\n                        .getRetrySettings()\n                        .toBuilder()\n                        .setTotalTimeout(Duration.ofSeconds(60))\n                        .build()\n                );\n\n            BigQueryWriteClient bigQueryWriteClient = BigQueryWriteClient.create(\n                bigQueryWriteSettingsBuilder\n                    .setTransportChannelProvider(\n                        FixedTransportChannelProvider.create(\n                            GrpcTransportChannel.create(\n                                ManagedChannelBuilder\n                                    .forAddress(container.getHost(), container.getEmulatorGrpcPort())\n                                    .usePlaintext()\n                                    .build()\n                            )\n                        )\n                    )\n                    .setCredentialsProvider(NoCredentialsProvider.create())\n                    .build()\n            );\n\n            TableName parentTable = TableName.of(container.getProjectId(), datasetName, tableName);\n            CreateWriteStreamRequest createWriteStreamRequest = CreateWriteStreamRequest\n                .newBuilder()\n                .setParent(parentTable.toString())\n                .setWriteStream(WriteStream.newBuilder().setType(WriteStream.Type.PENDING))\n                .build();\n\n            WriteStream writeStream = bigQueryWriteClient.createWriteStream(createWriteStreamRequest);\n\n            JsonStreamWriter writer = JsonStreamWriter\n                .newBuilder(writeStream.getName(), writeStream.getTableSchema(), bigQueryWriteClient)\n                .build();\n\n            JSONArray jsonArray = new JSONArray();\n            JSONObject record1 = new JSONObject();\n            record1.put(\"name\", \"Alice\");\n            jsonArray.put(record1);\n\n            JSONObject record2 = new JSONObject();\n            record2.put(\"name\", \"Bob\");\n            jsonArray.put(record2);\n\n            ApiFuture<AppendRowsResponse> future = writer.append(jsonArray);\n            AppendRowsResponse response = future.get();\n\n            FinalizeWriteStreamRequest finalizeRequest = FinalizeWriteStreamRequest\n                .newBuilder()\n                .setName(writeStream.getName())\n                .build();\n            FinalizeWriteStreamResponse finalizeResponse = bigQueryWriteClient.finalizeWriteStream(finalizeRequest);\n\n            BatchCommitWriteStreamsRequest commitRequest = BatchCommitWriteStreamsRequest\n                .newBuilder()\n                .setParent(parentTable.toString())\n                .addWriteStreams(writeStream.getName())\n                .build();\n            BatchCommitWriteStreamsResponse commitResponse = bigQueryWriteClient.batchCommitWriteStreams(commitRequest);\n\n            writer.close();\n\n            String sql = String.format(\n                \"SELECT name FROM `%s.%s.%s` ORDER BY name\",\n                container.getProjectId(),\n                datasetName,\n                tableName\n            );\n            TableResult result = bigQuery.query(QueryJobConfiguration.newBuilder(sql).build());\n\n            List<String> names = result\n                .streamValues()\n                .map(row -> row.get(\"name\").getStringValue())\n                .collect(Collectors.toList());\n\n            assertThat(names).containsExactly(\"Alice\", \"Bob\");\n\n            bigQueryWriteClient.shutdown();\n            bigQueryWriteClient.close();\n        }\n    }\n\n    private BigQuery getBigQuery(BigQueryEmulatorContainer container) {\n        String url = container.getEmulatorHttpEndpoint();\n        return BigQueryOptions\n            .newBuilder()\n            .setProjectId(container.getProjectId())\n            .setHost(url)\n            .setLocation(url)\n            .setCredentials(NoCredentials.getInstance())\n            .build()\n            .getService();\n    }\n}\n"
  },
  {
    "path": "modules/gcloud/src/test/java/org/testcontainers/gcloud/BigtableEmulatorContainerTest.java",
    "content": "package org.testcontainers.gcloud;\n\nimport com.google.api.gax.core.CredentialsProvider;\nimport com.google.api.gax.core.NoCredentialsProvider;\nimport com.google.api.gax.grpc.GrpcTransportChannel;\nimport com.google.api.gax.rpc.FixedTransportChannelProvider;\nimport com.google.api.gax.rpc.TransportChannelProvider;\nimport com.google.cloud.bigtable.admin.v2.BigtableTableAdminClient;\nimport com.google.cloud.bigtable.admin.v2.models.CreateTableRequest;\nimport com.google.cloud.bigtable.admin.v2.models.Table;\nimport com.google.cloud.bigtable.admin.v2.stub.BigtableTableAdminStubSettings;\nimport com.google.cloud.bigtable.admin.v2.stub.EnhancedBigtableTableAdminStub;\nimport com.google.cloud.bigtable.data.v2.BigtableDataClient;\nimport com.google.cloud.bigtable.data.v2.BigtableDataSettings;\nimport com.google.cloud.bigtable.data.v2.internal.TableAdminRequestContext;\nimport com.google.cloud.bigtable.data.v2.models.Row;\nimport com.google.cloud.bigtable.data.v2.models.RowCell;\nimport com.google.cloud.bigtable.data.v2.models.RowMutation;\nimport com.google.cloud.bigtable.data.v2.models.TableId;\nimport io.grpc.ManagedChannel;\nimport io.grpc.ManagedChannelBuilder;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.IOException;\nimport java.util.List;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass BigtableEmulatorContainerTest {\n\n    private static final String PROJECT_ID = \"test-project\";\n\n    private static final String INSTANCE_ID = \"test-instance\";\n\n    @Test\n    // testWithEmulatorContainer {\n    void testSimple() throws IOException {\n        try (\n            // emulatorContainer {\n            BigtableEmulatorContainer emulator = new BigtableEmulatorContainer(\n                DockerImageName.parse(\"gcr.io/google.com/cloudsdktool/google-cloud-cli:441.0.0-emulators\")\n            );\n            // }\n        ) {\n            emulator.start();\n            ManagedChannel channel = ManagedChannelBuilder\n                .forTarget(emulator.getEmulatorEndpoint())\n                .usePlaintext()\n                .build();\n\n            TransportChannelProvider channelProvider = FixedTransportChannelProvider.create(\n                GrpcTransportChannel.create(channel)\n            );\n            NoCredentialsProvider credentialsProvider = NoCredentialsProvider.create();\n            createTable(channelProvider, credentialsProvider, \"test-table\");\n            try (\n                BigtableDataClient client = BigtableDataClient.create(\n                    BigtableDataSettings\n                        .newBuilderForEmulator(emulator.getHost(), emulator.getEmulatorPort())\n                        .setProjectId(PROJECT_ID)\n                        .setInstanceId(INSTANCE_ID)\n                        .build()\n                )\n            ) {\n                client.mutateRow(RowMutation.create(TableId.of(\"test-table\"), \"1\").setCell(\"name\", \"firstName\", \"Ray\"));\n\n                Row row = client.readRow(TableId.of(\"test-table\"), \"1\");\n                List<RowCell> cells = row.getCells(\"name\", \"firstName\");\n\n                assertThat(cells).isNotNull().hasSize(1);\n                assertThat(cells.get(0).getValue().toStringUtf8()).isEqualTo(\"Ray\");\n            } finally {\n                channel.shutdown();\n            }\n        }\n    }\n\n    // }\n\n    // createTable {\n    private void createTable(\n        TransportChannelProvider channelProvider,\n        CredentialsProvider credentialsProvider,\n        String tableName\n    ) throws IOException {\n        TableAdminRequestContext requestContext = TableAdminRequestContext.create(PROJECT_ID, INSTANCE_ID);\n        EnhancedBigtableTableAdminStub stub = EnhancedBigtableTableAdminStub.createEnhanced(\n            BigtableTableAdminStubSettings\n                .newBuilder()\n                .setTransportChannelProvider(channelProvider)\n                .setCredentialsProvider(credentialsProvider)\n                .build(),\n            requestContext\n        );\n\n        try (BigtableTableAdminClient client = BigtableTableAdminClient.create(PROJECT_ID, INSTANCE_ID, stub)) {\n            Table table = client.createTable(CreateTableRequest.of(tableName).addFamily(\"name\"));\n        }\n    }\n    // }\n}\n"
  },
  {
    "path": "modules/gcloud/src/test/java/org/testcontainers/gcloud/DatastoreEmulatorContainerTest.java",
    "content": "package org.testcontainers.gcloud;\n\nimport com.google.cloud.NoCredentials;\nimport com.google.cloud.ServiceOptions;\nimport com.google.cloud.datastore.Datastore;\nimport com.google.cloud.datastore.DatastoreOptions;\nimport com.google.cloud.datastore.Entity;\nimport com.google.cloud.datastore.Key;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.IOException;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\npublic class DatastoreEmulatorContainerTest {\n\n    //  startingDatastoreEmulatorContainer {\n    @Test\n    public void testSimple() {\n        try (\n            // creatingDatastoreEmulatorContainer {\n            DatastoreEmulatorContainer emulator = new DatastoreEmulatorContainer(\n                DockerImageName.parse(\"gcr.io/google.com/cloudsdktool/google-cloud-cli:441.0.0-emulators\")\n            );\n            // }\n        ) {\n            emulator.start();\n            DatastoreOptions options = DatastoreOptions\n                .newBuilder()\n                .setHost(emulator.getEmulatorEndpoint())\n                .setCredentials(NoCredentials.getInstance())\n                .setRetrySettings(ServiceOptions.getNoRetrySettings())\n                .setProjectId(emulator.getProjectId())\n                .build();\n            Datastore datastore = options.getService();\n\n            Key key = datastore.newKeyFactory().setKind(\"Task\").newKey(\"sample\");\n            Entity entity = Entity.newBuilder(key).set(\"description\", \"my description\").build();\n            datastore.put(entity);\n\n            assertThat(datastore.get(key).getString(\"description\")).isEqualTo(\"my description\");\n        }\n    }\n\n    // }\n\n    @Test\n    void testWithFlags() throws IOException, InterruptedException {\n        try (\n            DatastoreEmulatorContainer emulator = new DatastoreEmulatorContainer(\n                \"gcr.io/google.com/cloudsdktool/google-cloud-cli:441.0.0-emulators\"\n            )\n                .withFlags(\"--consistency 1.0\")\n        ) {\n            emulator.start();\n\n            assertThat(emulator.getContainerInfo().getConfig().getCmd()).anyMatch(e -> e.contains(\"--consistency 1.0\"));\n            assertThat(emulator.execInContainer(\"ls\", \"/root/.config/\").getStdout()).contains(\"gcloud\");\n        }\n    }\n\n    @Test\n    void testWithMultipleFlags() throws IOException, InterruptedException {\n        try (\n            DatastoreEmulatorContainer emulator = new DatastoreEmulatorContainer(\n                \"gcr.io/google.com/cloudsdktool/google-cloud-cli:441.0.0-emulators\"\n            )\n                .withFlags(\"--consistency 1.0 --data-dir /root/.config/test-gcloud\")\n        ) {\n            emulator.start();\n\n            assertThat(emulator.getContainerInfo().getConfig().getCmd()).anyMatch(e -> e.contains(\"--consistency 1.0\"));\n            assertThat(emulator.execInContainer(\"ls\", \"/root/.config/\").getStdout()).contains(\"test-gcloud\");\n        }\n    }\n}\n"
  },
  {
    "path": "modules/gcloud/src/test/java/org/testcontainers/gcloud/FirestoreEmulatorContainerTest.java",
    "content": "package org.testcontainers.gcloud;\n\nimport com.google.api.core.ApiFuture;\nimport com.google.cloud.NoCredentials;\nimport com.google.cloud.firestore.CollectionReference;\nimport com.google.cloud.firestore.DocumentReference;\nimport com.google.cloud.firestore.Firestore;\nimport com.google.cloud.firestore.FirestoreOptions;\nimport com.google.cloud.firestore.QuerySnapshot;\nimport com.google.cloud.firestore.WriteResult;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.ExecutionException;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass FirestoreEmulatorContainerTest {\n\n    // testWithEmulatorContainer {\n    @Test\n    void testSimple() throws ExecutionException, InterruptedException {\n        try (\n            // emulatorContainer {\n            FirestoreEmulatorContainer emulator = new FirestoreEmulatorContainer(\n                DockerImageName.parse(\"gcr.io/google.com/cloudsdktool/google-cloud-cli:441.0.0-emulators\")\n            );\n            // }\n        ) {\n            emulator.start();\n            FirestoreOptions options = FirestoreOptions\n                .getDefaultInstance()\n                .toBuilder()\n                .setHost(emulator.getEmulatorEndpoint())\n                .setCredentials(NoCredentials.getInstance())\n                .setProjectId(\"test-project\")\n                .build();\n            Firestore firestore = options.getService();\n\n            CollectionReference users = firestore.collection(\"users\");\n            DocumentReference docRef = users.document(\"alovelace\");\n            Map<String, Object> data = new HashMap<>();\n            data.put(\"first\", \"Ada\");\n            data.put(\"last\", \"Lovelace\");\n            ApiFuture<WriteResult> result = docRef.set(data);\n            result.get();\n\n            ApiFuture<QuerySnapshot> query = users.get();\n            QuerySnapshot querySnapshot = query.get();\n\n            assertThat(querySnapshot.getDocuments().get(0).getData()).containsEntry(\"first\", \"Ada\");\n        }\n    }\n\n    // }\n\n    @Test\n    void testWithFlags() {\n        try (\n            FirestoreEmulatorContainer emulator = new FirestoreEmulatorContainer(\n                \"gcr.io/google.com/cloudsdktool/google-cloud-cli:465.0.0-emulators\"\n            )\n                .withFlags(\"--database-mode datastore-mode\")\n        ) {\n            emulator.start();\n\n            assertThat(emulator.getContainerInfo().getConfig().getCmd())\n                .anyMatch(e -> e.contains(\"--database-mode datastore-mode\"));\n        }\n    }\n}\n"
  },
  {
    "path": "modules/gcloud/src/test/java/org/testcontainers/gcloud/PubSubEmulatorContainerTest.java",
    "content": "package org.testcontainers.gcloud;\n\nimport com.google.api.gax.core.NoCredentialsProvider;\nimport com.google.api.gax.grpc.GrpcTransportChannel;\nimport com.google.api.gax.rpc.FixedTransportChannelProvider;\nimport com.google.api.gax.rpc.TransportChannelProvider;\nimport com.google.cloud.pubsub.v1.Publisher;\nimport com.google.cloud.pubsub.v1.SubscriptionAdminClient;\nimport com.google.cloud.pubsub.v1.SubscriptionAdminSettings;\nimport com.google.cloud.pubsub.v1.TopicAdminClient;\nimport com.google.cloud.pubsub.v1.TopicAdminSettings;\nimport com.google.cloud.pubsub.v1.stub.GrpcSubscriberStub;\nimport com.google.cloud.pubsub.v1.stub.SubscriberStub;\nimport com.google.cloud.pubsub.v1.stub.SubscriberStubSettings;\nimport com.google.protobuf.ByteString;\nimport com.google.pubsub.v1.ProjectSubscriptionName;\nimport com.google.pubsub.v1.PubsubMessage;\nimport com.google.pubsub.v1.PullRequest;\nimport com.google.pubsub.v1.PullResponse;\nimport com.google.pubsub.v1.PushConfig;\nimport com.google.pubsub.v1.SubscriptionName;\nimport com.google.pubsub.v1.TopicName;\nimport io.grpc.ManagedChannel;\nimport io.grpc.ManagedChannelBuilder;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.IOException;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass PubSubEmulatorContainerTest {\n\n    private static final String PROJECT_ID = \"my-project-id\";\n\n    @Test\n    // testWithEmulatorContainer {\n    void testSimple() throws IOException {\n        try (\n            // emulatorContainer {\n            PubSubEmulatorContainer emulator = new PubSubEmulatorContainer(\n                DockerImageName.parse(\"gcr.io/google.com/cloudsdktool/google-cloud-cli:441.0.0-emulators\")\n            );\n            // }\n        ) {\n            emulator.start();\n            String hostport = emulator.getEmulatorEndpoint();\n            ManagedChannel channel = ManagedChannelBuilder.forTarget(hostport).usePlaintext().build();\n            try {\n                TransportChannelProvider channelProvider = FixedTransportChannelProvider.create(\n                    GrpcTransportChannel.create(channel)\n                );\n                NoCredentialsProvider credentialsProvider = NoCredentialsProvider.create();\n\n                String topicId = \"my-topic-id\";\n                createTopic(topicId, channelProvider, credentialsProvider);\n\n                String subscriptionId = \"my-subscription-id\";\n                createSubscription(subscriptionId, topicId, channelProvider, credentialsProvider);\n\n                Publisher publisher = Publisher\n                    .newBuilder(TopicName.of(PROJECT_ID, topicId))\n                    .setChannelProvider(channelProvider)\n                    .setCredentialsProvider(credentialsProvider)\n                    .build();\n                PubsubMessage message = PubsubMessage\n                    .newBuilder()\n                    .setData(ByteString.copyFromUtf8(\"test message\"))\n                    .build();\n                publisher.publish(message);\n\n                SubscriberStubSettings subscriberStubSettings = SubscriberStubSettings\n                    .newBuilder()\n                    .setTransportChannelProvider(channelProvider)\n                    .setCredentialsProvider(credentialsProvider)\n                    .build();\n                try (SubscriberStub subscriber = GrpcSubscriberStub.create(subscriberStubSettings)) {\n                    PullRequest pullRequest = PullRequest\n                        .newBuilder()\n                        .setMaxMessages(1)\n                        .setSubscription(ProjectSubscriptionName.format(PROJECT_ID, subscriptionId))\n                        .build();\n                    PullResponse pullResponse = subscriber.pullCallable().call(pullRequest);\n\n                    assertThat(pullResponse.getReceivedMessagesList()).hasSize(1);\n                    assertThat(pullResponse.getReceivedMessages(0).getMessage().getData().toStringUtf8())\n                        .isEqualTo(\"test message\");\n                }\n            } finally {\n                channel.shutdown();\n            }\n        }\n    }\n\n    // }\n\n    // createTopic {\n    private void createTopic(\n        String topicId,\n        TransportChannelProvider channelProvider,\n        NoCredentialsProvider credentialsProvider\n    ) throws IOException {\n        TopicAdminSettings topicAdminSettings = TopicAdminSettings\n            .newBuilder()\n            .setTransportChannelProvider(channelProvider)\n            .setCredentialsProvider(credentialsProvider)\n            .build();\n        try (TopicAdminClient topicAdminClient = TopicAdminClient.create(topicAdminSettings)) {\n            TopicName topicName = TopicName.of(PROJECT_ID, topicId);\n            topicAdminClient.createTopic(topicName);\n        }\n    }\n\n    // }\n\n    // createSubscription {\n    private void createSubscription(\n        String subscriptionId,\n        String topicId,\n        TransportChannelProvider channelProvider,\n        NoCredentialsProvider credentialsProvider\n    ) throws IOException {\n        SubscriptionAdminSettings subscriptionAdminSettings = SubscriptionAdminSettings\n            .newBuilder()\n            .setTransportChannelProvider(channelProvider)\n            .setCredentialsProvider(credentialsProvider)\n            .build();\n        SubscriptionAdminClient subscriptionAdminClient = SubscriptionAdminClient.create(subscriptionAdminSettings);\n        SubscriptionName subscriptionName = SubscriptionName.of(PROJECT_ID, subscriptionId);\n        subscriptionAdminClient.createSubscription(\n            subscriptionName,\n            TopicName.of(PROJECT_ID, topicId),\n            PushConfig.getDefaultInstance(),\n            10\n        );\n    }\n    // }\n\n}\n"
  },
  {
    "path": "modules/gcloud/src/test/java/org/testcontainers/gcloud/SpannerEmulatorContainerTest.java",
    "content": "package org.testcontainers.gcloud;\n\nimport com.google.cloud.NoCredentials;\nimport com.google.cloud.spanner.Database;\nimport com.google.cloud.spanner.DatabaseAdminClient;\nimport com.google.cloud.spanner.DatabaseClient;\nimport com.google.cloud.spanner.DatabaseId;\nimport com.google.cloud.spanner.Instance;\nimport com.google.cloud.spanner.InstanceAdminClient;\nimport com.google.cloud.spanner.InstanceConfigId;\nimport com.google.cloud.spanner.InstanceId;\nimport com.google.cloud.spanner.InstanceInfo;\nimport com.google.cloud.spanner.ResultSet;\nimport com.google.cloud.spanner.Spanner;\nimport com.google.cloud.spanner.SpannerOptions;\nimport com.google.cloud.spanner.Statement;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.util.Arrays;\nimport java.util.concurrent.ExecutionException;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass SpannerEmulatorContainerTest {\n\n    private static final String PROJECT_NAME = \"test-project\";\n\n    private static final String INSTANCE_NAME = \"test-instance\";\n\n    private static final String DATABASE_NAME = \"test-database\";\n\n    // testWithEmulatorContainer {\n    @Test\n    void testSimple() throws ExecutionException, InterruptedException {\n        try (\n            // emulatorContainer {\n            SpannerEmulatorContainer emulator = new SpannerEmulatorContainer(\n                DockerImageName.parse(\"gcr.io/cloud-spanner-emulator/emulator:1.4.0\")\n            );\n            // }\n        ) {\n            emulator.start();\n            SpannerOptions options = SpannerOptions\n                .newBuilder()\n                .setEmulatorHost(emulator.getEmulatorGrpcEndpoint())\n                .setCredentials(NoCredentials.getInstance())\n                .setProjectId(PROJECT_NAME)\n                .build();\n\n            Spanner spanner = options.getService();\n\n            InstanceId instanceId = createInstance(spanner);\n\n            createDatabase(spanner);\n\n            DatabaseId databaseId = DatabaseId.of(instanceId, DATABASE_NAME);\n            DatabaseClient dbClient = spanner.getDatabaseClient(databaseId);\n            dbClient\n                .readWriteTransaction()\n                .run(tx -> {\n                    String sql1 = \"Delete from TestTable where 1=1\";\n                    tx.executeUpdate(Statement.of(sql1));\n                    String sql = \"INSERT INTO TestTable (Key, Value) VALUES (1, 'Java'), (2, 'Go')\";\n                    tx.executeUpdate(Statement.of(sql));\n                    return null;\n                });\n\n            ResultSet resultSet = dbClient\n                .readOnlyTransaction()\n                .executeQuery(Statement.of(\"select * from TestTable order by Key\"));\n            resultSet.next();\n            assertThat(resultSet.getLong(0)).isEqualTo(1);\n            assertThat(resultSet.getString(1)).isEqualTo(\"Java\");\n        }\n    }\n\n    // }\n\n    // createDatabase {\n    private void createDatabase(Spanner spanner) throws InterruptedException, ExecutionException {\n        DatabaseAdminClient dbAdminClient = spanner.getDatabaseAdminClient();\n        Database database = dbAdminClient\n            .createDatabase(\n                INSTANCE_NAME,\n                DATABASE_NAME,\n                Arrays.asList(\"CREATE TABLE TestTable (Key INT64, Value STRING(MAX)) PRIMARY KEY (Key)\")\n            )\n            .get();\n    }\n\n    // }\n\n    // createInstance {\n    private InstanceId createInstance(Spanner spanner) throws InterruptedException, ExecutionException {\n        InstanceConfigId instanceConfig = InstanceConfigId.of(PROJECT_NAME, \"emulator-config\");\n        InstanceId instanceId = InstanceId.of(PROJECT_NAME, INSTANCE_NAME);\n        InstanceAdminClient insAdminClient = spanner.getInstanceAdminClient();\n        Instance instance = insAdminClient\n            .createInstance(\n                InstanceInfo\n                    .newBuilder(instanceId)\n                    .setNodeCount(1)\n                    .setDisplayName(\"Test instance\")\n                    .setInstanceConfigId(instanceConfig)\n                    .build()\n            )\n            .get();\n        return instanceId;\n    }\n    // }\n\n}\n"
  },
  {
    "path": "modules/gcloud/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/grafana/build.gradle",
    "content": "description = \"Testcontainers :: Grafana\"\n\ndependencies {\n    api project(':testcontainers')\n\n    testImplementation 'io.rest-assured:rest-assured:5.5.6'\n    testImplementation 'io.micrometer:micrometer-registry-otlp:1.16.1'\n    testImplementation 'uk.org.webcompere:system-stubs-jupiter:2.1.8'\n\n    testImplementation platform('io.opentelemetry:opentelemetry-bom:1.57.0')\n    testImplementation 'io.opentelemetry:opentelemetry-api'\n    testImplementation 'io.opentelemetry:opentelemetry-sdk'\n    testImplementation 'io.opentelemetry:opentelemetry-exporter-otlp'\n}\n"
  },
  {
    "path": "modules/grafana/src/main/java/org/testcontainers/grafana/LgtmStackContainer.java",
    "content": "package org.testcontainers.grafana;\n\nimport com.github.dockerjava.api.command.InspectContainerResponse;\nimport lombok.extern.slf4j.Slf4j;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.utility.DockerImageName;\n\n/**\n * Testcontainers implementation for Grafana OTel LGTM.\n * <p>\n * Supported image: {@code grafana/otel-lgtm}\n * <p>\n * Exposed ports:\n * <ul>\n *     <li>Grafana: 3000</li>\n *     <li>Tempo: 3200</li>\n *     <li>OTel Http: 4317</li>\n *     <li>OTel Grpc: 4318</li>\n *     <li>Prometheus: 9090</li>\n * </ul>\n */\n@Slf4j\npublic class LgtmStackContainer extends GenericContainer<LgtmStackContainer> {\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"grafana/otel-lgtm\");\n\n    private static final int GRAFANA_PORT = 3000;\n\n    private static final int OTLP_GRPC_PORT = 4317;\n\n    private static final int OTLP_HTTP_PORT = 4318;\n\n    private static final int LOKI_PORT = 3100;\n\n    private static final int TEMPO_PORT = 3200;\n\n    private static final int PROMETHEUS_PORT = 9090;\n\n    public LgtmStackContainer(String image) {\n        this(DockerImageName.parse(image));\n    }\n\n    public LgtmStackContainer(DockerImageName image) {\n        super(image);\n        image.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n        withExposedPorts(GRAFANA_PORT, TEMPO_PORT, LOKI_PORT, OTLP_GRPC_PORT, OTLP_HTTP_PORT, PROMETHEUS_PORT);\n        waitingFor(\n            Wait.forLogMessage(\".*The OpenTelemetry collector and the Grafana LGTM stack are up and running.*\\\\s\", 1)\n        );\n    }\n\n    @Override\n    protected void containerIsStarted(InspectContainerResponse containerInfo) {\n        log.info(\"Access to the Grafana dashboard: {}\", getGrafanaHttpUrl());\n    }\n\n    public String getOtlpGrpcUrl() {\n        return \"http://\" + getHost() + \":\" + getMappedPort(OTLP_GRPC_PORT);\n    }\n\n    public String getTempoUrl() {\n        return \"http://\" + getHost() + \":\" + getMappedPort(TEMPO_PORT);\n    }\n\n    public String getLokiUrl() {\n        return \"http://\" + getHost() + \":\" + getMappedPort(LOKI_PORT);\n    }\n\n    public String getOtlpHttpUrl() {\n        return \"http://\" + getHost() + \":\" + getMappedPort(OTLP_HTTP_PORT);\n    }\n\n    public String getPrometheusHttpUrl() {\n        return \"http://\" + getHost() + \":\" + getMappedPort(PROMETHEUS_PORT);\n    }\n\n    public String getGrafanaHttpUrl() {\n        return \"http://\" + getHost() + \":\" + getMappedPort(GRAFANA_PORT);\n    }\n}\n"
  },
  {
    "path": "modules/grafana/src/test/java/org/testcontainers/grafana/LgtmStackContainerTest.java",
    "content": "package org.testcontainers.grafana;\n\nimport io.micrometer.core.instrument.Clock;\nimport io.micrometer.core.instrument.Counter;\nimport io.micrometer.core.instrument.MeterRegistry;\nimport io.micrometer.registry.otlp.OtlpConfig;\nimport io.micrometer.registry.otlp.OtlpMeterRegistry;\nimport io.opentelemetry.api.common.AttributeKey;\nimport io.opentelemetry.api.common.Attributes;\nimport io.opentelemetry.api.logs.Logger;\nimport io.opentelemetry.api.trace.Span;\nimport io.opentelemetry.api.trace.Tracer;\nimport io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporter;\nimport io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;\nimport io.opentelemetry.sdk.OpenTelemetrySdk;\nimport io.opentelemetry.sdk.logs.SdkLoggerProvider;\nimport io.opentelemetry.sdk.logs.export.SimpleLogRecordProcessor;\nimport io.opentelemetry.sdk.resources.Resource;\nimport io.opentelemetry.sdk.trace.SdkTracerProvider;\nimport io.opentelemetry.sdk.trace.export.BatchSpanProcessor;\nimport io.restassured.RestAssured;\nimport io.restassured.response.Response;\nimport org.awaitility.Awaitility;\nimport org.junit.jupiter.api.Test;\nimport uk.org.webcompere.systemstubs.SystemStubs;\n\nimport java.time.Duration;\nimport java.util.concurrent.TimeUnit;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass LgtmStackContainerTest {\n\n    @Test\n    void shouldPublishMetricsTracesAndLogs() throws Exception {\n        try ( // container {\n            LgtmStackContainer lgtm = new LgtmStackContainer(\"grafana/otel-lgtm:0.11.1\")\n            // }\n        ) {\n            lgtm.start();\n\n            OtlpGrpcSpanExporter spanExporter = OtlpGrpcSpanExporter\n                .builder()\n                .setTimeout(Duration.ofSeconds(1))\n                .setEndpoint(lgtm.getOtlpGrpcUrl())\n                .build();\n\n            OtlpGrpcLogRecordExporter logExporter = OtlpGrpcLogRecordExporter\n                .builder()\n                .setTimeout(Duration.ofSeconds(1))\n                .setEndpoint(lgtm.getOtlpGrpcUrl())\n                .build();\n\n            BatchSpanProcessor spanProcessor = BatchSpanProcessor\n                .builder(spanExporter)\n                .setScheduleDelay(500, TimeUnit.MILLISECONDS)\n                .build();\n\n            SdkTracerProvider tracerProvider = SdkTracerProvider\n                .builder()\n                .addSpanProcessor(spanProcessor)\n                .setResource(Resource.create(Attributes.of(AttributeKey.stringKey(\"service.name\"), \"test-service\")))\n                .build();\n\n            SdkLoggerProvider loggerProvider = SdkLoggerProvider\n                .builder()\n                .addLogRecordProcessor(SimpleLogRecordProcessor.create(logExporter))\n                .build();\n\n            OpenTelemetrySdk openTelemetry = OpenTelemetrySdk\n                .builder()\n                .setTracerProvider(tracerProvider)\n                .setLoggerProvider(loggerProvider)\n                .build();\n\n            String version = RestAssured\n                .get(String.format(\"http://%s:%s/api/health\", lgtm.getHost(), lgtm.getMappedPort(3000)))\n                .jsonPath()\n                .get(\"version\");\n            assertThat(version).isEqualTo(\"12.0.0\");\n\n            OtlpConfig otlpConfig = createOtlpConfig(lgtm);\n            MeterRegistry meterRegistry = SystemStubs\n                .withEnvironmentVariable(\"OTEL_SERVICE_NAME\", \"testcontainers\")\n                .execute(() -> new OtlpMeterRegistry(otlpConfig, Clock.SYSTEM));\n            Counter.builder(\"test.counter\").register(meterRegistry).increment(2);\n\n            Logger logger = openTelemetry.getSdkLoggerProvider().loggerBuilder(\"test\").build();\n            logger\n                .logRecordBuilder()\n                .setBody(\"Test log!\")\n                .setAttribute(AttributeKey.stringKey(\"job\"), \"test-job\")\n                .emit();\n\n            Tracer tracer = openTelemetry.getTracer(\"test\");\n            Span span = tracer.spanBuilder(\"test\").startSpan();\n            span.end();\n\n            Awaitility\n                .given()\n                .pollInterval(Duration.ofSeconds(2))\n                .atMost(Duration.ofSeconds(5))\n                .ignoreExceptions()\n                .untilAsserted(() -> {\n                    Response metricResponse = RestAssured\n                        .given()\n                        .queryParam(\"query\", \"test_counter_total{job=\\\"testcontainers\\\"}\")\n                        .get(String.format(\"%s/api/v1/query\", lgtm.getPrometheusHttpUrl()))\n                        .prettyPeek()\n                        .thenReturn();\n                    assertThat(metricResponse.getStatusCode()).isEqualTo(200);\n                    assertThat(metricResponse.body().jsonPath().getList(\"data.result[0].value\")).contains(\"2\");\n\n                    Response logResponse = RestAssured\n                        .given()\n                        .queryParam(\"query\", \"{service_name=\\\"unknown_service:java\\\"}\")\n                        .get(String.format(\"%s/loki/api/v1/query_range\", lgtm.getLokiUrl()))\n                        .prettyPeek()\n                        .thenReturn();\n                    assertThat(logResponse.getStatusCode()).isEqualTo(200);\n                    assertThat(logResponse.body().jsonPath().getString(\"data.result[0].values[0][1]\"))\n                        .isEqualTo(\"Test log!\");\n\n                    Response traceResponse = RestAssured\n                        .given()\n                        .get(String.format(\"%s/api/search\", lgtm.getTempoUrl()))\n                        .prettyPeek()\n                        .thenReturn();\n                    assertThat(traceResponse.getStatusCode()).isEqualTo(200);\n                    assertThat(traceResponse.body().jsonPath().getString(\"traces[0].rootServiceName\"))\n                        .isEqualTo(\"test-service\");\n                });\n\n            openTelemetry.close();\n        }\n    }\n\n    private static OtlpConfig createOtlpConfig(LgtmStackContainer lgtm) {\n        return new OtlpConfig() {\n            @Override\n            public String url() {\n                return String.format(\"%s/v1/metrics\", lgtm.getOtlpHttpUrl());\n            }\n\n            @Override\n            public Duration step() {\n                return Duration.ofSeconds(1);\n            }\n\n            @Override\n            public String get(String s) {\n                return null;\n            }\n        };\n    }\n}\n"
  },
  {
    "path": "modules/grafana/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/hivemq/build.gradle",
    "content": "description = \"Testcontainers :: HiveMQ\"\n\ndependencies {\n    api(project(\":testcontainers\"))\n    api(\"org.jetbrains:annotations:26.0.2-1\")\n\n    shaded(\"org.apache.commons:commons-lang3:3.20.0\")\n    shaded(\"commons-io:commons-io:2.21.0\")\n    shaded(\"org.javassist:javassist:3.30.2-GA\")\n    shaded(\"org.jboss.shrinkwrap:shrinkwrap-api:1.2.6\")\n    shaded(\"org.jboss.shrinkwrap:shrinkwrap-impl-base:1.2.6\")\n    shaded(\"net.lingala.zip4j:zip4j:2.11.5\")\n\n    testImplementation(project(\":testcontainers-junit-jupiter\"))\n    testImplementation(\"com.hivemq:hivemq-extension-sdk:4.47.1\")\n    testImplementation(\"com.hivemq:hivemq-mqtt-client:1.3.10\")\n    testImplementation(\"org.apache.httpcomponents:httpclient:4.5.14\")\n    testImplementation(\"ch.qos.logback:logback-classic:1.5.22\")\n}\n\ntest {\n    javaLauncher = javaToolchains.launcherFor {\n        languageVersion = JavaLanguageVersion.of(11)\n    }\n}\n\ncompileTestJava {\n    javaCompiler = javaToolchains.compilerFor {\n        languageVersion = JavaLanguageVersion.of(11)\n    }\n    options.release.set(11)\n}\n"
  },
  {
    "path": "modules/hivemq/src/main/java/org/testcontainers/hivemq/HiveMQContainer.java",
    "content": "package org.testcontainers.hivemq;\n\nimport com.github.dockerjava.api.command.InspectContainerResponse;\nimport org.apache.commons.io.FileUtils;\nimport org.jetbrains.annotations.NotNull;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.slf4j.event.Level;\nimport org.testcontainers.containers.ContainerLaunchException;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.containers.wait.strategy.WaitAllStrategy;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.MountableFile;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.time.Duration;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Set;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport java.util.stream.Collectors;\n\n/**\n * Testcontainers implementation for HiveMQ.\n * <p>\n * Supported images: {@code hivemq/hivemq4}, {@code hivemq/hivemq-ce}\n * <p>\n * Exposed ports:\n * <ul>\n *     <li>MQTT: 1883</li>\n *     <li>Control Center: 8080</li>\n *     <li>Debug: 9000</li>\n * </ul>\n */\npublic class HiveMQContainer extends GenericContainer<HiveMQContainer> {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(HiveMQContainer.class);\n\n    private static final DockerImageName DEFAULT_HIVEMQ_EE_IMAGE_NAME = DockerImageName.parse(\"hivemq/hivemq4\");\n\n    private static final DockerImageName DEFAULT_HIVEMQ_CE_IMAGE_NAME = DockerImageName.parse(\"hivemq/hivemq-ce\");\n\n    private static final int DEBUGGING_PORT = 9000;\n\n    private static final int MQTT_PORT = 1883;\n\n    private static final int CONTROL_CENTER_PORT = 8080;\n\n    @SuppressWarnings(\"OctalInteger\")\n    private static final int MODE = 0777;\n\n    @NotNull\n    private static final Pattern EXTENSION_ID_PATTERN = Pattern.compile(\"<id>(.+?)</id>\");\n\n    @NotNull\n    private final ConcurrentHashMap<String, CountDownLatch> containerOutputLatches = new ConcurrentHashMap<>();\n\n    private boolean controlCenterEnabled = false;\n\n    private boolean debugging = false;\n\n    @NotNull\n    private final Set<String> prepackagedExtensionsToRemove = new HashSet<>();\n\n    private boolean removeAllPrepackagedExtensions = false;\n\n    @NotNull\n    private final WaitAllStrategy waitStrategy = new WaitAllStrategy();\n\n    public HiveMQContainer(final @NotNull DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_HIVEMQ_CE_IMAGE_NAME, DEFAULT_HIVEMQ_EE_IMAGE_NAME);\n\n        addExposedPort(MQTT_PORT);\n\n        waitStrategy.withStrategy(Wait.forLogMessage(\"(.*)Started HiveMQ in(.*)\", 1));\n        waitingFor(waitStrategy);\n\n        withLogConsumer(outputFrame -> {\n            final String utf8String = outputFrame.getUtf8String();\n            if (debugging && utf8String.startsWith(\"Listening for transport dt_socket at address:\")) {\n                System.out.println(\"Listening for transport dt_socket at address: \" + getMappedPort(DEBUGGING_PORT));\n            }\n            if (!containerOutputLatches.isEmpty()) {\n                containerOutputLatches.forEach((regEx, latch) -> {\n                    if (outputFrame.getUtf8String().matches(\"(?s)\" + regEx)) {\n                        LOGGER.debug(\"Container Output '{}' matched RegEx '{}'\", utf8String, regEx);\n                        latch.countDown();\n                    } else {\n                        LOGGER.debug(\"Container Output '{}' did not match RegEx '{}'\", utf8String, regEx);\n                    }\n                });\n            }\n        });\n\n        final HashMap<String, String> tmpFs = new HashMap<>();\n        if (dockerImageName.isCompatibleWith(DEFAULT_HIVEMQ_EE_IMAGE_NAME)) {\n            tmpFs.put(\"/opt/hivemq/audit\", \"rw\");\n            tmpFs.put(\"/opt/hivemq/backup\", \"rw\");\n        }\n\n        tmpFs.put(\"/opt/hivemq/log\", \"rw\");\n        tmpFs.put(\"/opt/hivemq/data\", \"rw\");\n        withTmpFs(tmpFs);\n    }\n\n    @Override\n    protected void configure() {\n        final String removeCommand;\n        withCreateContainerCmdModifier(it -> it.withEntrypoint(\"/bin/sh\"));\n\n        if (removeAllPrepackagedExtensions || !prepackagedExtensionsToRemove.isEmpty()) {\n            if (removeAllPrepackagedExtensions) {\n                removeCommand = \"rm -rf /opt/hivemq/extensions/** &&\";\n            } else {\n                removeCommand =\n                    prepackagedExtensionsToRemove\n                        .stream()\n                        .map(extensionId -> \"rm -rf /opt/hivemq/extensions/\" + extensionId + \"&&\")\n                        .collect(Collectors.joining());\n            }\n        } else {\n            removeCommand = \"\";\n        }\n        setCommand(\n            \"-c\",\n            removeCommand +\n            \"cp -r '/opt/hivemq/temp-extensions/'* /opt/hivemq/extensions/ ; \" +\n            \"chmod -R 777 /opt/hivemq/extensions ; \" +\n            \"/opt/docker-entrypoint.sh /opt/hivemq/bin/run.sh\"\n        );\n    }\n\n    protected void containerIsStarted(final @NotNull InspectContainerResponse containerInfo) {\n        if (controlCenterEnabled) {\n            LOGGER.info(\n                \"The HiveMQ Control Center is reachable under: http://{}:{}\",\n                getHost(),\n                getMappedPort(CONTROL_CENTER_PORT)\n            );\n        }\n    }\n\n    /**\n     * Adds a wait condition for the extension with this name.\n     * <p>\n     * Must be called before the container is started.\n     *\n     * @param extensionName the extension to wait for\n     * @return self\n     */\n    public @NotNull HiveMQContainer waitForExtension(final @NotNull String extensionName) {\n        final String regEX = \"(.*)Extension \\\"\" + extensionName + \"\\\" version (.*) started successfully(.*)\";\n        waitStrategy.withStrategy(Wait.forLogMessage(regEX, 1));\n        return self();\n    }\n\n    /**\n     * Adds a wait condition for this {@link HiveMQExtension}\n     * <p>\n     * Must be called before the container is started.\n     *\n     * @param extension the extension to wait for\n     * @return self\n     */\n    public @NotNull HiveMQContainer waitForExtension(final @NotNull HiveMQExtension extension) {\n        return this.waitForExtension(extension.getName());\n    }\n\n    /**\n     * Enables the possibility for remote debugging clients to connect.\n     * <p>\n     * Must be called before the container is started.\n     *\n     * @return self\n     */\n    public @NotNull HiveMQContainer withDebugging() {\n        debugging = true;\n        addExposedPorts(DEBUGGING_PORT);\n        withEnv(\n            \"JAVA_OPTS\",\n            \"-agentlib:jdwp=transport=dt_socket,address=0.0.0.0:\" + DEBUGGING_PORT + \",server=y,suspend=y\"\n        );\n        return self();\n    }\n\n    /**\n     * Sets the logging {@link Level} inside the container.\n     * <p>\n     * Must be called before the container is started.\n     *\n     * @param level the {@link Level}\n     * @return self\n     */\n    public @NotNull HiveMQContainer withLogLevel(final @NotNull Level level) {\n        this.withEnv(\"HIVEMQ_LOG_LEVEL\", level.name());\n        return self();\n    }\n\n    /**\n     * Wraps the given class and all its subclasses into an extension\n     * and puts it into '/opt/hivemq/temp-extensions/{extension-id}' inside the container.\n     * <p>\n     * Must be called before the container is started.\n     * <p>\n     * The contents of the '/opt/hivemq/temp-extensions/' directory are copied to '/opt/hivemq/extensions/' before the container is started.\n     *\n     * @param hiveMQExtension the {@link HiveMQExtension} of the extension\n     * @return self\n     */\n    public @NotNull HiveMQContainer withExtension(final @NotNull HiveMQExtension hiveMQExtension) {\n        try {\n            final File extension = hiveMQExtension.createExtension(hiveMQExtension);\n            final MountableFile mountableExtension = MountableFile.forHostPath(extension.getPath(), MODE);\n            withCopyFileToContainer(mountableExtension, \"/opt/hivemq/temp-extensions/\" + hiveMQExtension.getId());\n        } catch (final Exception e) {\n            throw new ContainerLaunchException(e.getMessage() == null ? \"\" : e.getMessage(), e);\n        }\n        return self();\n    }\n\n    /**\n     * Puts the given extension folder into '/opt/hivemq/temp-extensions/{directory-name}' inside the container.\n     * It must at least contain a valid hivemq-extension.xml and a valid extension.jar in order to be executed.\n     * The directory-name is taken from the id defined in the hivemq-extension.xml.\n     * <p>\n     * Must be called before the container is started.\n     * <p>\n     * The contents of the '/opt/hivemq/temp-extensions/' directory are copied to '/opt/hivemq/extensions/' before the container is started.\n     *\n     * @param mountableExtension the extension folder on the host machine\n     * @return self\n     */\n    public @NotNull HiveMQContainer withExtension(final @NotNull MountableFile mountableExtension) {\n        final File extensionDir = new File(mountableExtension.getResolvedPath());\n        if (!extensionDir.exists()) {\n            throw new ContainerLaunchException(\n                \"Extension '\" + mountableExtension.getFilesystemPath() + \"' could not be mounted. It does not exist.\"\n            );\n        }\n        if (!extensionDir.isDirectory()) {\n            throw new ContainerLaunchException(\n                \"Extension '\" +\n                mountableExtension.getFilesystemPath() +\n                \"' could not be mounted. It is not a directory.\"\n            );\n        }\n        try {\n            final String extensionDirName = getExtensionDirectoryName(extensionDir);\n            final String containerPath = \"/opt/hivemq/temp-extensions/\" + extensionDirName;\n            withCopyFileToContainer(cloneWithFileMode(mountableExtension), containerPath);\n            LOGGER.info(\"Putting extension '{}' into '{}'\", extensionDirName, containerPath);\n        } catch (final Exception e) {\n            throw new ContainerLaunchException(e.getMessage() == null ? \"\" : e.getMessage(), e);\n        }\n        return self();\n    }\n\n    private @NotNull String getExtensionDirectoryName(final @NotNull File extensionDirectory) throws IOException {\n        final File file = new File(extensionDirectory, \"hivemq-extension.xml\");\n        final String xml = FileUtils.readFileToString(file, StandardCharsets.UTF_8);\n        final Matcher matcher = EXTENSION_ID_PATTERN.matcher(xml);\n\n        if (!matcher.find()) {\n            throw new IllegalStateException(\"Could not parse extension id from '\" + file.getAbsolutePath() + \"'\");\n        }\n        return matcher.group(1);\n    }\n\n    /**\n     * Removes the specified prepackaged extension folders from '/opt/hivemq/extensions' before the container is started.\n     * <p>\n     * Must be called before the container is started.\n     *\n     * @param extensionIds the prepackaged extensions to remove\n     * @return self\n     */\n    public @NotNull HiveMQContainer withoutPrepackagedExtensions(final @NotNull String... extensionIds) {\n        Collections.addAll(prepackagedExtensionsToRemove, extensionIds);\n        return self();\n    }\n\n    /**\n     * Removes all prepackaged extension folders from '/opt/hivemq/extensions' before the container is started.\n     * <p>\n     * Must be called before the container is started.\n     *\n     * @return self\n     */\n    public @NotNull HiveMQContainer withoutPrepackagedExtensions() {\n        removeAllPrepackagedExtensions = true;\n        return self();\n    }\n\n    /**\n     * Puts the given license into '/opt/hivemq/license/' inside the container.\n     * It must end with '.lic' or '.elic'.\n     * <p>\n     * Must be called before the container is started.\n     *\n     * @param mountableLicense the license file on the host machine\n     * @return self\n     */\n    public @NotNull HiveMQContainer withLicense(final @NotNull MountableFile mountableLicense) {\n        final File licenseFile = new File(mountableLicense.getResolvedPath());\n        if (!licenseFile.exists()) {\n            throw new ContainerLaunchException(\n                \"License file '\" + mountableLicense.getFilesystemPath() + \"' does not exist.\"\n            );\n        }\n        if (!licenseFile.getName().endsWith(\".lic\") && !licenseFile.getName().endsWith(\".elic\")) {\n            throw new ContainerLaunchException(\n                \"License file '\" + mountableLicense.getFilesystemPath() + \"' does not end wit '.lic' or '.elic'.\"\n            );\n        }\n        final String containerPath = \"/opt/hivemq/license/\" + licenseFile.getName();\n        withCopyFileToContainer(cloneWithFileMode(mountableLicense), containerPath);\n        LOGGER.info(\"Putting license '{}' into '{}'.\", licenseFile.getAbsolutePath(), containerPath);\n        return self();\n    }\n\n    /**\n     * Overwrites the HiveMQ configuration in '/opt/hivemq/conf/' inside the container.\n     * <p>\n     * Must be called before the container is started.\n     *\n     * @param mountableConfig the config file on the host machine\n     * @return self\n     */\n    public @NotNull HiveMQContainer withHiveMQConfig(final @NotNull MountableFile mountableConfig) {\n        final File config = new File(mountableConfig.getResolvedPath());\n        if (!config.exists()) {\n            throw new ContainerLaunchException(\n                \"HiveMQ config file '\" + mountableConfig.getFilesystemPath() + \"' does not exist.\"\n            );\n        }\n        final String containerPath = \"/opt/hivemq/conf/config.xml\";\n        withCopyFileToContainer(cloneWithFileMode(mountableConfig), containerPath);\n        LOGGER.info(\"Putting '{}' into '{}'.\", config.getAbsolutePath(), containerPath);\n        return self();\n    }\n\n    /**\n     * Puts the given file into the root of the extension's home '/opt/hivemq/temp-extensions/{extensionId}/'.\n     * <p>\n     * Must be called before the container is started.\n     * <p>\n     * The contents of the '/opt/hivemq/temp-extensions/' directory are copied to '/opt/hivemq/extensions/' before the container is started.\n     *\n     * @param file        the file on the host machine\n     * @param extensionId the extension\n     * @return self\n     */\n    public @NotNull HiveMQContainer withFileInExtensionHomeFolder(\n        final @NotNull MountableFile file,\n        final @NotNull String extensionId\n    ) {\n        return withFileInExtensionHomeFolder(file, extensionId, \"\");\n    }\n\n    /**\n     * Puts the given file into given subdirectory of the extensions's home '/opt/hivemq/temp-extensions/{id}/{pathInExtensionHome}/'\n     * <p>\n     * Must be called before the container is started.\n     * <p>\n     * The contents of the '/opt/hivemq/temp-extensions/' directory are copied to '/opt/hivemq/extensions/' before the container is started.\n     *\n     * @param file                the file on the host machine\n     * @param extensionId         the extension\n     * @param pathInExtensionHome the path\n     * @return self\n     */\n    public @NotNull HiveMQContainer withFileInExtensionHomeFolder(\n        final @NotNull MountableFile file,\n        final @NotNull String extensionId,\n        final @NotNull String pathInExtensionHome\n    ) {\n        return withFileInHomeFolder(\n            file,\n            \"/temp-extensions/\" + extensionId + PathUtil.prepareAppendPath(pathInExtensionHome)\n        );\n    }\n\n    /**\n     * Puts the given file into the given subdirectory of the HiveMQ home folder '/opt/hivemq/{pathInHomeFolder}'.\n     * <p>\n     * Must be called before the container is started.\n     *\n     * @param mountableFile    the file on the host machine\n     * @param pathInHomeFolder the path\n     * @return self\n     */\n    public @NotNull HiveMQContainer withFileInHomeFolder(\n        final @NotNull MountableFile mountableFile,\n        final @NotNull String pathInHomeFolder\n    ) {\n        final File file = new File(mountableFile.getResolvedPath());\n\n        if (pathInHomeFolder.trim().isEmpty()) {\n            throw new ContainerLaunchException(\"pathInHomeFolder must not be empty\");\n        }\n\n        if (!file.exists()) {\n            throw new ContainerLaunchException(\"File '\" + mountableFile.getFilesystemPath() + \"' does not exist.\");\n        }\n        final String containerPath = \"/opt/hivemq\" + PathUtil.prepareAppendPath(pathInHomeFolder);\n        withCopyFileToContainer(cloneWithFileMode(mountableFile), containerPath);\n        LOGGER.info(\"Putting file '{}' into container path '{}'.\", file.getAbsolutePath(), containerPath);\n        return self();\n    }\n\n    /**\n     * Disables the extension with the given name and extension directory name.\n     * This method blocks until the HiveMQ log for successful disabling is consumed or it times out after {timeOut}.\n     * Note: Disabling Extensions is a HiveMQ Enterprise feature, it will not work when using the HiveMQ Community Edition.\n     * <p>\n     * This can only be called once the container is started.\n     *\n     * @param extensionName      the name of the extension to disable\n     * @param extensionDirectory the name of the extension's directory\n     * @param timeout            the timeout\n     * @throws TimeoutException if the extension was not disabled within the configured timeout\n     */\n    public void disableExtension(\n        final @NotNull String extensionName,\n        final @NotNull String extensionDirectory,\n        final @NotNull Duration timeout\n    ) throws TimeoutException {\n        final String regEX = \"(.*)Extension \\\"\" + extensionName + \"\\\" version (.*) stopped successfully(.*)\";\n        try {\n            final String containerPath =\n                \"/opt/hivemq/extensions\" + PathUtil.prepareInnerPath(extensionDirectory) + \"DISABLED\";\n\n            final CountDownLatch latch = new CountDownLatch(1);\n            containerOutputLatches.put(regEX, latch);\n\n            execInContainer(\"touch\", containerPath);\n            LOGGER.info(\"Putting DISABLED file into container path '{}'\", containerPath);\n\n            final boolean await = latch.await(timeout.getSeconds(), TimeUnit.SECONDS);\n            if (!await) {\n                throw new TimeoutException(\n                    \"Extension disabling timed out after '\" +\n                    timeout.getSeconds() +\n                    \"' seconds. \" +\n                    \"Maybe you are using a HiveMQ Community Edition image, \" +\n                    \"which does not support disabling of extensions\"\n                );\n            }\n        } catch (final InterruptedException | IOException e) {\n            throw new RuntimeException(e);\n        } finally {\n            containerOutputLatches.remove(regEX);\n        }\n    }\n\n    /**\n     * Disables the extension with the given name and extension directory name.\n     * This method blocks until the HiveMQ log for successful disabling is consumed or it times out after 60 seconds.\n     * Note: Disabling Extensions is a HiveMQ Enterprise feature, it will not work when using the HiveMQ Community Edition.\n     * <p>\n     * This can only be called once the container is started.\n     *\n     * @param extensionName      the name of the extension to disable\n     * @param extensionDirectory the name of the extension's directory\n     * @throws TimeoutException if the extension was not disabled within 60 seconds\n     */\n    public void disableExtension(final @NotNull String extensionName, final @NotNull String extensionDirectory)\n        throws TimeoutException {\n        disableExtension(extensionName, extensionDirectory, Duration.ofSeconds(60));\n    }\n\n    /**\n     * Disables the extension.\n     * This method blocks until the HiveMQ log for successful disabling is consumed or it times out after {timeOut}.\n     * Note: Disabling Extensions is a HiveMQ Enterprise feature, it will not work when using the HiveMQ Community Edition.\n     * <p>\n     * This can only be called once the container is started.\n     *\n     * @param hiveMQExtension the extension\n     * @param timeout         the timeout\n     * @throws TimeoutException if the extension was not disabled within the configured timeout\n     */\n    public void disableExtension(final @NotNull HiveMQExtension hiveMQExtension, final @NotNull Duration timeout)\n        throws TimeoutException {\n        disableExtension(hiveMQExtension.getName(), hiveMQExtension.getId(), timeout);\n    }\n\n    /**\n     * Disables the extension.\n     * This method blocks until the HiveMQ log for successful disabling is consumed or it times out after 60 seconds.\n     * Note: Disabling Extensions is a HiveMQ Enterprise feature, it will not work when using the HiveMQ Community Edition.\n     * <p>\n     * This can only be called once the container is started.\n     *\n     * @param hiveMQExtension the extension\n     * @throws TimeoutException if the extension was not disabled within 60 seconds\n     */\n    public void disableExtension(final @NotNull HiveMQExtension hiveMQExtension) throws TimeoutException {\n        disableExtension(hiveMQExtension, Duration.ofSeconds(60));\n    }\n\n    /**\n     * Enables the extension with the given name and extension directory name.\n     * This method blocks until the HiveMQ log for successful enabling is consumed or it times out after {timeOut}.\n     * Note: Enabling Extensions is a HiveMQ Enterprise feature, it will not work when using the HiveMQ Community Edition.\n     * <p>\n     * This can only be called once the container is started.\n     *\n     * @param extensionName      the name of the extension to disable\n     * @param extensionDirectory the name of the extension's directory\n     * @param timeout            the timeout\n     * @throws TimeoutException if the extension was not enabled within the configured timeout\n     */\n    public void enableExtension(\n        final @NotNull String extensionName,\n        final @NotNull String extensionDirectory,\n        final @NotNull Duration timeout\n    ) throws TimeoutException {\n        final String regEX = \"(.*)Extension \\\"\" + extensionName + \"\\\" version (.*) started successfully(.*)\";\n        try {\n            final String containerPath =\n                \"/opt/hivemq/extensions\" + PathUtil.prepareInnerPath(extensionDirectory) + \"DISABLED\";\n\n            final CountDownLatch latch = new CountDownLatch(1);\n            containerOutputLatches.put(regEX, latch);\n\n            execInContainer(\"rm\", \"-rf\", containerPath);\n            LOGGER.info(\"Removing DISABLED file in container path '{}'\", containerPath);\n\n            final boolean await = latch.await(timeout.getSeconds(), TimeUnit.SECONDS);\n            if (!await) {\n                throw new TimeoutException(\n                    \"Extension enabling timed out after '\" +\n                    timeout.getSeconds() +\n                    \"' seconds. \" +\n                    \"Maybe you are using a HiveMQ Community Edition image, \" +\n                    \"which does not support disabling of extensions\"\n                );\n            }\n        } catch (final InterruptedException | IOException e) {\n            throw new RuntimeException(e);\n        } finally {\n            containerOutputLatches.remove(regEX);\n        }\n    }\n\n    /**\n     * Enables the extension with the given name and extension directory name.\n     * This method blocks until the HiveMQ log for successful enabling is consumed or it times out after 60 seconds.\n     * Note: Enabling Extensions is a HiveMQ Enterprise feature, it will not work when using the HiveMQ Community Edition.\n     * <p>\n     * This can only be called once the container is started.\n     *\n     * @param extensionName      the name of the extension to disable\n     * @param extensionDirectory the name of the extension's directory\n     * @throws TimeoutException if the extension was not enabled within 60 seconds\n     */\n    public void enableExtension(final @NotNull String extensionName, final @NotNull String extensionDirectory)\n        throws TimeoutException {\n        enableExtension(extensionName, extensionDirectory, Duration.ofSeconds(60));\n    }\n\n    /**\n     * Enables the extension.\n     * This method blocks until the HiveMQ log for successful enabling is consumed or it times out after {timeOut}.\n     * Note: Enabling Extensions is a HiveMQ Enterprise feature, it will not work when using the HiveMQ Community Edition.\n     * <p>\n     * This can only be called once the container is started.\n     *\n     * @param hiveMQExtension the extension\n     * @param timeout         the timeout\n     * @throws TimeoutException if the extension was not enabled within the configured timeout\n     */\n    public void enableExtension(final @NotNull HiveMQExtension hiveMQExtension, final @NotNull Duration timeout)\n        throws TimeoutException {\n        enableExtension(hiveMQExtension.getName(), hiveMQExtension.getId(), timeout);\n    }\n\n    /**\n     * Enables the extension.\n     * This method blocks until the HiveMQ log for successful enabling is consumed or it times out after {timeOut}.\n     * Note: Enabling Extensions is a HiveMQ Enterprise feature, it will not work when using the HiveMQ Community Edition.\n     * <p>\n     * This can only be called once the container is started.\n     *\n     * @param hiveMQExtension the extension\n     * @throws TimeoutException if the extension was not enabled within 60 seconds\n     */\n    public void enableExtension(final @NotNull HiveMQExtension hiveMQExtension) throws TimeoutException {\n        enableExtension(hiveMQExtension, Duration.ofSeconds(60));\n    }\n\n    /**\n     * Enables connection to the HiveMQ Control Center on host port 8080.\n     * Note: the control center is a HiveMQ 4 Enterprise feature.\n     * <p>\n     * Must be called before the container is started.\n     *\n     * @return self\n     */\n    public @NotNull HiveMQContainer withControlCenter() {\n        addExposedPorts(CONTROL_CENTER_PORT);\n        controlCenterEnabled = true;\n        return self();\n    }\n\n    /**\n     * Get the mapped port for the MQTT port of the container.\n     * <p>\n     * Must be called after the container is started.\n     *\n     * @return the port on the host machine for mqtt clients to connect\n     */\n    public int getMqttPort() {\n        return this.getMappedPort(MQTT_PORT);\n    }\n\n    private @NotNull MountableFile cloneWithFileMode(final @NotNull MountableFile mountableFile) {\n        return MountableFile.forHostPath(mountableFile.getResolvedPath(), HiveMQContainer.MODE);\n    }\n}\n"
  },
  {
    "path": "modules/hivemq/src/main/java/org/testcontainers/hivemq/HiveMQExtension.java",
    "content": "package org.testcontainers.hivemq;\n\nimport lombok.Getter;\nimport org.apache.commons.io.FileUtils;\nimport org.jboss.shrinkwrap.api.ExtensionLoader;\nimport org.jboss.shrinkwrap.api.ShrinkWrap;\nimport org.jboss.shrinkwrap.api.exporter.ZipExporter;\nimport org.jboss.shrinkwrap.api.spec.JavaArchive;\nimport org.jboss.shrinkwrap.impl.base.exporter.zip.ZipExporterImpl;\nimport org.jboss.shrinkwrap.impl.base.spec.JavaArchiveImpl;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.testcontainers.containers.ContainerLaunchException;\n\nimport java.io.File;\nimport java.nio.charset.Charset;\nimport java.nio.file.Files;\nimport java.util.Collections;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Set;\nimport javassist.ClassPool;\nimport javassist.NotFoundException;\n\npublic class HiveMQExtension {\n\n    private static final String VALID_EXTENSION_XML =\n        \"<hivemq-extension>\" + //\n        \"   <id>%s</id>\" + //\n        \"   <name>%s</name>\" + //\n        \"   <version>%s</version>\" + //\n        \"   <priority>%s</priority>\" + //\n        \"   <start-priority>%s</start-priority>\" + //\n        \"</hivemq-extension>\";\n\n    private static final String EXTENSION_MAIN_CLASS_NAME = \"com.hivemq.extension.sdk.api.ExtensionMain\";\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(HiveMQExtension.class);\n\n    @Getter\n    @NotNull\n    private final String id;\n\n    @Getter\n    @NotNull\n    private final String name;\n\n    @Getter\n    @NotNull\n    private final String version;\n\n    @Getter\n    private final int priority;\n\n    @Getter\n    private final int startPriority;\n\n    @Getter\n    private final boolean disabledOnStartup;\n\n    @Getter\n    @NotNull\n    private final Class<?> mainClass;\n\n    @NotNull\n    private final List<Class<?>> additionalClasses;\n\n    private HiveMQExtension(\n        final @NotNull String id,\n        final @NotNull String name,\n        final @NotNull String version,\n        final int priority,\n        final int startPriority,\n        final boolean disabledOnStartup,\n        final @NotNull Class<?> mainClass,\n        final @NotNull List<Class<?>> additionalClasses\n    ) {\n        this.id = id;\n        this.name = name;\n        this.version = version;\n        this.priority = priority;\n        this.startPriority = startPriority;\n        this.disabledOnStartup = disabledOnStartup;\n        this.mainClass = mainClass;\n        this.additionalClasses = additionalClasses;\n    }\n\n    @NotNull\n    File createExtension(final @NotNull HiveMQExtension hiveMQExtension) throws Exception {\n        final File tempDir = Files.createTempDirectory(\"\").toFile();\n\n        final File extensionDir = new File(tempDir, hiveMQExtension.getId());\n        FileUtils.writeStringToFile(\n            new File(extensionDir, \"hivemq-extension.xml\"),\n            String.format(\n                VALID_EXTENSION_XML,\n                hiveMQExtension.getId(),\n                hiveMQExtension.getName(),\n                hiveMQExtension.getVersion(),\n                hiveMQExtension.getPriority(),\n                hiveMQExtension.getStartPriority()\n            ),\n            Charset.defaultCharset()\n        );\n\n        if (hiveMQExtension.isDisabledOnStartup()) {\n            final File disabled = new File(extensionDir, \"DISABLED\");\n            final boolean newFile = disabled.createNewFile();\n            if (!newFile) {\n                throw new ContainerLaunchException(\n                    \"Could not create DISABLED file '\" + disabled.getAbsolutePath() + \"' on host machine.\"\n                );\n            }\n        }\n\n        // Shadow Gradle plugin doesn't know how to handle ShrinkWrap's SPI definitions\n        // This workaround creates the mappings programmatically\n        // TODO write a custom Gradle Shadow transformer?\n        ExtensionLoader extensionLoader = ShrinkWrap.getDefaultDomain().getConfiguration().getExtensionLoader();\n        extensionLoader.addOverride(JavaArchive.class, JavaArchiveImpl.class);\n        extensionLoader.addOverride(ZipExporter.class, ZipExporterImpl.class);\n\n        final JavaArchive javaArchive = ShrinkWrap\n            .create(JavaArchive.class)\n            .addAsServiceProvider(EXTENSION_MAIN_CLASS_NAME, hiveMQExtension.getMainClass().getName());\n\n        putSubclassesIntoJar(hiveMQExtension.getId(), hiveMQExtension.getMainClass(), javaArchive);\n        for (final Class<?> additionalClass : hiveMQExtension.getAdditionalClasses()) {\n            javaArchive.addClass(additionalClass);\n            putSubclassesIntoJar(hiveMQExtension.getId(), additionalClass, javaArchive);\n        }\n\n        javaArchive.as(ZipExporter.class).exportTo(new File(extensionDir, \"extension.jar\"));\n\n        return extensionDir;\n    }\n\n    private void putSubclassesIntoJar(\n        final @NotNull String extensionId,\n        final @Nullable Class<?> clazz,\n        final @NotNull JavaArchive javaArchive\n    ) throws NotFoundException {\n        if (clazz != null) {\n            final Set<String> subClassNames = ClassPool\n                .getDefault()\n                .get(clazz.getName())\n                .getClassFile()\n                .getConstPool()\n                .getClassNames();\n            for (final String subClassName : subClassNames) {\n                final String className = subClassName.replaceAll(\"/\", \".\");\n\n                if (!className.startsWith(\"[L\")) {\n                    LOGGER.debug(\"Trying to package subclass '{}' into extension '{}'.\", className, extensionId);\n                    javaArchive.addClass(className);\n                } else {\n                    LOGGER.debug(\"Class '{}' will be ignored.\", className);\n                }\n            }\n        }\n    }\n\n    public @NotNull List<Class<?>> getAdditionalClasses() {\n        return Collections.unmodifiableList(additionalClasses);\n    }\n\n    public static @NotNull Builder builder() {\n        return new Builder();\n    }\n\n    public static final class Builder {\n\n        @Nullable\n        private String id;\n\n        @Nullable\n        private String name;\n\n        @Nullable\n        private String version;\n\n        private int priority = 0;\n\n        private int startPriority = 0;\n\n        private boolean disabledOnStartup = false;\n\n        @Nullable\n        private Class<?> mainClass;\n\n        @NotNull\n        private final LinkedList<Class<?>> additionalClasses = new LinkedList<>();\n\n        /**\n         * Builds the {@link HiveMQExtension} with the provided values or default values.\n         * @return the HiveMQ Extension\n         */\n        public @NotNull HiveMQExtension build() {\n            if (id == null || id.isEmpty()) {\n                throw new IllegalArgumentException(\"extension id must not be null or empty\");\n            }\n            if (name == null || name.isEmpty()) {\n                throw new IllegalArgumentException(\"extension name must not be null or empty\");\n            }\n            if (version == null || version.isEmpty()) {\n                throw new IllegalArgumentException(\"extension version must not be null or empty\");\n            }\n            if (mainClass == null) {\n                throw new IllegalArgumentException(\"extension main class must not be null\");\n            }\n            return new HiveMQExtension(\n                id,\n                name,\n                version,\n                priority,\n                startPriority,\n                disabledOnStartup,\n                mainClass,\n                additionalClasses\n            );\n        }\n\n        /**\n         * Sets the identifier of the {@link HiveMQExtension}.\n         *\n         * @param id the identifier, must not be empty\n         * @return the {@link Builder}\n         */\n        public @NotNull Builder id(final @NotNull String id) {\n            this.id = id;\n            return this;\n        }\n\n        /**\n         * Sets the name of the {@link HiveMQExtension}.\n         *\n         * @param name the identifier, must not be empty\n         * @return the {@link Builder}\n         */\n        public @NotNull Builder name(final @NotNull String name) {\n            this.name = name;\n            return this;\n        }\n\n        /**\n         * Sets the version of the {@link HiveMQExtension}.\n         *\n         * @param version the version, must not be empty\n         * @return the {@link Builder}\n         */\n        public @NotNull Builder version(final @NotNull String version) {\n            this.version = version;\n            return this;\n        }\n\n        /**\n         * Sets the priority of the {@link HiveMQExtension}.\n         *\n         * @param priority the priority\n         * @return the {@link Builder}\n         */\n        public @NotNull Builder priority(final int priority) {\n            this.priority = priority;\n            return this;\n        }\n\n        /**\n         * Sets the start-priority of the {@link HiveMQExtension}.\n         *\n         * @param startPriority the start-priority\n         * @return the {@link Builder}\n         */\n        public @NotNull Builder startPriority(final int startPriority) {\n            this.startPriority = startPriority;\n            return this;\n        }\n\n        /**\n         * Flag, that indicates whether the {@link HiveMQExtension} should be disabled when HiveMQ starts.\n         * Disabling on startup is achieved by placing a DISABLED file in the {@link HiveMQExtension}'s directory before coping it to the container.\n         *\n         * @param disabledOnStartup if the {@link HiveMQExtension} should be disabled when HiveMQ starts\n         * @return the {@link Builder}\n         */\n        public @NotNull Builder disabledOnStartup(final boolean disabledOnStartup) {\n            this.disabledOnStartup = disabledOnStartup;\n            return this;\n        }\n\n        /**\n         * The main class of the {@link HiveMQExtension}.\n         * This class MUST implement com.hivemq.extension.sdk.api.ExtensionMain.\n         *\n         * @param mainClass the main class\n         * @return the {@link Builder}\n         * @throws IllegalArgumentException if the provides class does not implement com.hivemq.extension.sdk.api.ExtensionMain}\n         * @throws IllegalStateException if com.hivemq.extension.sdk.api.ExtensionMain is not found in the classpath\n         */\n        public @NotNull Builder mainClass(final @NotNull Class<?> mainClass) {\n            try {\n                final Class<?> extensionMain = Class.forName(EXTENSION_MAIN_CLASS_NAME);\n                if (!extensionMain.isAssignableFrom(mainClass)) {\n                    throw new IllegalArgumentException(\n                        \"The provided class does not implement '\" + EXTENSION_MAIN_CLASS_NAME + \"'\"\n                    );\n                }\n                this.mainClass = mainClass;\n                return this;\n            } catch (final ClassNotFoundException e) {\n                throw new IllegalStateException(\n                    \"The class '\" + EXTENSION_MAIN_CLASS_NAME + \"' was not found in the classpath.\"\n                );\n            }\n        }\n\n        /**\n         * Adds an additional class to the .jar file of the {@link HiveMQExtension}.\n         *\n         * @param clazz the additional class\n         * @return the {@link Builder}\n         */\n        public @NotNull Builder addAdditionalClass(final @NotNull Class<?> clazz) {\n            this.additionalClasses.add(clazz);\n            return this;\n        }\n    }\n}\n"
  },
  {
    "path": "modules/hivemq/src/main/java/org/testcontainers/hivemq/PathUtil.java",
    "content": "package org.testcontainers.hivemq;\n\nimport org.jetbrains.annotations.NotNull;\n\nclass PathUtil {\n\n    static @NotNull String prepareInnerPath(@NotNull String innerPath) {\n        if (\"/\".equals(innerPath) || innerPath.isEmpty()) {\n            return \"/\";\n        }\n        if (!innerPath.startsWith(\"/\")) {\n            innerPath = \"/\" + innerPath;\n        }\n        if (!innerPath.endsWith(\"/\")) {\n            innerPath += \"/\";\n        }\n        return innerPath;\n    }\n\n    static @NotNull String prepareAppendPath(@NotNull String appendPath) {\n        if (!appendPath.startsWith(\"/\")) {\n            appendPath = \"/\" + appendPath;\n        }\n        return appendPath;\n    }\n}\n"
  },
  {
    "path": "modules/hivemq/src/test/java/org/testcontainers/hivemq/ContainerWithControlCenterIT.java",
    "content": "package org.testcontainers.hivemq;\n\nimport org.apache.http.client.methods.HttpGet;\nimport org.apache.http.client.methods.HttpUriRequest;\nimport org.apache.http.impl.client.CloseableHttpClient;\nimport org.apache.http.impl.client.HttpClientBuilder;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Timeout;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.util.concurrent.TimeUnit;\n\nclass ContainerWithControlCenterIT {\n\n    public static final int CONTROL_CENTER_PORT = 8080;\n\n    @Test\n    @Timeout(value = 3, unit = TimeUnit.MINUTES)\n    void test() throws Exception {\n        try (\n            final HiveMQContainer hivemq = new HiveMQContainer(DockerImageName.parse(\"hivemq/hivemq4\").withTag(\"4.7.4\"))\n                .withControlCenter()\n        ) {\n            hivemq.start();\n\n            try (final CloseableHttpClient httpClient = HttpClientBuilder.create().build()) {\n                final HttpUriRequest request = new HttpGet(\n                    \"http://\" + hivemq.getHost() + \":\" + hivemq.getMappedPort(CONTROL_CENTER_PORT)\n                );\n                httpClient.execute(request);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "modules/hivemq/src/test/java/org/testcontainers/hivemq/ContainerWithCustomConfigIT.java",
    "content": "package org.testcontainers.hivemq;\n\nimport com.hivemq.client.mqtt.datatypes.MqttQos;\nimport com.hivemq.client.mqtt.exceptions.MqttSessionExpiredException;\nimport com.hivemq.client.mqtt.mqtt5.Mqtt5BlockingClient;\nimport com.hivemq.client.mqtt.mqtt5.Mqtt5Client;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Timeout;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.MountableFile;\n\nimport java.util.concurrent.TimeUnit;\n\nimport static org.assertj.core.api.Assertions.assertThatExceptionOfType;\n\nclass ContainerWithCustomConfigIT {\n\n    @Test\n    @Timeout(value = 3, unit = TimeUnit.MINUTES)\n    void test() throws Exception {\n        try (\n            final HiveMQContainer hivemq = new HiveMQContainer(DockerImageName.parse(\"hivemq/hivemq4\").withTag(\"4.7.4\"))\n                .withHiveMQConfig(MountableFile.forClasspathResource(\"/config.xml\"))\n        ) {\n            hivemq.start();\n\n            final Mqtt5BlockingClient publisher = Mqtt5Client\n                .builder()\n                .identifier(\"publisher\")\n                .serverPort(hivemq.getMqttPort())\n                .serverHost(hivemq.getHost())\n                .buildBlocking();\n\n            publisher.connect();\n\n            assertThatExceptionOfType(MqttSessionExpiredException.class)\n                .isThrownBy(() -> {\n                    // this should fail since only QoS 0 is allowed by the configuration\n                    publisher.publishWith().topic(\"test/topic\").qos(MqttQos.EXACTLY_ONCE).send();\n                });\n        }\n    }\n}\n"
  },
  {
    "path": "modules/hivemq/src/test/java/org/testcontainers/hivemq/ContainerWithExtensionFromDirectoryIT.java",
    "content": "package org.testcontainers.hivemq;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Timeout;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\nimport org.slf4j.event.Level;\nimport org.testcontainers.hivemq.util.TestPublishModifiedUtil;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.MountableFile;\n\nimport java.util.concurrent.TimeUnit;\n\nclass ContainerWithExtensionFromDirectoryIT {\n\n    @ParameterizedTest\n    @ValueSource(\n        strings = {\n            \"2020.1\", // first version that provided a container image\n            \"2024.3\", // version that runs the image as a non-root user by default\n        }\n    )\n    @Timeout(value = 3, unit = TimeUnit.MINUTES)\n    void test(final @NotNull String hivemqCeTag) throws Exception {\n        try (\n            final HiveMQContainer hivemq = new HiveMQContainer(\n                DockerImageName.parse(\"hivemq/hivemq-ce\").withTag(hivemqCeTag)\n            )\n                .withExtension(MountableFile.forClasspathResource(\"/modifier-extension\"))\n                .waitForExtension(\"Modifier Extension\")\n                .withHiveMQConfig(MountableFile.forClasspathResource(\"/inMemoryConfig.xml\"))\n                .withLogLevel(Level.DEBUG)\n        ) {\n            hivemq.start();\n            TestPublishModifiedUtil.testPublishModified(hivemq.getMqttPort(), hivemq.getHost());\n        }\n    }\n\n    @Test\n    @Timeout(value = 3, unit = TimeUnit.MINUTES)\n    void test_wrongDirectoryName() throws Exception {\n        try (\n            final HiveMQContainer hivemq = new HiveMQContainer(\n                DockerImageName.parse(\"hivemq/hivemq-ce\").withTag(\"2024.3\")\n            )\n                .withExtension(MountableFile.forClasspathResource(\"/modifier-extension-wrong-name\"))\n                .waitForExtension(\"Modifier Extension\")\n                .withLogLevel(Level.DEBUG)\n        ) {\n            hivemq.start();\n            TestPublishModifiedUtil.testPublishModified(hivemq.getMqttPort(), hivemq.getHost());\n        }\n    }\n}\n"
  },
  {
    "path": "modules/hivemq/src/test/java/org/testcontainers/hivemq/ContainerWithExtensionIT.java",
    "content": "package org.testcontainers.hivemq;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.junit.jupiter.api.Timeout;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\nimport org.testcontainers.hivemq.util.MyExtension;\nimport org.testcontainers.hivemq.util.TestPublishModifiedUtil;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.MountableFile;\n\nimport java.util.concurrent.TimeUnit;\n\nclass ContainerWithExtensionIT {\n\n    @ParameterizedTest\n    @ValueSource(\n        strings = {\n            \"2020.1\", // first version that provided a container image\n            \"2024.3\", // version that runs the image as a non-root user by default\n        }\n    )\n    @Timeout(value = 3, unit = TimeUnit.MINUTES)\n    void test(final @NotNull String hivemqCeTag) throws Exception {\n        final HiveMQExtension hiveMQExtension = HiveMQExtension\n            .builder()\n            .id(\"extension-1\")\n            .name(\"my-extension\")\n            .version(\"1.0\")\n            .mainClass(MyExtension.class)\n            .build();\n\n        try (\n            final HiveMQContainer hivemq = new HiveMQContainer(\n                DockerImageName.parse(\"hivemq/hivemq-ce\").withTag(hivemqCeTag)\n            )\n                .withHiveMQConfig(MountableFile.forClasspathResource(\"/inMemoryConfig.xml\"))\n                .waitForExtension(hiveMQExtension)\n                .withExtension(hiveMQExtension)\n        ) {\n            hivemq.start();\n            TestPublishModifiedUtil.testPublishModified(hivemq.getMqttPort(), hivemq.getHost());\n            hivemq.stop();\n\n            hivemq.start();\n            TestPublishModifiedUtil.testPublishModified(hivemq.getMqttPort(), hivemq.getHost());\n            hivemq.stop();\n\n            hivemq.start();\n            TestPublishModifiedUtil.testPublishModified(hivemq.getMqttPort(), hivemq.getHost());\n        }\n    }\n}\n"
  },
  {
    "path": "modules/hivemq/src/test/java/org/testcontainers/hivemq/ContainerWithExtensionSubclassIT.java",
    "content": "package org.testcontainers.hivemq;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Timeout;\nimport org.slf4j.event.Level;\nimport org.testcontainers.hivemq.util.MyExtensionWithSubclasses;\nimport org.testcontainers.hivemq.util.TestPublishModifiedUtil;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.MountableFile;\n\nimport java.util.concurrent.TimeUnit;\n\nclass ContainerWithExtensionSubclassIT {\n\n    @Test\n    @Timeout(value = 3, unit = TimeUnit.MINUTES)\n    void test() throws Exception {\n        final HiveMQExtension hiveMQExtension = HiveMQExtension\n            .builder()\n            .id(\"extension-1\")\n            .name(\"my-extension\")\n            .version(\"1.0\")\n            .mainClass(MyExtensionWithSubclasses.class)\n            .build();\n\n        try (\n            final HiveMQContainer hivemq = new HiveMQContainer(\n                DockerImageName.parse(\"hivemq/hivemq-ce\").withTag(\"2024.3\")\n            )\n                .waitForExtension(hiveMQExtension)\n                .withExtension(hiveMQExtension)\n                .withHiveMQConfig(MountableFile.forClasspathResource(\"/inMemoryConfig.xml\"))\n                .withLogLevel(Level.DEBUG)\n        ) {\n            hivemq.start();\n            TestPublishModifiedUtil.testPublishModified(hivemq.getMqttPort(), hivemq.getHost());\n        }\n    }\n}\n"
  },
  {
    "path": "modules/hivemq/src/test/java/org/testcontainers/hivemq/ContainerWithFileInExtensionHomeIT.java",
    "content": "package org.testcontainers.hivemq;\n\nimport com.hivemq.extension.sdk.api.ExtensionMain;\nimport com.hivemq.extension.sdk.api.interceptor.publish.PublishInboundInterceptor;\nimport com.hivemq.extension.sdk.api.parameter.ExtensionStartInput;\nimport com.hivemq.extension.sdk.api.parameter.ExtensionStartOutput;\nimport com.hivemq.extension.sdk.api.parameter.ExtensionStopInput;\nimport com.hivemq.extension.sdk.api.parameter.ExtensionStopOutput;\nimport com.hivemq.extension.sdk.api.services.Services;\nimport com.hivemq.extension.sdk.api.services.intializer.ClientInitializer;\nimport org.jetbrains.annotations.NotNull;\nimport org.junit.jupiter.api.Timeout;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\nimport org.testcontainers.hivemq.util.TestPublishModifiedUtil;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.MountableFile;\n\nimport java.io.File;\nimport java.nio.ByteBuffer;\nimport java.nio.charset.StandardCharsets;\nimport java.util.concurrent.TimeUnit;\n\nclass ContainerWithFileInExtensionHomeIT {\n\n    @ParameterizedTest\n    @ValueSource(\n        strings = {\n            \"2020.1\", // first version that provided a container image\n            \"2024.3\", // version that runs the image as a non-root user by default\n        }\n    )\n    @Timeout(value = 3, unit = TimeUnit.MINUTES)\n    void test(final @NotNull String hivemqCeTag) throws Exception {\n        final HiveMQExtension hiveMQExtension = HiveMQExtension\n            .builder()\n            .id(\"extension-1\")\n            .name(\"my-extension\")\n            .version(\"1.0\")\n            .mainClass(FileCheckerExtension.class)\n            .build();\n\n        try (\n            final HiveMQContainer hivemq = new HiveMQContainer(\n                DockerImageName.parse(\"hivemq/hivemq-ce\").withTag(hivemqCeTag)\n            )\n                .withHiveMQConfig(MountableFile.forClasspathResource(\"/inMemoryConfig.xml\"))\n                .withExtension(hiveMQExtension)\n                .waitForExtension(hiveMQExtension)\n                .withFileInExtensionHomeFolder(\n                    MountableFile.forClasspathResource(\"/additionalFile.txt\"),\n                    \"extension-1\",\n                    \"/additionalFiles/my-file.txt\"\n                )\n        ) {\n            hivemq.start();\n            TestPublishModifiedUtil.testPublishModified(hivemq.getMqttPort(), hivemq.getHost());\n        }\n    }\n\n    public static class FileCheckerExtension implements ExtensionMain {\n\n        @Override\n        public void extensionStart(\n            final @NotNull ExtensionStartInput extensionStartInput,\n            final @NotNull ExtensionStartOutput extensionStartOutput\n        ) {\n            final PublishInboundInterceptor publishInboundInterceptor = (publishInboundInput, publishInboundOutput) -> {\n                final File extensionHomeFolder = extensionStartInput.getExtensionInformation().getExtensionHomeFolder();\n\n                final File additionalFile = new File(extensionHomeFolder, \"additionalFiles/my-file.txt\");\n\n                if (additionalFile.exists()) {\n                    publishInboundOutput\n                        .getPublishPacket()\n                        .setPayload(ByteBuffer.wrap(\"modified\".getBytes(StandardCharsets.UTF_8)));\n                }\n            };\n\n            final ClientInitializer clientInitializer = (initializerInput, clientContext) -> {\n                clientContext.addPublishInboundInterceptor(publishInboundInterceptor);\n            };\n\n            Services.initializerRegistry().setClientInitializer(clientInitializer);\n        }\n\n        @Override\n        public void extensionStop(\n            final @NotNull ExtensionStopInput extensionStopInput,\n            final @NotNull ExtensionStopOutput extensionStopOutput\n        ) {}\n    }\n}\n"
  },
  {
    "path": "modules/hivemq/src/test/java/org/testcontainers/hivemq/ContainerWithFileInHomeIT.java",
    "content": "package org.testcontainers.hivemq;\n\nimport com.hivemq.extension.sdk.api.ExtensionMain;\nimport com.hivemq.extension.sdk.api.interceptor.publish.PublishInboundInterceptor;\nimport com.hivemq.extension.sdk.api.parameter.ExtensionStartInput;\nimport com.hivemq.extension.sdk.api.parameter.ExtensionStartOutput;\nimport com.hivemq.extension.sdk.api.parameter.ExtensionStopInput;\nimport com.hivemq.extension.sdk.api.parameter.ExtensionStopOutput;\nimport com.hivemq.extension.sdk.api.services.Services;\nimport com.hivemq.extension.sdk.api.services.intializer.ClientInitializer;\nimport org.jetbrains.annotations.NotNull;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Timeout;\nimport org.testcontainers.hivemq.util.TestPublishModifiedUtil;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.MountableFile;\n\nimport java.io.File;\nimport java.nio.ByteBuffer;\nimport java.nio.charset.StandardCharsets;\nimport java.util.concurrent.TimeUnit;\n\nclass ContainerWithFileInHomeIT {\n\n    @Test\n    @Timeout(value = 3, unit = TimeUnit.MINUTES)\n    void test() throws Exception {\n        final HiveMQExtension hiveMQExtension = HiveMQExtension\n            .builder()\n            .id(\"extension-1\")\n            .name(\"my-extension\")\n            .version(\"1.0\")\n            .mainClass(FileCheckerExtension.class)\n            .build();\n\n        try (\n            final HiveMQContainer hivemq = new HiveMQContainer(\n                DockerImageName.parse(\"hivemq/hivemq-ce\").withTag(\"2024.3\")\n            )\n                .withHiveMQConfig(MountableFile.forClasspathResource(\"/inMemoryConfig.xml\"))\n                .withExtension(hiveMQExtension)\n                .waitForExtension(hiveMQExtension)\n                .withFileInHomeFolder(\n                    MountableFile.forClasspathResource(\"/additionalFile.txt\"),\n                    \"/additionalFiles/my-file.txt\"\n                )\n        ) {\n            hivemq.start();\n            TestPublishModifiedUtil.testPublishModified(hivemq.getMqttPort(), hivemq.getHost());\n        }\n    }\n\n    public static class FileCheckerExtension implements ExtensionMain {\n\n        @Override\n        public void extensionStart(\n            @NotNull ExtensionStartInput extensionStartInput,\n            @NotNull ExtensionStartOutput extensionStartOutput\n        ) {\n            final PublishInboundInterceptor publishInboundInterceptor = (publishInboundInput, publishInboundOutput) -> {\n                final File homeFolder = extensionStartInput.getServerInformation().getHomeFolder();\n\n                final File additionalFile = new File(homeFolder, \"additionalFiles/my-file.txt\");\n\n                if (additionalFile.exists()) {\n                    publishInboundOutput\n                        .getPublishPacket()\n                        .setPayload(ByteBuffer.wrap(\"modified\".getBytes(StandardCharsets.UTF_8)));\n                }\n            };\n\n            final ClientInitializer clientInitializer = (initializerInput, clientContext) -> {\n                clientContext.addPublishInboundInterceptor(publishInboundInterceptor);\n            };\n\n            Services.initializerRegistry().setClientInitializer(clientInitializer);\n        }\n\n        @Override\n        public void extensionStop(\n            @NotNull ExtensionStopInput extensionStopInput,\n            @NotNull ExtensionStopOutput extensionStopOutput\n        ) {}\n    }\n}\n"
  },
  {
    "path": "modules/hivemq/src/test/java/org/testcontainers/hivemq/ContainerWithLicenseIT.java",
    "content": "package org.testcontainers.hivemq;\n\nimport com.hivemq.extension.sdk.api.ExtensionMain;\nimport com.hivemq.extension.sdk.api.interceptor.publish.PublishInboundInterceptor;\nimport com.hivemq.extension.sdk.api.parameter.ExtensionStartInput;\nimport com.hivemq.extension.sdk.api.parameter.ExtensionStartOutput;\nimport com.hivemq.extension.sdk.api.parameter.ExtensionStopInput;\nimport com.hivemq.extension.sdk.api.parameter.ExtensionStopOutput;\nimport com.hivemq.extension.sdk.api.services.Services;\nimport com.hivemq.extension.sdk.api.services.intializer.ClientInitializer;\nimport org.jetbrains.annotations.NotNull;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Timeout;\nimport org.testcontainers.hivemq.util.TestPublishModifiedUtil;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.MountableFile;\n\nimport java.io.File;\nimport java.nio.ByteBuffer;\nimport java.nio.charset.StandardCharsets;\nimport java.util.concurrent.TimeUnit;\n\nclass ContainerWithLicenseIT {\n\n    @Test\n    @Timeout(value = 3, unit = TimeUnit.MINUTES)\n    void test() throws Exception {\n        final HiveMQExtension hiveMQExtension = HiveMQExtension\n            .builder()\n            .id(\"extension-1\")\n            .name(\"my-extension\")\n            .version(\"1.0\")\n            .mainClass(LicenceCheckerExtension.class)\n            .build();\n\n        try (\n            final HiveMQContainer hivemq = new HiveMQContainer(\n                DockerImageName.parse(\"hivemq/hivemq-ce\").withTag(\"2024.3\")\n            )\n                .withHiveMQConfig(MountableFile.forClasspathResource(\"/inMemoryConfig.xml\"))\n                .withExtension(hiveMQExtension)\n                .waitForExtension(hiveMQExtension)\n                .withLicense(MountableFile.forClasspathResource(\"/myLicense.lic\"))\n                .withLicense(MountableFile.forClasspathResource(\"/myExtensionLicense.elic\"))\n        ) {\n            hivemq.start();\n            TestPublishModifiedUtil.testPublishModified(hivemq.getMqttPort(), hivemq.getHost());\n        }\n    }\n\n    @SuppressWarnings(\"CodeBlock2Expr\")\n    public static class LicenceCheckerExtension implements ExtensionMain {\n\n        @Override\n        public void extensionStart(\n            @NotNull ExtensionStartInput extensionStartInput,\n            @NotNull ExtensionStartOutput extensionStartOutput\n        ) {\n            final PublishInboundInterceptor publishInboundInterceptor = (publishInboundInput, publishInboundOutput) -> {\n                final File homeFolder = extensionStartInput.getServerInformation().getHomeFolder();\n                final File myLicence = new File(homeFolder, \"license/myLicense.lic\");\n                final File myExtensionLicence = new File(homeFolder, \"license/myExtensionLicense.elic\");\n\n                if (myLicence.exists() && myExtensionLicence.exists()) {\n                    publishInboundOutput\n                        .getPublishPacket()\n                        .setPayload(ByteBuffer.wrap(\"modified\".getBytes(StandardCharsets.UTF_8)));\n                }\n            };\n\n            final ClientInitializer clientInitializer = (initializerInput, clientContext) -> {\n                clientContext.addPublishInboundInterceptor(publishInboundInterceptor);\n            };\n\n            Services.initializerRegistry().setClientInitializer(clientInitializer);\n        }\n\n        @Override\n        public void extensionStop(\n            @NotNull ExtensionStopInput extensionStopInput,\n            @NotNull ExtensionStopOutput extensionStopOutput\n        ) {}\n    }\n}\n"
  },
  {
    "path": "modules/hivemq/src/test/java/org/testcontainers/hivemq/ContainerWithoutPlatformExtensionsIT.java",
    "content": "package org.testcontainers.hivemq;\n\nimport com.hivemq.client.mqtt.MqttClient;\nimport com.hivemq.client.mqtt.MqttGlobalPublishFilter;\nimport com.hivemq.client.mqtt.mqtt5.Mqtt5BlockingClient;\nimport com.hivemq.client.mqtt.mqtt5.message.publish.Mqtt5Publish;\nimport com.hivemq.extension.sdk.api.ExtensionMain;\nimport com.hivemq.extension.sdk.api.auth.SimpleAuthenticator;\nimport com.hivemq.extension.sdk.api.parameter.ExtensionStartInput;\nimport com.hivemq.extension.sdk.api.parameter.ExtensionStartOutput;\nimport com.hivemq.extension.sdk.api.parameter.ExtensionStopInput;\nimport com.hivemq.extension.sdk.api.parameter.ExtensionStopOutput;\nimport com.hivemq.extension.sdk.api.services.Services;\nimport com.hivemq.extension.sdk.api.services.builder.Builders;\nimport org.jetbrains.annotations.NotNull;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Timeout;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.File;\nimport java.nio.ByteBuffer;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Arrays;\nimport java.util.concurrent.TimeUnit;\nimport java.util.stream.Collectors;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass ContainerWithoutPlatformExtensionsIT {\n\n    @NotNull\n    private final HiveMQExtension hiveMQExtension = HiveMQExtension\n        .builder()\n        .name(\"MyExtension\")\n        .id(\"my-extension\")\n        .version(\"1.0.0\")\n        .mainClass(CheckerExtension.class)\n        .build();\n\n    @Test\n    @Timeout(value = 3, unit = TimeUnit.MINUTES)\n    void removeAllPlatformExtensions() throws InterruptedException {\n        try (\n            final HiveMQContainer hivemq = new HiveMQContainer(DockerImageName.parse(\"hivemq/hivemq4\").withTag(\"4.7.4\"))\n                .withExtension(hiveMQExtension)\n                .waitForExtension(hiveMQExtension)\n                .withoutPrepackagedExtensions()\n        ) {\n            hivemq.start();\n\n            final Mqtt5BlockingClient client = MqttClient\n                .builder()\n                .serverPort(hivemq.getMqttPort())\n                .serverHost(hivemq.getHost())\n                .useMqttVersion5()\n                .buildBlocking();\n\n            client.connect();\n            final Mqtt5BlockingClient.Mqtt5Publishes publishes = client.publishes(MqttGlobalPublishFilter.ALL);\n            client.subscribeWith().topicFilter(\"extensions\").send();\n\n            final Mqtt5Publish receive = publishes.receive();\n            assertThat(receive.getPayload()).isPresent();\n            final String extensionInfo = new String(receive.getPayloadAsBytes());\n\n            assertThat(extensionInfo).doesNotContain(\"hivemq-allow-all-extension\");\n            assertThat(extensionInfo).doesNotContain(\"hivemq-kafka-extension\");\n            assertThat(extensionInfo).doesNotContain(\"hivemq-bridge-extension\");\n            assertThat(extensionInfo).doesNotContain(\"hivemq-enterprise-security-extension\");\n\n            hivemq.start();\n        }\n    }\n\n    @Test\n    @Timeout(value = 3, unit = TimeUnit.MINUTES)\n    void removeKafkaExtension() throws InterruptedException {\n        try (\n            final HiveMQContainer hivemq = new HiveMQContainer(DockerImageName.parse(\"hivemq/hivemq4\").withTag(\"4.7.4\"))\n                .withExtension(hiveMQExtension)\n                .waitForExtension(hiveMQExtension)\n                .withoutPrepackagedExtensions(\"hivemq-kafka-extension\")\n        ) {\n            hivemq.start();\n\n            final Mqtt5BlockingClient client = MqttClient\n                .builder()\n                .serverPort(hivemq.getMqttPort())\n                .serverHost(hivemq.getHost())\n                .useMqttVersion5()\n                .buildBlocking();\n\n            client.connect();\n            final Mqtt5BlockingClient.Mqtt5Publishes publishes = client.publishes(MqttGlobalPublishFilter.ALL);\n            client.subscribeWith().topicFilter(\"extensions\").send();\n\n            final Mqtt5Publish receive = publishes.receive();\n            assertThat(receive.getPayload().isPresent()).isTrue();\n            final String extensionInfo = new String(receive.getPayloadAsBytes());\n\n            assertThat(extensionInfo).contains(\"hivemq-allow-all-extension\");\n            assertThat(extensionInfo).doesNotContain(\"hivemq-kafka-extension\");\n            assertThat(extensionInfo).contains(\"hivemq-bridge-extension\");\n            assertThat(extensionInfo).contains(\"hivemq-enterprise-security-extension\");\n        }\n    }\n\n    public static class CheckerExtension implements ExtensionMain {\n\n        @Override\n        public void extensionStart(\n            final @NotNull ExtensionStartInput extensionStartInput,\n            final @NotNull ExtensionStartOutput extensionStartOutput\n        ) {\n            final String extensionFolders = Arrays\n                .stream(extensionStartInput.getServerInformation().getExtensionsFolder().listFiles())\n                .filter(File::isDirectory)\n                .map(File::getName)\n                .collect(Collectors.joining(\"\\n\"));\n\n            final byte[] bytes = extensionFolders.getBytes(StandardCharsets.UTF_8);\n            Services\n                .publishService()\n                .publish(Builders.publish().topic(\"extensions\").retain(true).payload(ByteBuffer.wrap(bytes)).build());\n\n            Services\n                .securityRegistry()\n                .setAuthenticatorProvider(authenticatorProviderInput -> {\n                    return (SimpleAuthenticator) (simpleAuthInput, simpleAuthOutput) -> {\n                        simpleAuthOutput.authenticateSuccessfully();\n                    };\n                });\n        }\n\n        @Override\n        public void extensionStop(\n            final @NotNull ExtensionStopInput extensionStopInput,\n            final @NotNull ExtensionStopOutput extensionStopOutput\n        ) {}\n    }\n}\n"
  },
  {
    "path": "modules/hivemq/src/test/java/org/testcontainers/hivemq/CreateFileInCopiedDirectoryIT.java",
    "content": "package org.testcontainers.hivemq;\n\nimport com.hivemq.extension.sdk.api.ExtensionMain;\nimport com.hivemq.extension.sdk.api.interceptor.publish.PublishInboundInterceptor;\nimport com.hivemq.extension.sdk.api.parameter.ExtensionStartInput;\nimport com.hivemq.extension.sdk.api.parameter.ExtensionStartOutput;\nimport com.hivemq.extension.sdk.api.parameter.ExtensionStopInput;\nimport com.hivemq.extension.sdk.api.parameter.ExtensionStopOutput;\nimport com.hivemq.extension.sdk.api.services.Services;\nimport com.hivemq.extension.sdk.api.services.intializer.ClientInitializer;\nimport org.jetbrains.annotations.NotNull;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Timeout;\nimport org.testcontainers.hivemq.util.TestPublishModifiedUtil;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.MountableFile;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.ByteBuffer;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.util.concurrent.TimeUnit;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass CreateFileInCopiedDirectoryIT {\n\n    private @NotNull MountableFile createDirectory() throws IOException {\n        final File directory = new File(Files.createTempDirectory(\"\").toFile(), \"directory\");\n        assertThat(directory.mkdir()).isTrue();\n        final File subdirectory = new File(directory, \"sub-directory\");\n        assertThat(subdirectory.mkdir()).isTrue();\n        return MountableFile.forHostPath(directory.getPath());\n    }\n\n    @Test\n    @Timeout(value = 3, unit = TimeUnit.MINUTES)\n    void test() throws Exception {\n        final HiveMQExtension extension = HiveMQExtension\n            .builder()\n            .id(\"extension-1\")\n            .name(\"my-extension\")\n            .version(\"1.0\")\n            .mainClass(FileCreatorExtension.class)\n            .build();\n\n        try (\n            final HiveMQContainer hivemq = new HiveMQContainer(\n                DockerImageName.parse(\"hivemq/hivemq-ce\").withTag(\"2024.3\")\n            )\n                .withHiveMQConfig(MountableFile.forClasspathResource(\"/inMemoryConfig.xml\"))\n                .withExtension(extension)\n                .waitForExtension(extension)\n                .withFileInHomeFolder(createDirectory(), \"directory\")\n        ) {\n            hivemq.start();\n            TestPublishModifiedUtil.testPublishModified(hivemq.getMqttPort(), hivemq.getHost());\n        }\n    }\n\n    public static class FileCreatorExtension implements ExtensionMain {\n\n        @Override\n        public void extensionStart(\n            @NotNull ExtensionStartInput extensionStartInput,\n            @NotNull ExtensionStartOutput extensionStartOutput\n        ) {\n            final PublishInboundInterceptor publishInboundInterceptor = (publishInboundInput, publishInboundOutput) -> {\n                final File homeFolder = extensionStartInput.getServerInformation().getHomeFolder();\n\n                final File dir = new File(homeFolder, \"directory\");\n                final File dirFile = new File(dir, \"file.txt\");\n                final File subDir = new File(dir, \"sub-directory\");\n                final File subDirFile = new File(subDir, \"file.txt\");\n\n                try {\n                    if (dirFile.createNewFile() && subDirFile.createNewFile()) {\n                        publishInboundOutput\n                            .getPublishPacket()\n                            .setPayload(ByteBuffer.wrap(\"modified\".getBytes(StandardCharsets.UTF_8)));\n                    }\n                } catch (IOException e) {\n                    e.printStackTrace();\n                }\n            };\n\n            final ClientInitializer clientInitializer = (initializerInput, clientContext) -> {\n                clientContext.addPublishInboundInterceptor(publishInboundInterceptor);\n            };\n\n            Services.initializerRegistry().setClientInitializer(clientInitializer);\n        }\n\n        @Override\n        public void extensionStop(\n            @NotNull ExtensionStopInput extensionStopInput,\n            @NotNull ExtensionStopOutput extensionStopOutput\n        ) {}\n    }\n}\n"
  },
  {
    "path": "modules/hivemq/src/test/java/org/testcontainers/hivemq/CreateFileInExtensionDirectoryIT.java",
    "content": "package org.testcontainers.hivemq;\n\nimport com.hivemq.extension.sdk.api.ExtensionMain;\nimport com.hivemq.extension.sdk.api.interceptor.publish.PublishInboundInterceptor;\nimport com.hivemq.extension.sdk.api.parameter.ExtensionStartInput;\nimport com.hivemq.extension.sdk.api.parameter.ExtensionStartOutput;\nimport com.hivemq.extension.sdk.api.parameter.ExtensionStopInput;\nimport com.hivemq.extension.sdk.api.parameter.ExtensionStopOutput;\nimport com.hivemq.extension.sdk.api.services.Services;\nimport com.hivemq.extension.sdk.api.services.intializer.ClientInitializer;\nimport org.jetbrains.annotations.NotNull;\nimport org.junit.jupiter.api.Timeout;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.ValueSource;\nimport org.testcontainers.hivemq.util.TestPublishModifiedUtil;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.MountableFile;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.ByteBuffer;\nimport java.nio.charset.StandardCharsets;\nimport java.util.concurrent.TimeUnit;\n\nclass CreateFileInExtensionDirectoryIT {\n\n    @ParameterizedTest\n    @ValueSource(\n        strings = {\n            \"2020.1\", // first version that provided a container image\n            \"2024.3\", // version that runs the image as a non-root user by default\n        }\n    )\n    @Timeout(value = 3, unit = TimeUnit.MINUTES)\n    void test(final @NotNull String hivemqCeTag) throws Exception {\n        final HiveMQExtension hiveMQExtension = HiveMQExtension\n            .builder()\n            .id(\"extension-1\")\n            .name(\"my-extension\")\n            .version(\"1.0\")\n            .mainClass(FileCreatorExtension.class)\n            .build();\n\n        try (\n            final HiveMQContainer hivemq = new HiveMQContainer(\n                DockerImageName.parse(\"hivemq/hivemq-ce\").withTag(hivemqCeTag)\n            )\n                .withHiveMQConfig(MountableFile.forClasspathResource(\"/inMemoryConfig.xml\"))\n                .waitForExtension(hiveMQExtension)\n                .withExtension(hiveMQExtension)\n        ) {\n            hivemq.start();\n            TestPublishModifiedUtil.testPublishModified(hivemq.getMqttPort(), hivemq.getHost());\n        }\n    }\n\n    public static class FileCreatorExtension implements ExtensionMain {\n\n        @Override\n        public void extensionStart(\n            @NotNull ExtensionStartInput extensionStartInput,\n            @NotNull ExtensionStartOutput extensionStartOutput\n        ) {\n            final PublishInboundInterceptor publishInboundInterceptor = (publishInboundInput, publishInboundOutput) -> {\n                final File extensionHomeFolder = extensionStartInput.getExtensionInformation().getExtensionHomeFolder();\n\n                final File file = new File(extensionHomeFolder, \"myfile.txt\");\n                try {\n                    if (file.createNewFile()) {\n                        publishInboundOutput\n                            .getPublishPacket()\n                            .setPayload(ByteBuffer.wrap(\"modified\".getBytes(StandardCharsets.UTF_8)));\n                    }\n                } catch (IOException e) {\n                    e.printStackTrace();\n                }\n            };\n\n            final ClientInitializer clientInitializer = (initializerInput, clientContext) -> {\n                clientContext.addPublishInboundInterceptor(publishInboundInterceptor);\n            };\n\n            Services.initializerRegistry().setClientInitializer(clientInitializer);\n        }\n\n        @Override\n        public void extensionStop(\n            @NotNull ExtensionStopInput extensionStopInput,\n            @NotNull ExtensionStopOutput extensionStopOutput\n        ) {}\n    }\n}\n"
  },
  {
    "path": "modules/hivemq/src/test/java/org/testcontainers/hivemq/DisableEnableExtensionFromDirectoryIT.java",
    "content": "package org.testcontainers.hivemq;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Timeout;\nimport org.slf4j.event.Level;\nimport org.testcontainers.hivemq.util.TestPublishModifiedUtil;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.MountableFile;\n\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.TimeUnit;\n\nimport static org.assertj.core.api.Assertions.assertThatExceptionOfType;\n\nclass DisableEnableExtensionFromDirectoryIT {\n\n    @Test\n    @Timeout(value = 3, unit = TimeUnit.MINUTES)\n    void test() throws Exception {\n        try (\n            final HiveMQContainer hivemq = new HiveMQContainer(DockerImageName.parse(\"hivemq/hivemq4\").withTag(\"4.7.4\"))\n                .withExtension(MountableFile.forClasspathResource(\"/modifier-extension\"))\n                .waitForExtension(\"Modifier Extension\")\n                .withLogLevel(Level.DEBUG)\n        ) {\n            hivemq.start();\n\n            TestPublishModifiedUtil.testPublishModified(hivemq.getMqttPort(), hivemq.getHost());\n            hivemq.disableExtension(\"Modifier Extension\", \"modifier-extension\");\n            assertThatExceptionOfType(ExecutionException.class)\n                .isThrownBy(() -> TestPublishModifiedUtil.testPublishModified(hivemq.getMqttPort(), hivemq.getHost()));\n            hivemq.enableExtension(\"Modifier Extension\", \"modifier-extension\");\n            TestPublishModifiedUtil.testPublishModified(hivemq.getMqttPort(), hivemq.getHost());\n        }\n    }\n}\n"
  },
  {
    "path": "modules/hivemq/src/test/java/org/testcontainers/hivemq/DisableEnableExtensionIT.java",
    "content": "package org.testcontainers.hivemq;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Timeout;\nimport org.slf4j.event.Level;\nimport org.testcontainers.hivemq.util.MyExtension;\nimport org.testcontainers.hivemq.util.TestPublishModifiedUtil;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.TimeUnit;\n\nimport static org.assertj.core.api.Assertions.assertThatExceptionOfType;\n\nclass DisableEnableExtensionIT {\n\n    @NotNull\n    private final HiveMQExtension hiveMQExtension = HiveMQExtension\n        .builder()\n        .id(\"extension-1\")\n        .name(\"my-extension\")\n        .version(\"1.0\")\n        .disabledOnStartup(true)\n        .mainClass(MyExtension.class)\n        .build();\n\n    @Test\n    @Timeout(value = 3, unit = TimeUnit.MINUTES)\n    void test() throws Exception {\n        try (\n            final HiveMQContainer hivemq = new HiveMQContainer(DockerImageName.parse(\"hivemq/hivemq4\").withTag(\"4.7.4\"))\n                .withExtension(hiveMQExtension)\n                .withLogLevel(Level.DEBUG)\n        ) {\n            hivemq.start();\n\n            assertThatExceptionOfType(ExecutionException.class)\n                .isThrownBy(() -> TestPublishModifiedUtil.testPublishModified(hivemq.getMqttPort(), hivemq.getHost()));\n            hivemq.enableExtension(hiveMQExtension);\n            TestPublishModifiedUtil.testPublishModified(hivemq.getMqttPort(), hivemq.getHost());\n            hivemq.disableExtension(hiveMQExtension);\n            assertThatExceptionOfType(ExecutionException.class)\n                .isThrownBy(() -> TestPublishModifiedUtil.testPublishModified(hivemq.getMqttPort(), hivemq.getHost()));\n            hivemq.enableExtension(hiveMQExtension);\n            TestPublishModifiedUtil.testPublishModified(hivemq.getMqttPort(), hivemq.getHost());\n        }\n    }\n}\n"
  },
  {
    "path": "modules/hivemq/src/test/java/org/testcontainers/hivemq/HiveMQExtensionTest.java",
    "content": "package org.testcontainers.hivemq;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThatExceptionOfType;\n\nclass HiveMQExtensionTest {\n\n    @Test\n    void builder_classDoesNotImplementExtensionMain_exception() {\n        assertThatExceptionOfType(IllegalArgumentException.class)\n            .isThrownBy(() -> HiveMQExtension.builder().mainClass(HiveMQExtensionTest.class));\n    }\n}\n"
  },
  {
    "path": "modules/hivemq/src/test/java/org/testcontainers/hivemq/HiveMQTestContainerCore.java",
    "content": "package org.testcontainers.hivemq;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.testcontainers.containers.ContainerLaunchException;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.MountableFile;\n\nimport java.io.File;\nimport java.io.IOException;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatExceptionOfType;\n\nclass HiveMQTestContainerCore {\n\n    @NotNull\n    final HiveMQContainer container = new HiveMQContainer(DockerImageName.parse(\"hivemq/hivemq-ce\").withTag(\"2024.3\"));\n\n    @TempDir\n    File tempDir;\n\n    @Test\n    void withExtension_fileDoesNotExist_Exception() {\n        final MountableFile mountableFile = MountableFile.forHostPath(\"/this/does/not/exist\");\n        assertThatExceptionOfType(ContainerLaunchException.class)\n            .isThrownBy(() -> container.withExtension(mountableFile));\n    }\n\n    @Test\n    void withExtension_fileNoDirectory_Exception() throws IOException {\n        final File extension = new File(tempDir, \"extension\");\n        assertThat(extension.createNewFile()).isTrue();\n        final MountableFile mountableFile = MountableFile.forHostPath(extension.getAbsolutePath());\n        assertThatExceptionOfType(ContainerLaunchException.class)\n            .isThrownBy(() -> container.withExtension(mountableFile));\n    }\n\n    @Test\n    void withLicense_fileDoesNotExist_Exception() {\n        final MountableFile mountableFile = MountableFile.forHostPath(\"/this/does/not/exist\");\n        assertThatExceptionOfType(ContainerLaunchException.class)\n            .isThrownBy(() -> container.withLicense(mountableFile));\n    }\n\n    @Test\n    void withExtension_fileEndingWrong_Exception() throws IOException {\n        final File extension = new File(tempDir, \"extension.wrong\");\n        assertThat(extension.createNewFile()).isTrue();\n        final MountableFile mountableFile = MountableFile.forHostPath(extension.getAbsolutePath());\n        assertThatExceptionOfType(ContainerLaunchException.class)\n            .isThrownBy(() -> container.withLicense(mountableFile));\n    }\n\n    @Test\n    void withHiveMQConfig_fileDoesNotExist_Exception() {\n        final MountableFile mountableFile = MountableFile.forHostPath(\"/this/does/not/exist\");\n        assertThatExceptionOfType(ContainerLaunchException.class)\n            .isThrownBy(() -> container.withHiveMQConfig(mountableFile));\n    }\n\n    @Test\n    void withFileInHomeFolder_fileDoesNotExist_Exception() {\n        final MountableFile mountableFile = MountableFile.forHostPath(\"/this/does/not/exist\");\n        assertThatExceptionOfType(ContainerLaunchException.class)\n            .isThrownBy(() -> container.withFileInHomeFolder(mountableFile, \"some/path\"));\n    }\n\n    @Test\n    void withFileInHomeFolder_pathEmpty_Exception() {\n        final MountableFile mountableFile = MountableFile.forHostPath(\"/this/does/not/exist\");\n        assertThatExceptionOfType(ContainerLaunchException.class)\n            .isThrownBy(() -> container.withFileInHomeFolder(mountableFile, \"\"));\n    }\n\n    @Test\n    void withFileInExtensionHomeFolder_withPath_fileDoesNotExist_Exception() {\n        final MountableFile mountableFile = MountableFile.forHostPath(\"/this/does/not/exist\");\n        assertThatExceptionOfType(ContainerLaunchException.class)\n            .isThrownBy(() -> container.withFileInExtensionHomeFolder(mountableFile, \"my-extension\", \"some/path\"));\n    }\n\n    @Test\n    void withFileInExtensionHomeFolder_fileDoesNotExist_Exception() {\n        final MountableFile mountableFile = MountableFile.forHostPath(\"/this/does/not/exist\");\n        assertThatExceptionOfType(ContainerLaunchException.class)\n            .isThrownBy(() -> {\n                final HiveMQContainer hiveMQContainer = container.withFileInExtensionHomeFolder(\n                    mountableFile,\n                    \"my-extension\"\n                );\n            });\n    }\n}\n"
  },
  {
    "path": "modules/hivemq/src/test/java/org/testcontainers/hivemq/PathUtilTest.java",
    "content": "package org.testcontainers.hivemq;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass PathUtilTest {\n\n    @Test\n    void prepareInnerPath_emptyString() {\n        assertThat(PathUtil.prepareInnerPath(\"\")).isEqualTo(\"/\");\n    }\n\n    @Test\n    void prepareInnerPath_onlyDelimiter() {\n        assertThat(PathUtil.prepareInnerPath(\"/\")).isEqualTo(\"/\");\n    }\n\n    @Test\n    void prepareInnerPath_noDelimiter() {\n        assertThat(PathUtil.prepareInnerPath(\"path\")).isEqualTo(\"/path/\");\n    }\n\n    @Test\n    void prepareInnerPath_delimiterAtEnd() {\n        assertThat(PathUtil.prepareInnerPath(\"path/\")).isEqualTo(\"/path/\");\n    }\n\n    @Test\n    void prepareInnerPath_delimiterAtBeginning() {\n        assertThat(PathUtil.prepareInnerPath(\"/path\")).isEqualTo(\"/path/\");\n    }\n\n    @Test\n    void prepareAppendPath_delimiterAtBeginning_noChange() {\n        assertThat(PathUtil.prepareAppendPath(\"/path\")).isEqualTo(\"/path\");\n    }\n\n    @Test\n    void prepareAppendPath_delimiterAtBeginning_delimiterAdded() {\n        assertThat(PathUtil.prepareAppendPath(\"path\")).isEqualTo(\"/path\");\n    }\n}\n"
  },
  {
    "path": "modules/hivemq/src/test/java/org/testcontainers/hivemq/docs/DemoDisableExtensionsIT.java",
    "content": "package org.testcontainers.hivemq.docs;\n\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.hivemq.HiveMQContainer;\nimport org.testcontainers.hivemq.HiveMQExtension;\nimport org.testcontainers.hivemq.util.MyExtension;\nimport org.testcontainers.junit.jupiter.Container;\nimport org.testcontainers.junit.jupiter.Testcontainers;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.MountableFile;\n\n@Testcontainers\nclass DemoDisableExtensionsIT {\n\n    // noExtensions {\n    @Container\n    final HiveMQContainer hivemqNoExtensions = new HiveMQContainer(\n        DockerImageName.parse(\"hivemq/hivemq4\").withTag(\"4.7.4\")\n    )\n        .withoutPrepackagedExtensions();\n\n    // }\n\n    // noKafkaExtension {\n    @Container\n    final HiveMQContainer hivemqNoKafkaExtension = new HiveMQContainer(\n        DockerImageName.parse(\"hivemq/hivemq4\").withTag(\"4.7.4\")\n    )\n        .withoutPrepackagedExtensions(\"hivemq-kafka-extension\");\n\n    // }\n\n    // startDisabled {\n    private final HiveMQExtension hiveMQExtension = HiveMQExtension\n        .builder()\n        .id(\"extension-1\")\n        .name(\"my-extension\")\n        .version(\"1.0\")\n        .disabledOnStartup(true)\n        .mainClass(MyExtension.class)\n        .build();\n\n    @Container\n    final HiveMQContainer hivemq = new HiveMQContainer(DockerImageName.parse(\"hivemq/hivemq4\").withTag(\"4.7.4\"))\n        .withExtension(hiveMQExtension);\n\n    // }\n\n    // startFromFilesystem {\n    @Container\n    final HiveMQContainer hivemqExtensionFromFilesystem = new HiveMQContainer(\n        DockerImageName.parse(\"hivemq/hivemq4\").withTag(\"4.7.4\")\n    )\n        .withExtension(MountableFile.forHostPath(\"src/test/resources/modifier-extension\"));\n\n    // }\n\n    // hiveRuntimeEnable {\n    @Test\n    void test_disable_enable_extension() throws Exception {\n        hivemq.enableExtension(hiveMQExtension);\n        hivemq.disableExtension(hiveMQExtension);\n    }\n\n    // }\n\n    // runtimeEnableFilesystem {\n    @Test\n    void test_disable_enable_extension_from_filesystem() throws Exception {\n        hivemqExtensionFromFilesystem.disableExtension(\"Modifier Extension\", \"modifier-extension\");\n        hivemqExtensionFromFilesystem.enableExtension(\"Modifier Extension\", \"modifier-extension\");\n    }\n    // }\n}\n"
  },
  {
    "path": "modules/hivemq/src/test/java/org/testcontainers/hivemq/docs/DemoExtensionTestsIT.java",
    "content": "package org.testcontainers.hivemq.docs;\n\nimport com.hivemq.client.mqtt.mqtt5.Mqtt5BlockingClient;\nimport com.hivemq.client.mqtt.mqtt5.Mqtt5Client;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Timeout;\nimport org.testcontainers.hivemq.HiveMQContainer;\nimport org.testcontainers.hivemq.HiveMQExtension;\nimport org.testcontainers.hivemq.util.MyExtensionWithSubclasses;\nimport org.testcontainers.junit.jupiter.Container;\nimport org.testcontainers.junit.jupiter.Testcontainers;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.MountableFile;\n\nimport java.util.concurrent.TimeUnit;\n\n@Testcontainers\nclass DemoExtensionTestsIT {\n\n    // waitStrategy {\n    @Container\n    final HiveMQContainer hivemqWithWaitStrategy = new HiveMQContainer(\n        DockerImageName.parse(\"hivemq/hivemq4\").withTag(\"4.7.4\")\n    )\n        .withExtension(MountableFile.forClasspathResource(\"/modifier-extension\"))\n        .waitForExtension(\"Modifier Extension\");\n\n    // }\n\n    // extensionClasspath {\n    final HiveMQExtension hiveMQEClasspathxtension = HiveMQExtension\n        .builder()\n        .id(\"extension-1\")\n        .name(\"my-extension\")\n        .version(\"1.0\")\n        .mainClass(MyExtensionWithSubclasses.class)\n        .build();\n\n    @Container\n    final HiveMQContainer hivemqWithClasspathExtension = new HiveMQContainer(\n        DockerImageName.parse(\"hivemq/hivemq-ce\").withTag(\"2024.3\")\n    )\n        .waitForExtension(hiveMQEClasspathxtension)\n        .withExtension(hiveMQEClasspathxtension)\n        .withHiveMQConfig(MountableFile.forClasspathResource(\"/inMemoryConfig.xml\"));\n\n    // }\n\n    @Test\n    @Timeout(value = 3, unit = TimeUnit.MINUTES)\n    void test() throws Exception {\n        // mqtt5client {\n        final Mqtt5BlockingClient client = Mqtt5Client\n            .builder()\n            .serverPort(hivemqWithClasspathExtension.getMqttPort())\n            .serverHost(hivemqWithClasspathExtension.getHost())\n            .buildBlocking();\n\n        client.connect();\n        client.disconnect();\n        // }\n\n    }\n}\n"
  },
  {
    "path": "modules/hivemq/src/test/java/org/testcontainers/hivemq/docs/DemoFilesIT.java",
    "content": "package org.testcontainers.hivemq.docs;\n\nimport com.hivemq.client.mqtt.mqtt5.Mqtt5BlockingClient;\nimport com.hivemq.client.mqtt.mqtt5.Mqtt5Client;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Timeout;\nimport org.testcontainers.hivemq.HiveMQContainer;\nimport org.testcontainers.hivemq.HiveMQExtension;\nimport org.testcontainers.hivemq.util.MyExtension;\nimport org.testcontainers.junit.jupiter.Container;\nimport org.testcontainers.junit.jupiter.Testcontainers;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.MountableFile;\n\nimport java.util.concurrent.TimeUnit;\n\n@Testcontainers\nclass DemoFilesIT {\n\n    // hivemqHome {\n    final HiveMQContainer hivemqFileInHome = new HiveMQContainer(\n        DockerImageName.parse(\"hivemq/hivemq-ce\").withTag(\"2024.3\")\n    )\n        .withFileInHomeFolder(\n            MountableFile.forHostPath(\"src/test/resources/additionalFile.txt\"),\n            \"/path/in/home/folder\"\n        );\n\n    // }\n\n    // extensionHome {\n    @Container\n    final HiveMQContainer hivemqFileInExtensionHome = new HiveMQContainer(\n        DockerImageName.parse(\"hivemq/hivemq-ce\").withTag(\"2024.3\")\n    )\n        .withExtension(\n            HiveMQExtension\n                .builder()\n                .id(\"extension-1\")\n                .name(\"my-extension\")\n                .version(\"1.0\")\n                .mainClass(MyExtension.class)\n                .build()\n        )\n        .withFileInExtensionHomeFolder(\n            MountableFile.forHostPath(\"src/test/resources/additionalFile.txt\"),\n            \"extension-1\",\n            \"/path/in/extension/home\"\n        );\n\n    // }\n\n    // withLicenses {\n    @Container\n    final HiveMQContainer hivemq = new HiveMQContainer(DockerImageName.parse(\"hivemq/hivemq-ce\").withTag(\"2024.3\"))\n        .withLicense(MountableFile.forHostPath(\"src/test/resources/myLicense.lic\"))\n        .withLicense(MountableFile.forHostPath(\"src/test/resources/myExtensionLicense.elic\"));\n\n    // }\n\n    @Test\n    @Timeout(value = 3, unit = TimeUnit.MINUTES)\n    void test() throws Exception {\n        // mqtt5client {\n        final Mqtt5BlockingClient client = Mqtt5Client\n            .builder()\n            .serverPort(hivemq.getMqttPort())\n            .serverHost(hivemq.getHost())\n            .buildBlocking();\n\n        client.connect();\n        client.disconnect();\n        // }\n\n    }\n}\n"
  },
  {
    "path": "modules/hivemq/src/test/java/org/testcontainers/hivemq/docs/DemoHiveMQContainerIT.java",
    "content": "package org.testcontainers.hivemq.docs;\n\nimport com.hivemq.client.mqtt.mqtt5.Mqtt5BlockingClient;\nimport com.hivemq.client.mqtt.mqtt5.Mqtt5Client;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.Timeout;\nimport org.slf4j.event.Level;\nimport org.testcontainers.hivemq.HiveMQContainer;\nimport org.testcontainers.junit.jupiter.Container;\nimport org.testcontainers.junit.jupiter.Testcontainers;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.MountableFile;\n\nimport java.util.concurrent.TimeUnit;\n\n@Testcontainers\nclass DemoHiveMQContainerIT {\n\n    // ceVersion {\n    @Container\n    final HiveMQContainer hivemqCe = new HiveMQContainer(DockerImageName.parse(\"hivemq/hivemq-ce\").withTag(\"2024.3\"))\n        .withLogLevel(Level.DEBUG);\n\n    // }\n\n    // hiveEEVersion {\n    @Container\n    final HiveMQContainer hivemqEe = new HiveMQContainer(DockerImageName.parse(\"hivemq/hivemq4\").withTag(\"4.7.4\"))\n        .withLogLevel(Level.DEBUG);\n\n    // }\n\n    // eeVersionWithControlCenter {\n    @Container\n    final HiveMQContainer hivemqEeWithControlCenter = new HiveMQContainer(\n        DockerImageName.parse(\"hivemq/hivemq4\").withTag(\"4.7.4\")\n    )\n        .withLogLevel(Level.DEBUG)\n        .withHiveMQConfig(MountableFile.forClasspathResource(\"/inMemoryConfig.xml\"))\n        .withControlCenter();\n\n    // }\n\n    // specificVersion {\n    @Container\n    final HiveMQContainer hivemqSpecificVersion = new HiveMQContainer(DockerImageName.parse(\"hivemq/hivemq-ce:2024.3\"));\n\n    // }\n\n    @Test\n    @Timeout(value = 3, unit = TimeUnit.MINUTES)\n    void test() throws Exception {\n        // mqtt5client {\n        final Mqtt5BlockingClient client = Mqtt5Client\n            .builder()\n            .serverPort(hivemqCe.getMqttPort())\n            .serverHost(hivemqCe.getHost())\n            .buildBlocking();\n\n        client.connect();\n        client.disconnect();\n        // }\n\n    }\n}\n"
  },
  {
    "path": "modules/hivemq/src/test/java/org/testcontainers/hivemq/util/MyExtension.java",
    "content": "package org.testcontainers.hivemq.util;\n\nimport com.hivemq.extension.sdk.api.ExtensionMain;\nimport com.hivemq.extension.sdk.api.interceptor.publish.PublishInboundInterceptor;\nimport com.hivemq.extension.sdk.api.parameter.ExtensionStartInput;\nimport com.hivemq.extension.sdk.api.parameter.ExtensionStartOutput;\nimport com.hivemq.extension.sdk.api.parameter.ExtensionStopInput;\nimport com.hivemq.extension.sdk.api.parameter.ExtensionStopOutput;\nimport com.hivemq.extension.sdk.api.services.Services;\nimport com.hivemq.extension.sdk.api.services.intializer.ClientInitializer;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.nio.ByteBuffer;\nimport java.nio.charset.StandardCharsets;\n\n@SuppressWarnings(\"CodeBlock2Expr\")\npublic class MyExtension implements ExtensionMain {\n\n    @Override\n    public void extensionStart(\n        final @NotNull ExtensionStartInput extensionStartInput,\n        final @NotNull ExtensionStartOutput extensionStartOutput\n    ) {\n        final PublishInboundInterceptor publishInboundInterceptor = (publishInboundInput, publishInboundOutput) -> {\n            publishInboundOutput\n                .getPublishPacket()\n                .setPayload(ByteBuffer.wrap(\"modified\".getBytes(StandardCharsets.UTF_8)));\n        };\n\n        final ClientInitializer clientInitializer = (initializerInput, clientContext) -> {\n            clientContext.addPublishInboundInterceptor(publishInboundInterceptor);\n        };\n\n        Services.initializerRegistry().setClientInitializer(clientInitializer);\n    }\n\n    @Override\n    public void extensionStop(\n        final @NotNull ExtensionStopInput extensionStopInput,\n        final @NotNull ExtensionStopOutput extensionStopOutput\n    ) {}\n}\n"
  },
  {
    "path": "modules/hivemq/src/test/java/org/testcontainers/hivemq/util/MyExtensionWithSubclasses.java",
    "content": "package org.testcontainers.hivemq.util;\n\nimport com.hivemq.extension.sdk.api.ExtensionMain;\nimport com.hivemq.extension.sdk.api.parameter.ExtensionStartInput;\nimport com.hivemq.extension.sdk.api.parameter.ExtensionStartOutput;\nimport com.hivemq.extension.sdk.api.parameter.ExtensionStopInput;\nimport com.hivemq.extension.sdk.api.parameter.ExtensionStopOutput;\nimport com.hivemq.extension.sdk.api.services.Services;\nimport com.hivemq.extension.sdk.api.services.intializer.ClientInitializer;\nimport org.jetbrains.annotations.NotNull;\n\npublic class MyExtensionWithSubclasses implements ExtensionMain {\n\n    @Override\n    public void extensionStart(\n        final @NotNull ExtensionStartInput extensionStartInput,\n        final @NotNull ExtensionStartOutput extensionStartOutput\n    ) {\n        final ClientInitializer clientInitializer = (initializerInput, clientContext) -> {\n            clientContext.addPublishInboundInterceptor(new PublishModifier());\n        };\n\n        Services.initializerRegistry().setClientInitializer(clientInitializer);\n    }\n\n    @Override\n    public void extensionStop(\n        final @NotNull ExtensionStopInput extensionStopInput,\n        final @NotNull ExtensionStopOutput extensionStopOutput\n    ) {}\n}\n"
  },
  {
    "path": "modules/hivemq/src/test/java/org/testcontainers/hivemq/util/PublishModifier.java",
    "content": "package org.testcontainers.hivemq.util;\n\nimport com.hivemq.extension.sdk.api.interceptor.publish.PublishInboundInterceptor;\nimport com.hivemq.extension.sdk.api.interceptor.publish.parameter.PublishInboundInput;\nimport com.hivemq.extension.sdk.api.interceptor.publish.parameter.PublishInboundOutput;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.nio.ByteBuffer;\nimport java.nio.charset.StandardCharsets;\n\npublic class PublishModifier implements PublishInboundInterceptor {\n\n    @Override\n    public void onInboundPublish(\n        final @NotNull PublishInboundInput publishInboundInput,\n        final @NotNull PublishInboundOutput publishInboundOutput\n    ) {\n        publishInboundOutput\n            .getPublishPacket()\n            .setPayload(ByteBuffer.wrap(\"modified\".getBytes(StandardCharsets.UTF_8)));\n    }\n}\n"
  },
  {
    "path": "modules/hivemq/src/test/java/org/testcontainers/hivemq/util/TestPublishModifiedUtil.java",
    "content": "package org.testcontainers.hivemq.util;\n\nimport com.hivemq.client.mqtt.MqttGlobalPublishFilter;\nimport com.hivemq.client.mqtt.mqtt5.Mqtt5BlockingClient;\nimport com.hivemq.client.mqtt.mqtt5.Mqtt5Client;\nimport org.jetbrains.annotations.NotNull;\n\nimport java.nio.charset.StandardCharsets;\nimport java.util.Arrays;\nimport java.util.concurrent.CompletableFuture;\n\npublic class TestPublishModifiedUtil {\n\n    public static void testPublishModified(final int mqttPort, final @NotNull String host) throws Exception {\n        final CompletableFuture<Void> publishReceived = new CompletableFuture<>();\n\n        final Mqtt5BlockingClient publisher = Mqtt5Client\n            .builder()\n            .serverPort(mqttPort)\n            .serverHost(host)\n            .identifier(\"publisher\")\n            .buildBlocking();\n        publisher.connect();\n\n        final Mqtt5BlockingClient subscriber = Mqtt5Client\n            .builder()\n            .serverPort(mqttPort)\n            .serverHost(host)\n            .identifier(\"subscriber\")\n            .buildBlocking();\n        subscriber.connect();\n        subscriber.subscribeWith().topicFilter(\"test/topic\").send();\n        subscriber\n            .toAsync()\n            .publishes(\n                MqttGlobalPublishFilter.ALL,\n                publish -> {\n                    if (Arrays.equals(publish.getPayloadAsBytes(), \"modified\".getBytes(StandardCharsets.UTF_8))) {\n                        publishReceived.complete(null);\n                    } else {\n                        publishReceived.completeExceptionally(\n                            new IllegalArgumentException(\n                                \"unexpected payload: \" + new String(publish.getPayloadAsBytes())\n                            )\n                        );\n                    }\n                }\n            );\n\n        publisher.publishWith().topic(\"test/topic\").payload(\"unmodified\".getBytes(StandardCharsets.UTF_8)).send();\n\n        try {\n            publishReceived.get();\n        } finally {\n            publisher.disconnect();\n            subscriber.disconnect();\n        }\n    }\n}\n"
  },
  {
    "path": "modules/hivemq/src/test/resources/additionalFile.txt",
    "content": ""
  },
  {
    "path": "modules/hivemq/src/test/resources/config.xml",
    "content": "<?xml version=\"1.0\"?>\n<hivemq>\n\n    <listeners>\n        <tcp-listener>\n            <port>1883</port>\n            <bind-address>0.0.0.0</bind-address>\n        </tcp-listener>\n    </listeners>\n    <mqtt>\n        <quality-of-service>\n            <max-qos>0</max-qos>\n        </quality-of-service>\n    </mqtt>\n</hivemq>\n"
  },
  {
    "path": "modules/hivemq/src/test/resources/inMemoryConfig.xml",
    "content": "<?xml version=\"1.0\"?>\n<hivemq>\n    <persistence>\n        <mode>in-memory</mode>\n    </persistence>\n</hivemq>\n"
  },
  {
    "path": "modules/hivemq/src/test/resources/logback-test.xml",
    "content": "<configuration scan=\"false\">\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"com.hivemq.client\" level=\"WARN\"/>\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n    <logger name=\"com.github.dockerjava\" level=\"WARN\"/>\n    <logger name=\"io.netty\" level=\"WARN\"/>\n\n</configuration>\n"
  },
  {
    "path": "modules/hivemq/src/test/resources/modifier-extension/hivemq-extension.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<hivemq-extension>\n    <id>modifier-extension</id>\n    <version>1.0-SNAPSHOT</version>\n    <name>Modifier Extension</name>\n    <author>HiveMQ GmbH</author>\n    <priority>1000</priority>\n</hivemq-extension>\n"
  },
  {
    "path": "modules/hivemq/src/test/resources/modifier-extension-wrong-name/hivemq-extension.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<hivemq-extension>\n    <id>modifier-extension</id>\n    <version>1.0-SNAPSHOT</version>\n    <name>Modifier Extension</name>\n    <author>HiveMQ GmbH</author>\n    <priority>1000</priority>\n</hivemq-extension>\n"
  },
  {
    "path": "modules/hivemq/src/test/resources/myExtensionLicense.elic",
    "content": ""
  },
  {
    "path": "modules/hivemq/src/test/resources/myLicense.lic",
    "content": ""
  },
  {
    "path": "modules/influxdb/build.gradle",
    "content": "description = \"Testcontainers :: InfluxDB\"\n\ndependencies {\n    api project(':testcontainers')\n\n    compileOnly 'org.influxdb:influxdb-java:2.25'\n\n    testImplementation 'org.influxdb:influxdb-java:2.25'\n    testImplementation \"com.influxdb:influxdb-client-java:7.4.0\"\n}\n"
  },
  {
    "path": "modules/influxdb/src/main/java/org/testcontainers/containers/InfluxDBContainer.java",
    "content": "package org.testcontainers.containers;\n\nimport lombok.Getter;\nimport org.influxdb.InfluxDB;\nimport org.influxdb.InfluxDBFactory;\nimport org.testcontainers.containers.wait.strategy.HttpWaitStrategy;\nimport org.testcontainers.utility.ComparableVersion;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.util.Collections;\nimport java.util.Optional;\nimport java.util.Set;\n\n/**\n * Testcontainers implementation for InfluxDB.\n * <p>\n * Supported image: {@code influxdb}\n * <p>\n * Exposed ports: 8086\n */\npublic class InfluxDBContainer<SELF extends InfluxDBContainer<SELF>> extends GenericContainer<SELF> {\n\n    public static final Integer INFLUXDB_PORT = 8086;\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"influxdb\");\n\n    private static final String DEFAULT_TAG = \"1.4.3\";\n\n    @Deprecated\n    public static final String VERSION = DEFAULT_TAG;\n\n    private static final int NO_CONTENT_STATUS_CODE = 204;\n\n    @Getter\n    private String username = \"test-user\";\n\n    @Getter\n    private String password = \"test-password\";\n\n    /**\n     * Properties of InfluxDB 1.x\n     */\n    private boolean authEnabled = true;\n\n    private String admin = \"admin\";\n\n    private String adminPassword = \"password\";\n\n    @Getter\n    private String database;\n\n    /**\n     * Properties of InfluxDB 2.x\n     */\n    @Getter\n    private String bucket = \"test-bucket\";\n\n    @Getter\n    private String organization = \"test-org\";\n\n    @Getter\n    private Optional<String> retention = Optional.empty();\n\n    @Getter\n    private Optional<String> adminToken = Optional.empty();\n\n    private final boolean isAtLeastMajorVersion2;\n\n    /**\n     * @deprecated use {@link #InfluxDBContainer(DockerImageName)} instead\n     */\n    @Deprecated\n    public InfluxDBContainer() {\n        this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG));\n    }\n\n    /**\n     * @deprecated use {@link #InfluxDBContainer(DockerImageName)} instead\n     */\n    @Deprecated\n    public InfluxDBContainer(final String version) {\n        this(DEFAULT_IMAGE_NAME.withTag(version));\n    }\n\n    public InfluxDBContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n\n        this.waitStrategy =\n            new HttpWaitStrategy()\n                .forPath(\"/ping\")\n                .withBasicCredentials(this.username, this.password)\n                .forStatusCode(NO_CONTENT_STATUS_CODE);\n\n        this.isAtLeastMajorVersion2 =\n            new ComparableVersion(dockerImageName.getVersionPart()).isGreaterThanOrEqualTo(\"2.0.0\");\n        addExposedPort(INFLUXDB_PORT);\n    }\n\n    /**\n     * Sets the InfluxDB environment variables based on the version\n     */\n    @Override\n    protected void configure() {\n        if (this.isAtLeastMajorVersion2) {\n            configureInfluxDBV2();\n        } else {\n            configureInfluxDBV1();\n        }\n    }\n\n    /**\n     * Sets the InfluxDB 2.x environment variables\n     *\n     * @see <a href=\"https://hub.docker.com/_/influxdb\"> InfluxDB Dockerhub </a> for full documentation on InfluxDB's\n     * envrinoment variables</a>\n     */\n    private void configureInfluxDBV2() {\n        addEnv(\"DOCKER_INFLUXDB_INIT_MODE\", \"setup\");\n\n        addEnv(\"DOCKER_INFLUXDB_INIT_USERNAME\", this.username);\n        addEnv(\"DOCKER_INFLUXDB_INIT_PASSWORD\", this.password);\n\n        addEnv(\"DOCKER_INFLUXDB_INIT_ORG\", this.organization);\n        addEnv(\"DOCKER_INFLUXDB_INIT_BUCKET\", this.bucket);\n\n        this.retention.ifPresent(ret -> addEnv(\"DOCKER_INFLUXDB_INIT_RETENTION\", ret));\n        this.adminToken.ifPresent(token -> addEnv(\"DOCKER_INFLUXDB_INIT_ADMIN_TOKEN\", token));\n    }\n\n    /**\n     * Sets the InfluxDB 1.x environment variables\n     */\n    private void configureInfluxDBV1() {\n        addEnv(\"INFLUXDB_USER\", this.username);\n        addEnv(\"INFLUXDB_USER_PASSWORD\", this.password);\n\n        addEnv(\"INFLUXDB_HTTP_AUTH_ENABLED\", String.valueOf(this.authEnabled));\n\n        addEnv(\"INFLUXDB_ADMIN_USER\", this.admin);\n        addEnv(\"INFLUXDB_ADMIN_PASSWORD\", this.adminPassword);\n\n        addEnv(\"INFLUXDB_DB\", this.database);\n    }\n\n    @Override\n    public Set<Integer> getLivenessCheckPortNumbers() {\n        return Collections.singleton(getMappedPort(INFLUXDB_PORT));\n    }\n\n    /**\n     * Set user for InfluxDB\n     *\n     * @param username The username to set for the system's initial super-user\n     * @return a reference to this container instance\n     */\n    public InfluxDBContainer<SELF> withUsername(final String username) {\n        this.username = username;\n        return this;\n    }\n\n    /**\n     * Set password for InfluxDB\n     *\n     * @param password The password to set for the system's initial super-user\n     * @return a reference to this container instance\n     */\n    public InfluxDBContainer<SELF> withPassword(final String password) {\n        this.password = password;\n        return this;\n    }\n\n    /**\n     * Determines if authentication should be enabled or not\n     *\n     * @param authEnabled Enables authentication.\n     * @return a reference to this container instance\n     */\n    public InfluxDBContainer<SELF> withAuthEnabled(final boolean authEnabled) {\n        this.authEnabled = authEnabled;\n        return this.self();\n    }\n\n    /**\n     * Sets the admin user\n     *\n     * @param admin The name of the admin user to be created. If this is unset, no admin user is created.\n     * @return a reference to this container instance\n     */\n    public InfluxDBContainer<SELF> withAdmin(final String admin) {\n        this.admin = admin;\n        return this.self();\n    }\n\n    /**\n     * Sets the admin password\n     *\n     * @param adminPassword The password for the admin user. If this is unset, a random password is generated and\n     * printed to standard out.\n     * @return a reference to this container instance\n     */\n    public InfluxDBContainer<SELF> withAdminPassword(final String adminPassword) {\n        this.adminPassword = adminPassword;\n        return this.self();\n    }\n\n    /**\n     * Initializes database with given name\n     *\n     * @param database name of the database.\n     * @return a reference to this container instance\n     */\n    public InfluxDBContainer<SELF> withDatabase(final String database) {\n        this.database = database;\n        return this.self();\n    }\n\n    /**\n     * Sets the organization name\n     *\n     * @param organization The organization for the initial setup of influxDB.\n     * @return a reference to this container instance\n     */\n    public InfluxDBContainer<SELF> withOrganization(final String organization) {\n        this.organization = organization;\n        return this;\n    }\n\n    /**\n     * Initializes bucket with given name\n     *\n     * @param bucket name of the bucket.\n     * @return a reference to this container instance\n     */\n    public InfluxDBContainer<SELF> withBucket(final String bucket) {\n        this.bucket = bucket;\n        return this;\n    }\n\n    /**\n     * Sets the retention in days\n     *\n     * @param retention days bucket will retain data (0 is infinite, default is 0).\n     * @return a reference to this container instance\n     */\n    public InfluxDBContainer<SELF> withRetention(final String retention) {\n        this.retention = Optional.of(retention);\n        return this;\n    }\n\n    /**\n     * Sets the admin token\n     *\n     * @param adminToken Authentication token to associate with the admin user.\n     * @return a reference to this container instance\n     */\n    public InfluxDBContainer<SELF> withAdminToken(final String adminToken) {\n        this.adminToken = Optional.of(adminToken);\n        return this;\n    }\n\n    /**\n     * @return a url to InfluxDB\n     */\n    public String getUrl() {\n        return \"http://\" + getHost() + \":\" + getMappedPort(INFLUXDB_PORT);\n    }\n\n    /**\n     * @return a InfluxDB client for InfluxDB 1.x.\n     * @deprecated Use the new <a href=\"https://github.com/influxdata/influxdb-client-java\">InfluxDB client library.</a>\n     */\n    @Deprecated\n    public InfluxDB getNewInfluxDB() {\n        final InfluxDB influxDB = InfluxDBFactory.connect(getUrl(), this.username, this.password);\n        influxDB.setDatabase(this.database);\n        return influxDB;\n    }\n}\n"
  },
  {
    "path": "modules/influxdb/src/test/java/org/testcontainers/containers/InfluxDBContainerTest.java",
    "content": "package org.testcontainers.containers;\n\nimport com.influxdb.client.InfluxDBClient;\nimport com.influxdb.client.InfluxDBClientFactory;\nimport com.influxdb.client.InfluxDBClientOptions;\nimport com.influxdb.client.QueryApi;\nimport com.influxdb.client.WriteApi;\nimport com.influxdb.client.domain.Bucket;\nimport com.influxdb.client.domain.BucketRetentionRules;\nimport com.influxdb.client.domain.WritePrecision;\nimport com.influxdb.client.write.Point;\nimport com.influxdb.query.FluxRecord;\nimport com.influxdb.query.FluxTable;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.time.Instant;\nimport java.util.List;\nimport java.util.Optional;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass InfluxDBContainerTest {\n\n    private static final String USERNAME = \"new-test-user\";\n\n    private static final String PASSWORD = \"new-test-password\";\n\n    private static final String ORG = \"new-test-org\";\n\n    private static final String BUCKET = \"new-test-bucket\";\n\n    private static final String RETENTION = \"1w\";\n\n    private static final String ADMIN_TOKEN = \"super-secret-token\";\n\n    private static final int SECONDS_IN_WEEK = 604800;\n\n    @Test\n    void getInfluxDBClient() {\n        try (\n            // constructorWithDefaultVariables {\n            final InfluxDBContainer<?> influxDBContainer = new InfluxDBContainer<>(\n                DockerImageName.parse(\"influxdb:2.0.7\")\n            )\n            // }\n        ) {\n            influxDBContainer.start();\n\n            try (final InfluxDBClient influxDBClient = createClient(influxDBContainer)) {\n                assertThat(influxDBClient).isNotNull();\n                assertThat(influxDBClient.ping()).isTrue();\n            }\n        }\n    }\n\n    @Test\n    void getInfluxDBClientWithAdminToken() {\n        try (\n            // constructorWithAdminToken {\n            final InfluxDBContainer<?> influxDBContainer = new InfluxDBContainer<>(\n                DockerImageName.parse(\"influxdb:2.0.7\")\n            )\n                .withAdminToken(ADMIN_TOKEN)\n            // }\n        ) {\n            influxDBContainer.start();\n            final Optional<String> adminToken = influxDBContainer.getAdminToken();\n            assertThat(adminToken).isNotEmpty();\n\n            try (\n                final InfluxDBClient influxDBClient = createClientWithToken(\n                    influxDBContainer.getUrl(),\n                    adminToken.get()\n                )\n            ) {\n                assertThat(influxDBClient).isNotNull();\n                assertThat(influxDBClient.ping()).isTrue();\n            }\n        }\n    }\n\n    @Test\n    void getBucket() {\n        try (\n            // constructorWithCustomVariables {\n            final InfluxDBContainer<?> influxDBContainer = new InfluxDBContainer<>(\n                DockerImageName.parse(\"influxdb:2.0.7\")\n            )\n                .withUsername(USERNAME)\n                .withPassword(PASSWORD)\n                .withOrganization(ORG)\n                .withBucket(BUCKET)\n                .withRetention(RETENTION);\n            // }\n        ) {\n            influxDBContainer.start();\n\n            try (final InfluxDBClient influxDBClient = createClient(influxDBContainer)) {\n                final Bucket bucket = influxDBClient.getBucketsApi().findBucketByName(BUCKET);\n                assertThat(bucket).isNotNull();\n\n                assertThat(bucket.getName()).isEqualTo(BUCKET);\n                assertThat(bucket.getRetentionRules())\n                    .hasSize(1)\n                    .first()\n                    .extracting(BucketRetentionRules::getEverySeconds)\n                    .isEqualTo(SECONDS_IN_WEEK);\n            }\n        }\n    }\n\n    @Test\n    void queryForWriteAndRead() {\n        try (\n            final InfluxDBContainer<?> influxDBContainer = new InfluxDBContainer<>(\n                InfluxDBTestUtils.INFLUXDB_V2_TEST_IMAGE\n            )\n                .withUsername(USERNAME)\n                .withPassword(PASSWORD)\n                .withOrganization(ORG)\n                .withBucket(BUCKET)\n                .withRetention(RETENTION)\n        ) {\n            influxDBContainer.start();\n\n            try (final InfluxDBClient influxDBClient = createClient(influxDBContainer)) {\n                try (final WriteApi writeApi = influxDBClient.makeWriteApi()) {\n                    final Point point = Point\n                        .measurement(\"temperature\")\n                        .addTag(\"location\", \"west\")\n                        .addField(\"value\", 55.0D)\n                        .time(Instant.now().toEpochMilli(), WritePrecision.MS);\n\n                    writeApi.writePoint(point);\n                }\n\n                final String flux = String.format(\"from(bucket:\\\"%s\\\") |> range(start: 0)\", BUCKET);\n\n                final QueryApi queryApi = influxDBClient.getQueryApi();\n\n                final FluxTable fluxTable = queryApi.query(flux).get(0);\n                final List<FluxRecord> records = fluxTable.getRecords();\n                assertThat(records).hasSize(1);\n            }\n        }\n    }\n\n    // createInfluxDBClient {\n    public static InfluxDBClient createClient(final InfluxDBContainer<?> influxDBContainer) {\n        final InfluxDBClientOptions influxDBClientOptions = InfluxDBClientOptions\n            .builder()\n            .url(influxDBContainer.getUrl())\n            .authenticate(influxDBContainer.getUsername(), influxDBContainer.getPassword().toCharArray())\n            .bucket(influxDBContainer.getBucket())\n            .org(influxDBContainer.getOrganization())\n            .build();\n        return InfluxDBClientFactory.create(influxDBClientOptions);\n    }\n\n    // }\n\n    public static InfluxDBClient createClientWithToken(final String url, final String token) {\n        return InfluxDBClientFactory.create(url, token.toCharArray());\n    }\n}\n"
  },
  {
    "path": "modules/influxdb/src/test/java/org/testcontainers/containers/InfluxDBContainerV1Test.java",
    "content": "package org.testcontainers.containers;\n\nimport org.influxdb.InfluxDB;\nimport org.influxdb.InfluxDBFactory;\nimport org.influxdb.dto.Point;\nimport org.influxdb.dto.Query;\nimport org.influxdb.dto.QueryResult;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.util.concurrent.TimeUnit;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass InfluxDBContainerV1Test {\n\n    private static final String TEST_VERSION = InfluxDBTestUtils.INFLUXDB_V1_TEST_IMAGE.getVersionPart();\n\n    private static final String DATABASE = \"test\";\n\n    private static final String USER = \"new-test-user\";\n\n    private static final String PASSWORD = \"new-test-password\";\n\n    @Test\n    void createInfluxDBOnlyWithUrlAndCorrectVersion() {\n        try (\n            // constructorWithDefaultVariables {\n            final InfluxDBContainer<?> influxDBContainer = new InfluxDBContainer<>(\n                DockerImageName.parse(\"influxdb:1.4.3\")\n            )\n            // }\n        ) {\n            // Start the container. This step might take some time...\n            influxDBContainer.start();\n\n            try (final InfluxDB influxDBClient = createInfluxDBWithUrl(influxDBContainer)) {\n                assertThat(influxDBClient).isNotNull();\n                assertThat(influxDBClient.ping().isGood()).isTrue();\n                assertThat(influxDBClient.version()).isEqualTo(TEST_VERSION);\n            }\n        }\n    }\n\n    @Test\n    void getNewInfluxDBWithCorrectVersion() {\n        try (\n            final InfluxDBContainer<?> influxDBContainer = new InfluxDBContainer<>(\n                InfluxDBTestUtils.INFLUXDB_V1_TEST_IMAGE\n            )\n        ) {\n            // Start the container. This step might take some time...\n            influxDBContainer.start();\n\n            try (final InfluxDB influxDBClient = createInfluxDBWithUrl(influxDBContainer)) {\n                assertThat(influxDBClient).isNotNull();\n                assertThat(influxDBClient.ping().isGood()).isTrue();\n                assertThat(influxDBClient.version()).isEqualTo(TEST_VERSION);\n            }\n        }\n    }\n\n    @Test\n    void describeDatabases() {\n        try (\n            // constructorWithUserPassword {\n            final InfluxDBContainer<?> influxDBContainer = new InfluxDBContainer<>(\n                DockerImageName.parse(\"influxdb:1.4.3\")\n            )\n                .withDatabase(DATABASE)\n                .withUsername(USER)\n                .withPassword(PASSWORD)\n            // }\n        ) {\n            // Start the container. This step might take some time...\n            influxDBContainer.start();\n\n            try (final InfluxDB influxDBClient = createInfluxDBWithUrl(influxDBContainer)) {\n                assertThat(influxDBClient.describeDatabases()).contains(DATABASE);\n            }\n        }\n    }\n\n    @Test\n    void queryForWriteAndRead() {\n        try (\n            final InfluxDBContainer<?> influxDBContainer = new InfluxDBContainer<>(\n                InfluxDBTestUtils.INFLUXDB_V1_TEST_IMAGE\n            )\n                .withDatabase(DATABASE)\n                .withUsername(USER)\n                .withPassword(PASSWORD)\n        ) {\n            // Start the container. This step might take some time...\n            influxDBContainer.start();\n\n            try (final InfluxDB influxDBClient = createInfluxDBWithUrl(influxDBContainer)) {\n                final Point point = Point\n                    .measurement(\"cpu\")\n                    .time(System.currentTimeMillis(), TimeUnit.MILLISECONDS)\n                    .addField(\"idle\", 90L)\n                    .addField(\"user\", 9L)\n                    .addField(\"system\", 1L)\n                    .build();\n                influxDBClient.write(point);\n\n                final Query query = new Query(\"SELECT idle FROM cpu\", DATABASE);\n                final QueryResult actual = influxDBClient.query(query);\n\n                assertThat(actual).isNotNull();\n                assertThat(actual.getError()).isNull();\n                assertThat(actual.getResults()).isNotNull();\n                assertThat(actual.getResults()).hasSize(1);\n            }\n        }\n    }\n\n    // createInfluxDBClient {\n    public static InfluxDB createInfluxDBWithUrl(final InfluxDBContainer<?> container) {\n        InfluxDB influxDB = InfluxDBFactory.connect(\n            container.getUrl(),\n            container.getUsername(),\n            container.getPassword()\n        );\n        influxDB.setDatabase(container.getDatabase());\n        return influxDB;\n    }\n    // }\n}\n"
  },
  {
    "path": "modules/influxdb/src/test/java/org/testcontainers/containers/InfluxDBTestUtils.java",
    "content": "package org.testcontainers.containers;\n\nimport org.testcontainers.utility.DockerImageName;\n\npublic final class InfluxDBTestUtils {\n\n    static final DockerImageName INFLUXDB_V1_TEST_IMAGE = DockerImageName.parse(\"influxdb:1.4.3\");\n\n    static final DockerImageName INFLUXDB_V2_TEST_IMAGE = DockerImageName.parse(\"influxdb:2.0.7\");\n}\n"
  },
  {
    "path": "modules/influxdb/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/jdbc/build.gradle",
    "content": "description = \"Testcontainers :: JDBC\"\n\ndependencies {\n    api project(':testcontainers-database-commons')\n\n    compileOnly 'org.jetbrains:annotations:26.0.2-1'\n    testImplementation 'commons-dbutils:commons-dbutils:1.8.1'\n    testImplementation 'org.vibur:vibur-dbcp:26.0'\n    testImplementation 'org.apache.tomcat:tomcat-jdbc:11.0.14'\n    testImplementation 'com.zaxxer:HikariCP-java6:2.3.13'\n    testImplementation ('org.mockito:mockito-core:4.11.0') {\n        exclude(module: 'hamcrest-core')\n    }\n}\n"
  },
  {
    "path": "modules/jdbc/src/main/java/org/testcontainers/containers/JdbcDatabaseContainer.java",
    "content": "package org.testcontainers.containers;\n\nimport com.github.dockerjava.api.command.InspectContainerResponse;\nimport lombok.NonNull;\nimport lombok.SneakyThrows;\nimport org.apache.commons.lang3.StringUtils;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\nimport org.testcontainers.containers.traits.LinkableContainer;\nimport org.testcontainers.delegate.DatabaseDelegate;\nimport org.testcontainers.ext.ScriptUtils;\nimport org.testcontainers.jdbc.JdbcDatabaseDelegate;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.MountableFile;\n\nimport java.sql.Connection;\nimport java.sql.Driver;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Properties;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.TimeUnit;\nimport java.util.stream.Collectors;\n\n/**\n * Base class for containers that expose a JDBC connection\n */\npublic abstract class JdbcDatabaseContainer<SELF extends JdbcDatabaseContainer<SELF>>\n    extends GenericContainer<SELF>\n    implements LinkableContainer {\n\n    private static final Object DRIVER_LOAD_MUTEX = new Object();\n\n    private Driver driver;\n\n    private List<String> initScriptPaths = new ArrayList<>();\n\n    protected Map<String, String> parameters = new HashMap<>();\n\n    protected Map<String, String> urlParameters = new HashMap<>();\n\n    private int startupTimeoutSeconds = 120;\n\n    private int connectTimeoutSeconds = 120;\n\n    private static final String QUERY_PARAM_SEPARATOR = \"&\";\n\n    /**\n     * @deprecated use {@link #JdbcDatabaseContainer(DockerImageName)} instead\n     */\n    public JdbcDatabaseContainer(@NonNull final String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    public JdbcDatabaseContainer(@NonNull final Future<String> image) {\n        super(image);\n    }\n\n    public JdbcDatabaseContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n    }\n\n    /**\n     * @return the name of the actual JDBC driver to use\n     */\n    public abstract String getDriverClassName();\n\n    /**\n     * @return a JDBC URL that may be used to connect to the dockerized DB\n     */\n    public abstract String getJdbcUrl();\n\n    /**\n     * @return the database name\n     */\n    public String getDatabaseName() {\n        throw new UnsupportedOperationException();\n    }\n\n    /**\n     * @return the standard database username that should be used for connections\n     */\n    public abstract String getUsername();\n\n    /**\n     * @return the standard password that should be used for connections\n     */\n    public abstract String getPassword();\n\n    /**\n     * @return a test query string suitable for testing that this particular database type is alive\n     */\n    protected abstract String getTestQueryString();\n\n    public SELF withUsername(String username) {\n        throw new UnsupportedOperationException();\n    }\n\n    public SELF withPassword(String password) {\n        throw new UnsupportedOperationException();\n    }\n\n    public SELF withDatabaseName(String dbName) {\n        throw new UnsupportedOperationException();\n    }\n\n    public SELF withUrlParam(String paramName, String paramValue) {\n        urlParameters.put(paramName, paramValue);\n        return self();\n    }\n\n    /**\n     * Set startup time to allow, including image pull time, in seconds.\n     *\n     * @param startupTimeoutSeconds startup time to allow, including image pull time, in seconds\n     * @return self\n     */\n    public SELF withStartupTimeoutSeconds(int startupTimeoutSeconds) {\n        this.startupTimeoutSeconds = startupTimeoutSeconds;\n        return self();\n    }\n\n    /**\n     * Set time to allow for the database to start and establish an initial connection, in seconds.\n     *\n     * @param connectTimeoutSeconds time to allow for the database to start and establish an initial connection in seconds\n     * @return self\n     */\n    public SELF withConnectTimeoutSeconds(int connectTimeoutSeconds) {\n        this.connectTimeoutSeconds = connectTimeoutSeconds;\n        return self();\n    }\n\n    /**\n     * Sets a script for initialization.\n     *\n     * @param initScriptPath path to the script file\n     * @return self\n     */\n    public SELF withInitScript(String initScriptPath) {\n        this.initScriptPaths = new ArrayList<>();\n        this.initScriptPaths.add(initScriptPath);\n        return self();\n    }\n\n    /**\n     * Sets an ordered array of scripts for initialization.\n     *\n     * @param initScriptPaths paths to the script files\n     * @return self\n     */\n    public SELF withInitScripts(String... initScriptPaths) {\n        return withInitScripts(Arrays.asList(initScriptPaths));\n    }\n\n    /**\n     * Sets an ordered collection of scripts for initialization.\n     *\n     * @param initScriptPaths paths to the script files\n     * @return self\n     */\n    public SELF withInitScripts(Iterable<String> initScriptPaths) {\n        this.initScriptPaths = new ArrayList<>();\n        initScriptPaths.forEach(this.initScriptPaths::add);\n        return self();\n    }\n\n    @SneakyThrows(InterruptedException.class)\n    @Override\n    protected void waitUntilContainerStarted() {\n        logger()\n            .info(\n                \"Waiting for database connection to become available at {} using query '{}'\",\n                getJdbcUrl(),\n                getTestQueryString()\n            );\n\n        // Repeatedly try and open a connection to the DB and execute a test query\n        long start = System.nanoTime();\n\n        Exception lastConnectionException = null;\n        while ((System.nanoTime() - start) < TimeUnit.SECONDS.toNanos(startupTimeoutSeconds)) {\n            if (!isRunning()) {\n                Thread.sleep(100L);\n            } else {\n                try (Connection connection = createConnection(\"\"); Statement statement = connection.createStatement()) {\n                    boolean testQuerySucceeded = statement.execute(this.getTestQueryString());\n                    if (testQuerySucceeded) {\n                        return;\n                    }\n                } catch (NoDriverFoundException e) {\n                    // we explicitly want this exception to fail fast without retries\n                    throw e;\n                } catch (Exception e) {\n                    lastConnectionException = e;\n                    // ignore so that we can try again\n                    logger().debug(\"Failure when trying test query\", e);\n                    Thread.sleep(100L);\n                }\n            }\n        }\n\n        throw new IllegalStateException(\n            String.format(\n                \"Container is started, but cannot be accessed by (JDBC URL: %s), please check container logs\",\n                this.getJdbcUrl()\n            ),\n            lastConnectionException\n        );\n    }\n\n    @Override\n    protected void containerIsStarted(InspectContainerResponse containerInfo) {\n        logger().info(\"Container is started (JDBC URL: {})\", this.getJdbcUrl());\n        runInitScriptIfRequired();\n    }\n\n    /**\n     * Obtain an instance of the correct JDBC driver for this particular database container type\n     *\n     * @return a JDBC Driver\n     */\n    public Driver getJdbcDriverInstance() throws NoDriverFoundException {\n        synchronized (DRIVER_LOAD_MUTEX) {\n            if (driver == null) {\n                try {\n                    driver = (Driver) Class.forName(this.getDriverClassName()).newInstance();\n                } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {\n                    throw new NoDriverFoundException(\"Could not get Driver\", e);\n                }\n            }\n        }\n\n        return driver;\n    }\n\n    /**\n     * Creates a connection to the underlying containerized database instance.\n     *\n     * @param queryString query string parameters that should be appended to the JDBC connection URL.\n     *                    The '?' character must be included\n     * @return a Connection\n     * @throws SQLException if there is a repeated failure to create the connection\n     */\n    public Connection createConnection(String queryString) throws SQLException, NoDriverFoundException {\n        return createConnection(queryString, new Properties());\n    }\n\n    /**\n     * Creates a connection to the underlying containerized database instance.\n     *\n     * @param queryString query string parameters that should be appended to the JDBC connection URL.\n     *                    The '?' character must be included\n     * @param info  additional properties to be passed to the JDBC driver\n     * @return a Connection\n     * @throws SQLException if there is a repeated failure to create the connection\n     */\n    public Connection createConnection(String queryString, Properties info)\n        throws SQLException, NoDriverFoundException {\n        Properties properties = new Properties(info);\n        properties.put(\"user\", this.getUsername());\n        properties.put(\"password\", this.getPassword());\n        final String url = constructUrlForConnection(queryString);\n\n        final Driver jdbcDriverInstance = getJdbcDriverInstance();\n\n        SQLException lastException = null;\n        try {\n            long start = System.nanoTime();\n            // give up if we hit the time limit or the container stops running for some reason\n            while ((System.nanoTime() - start < TimeUnit.SECONDS.toNanos(connectTimeoutSeconds)) && isRunning()) {\n                try {\n                    logger()\n                        .debug(\n                            \"Trying to create JDBC connection using {} to {} with properties: {}\",\n                            jdbcDriverInstance.getClass().getName(),\n                            url,\n                            properties\n                        );\n\n                    return jdbcDriverInstance.connect(url, properties);\n                } catch (SQLException e) {\n                    lastException = e;\n                    Thread.sleep(100L);\n                }\n            }\n        } catch (InterruptedException e) {\n            Thread.currentThread().interrupt();\n        }\n        throw new SQLException(\"Could not create new connection\", lastException);\n    }\n\n    /**\n     * Template method for constructing the JDBC URL to be used for creating {@link Connection}s.\n     * This should be overridden if the JDBC URL and query string concatenation or URL string\n     * construction needs to be different to normal.\n     *\n     * @param queryString query string parameters that should be appended to the JDBC connection URL.\n     *                    The '?' character must be included\n     * @return a full JDBC URL including queryString\n     */\n    protected String constructUrlForConnection(String queryString) {\n        String baseUrl = getJdbcUrl();\n\n        if (\"\".equals(queryString)) {\n            return baseUrl;\n        }\n\n        if (!queryString.startsWith(\"?\")) {\n            throw new IllegalArgumentException(\"The '?' character must be included\");\n        }\n\n        return baseUrl.contains(\"?\")\n            ? baseUrl + QUERY_PARAM_SEPARATOR + queryString.substring(1)\n            : baseUrl + queryString;\n    }\n\n    protected String constructUrlParameters(String startCharacter, String delimiter) {\n        return constructUrlParameters(startCharacter, delimiter, StringUtils.EMPTY);\n    }\n\n    protected String constructUrlParameters(String startCharacter, String delimiter, String endCharacter) {\n        String urlParameters = \"\";\n        if (!this.urlParameters.isEmpty()) {\n            String additionalParameters =\n                this.urlParameters.entrySet().stream().map(Object::toString).collect(Collectors.joining(delimiter));\n            urlParameters = startCharacter + additionalParameters + endCharacter;\n        }\n        return urlParameters;\n    }\n\n    @Deprecated\n    protected void optionallyMapResourceParameterAsVolume(\n        @NotNull String paramName,\n        @NotNull String pathNameInContainer,\n        @NotNull String defaultResource\n    ) {\n        optionallyMapResourceParameterAsVolume(paramName, pathNameInContainer, defaultResource, null);\n    }\n\n    protected void optionallyMapResourceParameterAsVolume(\n        @NotNull String paramName,\n        @NotNull String pathNameInContainer,\n        @NotNull String defaultResource,\n        @Nullable Integer fileMode\n    ) {\n        String resourceName = parameters.getOrDefault(paramName, defaultResource);\n\n        if (resourceName != null) {\n            final MountableFile mountableFile = MountableFile.forClasspathResource(resourceName, fileMode);\n            withCopyFileToContainer(mountableFile, pathNameInContainer);\n        }\n    }\n\n    /**\n     * Load init script content and apply it to the database if initScriptPath is set\n     */\n    protected void runInitScriptIfRequired() {\n        initScriptPaths\n            .stream()\n            .filter(Objects::nonNull)\n            .forEach(path -> ScriptUtils.runInitScript(getDatabaseDelegate(), path));\n    }\n\n    public void setParameters(Map<String, String> parameters) {\n        this.parameters = parameters;\n    }\n\n    @SuppressWarnings(\"unused\")\n    public void addParameter(String paramName, String value) {\n        this.parameters.put(paramName, value);\n    }\n\n    /**\n     * @return startup time to allow, including image pull time, in seconds\n     * @deprecated should not be overridden anymore, use {@link #withStartupTimeoutSeconds(int)} in constructor instead\n     */\n    @Deprecated\n    protected int getStartupTimeoutSeconds() {\n        return startupTimeoutSeconds;\n    }\n\n    /**\n     * @return time to allow for the database to start and establish an initial connection, in seconds\n     * @deprecated should not be overridden anymore, use {@link #withConnectTimeoutSeconds(int)} in constructor instead\n     */\n    @Deprecated\n    protected int getConnectTimeoutSeconds() {\n        return connectTimeoutSeconds;\n    }\n\n    protected DatabaseDelegate getDatabaseDelegate() {\n        return new JdbcDatabaseDelegate(this, \"\");\n    }\n\n    public static class NoDriverFoundException extends RuntimeException {\n\n        public NoDriverFoundException(String message, Throwable e) {\n            super(message, e);\n        }\n    }\n}\n"
  },
  {
    "path": "modules/jdbc/src/main/java/org/testcontainers/containers/JdbcDatabaseContainerProvider.java",
    "content": "package org.testcontainers.containers;\n\nimport lombok.extern.slf4j.Slf4j;\nimport org.testcontainers.jdbc.ConnectionUrl;\n\nimport java.util.Objects;\n\n/**\n * Base class for classes that can provide a JDBC container.\n */\n@Slf4j\npublic abstract class JdbcDatabaseContainerProvider {\n\n    /**\n     * Tests if the specified database type is supported by this Container Provider. It should match to the base image name.\n     * @param databaseType {@link String}\n     * @return <code>true</code> when provider can handle this database type, else <code>false</code>.\n     */\n    public abstract boolean supports(String databaseType);\n\n    /**\n     * Instantiate a new {@link JdbcDatabaseContainer} without any specified image tag. Subclasses <i>should</i>\n     * override this method if possible, to provide a default tag that is more stable than <code>latest</code>`.\n     *\n     * @return Instance of {@link JdbcDatabaseContainer}\n     */\n    public JdbcDatabaseContainer newInstance() {\n        log.warn(\n            \"No explicit version tag was provided in JDBC URL and this class ({}) does not \" +\n            \"override newInstance() to set a default tag. `latest` will be used but results may \" +\n            \"be unreliable!\",\n            this.getClass().getCanonicalName()\n        );\n        return this.newInstance(\"latest\");\n    }\n\n    /**\n     * Instantiate a new {@link JdbcDatabaseContainer} with specified image tag.\n     * @param tag\n     * @return Instance of {@link JdbcDatabaseContainer}\n     */\n    public abstract JdbcDatabaseContainer newInstance(String tag);\n\n    /**\n     * Instantiate a new {@link JdbcDatabaseContainer} using information provided with {@link ConnectionUrl}.\n     * @param url {@link ConnectionUrl}\n     * @return Instance of {@link JdbcDatabaseContainer}\n     */\n    public JdbcDatabaseContainer newInstance(ConnectionUrl url) {\n        final JdbcDatabaseContainer result;\n        if (url.getImageTag().isPresent()) {\n            result = newInstance(url.getImageTag().get());\n        } else {\n            result = newInstance();\n        }\n        result.withReuse(url.isReusable());\n        return result;\n    }\n\n    protected JdbcDatabaseContainer newInstanceFromConnectionUrl(\n        ConnectionUrl connectionUrl,\n        final String userParamName,\n        final String pwdParamName\n    ) {\n        Objects.requireNonNull(connectionUrl, \"Connection URL cannot be null\");\n\n        final String databaseName = connectionUrl.getDatabaseName().orElse(\"test\");\n        final String user = connectionUrl.getQueryParameters().getOrDefault(userParamName, \"test\");\n        final String password = connectionUrl.getQueryParameters().getOrDefault(pwdParamName, \"test\");\n\n        final JdbcDatabaseContainer<?> instance;\n        if (connectionUrl.getImageTag().isPresent()) {\n            instance = newInstance(connectionUrl.getImageTag().get());\n        } else {\n            instance = newInstance();\n        }\n\n        return instance\n            .withReuse(connectionUrl.isReusable())\n            .withDatabaseName(databaseName)\n            .withUsername(user)\n            .withPassword(password);\n    }\n}\n"
  },
  {
    "path": "modules/jdbc/src/main/java/org/testcontainers/jdbc/ConnectionDelegate.java",
    "content": "package org.testcontainers.jdbc;\n\nimport lombok.RequiredArgsConstructor;\nimport lombok.experimental.Delegate;\n\nimport java.sql.Connection;\n\n@RequiredArgsConstructor\nclass ConnectionDelegate implements Connection {\n\n    @Delegate\n    private final Connection delegate;\n}\n"
  },
  {
    "path": "modules/jdbc/src/main/java/org/testcontainers/jdbc/ConnectionUrl.java",
    "content": "package org.testcontainers.jdbc;\n\nimport lombok.AllArgsConstructor;\nimport lombok.EqualsAndHashCode;\nimport lombok.Getter;\nimport org.testcontainers.UnstableAPI;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.Optional;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\n/**\n * This is an Immutable class holding JDBC Connection Url and its parsed components, used by {@link ContainerDatabaseDriver}.\n * <p>\n * {@link ConnectionUrl#parseUrl()} method must be called after instantiating this class.\n */\n@EqualsAndHashCode(of = \"url\")\n@Getter\npublic class ConnectionUrl {\n\n    private String url;\n\n    private String databaseType;\n\n    private Optional<String> imageTag;\n\n    /**\n     * This is a part of the connection string that may specify host:port/databasename.\n     * It may vary for different clients and so clients can parse it as needed.\n     */\n    private String dbHostString;\n\n    private boolean inDaemonMode = false;\n\n    private Optional<String> databaseHost = Optional.empty();\n\n    private Optional<Integer> databasePort = Optional.empty();\n\n    private Optional<String> databaseName = Optional.empty();\n\n    private Optional<String> initScriptPath = Optional.empty();\n\n    @UnstableAPI\n    private boolean reusable = false;\n\n    private Optional<InitFunctionDef> initFunction = Optional.empty();\n\n    private Optional<String> queryString;\n\n    private Map<String, String> containerParameters;\n\n    private Map<String, String> queryParameters;\n\n    private Map<String, String> tmpfsOptions = new HashMap<>();\n\n    public static ConnectionUrl newInstance(final String url) {\n        ConnectionUrl connectionUrl = new ConnectionUrl(url);\n        connectionUrl.parseUrl();\n        return connectionUrl;\n    }\n\n    private ConnectionUrl(final String url) {\n        this.url = Objects.requireNonNull(url, \"Connection URL cannot be null\");\n    }\n\n    public static boolean accepts(final String url) {\n        return url.startsWith(\"jdbc:tc:\");\n    }\n\n    /**\n     * This method applies various REGEX Patterns to parse the URL associated with this instance.\n     * This is called from a @{@link ConnectionUrl#newInstance(String)} static factory method to create immutable instance of\n     * {@link ConnectionUrl}.\n     * To avoid mutation after class is instantiated, this method should not be publicly accessible.\n     */\n    private void parseUrl() {\n        /*\n        Extract from the JDBC connection URL:\n         * The database type (e.g. mysql, postgresql, ...)\n         * The docker tag, if provided.\n         * The URL query string, if provided\n       */\n        Matcher urlMatcher = Patterns.URL_MATCHING_PATTERN.matcher(this.getUrl());\n        if (!urlMatcher.matches()) {\n            //Try for Oracle pattern\n            urlMatcher = Patterns.ORACLE_URL_MATCHING_PATTERN.matcher(this.getUrl());\n            if (!urlMatcher.matches()) {\n                throw new IllegalArgumentException(\n                    \"JDBC URL matches jdbc:tc: prefix but the database or tag name could not be identified\"\n                );\n            }\n        }\n        databaseType = urlMatcher.group(\"databaseType\");\n\n        imageTag = Optional.ofNullable(urlMatcher.group(\"imageTag\"));\n\n        //String like hostname:port/database name, which may vary based on target database.\n        //Clients can further parse it as needed.\n        dbHostString = urlMatcher.group(\"dbHostString\");\n\n        //In case it matches to the default pattern\n        Matcher dbInstanceMatcher = Patterns.DB_INSTANCE_MATCHING_PATTERN.matcher(dbHostString);\n        if (dbInstanceMatcher.matches()) {\n            databaseHost = Optional.ofNullable(dbInstanceMatcher.group(\"databaseHost\"));\n            databasePort = Optional.ofNullable(dbInstanceMatcher.group(\"databasePort\")).map(Integer::valueOf);\n            databaseName = Optional.of(dbInstanceMatcher.group(\"databaseName\"));\n        }\n\n        queryParameters =\n            Collections.unmodifiableMap(\n                parseQueryParameters(Optional.ofNullable(urlMatcher.group(\"queryParameters\")).orElse(\"\"))\n            );\n\n        String query = queryParameters\n            .entrySet()\n            .stream()\n            .map(e -> e.getKey() + \"=\" + e.getValue())\n            .collect(Collectors.joining(\"&\"));\n\n        if (query.trim().length() == 0) {\n            queryString = Optional.empty();\n        } else {\n            queryString = Optional.of(\"?\" + query);\n        }\n\n        containerParameters = Collections.unmodifiableMap(parseContainerParameters());\n\n        tmpfsOptions = parseTmpfsOptions(containerParameters);\n\n        initScriptPath = Optional.ofNullable(containerParameters.get(\"TC_INITSCRIPT\"));\n\n        reusable = Boolean.parseBoolean(containerParameters.get(\"TC_REUSABLE\"));\n\n        Matcher funcMatcher = Patterns.INITFUNCTION_MATCHING_PATTERN.matcher(this.getUrl());\n        if (funcMatcher.matches()) {\n            initFunction = Optional.of(new InitFunctionDef(funcMatcher.group(2), funcMatcher.group(4)));\n        }\n\n        Matcher daemonMatcher = Patterns.DAEMON_MATCHING_PATTERN.matcher(this.getUrl());\n        inDaemonMode = daemonMatcher.matches() && Boolean.parseBoolean(daemonMatcher.group(2));\n    }\n\n    private Map<String, String> parseTmpfsOptions(Map<String, String> containerParameters) {\n        if (!containerParameters.containsKey(\"TC_TMPFS\")) {\n            return Collections.emptyMap();\n        }\n\n        String tmpfsOptions = containerParameters.get(\"TC_TMPFS\");\n\n        return Stream\n            .of(tmpfsOptions.split(\",\"))\n            .collect(Collectors.toMap(string -> string.split(\":\")[0], string -> string.split(\":\")[1]));\n    }\n\n    /**\n     * Get the Testcontainers Parameters such as Init Function, Init Script path etc.\n     *\n     * @return {@link Map}\n     */\n    private Map<String, String> parseContainerParameters() {\n        Map<String, String> results = new HashMap<>();\n\n        Matcher matcher = Patterns.TC_PARAM_MATCHING_PATTERN.matcher(this.getUrl());\n        while (matcher.find()) {\n            String key = matcher.group(1);\n            String value = matcher.group(2);\n            results.put(key, value);\n        }\n\n        return results;\n    }\n\n    /**\n     * Get all Query parameters specified in the Connection URL after ?. This DOES NOT include Testcontainers (TC_*) parameters.\n     *\n     * @return {@link Map}\n     */\n    private Map<String, String> parseQueryParameters(final String queryString) {\n        Map<String, String> results = new HashMap<>();\n        Matcher matcher = Patterns.QUERY_PARAM_MATCHING_PATTERN.matcher(queryString);\n        while (matcher.find()) {\n            String key = matcher.group(1);\n            String value = matcher.group(2);\n            if (!key.matches(Patterns.TC_PARAM_NAME_PATTERN)) {\n                results.put(key, value);\n            }\n        }\n\n        return results;\n    }\n\n    public Map<String, String> getTmpfsOptions() {\n        return Collections.unmodifiableMap(tmpfsOptions);\n    }\n\n    /**\n     * This interface defines the Regex Patterns used by {@link ConnectionUrl}.\n     */\n    public interface Patterns {\n        Pattern URL_MATCHING_PATTERN = Pattern.compile(\n            \"jdbc:tc:\" +\n            \"(?<databaseType>[a-z0-9]+)\" +\n            \"(:(?<imageTag>[^:]+))?\" +\n            \"://\" +\n            \"(?<dbHostString>[^?]+)\" +\n            \"(?<queryParameters>\\\\?.*)?\"\n        );\n\n        Pattern ORACLE_URL_MATCHING_PATTERN = Pattern.compile(\n            \"jdbc:tc:\" +\n            \"(?<databaseType>[a-z]+)\" +\n            \"(:(?<imageTag>(?!thin).+))?:thin:(//)?\" +\n            \"(\" +\n            \"(?<username>[^:\" +\n            \"?^/]+)/(?<password>[^?^/]+)\" +\n            \")?\" +\n            \"@\" +\n            \"(?<dbHostString>[^?]+)\" +\n            \"(?<queryParameters>\\\\?.*)?\"\n        );\n\n        //Matches to part of string - hostname:port/databasename\n        Pattern DB_INSTANCE_MATCHING_PATTERN = Pattern.compile(\n            \"(?<databaseHost>[^:]+)?\" +\n            \"(:(?<databasePort>[0-9]+))?\" +\n            \"(\" +\n            \"(?<sidOrServiceName>[:/])\" +\n            \"|\" +\n            \";databaseName=\" +\n            \")\" +\n            \"(?<databaseName>[^\\\\\\\\?]+)\"\n        );\n\n        Pattern DAEMON_MATCHING_PATTERN = Pattern.compile(\".*([?&]?)TC_DAEMON=([^?&]+).*\");\n\n        /**\n         * @deprecated for removal\n         */\n        @Deprecated\n        Pattern INITSCRIPT_MATCHING_PATTERN = Pattern.compile(\".*([?&]?)TC_INITSCRIPT=([^?&]+).*\");\n\n        Pattern INITFUNCTION_MATCHING_PATTERN = Pattern.compile(\n            \".*([?&]?)TC_INITFUNCTION=\" +\n            \"((\\\\p{javaJavaIdentifierStart}\\\\p{javaJavaIdentifierPart}*\\\\.)*\\\\p{javaJavaIdentifierStart}\\\\p{javaJavaIdentifierPart}*)\" +\n            \"::\" +\n            \"(\\\\p{javaJavaIdentifierStart}\\\\p{javaJavaIdentifierPart}*)\" +\n            \".*\"\n        );\n\n        String TC_PARAM_NAME_PATTERN = \"(TC_[A-Z_]+)\";\n\n        Pattern TC_PARAM_MATCHING_PATTERN = Pattern.compile(TC_PARAM_NAME_PATTERN + \"=([^?&]+)\");\n\n        Pattern QUERY_PARAM_MATCHING_PATTERN = Pattern.compile(\"([^?&=]+)=([^?&]*)\");\n    }\n\n    @Getter\n    @AllArgsConstructor\n    public class InitFunctionDef {\n\n        private String className;\n\n        private String methodName;\n    }\n}\n"
  },
  {
    "path": "modules/jdbc/src/main/java/org/testcontainers/jdbc/ConnectionWrapper.java",
    "content": "package org.testcontainers.jdbc;\n\nimport java.sql.Connection;\nimport java.sql.SQLException;\n\npublic class ConnectionWrapper extends ConnectionDelegate {\n\n    private final Runnable closeCallback;\n\n    public ConnectionWrapper(Connection connection, Runnable runnable) {\n        super(connection);\n        this.closeCallback = runnable;\n    }\n\n    @Override\n    public void close() throws SQLException {\n        super.close();\n        try {\n            closeCallback.run();\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }\n}\n"
  },
  {
    "path": "modules/jdbc/src/main/java/org/testcontainers/jdbc/ContainerDatabaseDriver.java",
    "content": "package org.testcontainers.jdbc;\n\nimport org.apache.commons.io.IOUtils;\nimport org.slf4j.LoggerFactory;\nimport org.testcontainers.containers.JdbcDatabaseContainer;\nimport org.testcontainers.containers.JdbcDatabaseContainerProvider;\nimport org.testcontainers.delegate.DatabaseDelegate;\nimport org.testcontainers.ext.ScriptUtils;\n\nimport java.io.IOException;\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\nimport java.net.URL;\nimport java.nio.charset.StandardCharsets;\nimport java.sql.Connection;\nimport java.sql.Driver;\nimport java.sql.DriverManager;\nimport java.sql.DriverPropertyInfo;\nimport java.sql.SQLException;\nimport java.sql.SQLFeatureNotSupportedException;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Map;\nimport java.util.Properties;\nimport java.util.ServiceLoader;\nimport java.util.Set;\nimport java.util.logging.Logger;\n\nimport javax.script.ScriptException;\n\n/**\n * Test Containers JDBC proxy driver. This driver will handle JDBC URLs of the form:\n * <p>\n * <code>jdbc:tc:<i>type</i>://<i>host</i>:<i>port</i>/<i>database</i>?<i>querystring</i></code>\n * <p>\n * where <i>type</i> is a supported database type (e.g. mysql, postgresql, oracle). Behind the scenes a new\n * docker container will be launched running the required database engine. New JDBC connections will be created\n * using the database's standard driver implementation, connected to the container.\n * <p>\n * If <code>TC_INITSCRIPT</code> is set in <i>querystring</i>, it will be used as the path for an init script that\n * should be run to initialize the database after the container is created. This should be a classpath resource.\n * <p>\n * Similarly <code>TC_INITFUNCTION</code> may be a method reference for a function that can initialize the database.\n * Such a function must accept a javax.sql.Connection as its only parameter.\n * An example of a valid method reference would be <code>com.myapp.SomeClass::initFunction</code>\n */\npublic class ContainerDatabaseDriver implements Driver {\n\n    private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(ContainerDatabaseDriver.class);\n\n    private Driver delegate;\n\n    private static final Map<String, Set<Connection>> containerConnections = new HashMap<>();\n\n    private static final Map<String, JdbcDatabaseContainer> jdbcUrlContainerCache = new HashMap<>();\n\n    private static final Set<String> initializedContainers = new HashSet<>();\n\n    private static final String FILE_PATH_PREFIX = \"file:\";\n\n    static {\n        load();\n    }\n\n    private static void load() {\n        try {\n            DriverManager.registerDriver(new ContainerDatabaseDriver());\n        } catch (SQLException e) {\n            LOGGER.warn(\"Failed to register driver\", e);\n        }\n    }\n\n    @Override\n    public boolean acceptsURL(String url) throws SQLException {\n        return url.startsWith(\"jdbc:tc:\");\n    }\n\n    @Override\n    public synchronized Connection connect(String url, final Properties info) throws SQLException {\n        /*\n          The driver should return \"null\" if it realizes it is the wrong kind of driver to connect to the given URL.\n         */\n        if (!acceptsURL(url)) {\n            return null;\n        }\n\n        ConnectionUrl connectionUrl = ConnectionUrl.newInstance(url);\n\n        synchronized (jdbcUrlContainerCache) {\n            String queryString = connectionUrl.getQueryString().orElse(\"\");\n            /*\n              If we already have a running container for this exact connection string, we want to connect\n              to that rather than create a new container\n             */\n            JdbcDatabaseContainer container = jdbcUrlContainerCache.get(connectionUrl.getUrl());\n            if (container == null) {\n                LOGGER.debug(\"Container not found in cache, creating new instance\");\n\n                Map<String, String> parameters = connectionUrl.getContainerParameters();\n\n                /*\n                  Find a matching container type using ServiceLoader.\n                 */\n                ServiceLoader<JdbcDatabaseContainerProvider> databaseContainers = ServiceLoader.load(\n                    JdbcDatabaseContainerProvider.class\n                );\n                for (JdbcDatabaseContainerProvider candidateContainerType : databaseContainers) {\n                    if (candidateContainerType.supports(connectionUrl.getDatabaseType())) {\n                        container = candidateContainerType.newInstance(connectionUrl);\n                        container.withTmpFs(connectionUrl.getTmpfsOptions());\n                        delegate = container.getJdbcDriverInstance();\n                    }\n                }\n                if (container == null) {\n                    throw new UnsupportedOperationException(\n                        \"Database name \" + connectionUrl.getDatabaseType() + \" not supported\"\n                    );\n                }\n\n                /*\n                  Cache the container before starting to prevent race conditions when a connection\n                  pool is started up\n                 */\n                jdbcUrlContainerCache.put(url, container);\n\n                /*\n                  Pass possible container-specific parameters\n                 */\n                container.setParameters(parameters);\n\n                /*\n                  Start the container\n                 */\n                container.start();\n            }\n\n            /*\n              Create a connection using the delegated driver. The container must be ready to accept connections.\n             */\n            Connection connection = container.createConnection(queryString, info);\n\n            /*\n              If this container has not been initialized, AND\n              an init script or function has been specified, use it\n             */\n            if (!initializedContainers.contains(container.getContainerId())) {\n                DatabaseDelegate databaseDelegate = new JdbcDatabaseDelegate(container, queryString);\n                runInitScriptIfRequired(connectionUrl, databaseDelegate);\n                runInitFunctionIfRequired(connectionUrl, connection);\n                initializedContainers.add(container.getContainerId());\n            }\n\n            return wrapConnection(connection, container, connectionUrl);\n        }\n    }\n\n    /**\n     * Wrap the connection, setting up a callback to be called when the connection is closed.\n     * <p>\n     * When there are no more open connections, the container itself will be stopped.\n     *\n     * @param connection    the new connection to be wrapped\n     * @param container     the container which the connection is associated with\n     * @param connectionUrl {@link ConnectionUrl} instance representing JDBC Url for this connection\n     * @return the connection, wrapped\n     */\n    private Connection wrapConnection(\n        final Connection connection,\n        final JdbcDatabaseContainer container,\n        final ConnectionUrl connectionUrl\n    ) {\n        final boolean isDaemon = connectionUrl.isInDaemonMode() || connectionUrl.isReusable();\n\n        Set<Connection> connections = containerConnections.computeIfAbsent(\n            container.getContainerId(),\n            k -> new HashSet<>()\n        );\n\n        connections.add(connection);\n\n        final Set<Connection> finalConnections = connections;\n\n        return new ConnectionWrapper(\n            connection,\n            () -> {\n                finalConnections.remove(connection);\n                if (!isDaemon && finalConnections.isEmpty()) {\n                    synchronized (jdbcUrlContainerCache) {\n                        container.stop();\n                        jdbcUrlContainerCache.remove(connectionUrl.getUrl());\n                    }\n                }\n            }\n        );\n    }\n\n    /**\n     * Run an init script from the classpath.\n     *\n     * @param connectionUrl    {@link ConnectionUrl} instance representing JDBC Url with init script.\n     * @param databaseDelegate database delegate to apply init scripts to the database\n     * @throws SQLException on script or DB error\n     */\n    private void runInitScriptIfRequired(final ConnectionUrl connectionUrl, DatabaseDelegate databaseDelegate)\n        throws SQLException {\n        if (connectionUrl.getInitScriptPath().isPresent()) {\n            String initScriptPath = connectionUrl.getInitScriptPath().get();\n            try {\n                URL resource;\n                if (initScriptPath.startsWith(FILE_PATH_PREFIX)) {\n                    //relative workdir path\n                    resource = new URL(initScriptPath);\n                } else {\n                    //classpath resource\n                    resource = Thread.currentThread().getContextClassLoader().getResource(initScriptPath);\n                }\n                if (resource == null) {\n                    LOGGER.warn(\"Could not load classpath init script: {}\", initScriptPath);\n                    throw new SQLException(\n                        \"Could not load classpath init script: \" + initScriptPath + \". Resource not found.\"\n                    );\n                }\n\n                String sql = IOUtils.toString(resource, StandardCharsets.UTF_8);\n                ScriptUtils.executeDatabaseScript(databaseDelegate, initScriptPath, sql);\n            } catch (IOException e) {\n                LOGGER.warn(\"Could not load classpath init script: {}\", initScriptPath);\n                throw new SQLException(\"Could not load classpath init script: \" + initScriptPath, e);\n            } catch (ScriptException e) {\n                LOGGER.error(\"Error while executing init script: {}\", initScriptPath, e);\n                throw new SQLException(\"Error while executing init script: \" + initScriptPath, e);\n            }\n        }\n    }\n\n    /**\n     * Run an init function (must be a public static method on an accessible class).\n     *\n     * @param connectionUrl {@link ConnectionUrl} instance representing JDBC Url with r init function declarations.\n     * @param connection    JDBC connection to apply init functions to.\n     * @throws SQLException on script or DB error\n     */\n    private void runInitFunctionIfRequired(final ConnectionUrl connectionUrl, Connection connection)\n        throws SQLException {\n        if (connectionUrl.getInitFunction().isPresent()) {\n            String className = connectionUrl.getInitFunction().get().getClassName();\n            String methodName = connectionUrl.getInitFunction().get().getMethodName();\n\n            try {\n                Class<?> initFunctionClazz = Class.forName(className);\n                Method method = initFunctionClazz.getMethod(methodName, Connection.class);\n\n                method.invoke(null, connection);\n            } catch (\n                ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e\n            ) {\n                LOGGER.error(\"Error while executing init function: {}::{}\", className, methodName, e);\n                throw new SQLException(\"Error while executing init function: \" + className + \"::\" + methodName, e);\n            }\n        }\n    }\n\n    @Override\n    public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException {\n        return delegate != null ? delegate.getPropertyInfo(url, info) : new DriverPropertyInfo[0];\n    }\n\n    @Override\n    public int getMajorVersion() {\n        return delegate != null ? delegate.getMajorVersion() : 1;\n    }\n\n    @Override\n    public int getMinorVersion() {\n        return delegate != null ? delegate.getMinorVersion() : 0;\n    }\n\n    @Override\n    public boolean jdbcCompliant() {\n        return delegate != null && delegate.jdbcCompliant();\n    }\n\n    @Override\n    public Logger getParentLogger() throws SQLFeatureNotSupportedException {\n        if (delegate != null) {\n            return delegate.getParentLogger();\n        }\n        throw new SQLFeatureNotSupportedException(\"getParentLogger not supported\");\n    }\n\n    /**\n     * Utility method to kill ALL database containers directly from test support code. It shouldn't be necessary to use this,\n     * but it is provided for convenience - e.g. for situations where many different database containers are being\n     * tested and cleanup is needed to limit resource usage.\n     */\n    public static void killContainers() {\n        synchronized (jdbcUrlContainerCache) {\n            jdbcUrlContainerCache.values().forEach(JdbcDatabaseContainer::stop);\n            jdbcUrlContainerCache.clear();\n            containerConnections.clear();\n            initializedContainers.clear();\n        }\n    }\n\n    /**\n     * Utility method to kill a database container directly from test support code. It shouldn't be necessary to use this,\n     * but it is provided for convenience - e.g. for situations where many different database containers are being\n     * tested and cleanup is needed to limit resource usage.\n     *\n     * @param jdbcUrl the JDBC URL of the container which should be killed\n     */\n    public static void killContainer(String jdbcUrl) {\n        synchronized (jdbcUrlContainerCache) {\n            JdbcDatabaseContainer container = jdbcUrlContainerCache.get(jdbcUrl);\n            if (container != null) {\n                container.stop();\n                jdbcUrlContainerCache.remove(jdbcUrl);\n                containerConnections.remove(container.getContainerId());\n                initializedContainers.remove(container.getContainerId());\n            }\n        }\n    }\n\n    /**\n     * Utility method to get an instance of a database container given its JDBC URL.\n     *\n     * @param jdbcUrl the JDBC URL of the container instance to get\n     * @return an instance of database container or <code>null</code> if no container associated with JDBC URL\n     */\n    static JdbcDatabaseContainer getContainer(String jdbcUrl) {\n        synchronized (jdbcUrlContainerCache) {\n            return jdbcUrlContainerCache.get(jdbcUrl);\n        }\n    }\n}\n"
  },
  {
    "path": "modules/jdbc/src/main/java/org/testcontainers/jdbc/ContainerLessJdbcDelegate.java",
    "content": "package org.testcontainers.jdbc;\n\nimport lombok.extern.slf4j.Slf4j;\nimport org.testcontainers.exception.ConnectionCreationException;\n\nimport java.sql.Connection;\nimport java.sql.SQLException;\nimport java.sql.Statement;\n\n/**\n * Containerless jdbc database delegate\n *\n * Is used only with deprecated ScriptUtils\n *\n * @see org.testcontainers.ext.ScriptUtils\n */\n@Slf4j\npublic class ContainerLessJdbcDelegate extends JdbcDatabaseDelegate {\n\n    private Connection connection;\n\n    public ContainerLessJdbcDelegate(Connection connection) {\n        super(null, \"\");\n        this.connection = connection;\n    }\n\n    @Override\n    protected Statement createNewConnection() {\n        try {\n            return connection.createStatement();\n        } catch (SQLException e) {\n            log.error(\"Could create JDBC statement\");\n            throw new ConnectionCreationException(\"Could create JDBC statement\", e);\n        }\n    }\n}\n"
  },
  {
    "path": "modules/jdbc/src/main/java/org/testcontainers/jdbc/JdbcDatabaseDelegate.java",
    "content": "package org.testcontainers.jdbc;\n\nimport lombok.extern.slf4j.Slf4j;\nimport org.testcontainers.containers.JdbcDatabaseContainer;\nimport org.testcontainers.delegate.AbstractDatabaseDelegate;\nimport org.testcontainers.exception.ConnectionCreationException;\nimport org.testcontainers.ext.ScriptUtils;\n\nimport java.sql.Connection;\nimport java.sql.SQLException;\nimport java.sql.Statement;\n\n/**\n * JDBC database delegate\n */\n@Slf4j\npublic class JdbcDatabaseDelegate extends AbstractDatabaseDelegate<Statement> {\n\n    private JdbcDatabaseContainer container;\n\n    private Connection connection;\n\n    private String queryString;\n\n    public JdbcDatabaseDelegate(JdbcDatabaseContainer container, String queryString) {\n        this.container = container;\n        this.queryString = queryString;\n    }\n\n    @Override\n    protected Statement createNewConnection() {\n        try {\n            connection = container.createConnection(queryString);\n            return connection.createStatement();\n        } catch (SQLException e) {\n            log.error(\"Could not obtain JDBC connection\");\n            throw new ConnectionCreationException(\"Could not obtain JDBC connection\", e);\n        }\n    }\n\n    @Override\n    public void execute(\n        String statement,\n        String scriptPath,\n        int lineNumber,\n        boolean continueOnError,\n        boolean ignoreFailedDrops\n    ) {\n        try {\n            boolean rowsAffected = getConnection().execute(statement);\n            log.debug(\"{} returned as updateCount for SQL: {}\", rowsAffected, statement);\n        } catch (SQLException ex) {\n            boolean dropStatement = statement.trim().toLowerCase().startsWith(\"drop\");\n            if (continueOnError || (dropStatement && ignoreFailedDrops)) {\n                log.debug(\n                    \"Failed to execute SQL script statement at line {} of resource {}: {}\",\n                    lineNumber,\n                    scriptPath,\n                    statement,\n                    ex\n                );\n            } else {\n                throw new ScriptUtils.ScriptStatementFailedException(statement, lineNumber, scriptPath, ex);\n            }\n        }\n    }\n\n    @Override\n    protected void closeConnectionQuietly(Statement statement) {\n        try {\n            statement.close();\n            connection.close();\n        } catch (Exception e) {\n            log.error(\"Could not close JDBC connection\", e);\n        }\n    }\n}\n"
  },
  {
    "path": "modules/jdbc/src/main/java/org/testcontainers/jdbc/ext/ScriptUtils.java",
    "content": "/*\n * Copyright 2002-2014 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.testcontainers.jdbc.ext;\n\nimport org.testcontainers.jdbc.ContainerLessJdbcDelegate;\n\nimport java.sql.Connection;\nimport java.util.List;\n\nimport javax.script.ScriptException;\n\n/**\n * Wrapper for database-agnostic ScriptUtils\n *\n * @see org.testcontainers.ext.ScriptUtils\n * @deprecated Needed only to keep binary compatibility for this internal API. Consider using database-agnostic ScriptUtils\n */\npublic abstract class ScriptUtils {\n\n    /**\n     * Default statement separator within SQL scripts.\n     */\n    public static final String DEFAULT_STATEMENT_SEPARATOR = \";\";\n\n    /**\n     * Fallback statement separator within SQL scripts.\n     * <p>Used if neither a custom defined separator nor the\n     * {@link #DEFAULT_STATEMENT_SEPARATOR} is present in a given script.\n     */\n    public static final String FALLBACK_STATEMENT_SEPARATOR = \"\\n\";\n\n    /**\n     * Default prefix for line comments within SQL scripts.\n     */\n    public static final String DEFAULT_COMMENT_PREFIX = \"--\";\n\n    /**\n     * Default start delimiter for block comments within SQL scripts.\n     */\n    public static final String DEFAULT_BLOCK_COMMENT_START_DELIMITER = \"/*\";\n\n    /**\n     * Default end delimiter for block comments within SQL scripts.\n     */\n    public static final String DEFAULT_BLOCK_COMMENT_END_DELIMITER = \"*/\";\n\n    /**\n     * Prevent instantiation of this utility class.\n     */\n    private ScriptUtils() {\n        /* no-op */\n    }\n\n    /**\n     * @see org.testcontainers.ext.ScriptUtils\n     * @deprecated Needed only to keep binary compatibility for this internal API. Consider using database-agnostic ScriptUtils\n     */\n    public static void splitSqlScript(\n        String resource,\n        String script,\n        String separator,\n        String commentPrefix,\n        String blockCommentStartDelimiter,\n        String blockCommentEndDelimiter,\n        List<String> statements\n    ) {\n        org.testcontainers.ext.ScriptUtils.splitSqlScript(\n            resource,\n            script,\n            separator,\n            commentPrefix,\n            blockCommentStartDelimiter,\n            blockCommentEndDelimiter,\n            statements\n        );\n    }\n\n    /**\n     * @see org.testcontainers.ext.ScriptUtils\n     * @deprecated Needed only to keep binary compatibility for this internal API. Consider using database-agnostic ScriptUtils\n     */\n    public static boolean containsSqlScriptDelimiters(String script, String delim) {\n        return org.testcontainers.ext.ScriptUtils.containsSqlScriptDelimiters(script, delim);\n    }\n\n    /**\n     * @see org.testcontainers.ext.ScriptUtils\n     * @deprecated Needed only to keep binary compatibility for this internal API. Consider using database-agnostic ScriptUtils\n     */\n    public static void executeSqlScript(Connection connection, String scriptPath, String script)\n        throws ScriptException {\n        org.testcontainers.ext.ScriptUtils.executeDatabaseScript(\n            new ContainerLessJdbcDelegate(connection),\n            scriptPath,\n            script\n        );\n    }\n\n    /**\n     * @see org.testcontainers.ext.ScriptUtils\n     * @deprecated Needed only to keep binary compatibility for this internal API. Consider using database-agnostic ScriptUtils\n     */\n    public static void executeSqlScript(\n        Connection connection,\n        String scriptPath,\n        String script,\n        boolean continueOnError,\n        boolean ignoreFailedDrops,\n        String commentPrefix,\n        String separator,\n        String blockCommentStartDelimiter,\n        String blockCommentEndDelimiter\n    ) throws ScriptException {\n        org.testcontainers.ext.ScriptUtils.executeDatabaseScript(\n            new ContainerLessJdbcDelegate(connection),\n            scriptPath,\n            script,\n            continueOnError,\n            ignoreFailedDrops,\n            commentPrefix,\n            separator,\n            blockCommentStartDelimiter,\n            blockCommentEndDelimiter\n        );\n    }\n}\n"
  },
  {
    "path": "modules/jdbc/src/main/resources/META-INF/services/java.sql.Driver",
    "content": "org.testcontainers.jdbc.ContainerDatabaseDriver"
  },
  {
    "path": "modules/jdbc/src/test/java/org/testcontainers/containers/JdbcDatabaseContainerTest.java",
    "content": "package org.testcontainers.containers;\n\nimport lombok.NonNull;\nimport org.junit.jupiter.api.Test;\nimport org.slf4j.Logger;\n\nimport java.sql.Connection;\nimport java.sql.SQLException;\n\nimport static org.assertj.core.api.Assertions.assertThatExceptionOfType;\nimport static org.mockito.Mockito.mock;\n\nclass JdbcDatabaseContainerTest {\n\n    @Test\n    void anExceptionIsThrownIfJdbcIsNotAvailable() {\n        JdbcDatabaseContainer<?> jdbcContainer = new JdbcDatabaseContainerStub(\"mysql:latest\")\n            .withStartupTimeoutSeconds(1);\n\n        assertThatExceptionOfType(IllegalStateException.class).isThrownBy(jdbcContainer::waitUntilContainerStarted);\n    }\n\n    static class JdbcDatabaseContainerStub extends JdbcDatabaseContainer {\n\n        public JdbcDatabaseContainerStub(@NonNull String dockerImageName) {\n            super(dockerImageName);\n        }\n\n        @Override\n        public String getDriverClassName() {\n            return null;\n        }\n\n        @Override\n        public String getJdbcUrl() {\n            return null;\n        }\n\n        @Override\n        public String getUsername() {\n            return null;\n        }\n\n        @Override\n        public String getPassword() {\n            return null;\n        }\n\n        @Override\n        protected String getTestQueryString() {\n            return null;\n        }\n\n        @Override\n        public boolean isRunning() {\n            return true;\n        }\n\n        @Override\n        public Connection createConnection(String queryString) throws SQLException, NoDriverFoundException {\n            throw new SQLException(\"Could not create new connection\");\n        }\n\n        @Override\n        protected Logger logger() {\n            return mock(Logger.class);\n        }\n\n        @Override\n        public void setDockerImageName(@NonNull String dockerImageName) {}\n    }\n}\n"
  },
  {
    "path": "modules/jdbc/src/test/java/org/testcontainers/jdbc/ConnectionUrlDriversTests.java",
    "content": "package org.testcontainers.jdbc;\n\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport java.util.Optional;\nimport java.util.stream.Stream;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * This Test class validates that all supported JDBC URL's can be parsed by ConnectionUrl class.\n */\nclass ConnectionUrlDriversTests {\n\n    public static Stream<Arguments> data() {\n        return Stream.of(\n            Arguments.arguments(\n                \"jdbc:tc:mysql:8.0.36://hostname/test\",\n                \"mysql\",\n                Optional.of(\"8.0.36\"),\n                \"hostname/test\",\n                \"test\"\n            ),\n            Arguments.arguments(\"jdbc:tc:mysql://hostname/test\", \"mysql\", Optional.empty(), \"hostname/test\", \"test\"),\n            Arguments.arguments(\n                \"jdbc:tc:postgresql:1.2.3://hostname/test\",\n                \"postgresql\",\n                Optional.of(\"1.2.3\"),\n                \"hostname/test\",\n                \"test\"\n            ),\n            Arguments.arguments(\n                \"jdbc:tc:postgresql://hostname/test\",\n                \"postgresql\",\n                Optional.empty(),\n                \"hostname/test\",\n                \"test\"\n            ),\n            Arguments.arguments(\n                \"jdbc:tc:sqlserver:1.2.3://localhost;instance=SQLEXPRESS:1433;databaseName=test\",\n                \"sqlserver\",\n                Optional.of(\"1.2.3\"),\n                \"localhost;instance=SQLEXPRESS:1433;databaseName=test\",\n                \"test\"\n            ),\n            Arguments.arguments(\n                \"jdbc:tc:sqlserver://localhost;instance=SQLEXPRESS:1433;databaseName=test\",\n                \"sqlserver\",\n                Optional.empty(),\n                \"localhost;instance=SQLEXPRESS:1433;databaseName=test\",\n                \"test\"\n            ),\n            Arguments.arguments(\n                \"jdbc:tc:mariadb:1.2.3://localhost:3306/test\",\n                \"mariadb\",\n                Optional.of(\"1.2.3\"),\n                \"localhost:3306/test\",\n                \"test\"\n            ),\n            Arguments.arguments(\n                \"jdbc:tc:mariadb://localhost:3306/test\",\n                \"mariadb\",\n                Optional.empty(),\n                \"localhost:3306/test\",\n                \"test\"\n            ),\n            Arguments.arguments(\n                \"jdbc:tc:oracle:1.2.3:thin://@localhost:1521/test\",\n                \"oracle\",\n                Optional.of(\"1.2.3\"),\n                \"localhost:1521/test\",\n                \"test\"\n            ),\n            Arguments.arguments(\n                \"jdbc:tc:oracle:1.2.3:thin:@localhost:1521/test\",\n                \"oracle\",\n                Optional.of(\"1.2.3\"),\n                \"localhost:1521/test\",\n                \"test\"\n            ),\n            Arguments.arguments(\n                \"jdbc:tc:oracle:thin:@localhost:1521/test\",\n                \"oracle\",\n                Optional.empty(),\n                \"localhost:1521/test\",\n                \"test\"\n            ),\n            Arguments.arguments(\n                \"jdbc:tc:oracle:1.2.3:thin:@localhost:1521:test\",\n                \"oracle\",\n                Optional.of(\"1.2.3\"),\n                \"localhost:1521:test\",\n                \"test\"\n            ),\n            Arguments.arguments(\n                \"jdbc:tc:oracle:1.2.3:thin://@localhost:1521:test\",\n                \"oracle\",\n                Optional.of(\"1.2.3\"),\n                \"localhost:1521:test\",\n                \"test\"\n            ),\n            Arguments.arguments(\n                \"jdbc:tc:oracle:1.2.3-anything:thin://@localhost:1521:test\",\n                \"oracle\",\n                Optional.of(\"1.2.3-anything\"),\n                \"localhost:1521:test\",\n                \"test\"\n            ),\n            Arguments.arguments(\n                \"jdbc:tc:oracle:thin:@localhost:1521:test\",\n                \"oracle\",\n                Optional.empty(),\n                \"localhost:1521:test\",\n                \"test\"\n            )\n        );\n    }\n\n    @ParameterizedTest(name = \"{index} - {0}\")\n    @MethodSource(\"data\")\n    void test(String jdbcUrl, String databaseType, Optional<String> tag, String dbHostString, String databaseName) {\n        ConnectionUrl url = ConnectionUrl.newInstance(jdbcUrl);\n        assertThat(url.getDatabaseType()).as(\"Database Type is as expected\").isEqualTo(databaseType);\n        assertThat(url.getImageTag()).as(\"Image tag is as expected\").isEqualTo(tag);\n        assertThat(url.getDbHostString()).as(\"Database Host String is as expected\").isEqualTo(dbHostString);\n        assertThat(url.getDatabaseName().orElse(\"\")).as(\"Database Name is as expected\").isEqualTo(databaseName);\n    }\n}\n"
  },
  {
    "path": "modules/jdbc/src/test/java/org/testcontainers/jdbc/ConnectionUrlTest.java",
    "content": "package org.testcontainers.jdbc;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\n\nclass ConnectionUrlTest {\n\n    @Test\n    void testConnectionUrl1() {\n        String urlString = \"jdbc:tc:mysql:8.0.36://somehostname:3306/databasename?a=b&c=d\";\n        ConnectionUrl url = ConnectionUrl.newInstance(urlString);\n\n        assertThat(url.getDatabaseType()).as(\"Database Type value is as expected\").isEqualTo(\"mysql\");\n        assertThat(url.getImageTag()).as(\"Database Image tag value is as expected\").contains(\"8.0.36\");\n        assertThat(url.getDbHostString())\n            .as(\"Database Host String is as expected\")\n            .isEqualTo(\"somehostname:3306/databasename\");\n        assertThat(url.getQueryString()).as(\"Query String value is as expected\").contains(\"?a=b&c=d\");\n        assertThat(url.getDatabaseHost()).as(\"Database Host value is as expected\").contains(\"somehostname\");\n        assertThat(url.getDatabasePort()).as(\"Database Port value is as expected\").contains(3306);\n        assertThat(url.getDatabaseName()).as(\"Database Name value is as expected\").contains(\"databasename\");\n\n        assertThat(url.getQueryParameters()).as(\"Parameter a is captured\").containsEntry(\"a\", \"b\");\n        assertThat(url.getQueryParameters()).as(\"Parameter c is captured\").containsEntry(\"c\", \"d\");\n    }\n\n    @Test\n    void testConnectionUrl2() {\n        String urlString = \"jdbc:tc:mysql://somehostname/databasename\";\n        ConnectionUrl url = ConnectionUrl.newInstance(urlString);\n\n        assertThat(url.getDatabaseType()).as(\"Database Type value is as expected\").isEqualTo(\"mysql\");\n        assertThat(url.getImageTag()).as(\"Database Image tag value is as expected\").isNotPresent();\n        assertThat(url.getDbHostString())\n            .as(\"Database Host String is as expected\")\n            .isEqualTo(\"somehostname/databasename\");\n        assertThat(url.getQueryString()).as(\"Query String value is as expected\").isEmpty();\n        assertThat(url.getDatabaseHost()).as(\"Database Host value is as expected\").contains(\"somehostname\");\n        assertThat(url.getDatabasePort()).as(\"Database Port is null as expected\").isNotPresent();\n        assertThat(url.getDatabaseName()).as(\"Database Name value is as expected\").contains(\"databasename\");\n\n        assertThat(url.getQueryParameters().isEmpty()).as(\"Connection Parameters set is empty\").isTrue();\n    }\n\n    @Test\n    void testEmptyQueryParameter() {\n        ConnectionUrl url = ConnectionUrl.newInstance(\"jdbc:tc:mysql://somehostname/databasename?key=\");\n        assertThat(url.getQueryParameters().get(\"key\")).as(\"'key' property value\").isEqualTo(\"\");\n    }\n\n    @Test\n    void testTmpfsOption() {\n        String urlString = \"jdbc:tc:mysql://somehostname/databasename?TC_TMPFS=key:value,key1:value1\";\n        ConnectionUrl url = ConnectionUrl.newInstance(urlString);\n\n        assertThat(url.getQueryParameters()).as(\"Connection Parameters set is empty\").isEmpty();\n        assertThat(url.getContainerParameters()).as(\"Container Parameters set is not empty\").isNotEmpty();\n        assertThat(url.getContainerParameters())\n            .as(\"Container Parameter TC_TMPFS is true\")\n            .containsEntry(\"TC_TMPFS\", \"key:value,key1:value1\");\n        assertThat(url.getTmpfsOptions()).as(\"tmpfs option key has correct value\").containsEntry(\"key\", \"value\");\n        assertThat(url.getTmpfsOptions()).as(\"tmpfs option key1 has correct value\").containsEntry(\"key1\", \"value1\");\n    }\n\n    @Test\n    void testInitScriptPathCapture() {\n        String urlString =\n            \"jdbc:tc:mysql:8.0.36://somehostname:3306/databasename?a=b&c=d&TC_INITSCRIPT=somepath/init_mysql.sql\";\n        ConnectionUrl url = ConnectionUrl.newInstance(urlString);\n\n        assertThat(url.getInitScriptPath())\n            .as(\"Database Type value is as expected\")\n            .contains(\"somepath/init_mysql.sql\");\n        assertThat(url.getQueryString()).as(\"Query String value is as expected\").contains(\"?a=b&c=d\");\n        assertThat(url.getContainerParameters())\n            .as(\"INIT SCRIPT Path exists in Container Parameters\")\n            .containsEntry(\"TC_INITSCRIPT\", \"somepath/init_mysql.sql\");\n\n        //Parameter sets are unmodifiable\n        assertThatThrownBy(() -> url.getContainerParameters().remove(\"TC_INITSCRIPT\"))\n            .isInstanceOf(UnsupportedOperationException.class);\n        assertThatThrownBy(() -> url.getQueryParameters().remove(\"a\"))\n            .isInstanceOf(UnsupportedOperationException.class);\n    }\n\n    @Test\n    void testInitFunctionCapture() {\n        String urlString =\n            \"jdbc:tc:mysql:8.0.36://somehostname:3306/databasename?a=b&c=d&TC_INITFUNCTION=org.testcontainers.jdbc.JDBCDriverTest::sampleInitFunction\";\n        ConnectionUrl url = ConnectionUrl.newInstance(urlString);\n\n        assertThat(url.getInitFunction()).as(\"Init Function parameter exists\").isPresent();\n\n        assertThat(url.getInitFunction().get().getClassName())\n            .as(\"Init function class is as expected\")\n            .isEqualTo(\"org.testcontainers.jdbc.JDBCDriverTest\");\n        assertThat(url.getInitFunction().get().getMethodName())\n            .as(\"Init function class is as expected\")\n            .isEqualTo(\"sampleInitFunction\");\n    }\n\n    @Test\n    void testDaemonCapture() {\n        String urlString = \"jdbc:tc:mysql:8.0.36://somehostname:3306/databasename?a=b&c=d&TC_DAEMON=true\";\n        ConnectionUrl url = ConnectionUrl.newInstance(urlString);\n\n        assertThat(url.isInDaemonMode()).as(\"Daemon flag is set to true.\").isTrue();\n    }\n\n    @Test\n    void testHostLessConnectionUrl() {\n        String urlString = \"jdbc:tc:mysql:8.0.36:///databasename?a=b&c=d\";\n        ConnectionUrl url = ConnectionUrl.newInstance(urlString);\n\n        assertThat(url.getDatabaseType()).as(\"Database Type value is as expected\").isEqualTo(\"mysql\");\n        assertThat(url.getImageTag()).as(\"Database Image tag value is as expected\").contains(\"8.0.36\");\n        assertThat(url.getQueryString()).as(\"Query String value is as expected\").contains(\"?a=b&c=d\");\n        assertThat(url.getDatabaseHost()).as(\"Database Host value is as expected\").isEmpty();\n        assertThat(url.getDatabasePort()).as(\"Database Port value is as expected\").isEmpty();\n        assertThat(url.getDatabaseName()).as(\"Database Name value is as expected\").contains(\"databasename\");\n\n        assertThat(url.getQueryParameters()).as(\"Parameter a is captured\").containsEntry(\"a\", \"b\");\n        assertThat(url.getQueryParameters()).as(\"Parameter c is captured\").containsEntry(\"c\", \"d\");\n    }\n}\n"
  },
  {
    "path": "modules/jdbc/src/test/java/org/testcontainers/jdbc/ContainerDatabaseDriverTest.java",
    "content": "package org.testcontainers.jdbc;\n\nimport org.junit.jupiter.api.Test;\n\nimport java.sql.Connection;\nimport java.sql.DriverManager;\nimport java.sql.SQLException;\nimport java.util.Properties;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\n\nclass ContainerDatabaseDriverTest {\n\n    private static final String PLAIN_POSTGRESQL_JDBC_URL = \"jdbc:postgresql://localhost:5432/test\";\n\n    @Test\n    void shouldNotTryToConnectToNonMatchingJdbcUrlDirectly() throws SQLException {\n        ContainerDatabaseDriver driver = new ContainerDatabaseDriver();\n        Connection connection = driver.connect(PLAIN_POSTGRESQL_JDBC_URL, new Properties());\n        assertThat(connection).isNull();\n    }\n\n    @Test\n    void shouldNotTryToConnectToNonMatchingJdbcUrlViaDriverManager() throws SQLException {\n        assertThatThrownBy(() -> DriverManager.getConnection(PLAIN_POSTGRESQL_JDBC_URL))\n            .isInstanceOf(SQLException.class)\n            .hasMessageStartingWith(\"No suitable driver found for \");\n    }\n}\n"
  },
  {
    "path": "modules/jdbc/src/test/java/org/testcontainers/jdbc/JdbcDatabaseDelegateTest.java",
    "content": "package org.testcontainers.jdbc;\n\nimport lombok.NonNull;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.Mockito;\nimport org.slf4j.Logger;\nimport org.testcontainers.containers.JdbcDatabaseContainer;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.sql.Connection;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nclass JdbcDatabaseDelegateTest {\n\n    @Test\n    void testLeakedConnections() {\n        final JdbcDatabaseContainerStub stub = new JdbcDatabaseContainerStub(DockerImageName.parse(\"something\"));\n        try (JdbcDatabaseDelegate delegate = new JdbcDatabaseDelegate(stub, \"\")) {\n            delegate.execute(\"foo\", null, 0, false, false);\n        }\n        assertThat(stub.openConnectionsList.size()).isZero();\n    }\n\n    static class JdbcDatabaseContainerStub extends JdbcDatabaseContainer {\n\n        List<Connection> openConnectionsList = new ArrayList<>();\n\n        public JdbcDatabaseContainerStub(@NonNull DockerImageName dockerImageName) {\n            super(dockerImageName);\n        }\n\n        @Override\n        public String getDriverClassName() {\n            return null;\n        }\n\n        @Override\n        public String getJdbcUrl() {\n            return null;\n        }\n\n        @Override\n        public String getUsername() {\n            return null;\n        }\n\n        @Override\n        public String getPassword() {\n            return null;\n        }\n\n        @Override\n        protected String getTestQueryString() {\n            return null;\n        }\n\n        @Override\n        public boolean isRunning() {\n            return true;\n        }\n\n        @Override\n        public Connection createConnection(String queryString) throws NoDriverFoundException, SQLException {\n            final Connection connection = mock(Connection.class);\n            openConnectionsList.add(connection);\n            when(connection.createStatement()).thenReturn(mock(Statement.class));\n            connection.close();\n            Mockito.doAnswer(ignore -> openConnectionsList.remove(connection)).when(connection).close();\n            return connection;\n        }\n\n        @Override\n        protected Logger logger() {\n            return mock(Logger.class);\n        }\n\n        @Override\n        public void setDockerImageName(@NonNull String dockerImageName) {}\n    }\n}\n"
  },
  {
    "path": "modules/jdbc/src/test/java/org/testcontainers/jdbc/MissingJdbcDriverTest.java",
    "content": "package org.testcontainers.jdbc;\n\nimport com.google.common.base.Throwables;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.JdbcDatabaseContainer;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.sql.Connection;\nimport java.sql.SQLException;\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.fail;\n\nclass MissingJdbcDriverTest {\n\n    @Test\n    void shouldFailFastIfNoDriverFound() {\n        final MissingDriverContainer container = new MissingDriverContainer();\n\n        try {\n            container.start();\n            fail(\"The container is expected to fail to start\");\n        } catch (Exception e) {\n            final Throwable rootCause = Throwables.getRootCause(e);\n            assertThat(rootCause)\n                .as(\"ClassNotFoundException is the root cause\")\n                .isInstanceOf(ClassNotFoundException.class);\n        } finally {\n            container.stop();\n        }\n\n        assertThat(container.getConnectionAttempts())\n            .as(\"only one connection attempt should have been made\")\n            .isEqualTo(1);\n    }\n\n    /**\n     * Container class for the purposes of testing, with a known non-existent driver\n     */\n    static class MissingDriverContainer extends JdbcDatabaseContainer {\n\n        private final AtomicInteger connectionAttempts = new AtomicInteger();\n\n        MissingDriverContainer() {\n            super(DockerImageName.parse(\"mysql:8.0.36\"));\n            withEnv(\"MYSQL_ROOT_PASSWORD\", \"test\");\n            withExposedPorts(3306);\n        }\n\n        @Override\n        public String getDriverClassName() {\n            return \"nonexistent.ClassName\";\n        }\n\n        @Override\n        public String getJdbcUrl() {\n            return \"\";\n        }\n\n        @Override\n        public String getUsername() {\n            return \"root\";\n        }\n\n        @Override\n        public String getPassword() {\n            return \"test\";\n        }\n\n        @Override\n        protected String getTestQueryString() {\n            return \"\";\n        }\n\n        @Override\n        public Connection createConnection(String queryString) throws SQLException, NoDriverFoundException {\n            connectionAttempts.incrementAndGet(); //\n            return super.createConnection(queryString);\n        }\n\n        /**\n         * test window\n         * @return how many times a connection was attempted\n         */\n        int getConnectionAttempts() {\n            return connectionAttempts.get();\n        }\n    }\n}\n"
  },
  {
    "path": "modules/jdbc/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/jdbc-test/build.gradle",
    "content": "dependencies {\n    api project(':testcontainers-jdbc')\n\n    api 'com.google.guava:guava:33.5.0-jre'\n    api 'org.apache.commons:commons-lang3:3.20.0'\n    api 'com.zaxxer:HikariCP-java6:2.3.13'\n    api 'commons-dbutils:commons-dbutils:1.8.1'\n\n    api 'org.assertj:assertj-core:3.27.6'\n\n    api 'org.apache.tomcat:tomcat-jdbc:11.0.14'\n    api 'org.vibur:vibur-dbcp:26.0'\n    api 'com.mysql:mysql-connector-j:9.5.0'\n    api 'org.junit.jupiter:junit-jupiter:5.14.1'\n}\n"
  },
  {
    "path": "modules/jdbc-test/sql/init_mysql.sql",
    "content": "CREATE TABLE bar (\n  foo VARCHAR(255)\n);\n\nINSERT INTO bar (foo) VALUES ('hello world');"
  },
  {
    "path": "modules/jdbc-test/src/main/java/org/testcontainers/db/AbstractContainerDatabaseTest.java",
    "content": "package org.testcontainers.db;\n\nimport com.zaxxer.hikari.HikariConfig;\nimport com.zaxxer.hikari.HikariDataSource;\nimport org.testcontainers.containers.JdbcDatabaseContainer;\n\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\n\nimport javax.sql.DataSource;\n\npublic abstract class AbstractContainerDatabaseTest {\n\n    protected ResultSet performQuery(JdbcDatabaseContainer<?> container, String sql) throws SQLException {\n        DataSource ds = getDataSource(container);\n        Statement statement = ds.getConnection().createStatement();\n        statement.execute(sql);\n        ResultSet resultSet = statement.getResultSet();\n\n        resultSet.next();\n        return resultSet;\n    }\n\n    protected DataSource getDataSource(JdbcDatabaseContainer<?> container) {\n        HikariConfig hikariConfig = new HikariConfig();\n        hikariConfig.setJdbcUrl(container.getJdbcUrl());\n        hikariConfig.setUsername(container.getUsername());\n        hikariConfig.setPassword(container.getPassword());\n        hikariConfig.setDriverClassName(container.getDriverClassName());\n        return new HikariDataSource(hikariConfig);\n    }\n}\n"
  },
  {
    "path": "modules/jdbc-test/src/main/java/org/testcontainers/jdbc/AbstractJDBCDriverTest.java",
    "content": "package org.testcontainers.jdbc;\n\nimport com.zaxxer.hikari.HikariConfig;\nimport com.zaxxer.hikari.HikariDataSource;\nimport org.apache.commons.dbutils.QueryRunner;\nimport org.apache.commons.lang3.SystemUtils;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.Parameter;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport java.sql.Connection;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.EnumSet;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assumptions.assumeThat;\n\n@ParameterizedClass\n@MethodSource(\"data\")\npublic class AbstractJDBCDriverTest {\n\n    protected enum Options {\n        ScriptedSchema,\n        CharacterSet,\n        CustomIniFile,\n        JDBCParams,\n        PmdKnownBroken,\n    }\n\n    @Parameter(0)\n    public String jdbcUrl;\n\n    @Parameter(1)\n    public EnumSet<Options> options;\n\n    public static void sampleInitFunction(Connection connection) throws SQLException {\n        connection.createStatement().execute(\"CREATE TABLE bar (\\n\" + \"  foo VARCHAR(255)\\n\" + \");\");\n        connection.createStatement().execute(\"INSERT INTO bar (foo) VALUES ('hello world');\");\n        connection.createStatement().execute(\"CREATE TABLE my_counter (\\n\" + \"  n INT\\n\" + \");\");\n    }\n\n    @AfterAll\n    public static void testCleanup() {\n        ContainerDatabaseDriver.killContainers();\n    }\n\n    @Test\n    void test() throws SQLException {\n        try (HikariDataSource dataSource = getDataSource(jdbcUrl, 1)) {\n            performSimpleTest(dataSource);\n\n            if (options.contains(Options.ScriptedSchema)) {\n                performTestForScriptedSchema(dataSource);\n            }\n\n            if (options.contains(Options.JDBCParams)) {\n                performTestForJDBCParamUsage(dataSource);\n            }\n\n            if (options.contains(Options.CharacterSet)) {\n                performSimpleTestWithCharacterSet(jdbcUrl);\n\n                performTestForCharacterEncodingForInitialScriptConnection(dataSource);\n            }\n\n            if (options.contains(Options.CustomIniFile)) {\n                performTestForCustomIniFile(dataSource);\n            }\n        }\n    }\n\n    private void performSimpleTest(HikariDataSource dataSource) throws SQLException {\n        String query = \"SELECT 1\";\n        if (jdbcUrl.startsWith(\"jdbc:tc:db2:\")) {\n            query = \"SELECT 1 FROM SYSIBM.SYSDUMMY1\";\n        }\n\n        boolean result = new QueryRunner(dataSource, options.contains(Options.PmdKnownBroken))\n            .query(\n                query,\n                rs -> {\n                    rs.next();\n                    int resultSetInt = rs.getInt(1);\n                    assertThat(resultSetInt).as(\"A basic SELECT query succeeds\").isEqualTo(1);\n                    return true;\n                }\n            );\n\n        assertThat(result).as(\"The database returned a record as expected\").isTrue();\n    }\n\n    private void performTestForScriptedSchema(HikariDataSource dataSource) throws SQLException {\n        boolean result = new QueryRunner(dataSource)\n            .query(\n                \"SELECT foo FROM bar WHERE foo LIKE '%world'\",\n                rs -> {\n                    rs.next();\n                    String resultSetString = rs.getString(1);\n                    assertThat(resultSetString)\n                        .as(\"A basic SELECT query succeeds where the schema has been applied from a script\")\n                        .isEqualTo(\"hello world\");\n                    return true;\n                }\n            );\n\n        assertThat(result).as(\"The database returned a record as expected\").isTrue();\n    }\n\n    private void performTestForJDBCParamUsage(HikariDataSource dataSource) throws SQLException {\n        boolean result = new QueryRunner(dataSource)\n            .query(\n                \"select CURRENT_USER\",\n                rs -> {\n                    rs.next();\n                    String resultUser = rs.getString(1);\n                    // Not all databases (eg. Postgres) return @% at the end of user name. We just need to make sure the user name matches.\n                    if (resultUser.endsWith(\"@%\")) {\n                        resultUser = resultUser.substring(0, resultUser.length() - 2);\n                    }\n                    assertThat(resultUser).as(\"User from query param is created.\").isEqualTo(\"someuser\");\n                    return true;\n                }\n            );\n\n        assertThat(result).as(\"The database returned a record as expected\").isTrue();\n\n        String databaseQuery = \"SELECT DATABASE()\";\n        // Postgres does not have Database() as a function\n        String databaseType = ConnectionUrl.newInstance(jdbcUrl).getDatabaseType();\n        if (\n            databaseType.equalsIgnoreCase(\"postgresql\") ||\n            databaseType.equalsIgnoreCase(\"postgis\") ||\n            databaseType.equalsIgnoreCase(\"timescaledb\") ||\n            databaseType.equalsIgnoreCase(\"pgvector\")\n        ) {\n            databaseQuery = \"SELECT CURRENT_DATABASE()\";\n        }\n\n        result =\n            new QueryRunner(dataSource)\n                .query(\n                    databaseQuery,\n                    rs -> {\n                        rs.next();\n                        String resultDB = rs.getString(1);\n                        assertThat(resultDB).as(\"Database name from URL String is used.\").isEqualTo(\"databasename\");\n                        return true;\n                    }\n                );\n\n        assertThat(result).as(\"The database returned a record as expected\").isTrue();\n    }\n\n    private void performTestForCharacterEncodingForInitialScriptConnection(HikariDataSource dataSource)\n        throws SQLException {\n        boolean result = new QueryRunner(dataSource)\n            .query(\n                \"SELECT foo FROM bar WHERE foo LIKE '%мир'\",\n                rs -> {\n                    rs.next();\n                    String resultSetString = rs.getString(1);\n                    assertThat(resultSetString)\n                        .as(\"A basic SELECT query succeeds where the schema has been applied from a script\")\n                        .isEqualTo(\"привет мир\");\n                    return true;\n                }\n            );\n\n        assertThat(result).as(\"The database returned a record as expected\").isTrue();\n    }\n\n    /**\n     * This method intentionally verifies encoding twice to ensure that the query string parameters are used when\n     * Connections are created from cached containers.\n     *\n     * @param jdbcUrl\n     * @throws SQLException\n     */\n    private void performSimpleTestWithCharacterSet(String jdbcUrl) throws SQLException {\n        HikariDataSource datasource1 = verifyCharacterSet(jdbcUrl);\n        HikariDataSource datasource2 = verifyCharacterSet(jdbcUrl);\n        datasource1.close();\n        datasource2.close();\n    }\n\n    private HikariDataSource verifyCharacterSet(String jdbcUrl) throws SQLException {\n        HikariDataSource dataSource = getDataSource(jdbcUrl, 1);\n        boolean result = new QueryRunner(dataSource)\n            .query(\n                \"SHOW VARIABLES LIKE 'character\\\\_set\\\\_connection'\",\n                rs -> {\n                    rs.next();\n                    String resultSetString = rs.getString(2);\n                    assertThat(resultSetString)\n                        .as(\"Passing query parameters to set DB connection encoding is successful\")\n                        .startsWith(\"utf8\");\n                    return true;\n                }\n            );\n\n        assertThat(result).as(\"The database returned a record as expected\").isTrue();\n        return dataSource;\n    }\n\n    private void performTestForCustomIniFile(HikariDataSource dataSource) throws SQLException {\n        assumeThat(SystemUtils.IS_OS_WINDOWS).isFalse();\n        Statement statement = dataSource.getConnection().createStatement();\n        statement.execute(\"SELECT @@GLOBAL.innodb_max_undo_log_size\");\n        ResultSet resultSet = statement.getResultSet();\n\n        assertThat(resultSet.next()).as(\"The query returns a result\").isTrue();\n        long result = resultSet.getLong(1);\n\n        assertThat(result).as(\"The InnoDB max undo log size has been set by the ini file content\").isEqualTo(20000000);\n    }\n\n    private HikariDataSource getDataSource(String jdbcUrl, int poolSize) {\n        HikariConfig hikariConfig = new HikariConfig();\n        hikariConfig.setJdbcUrl(jdbcUrl);\n        hikariConfig.setConnectionTestQuery(\"SELECT 1\");\n        hikariConfig.setMinimumIdle(1);\n        hikariConfig.setMaximumPoolSize(poolSize);\n\n        return new HikariDataSource(hikariConfig);\n    }\n}\n"
  },
  {
    "path": "modules/jdbc-test/src/main/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/junit-jupiter/build.gradle",
    "content": "description = \"Testcontainers :: JUnit Jupiter Extension\"\n\ndependencies {\n    api project(':testcontainers')\n    implementation platform('org.junit:junit-bom:5.14.1')\n    implementation 'org.junit.jupiter:junit-jupiter-api'\n\n    testImplementation project(':testcontainers-mysql')\n    testImplementation project(':testcontainers-postgresql')\n    testImplementation 'com.zaxxer:HikariCP:7.0.2'\n    testImplementation 'redis.clients:jedis:7.1.0'\n    testImplementation 'org.apache.httpcomponents:httpclient:4.5.14'\n    testImplementation ('org.mockito:mockito-core:4.11.0') {\n        exclude(module: 'hamcrest-core')\n    }\n\n    testRuntimeOnly 'org.postgresql:postgresql:42.7.8'\n    testRuntimeOnly 'com.mysql:mysql-connector-j:9.5.0'\n}\n"
  },
  {
    "path": "modules/junit-jupiter/src/main/java/org/testcontainers/junit/jupiter/Container.java",
    "content": "package org.testcontainers.junit.jupiter;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * The {@code @Container} annotation is used in conjunction with the {@link Testcontainers} annotation\n * to mark containers that should be managed by the Testcontainers extension.\n *\n * @see Testcontainers\n */\n@Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE })\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface Container {\n}\n"
  },
  {
    "path": "modules/junit-jupiter/src/main/java/org/testcontainers/junit/jupiter/DockerAvailableDetector.java",
    "content": "package org.testcontainers.junit.jupiter;\n\nimport org.testcontainers.DockerClientFactory;\n\nclass DockerAvailableDetector {\n\n    public boolean isDockerAvailable() {\n        try {\n            DockerClientFactory.instance().client();\n            return true;\n        } catch (Throwable ex) {\n            return false;\n        }\n    }\n}\n"
  },
  {
    "path": "modules/junit-jupiter/src/main/java/org/testcontainers/junit/jupiter/EnabledIfDockerAvailable.java",
    "content": "package org.testcontainers.junit.jupiter;\n\nimport org.junit.jupiter.api.extension.ExtendWith;\n\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * {@code EnabledIfDockerAvailable} is a JUnit Jupiter extension to enable tests only if Docker is available.\n */\n@Target({ ElementType.TYPE, ElementType.METHOD })\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\n@ExtendWith(EnabledIfDockerAvailableCondition.class)\npublic @interface EnabledIfDockerAvailable {\n}\n"
  },
  {
    "path": "modules/junit-jupiter/src/main/java/org/testcontainers/junit/jupiter/EnabledIfDockerAvailableCondition.java",
    "content": "package org.testcontainers.junit.jupiter;\n\nimport org.junit.jupiter.api.extension.ConditionEvaluationResult;\nimport org.junit.jupiter.api.extension.ExecutionCondition;\nimport org.junit.jupiter.api.extension.ExtensionConfigurationException;\nimport org.junit.jupiter.api.extension.ExtensionContext;\nimport org.junit.platform.commons.support.AnnotationSupport;\n\nimport java.util.Optional;\n\nclass EnabledIfDockerAvailableCondition implements ExecutionCondition {\n\n    private final DockerAvailableDetector dockerDetector = new DockerAvailableDetector();\n\n    @Override\n    public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {\n        return findAnnotation(context)\n            .map(this::evaluate)\n            .orElseThrow(() -> new ExtensionConfigurationException(\"@EnabledIfDockerAvailable not found\"));\n    }\n\n    boolean isDockerAvailable() {\n        return this.dockerDetector.isDockerAvailable();\n    }\n\n    private ConditionEvaluationResult evaluate(EnabledIfDockerAvailable testcontainers) {\n        if (isDockerAvailable()) {\n            return ConditionEvaluationResult.enabled(\"Docker is available\");\n        }\n        return ConditionEvaluationResult.disabled(\"Docker is not available\");\n    }\n\n    private Optional<EnabledIfDockerAvailable> findAnnotation(ExtensionContext context) {\n        Optional<ExtensionContext> current = Optional.of(context);\n        while (current.isPresent()) {\n            Optional<EnabledIfDockerAvailable> enabledIfDockerAvailable = AnnotationSupport.findAnnotation(\n                current.get().getRequiredTestClass(),\n                EnabledIfDockerAvailable.class\n            );\n            if (enabledIfDockerAvailable.isPresent()) {\n                return enabledIfDockerAvailable;\n            }\n            current = current.get().getParent();\n        }\n        return Optional.empty();\n    }\n}\n"
  },
  {
    "path": "modules/junit-jupiter/src/main/java/org/testcontainers/junit/jupiter/FilesystemFriendlyNameGenerator.java",
    "content": "package org.testcontainers.junit.jupiter;\n\nimport org.junit.jupiter.api.extension.ExtensionContext;\n\nimport java.io.UnsupportedEncodingException;\nimport java.net.URLEncoder;\nimport java.nio.charset.StandardCharsets;\n\nclass FilesystemFriendlyNameGenerator {\n\n    private static final String UNKNOWN_NAME = \"unknown\";\n\n    static String filesystemFriendlyNameOf(ExtensionContext context) {\n        String contextId = context.getUniqueId();\n        try {\n            return (contextId == null || contextId.trim().isEmpty())\n                ? UNKNOWN_NAME\n                : URLEncoder.encode(contextId, StandardCharsets.UTF_8.toString());\n        } catch (UnsupportedEncodingException e) {\n            return UNKNOWN_NAME;\n        }\n    }\n}\n"
  },
  {
    "path": "modules/junit-jupiter/src/main/java/org/testcontainers/junit/jupiter/Testcontainers.java",
    "content": "package org.testcontainers.junit.jupiter;\n\nimport org.junit.jupiter.api.extension.ExtendWith;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Inherited;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * {@code @Testcontainers} is a JUnit Jupiter extension to activate automatic\n * startup and stop of containers used in a test case.\n *\n * <p>The Testcontainers extension finds all fields that are annotated with\n * {@link Container} and calls their container lifecycle methods. Containers\n * declared as static fields will be shared between test methods. They will be\n * started only once before any test method is executed and stopped after the\n * last test method has executed. Containers declared as instance fields will\n * be started and stopped for every test method.</p>\n *\n * <p>The annotation {@code @Testcontainers} can be used on a superclass in\n * the test hierarchy as well. All subclasses will automatically inherit\n * support for the extension.</p>\n *\n * <p><strong>Note:</strong> This extension has only been tested with sequential\n * test execution. Using it with parallel test execution is unsupported and\n * may have unintended side effects.</p>\n *\n * <p>Example:</p>\n *\n * <pre>\n * &#64;Testcontainers\n * class MyTestcontainersTests {\n *\n *     // will be shared between test methods\n *     &#64;Container\n *     private static final MySQLContainer MY_SQL_CONTAINER = new MySQLContainer();\n *\n *     // will be started before and stopped after each test method\n *     &#64;Container\n *     private PostgreSQLContainer postgresqlContainer = new PostgreSQLContainer()\n *             .withDatabaseName(\"foo\")\n *             .withUsername(\"foo\")\n *             .withPassword(\"secret\");\n *\n *     &#64;Test\n *     void test() {\n *         assertTrue(MY_SQL_CONTAINER.isRunning());\n *         assertTrue(postgresqlContainer.isRunning());\n *     }\n * }\n * </pre>\n *\n * @see Container\n */\n@Target(ElementType.TYPE)\n@Retention(RetentionPolicy.RUNTIME)\n@ExtendWith(TestcontainersExtension.class)\n@Inherited\npublic @interface Testcontainers {\n    /**\n     * Whether tests should be disabled (rather than failing) when Docker is not available. Defaults to\n     * {@code false}.\n     * @return if the tests should be disabled when Docker is not available\n     */\n    boolean disabledWithoutDocker() default false;\n\n    /**\n     * Whether containers should start in parallel. Defaults to {@code false}.\n     * @return if the containers should start in parallel\n     */\n    boolean parallel() default false;\n}\n"
  },
  {
    "path": "modules/junit-jupiter/src/main/java/org/testcontainers/junit/jupiter/TestcontainersExtension.java",
    "content": "package org.testcontainers.junit.jupiter;\n\nimport lombok.Getter;\nimport org.junit.jupiter.api.extension.AfterAllCallback;\nimport org.junit.jupiter.api.extension.AfterEachCallback;\nimport org.junit.jupiter.api.extension.BeforeAllCallback;\nimport org.junit.jupiter.api.extension.BeforeEachCallback;\nimport org.junit.jupiter.api.extension.ConditionEvaluationResult;\nimport org.junit.jupiter.api.extension.ExecutionCondition;\nimport org.junit.jupiter.api.extension.ExtensionConfigurationException;\nimport org.junit.jupiter.api.extension.ExtensionContext;\nimport org.junit.jupiter.api.extension.ExtensionContext.Namespace;\nimport org.junit.jupiter.api.extension.ExtensionContext.Store;\nimport org.junit.jupiter.api.extension.ExtensionContext.Store.CloseableResource;\nimport org.junit.platform.commons.support.AnnotationSupport;\nimport org.junit.platform.commons.support.HierarchyTraversalMode;\nimport org.junit.platform.commons.support.ModifierSupport;\nimport org.junit.platform.commons.support.ReflectionSupport;\nimport org.testcontainers.lifecycle.Startable;\nimport org.testcontainers.lifecycle.Startables;\nimport org.testcontainers.lifecycle.TestDescription;\nimport org.testcontainers.lifecycle.TestLifecycleAware;\n\nimport java.lang.reflect.Field;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.LinkedHashSet;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.function.Predicate;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\npublic class TestcontainersExtension\n    implements BeforeEachCallback, BeforeAllCallback, AfterEachCallback, AfterAllCallback, ExecutionCondition {\n\n    private static final Namespace NAMESPACE = Namespace.create(TestcontainersExtension.class);\n\n    private static final String SHARED_LIFECYCLE_AWARE_CONTAINERS = \"sharedLifecycleAwareContainers\";\n\n    private static final String LOCAL_LIFECYCLE_AWARE_CONTAINERS = \"localLifecycleAwareContainers\";\n\n    private final DockerAvailableDetector dockerDetector = new DockerAvailableDetector();\n\n    @Override\n    public void beforeAll(ExtensionContext context) {\n        Class<?> testClass = context\n            .getTestClass()\n            .orElseThrow(() -> {\n                return new ExtensionConfigurationException(\"TestcontainersExtension is only supported for classes.\");\n            });\n\n        Store store = context.getStore(NAMESPACE);\n        List<StoreAdapter> sharedContainersStoreAdapters = findSharedContainers(testClass);\n\n        startContainers(sharedContainersStoreAdapters, store, context);\n\n        List<TestLifecycleAware> lifecycleAwareContainers = sharedContainersStoreAdapters\n            .stream()\n            .filter(this::isTestLifecycleAware)\n            .map(lifecycleAwareAdapter -> (TestLifecycleAware) lifecycleAwareAdapter.container)\n            .collect(Collectors.toList());\n\n        store.put(SHARED_LIFECYCLE_AWARE_CONTAINERS, lifecycleAwareContainers);\n        signalBeforeTestToContainers(lifecycleAwareContainers, testDescriptionFrom(context));\n    }\n\n    private void startContainers(List<StoreAdapter> storeAdapters, Store store, ExtensionContext context) {\n        if (storeAdapters.isEmpty()) {\n            return;\n        }\n\n        if (isParallelExecutionEnabled(context)) {\n            Stream<Startable> startables = storeAdapters\n                .stream()\n                .map(storeAdapter -> {\n                    store.getOrComputeIfAbsent(storeAdapter.getKey(), k -> storeAdapter);\n                    return storeAdapter.container;\n                });\n            Startables.deepStart(startables).join();\n        } else {\n            storeAdapters.forEach(adapter -> store.getOrComputeIfAbsent(adapter.getKey(), k -> adapter.start()));\n        }\n    }\n\n    @Override\n    public void afterAll(ExtensionContext context) {\n        signalAfterTestToContainersFor(SHARED_LIFECYCLE_AWARE_CONTAINERS, context);\n    }\n\n    @Override\n    public void beforeEach(final ExtensionContext context) {\n        Store store = context.getStore(NAMESPACE);\n\n        List<StoreAdapter> restartContainers = collectParentTestInstances(context)\n            .parallelStream()\n            .flatMap(this::findRestartContainers)\n            .collect(Collectors.toList());\n\n        List<TestLifecycleAware> lifecycleAwareContainers = findTestLifecycleAwareContainers(\n            restartContainers,\n            store,\n            context\n        );\n\n        store.put(LOCAL_LIFECYCLE_AWARE_CONTAINERS, lifecycleAwareContainers);\n        signalBeforeTestToContainers(lifecycleAwareContainers, testDescriptionFrom(context));\n    }\n\n    private List<TestLifecycleAware> findTestLifecycleAwareContainers(\n        List<StoreAdapter> restartContainers,\n        Store store,\n        ExtensionContext context\n    ) {\n        startContainers(restartContainers, store, context);\n\n        return restartContainers\n            .stream()\n            .filter(this::isTestLifecycleAware)\n            .map(lifecycleAwareAdapter -> (TestLifecycleAware) lifecycleAwareAdapter.container)\n            .collect(Collectors.toList());\n    }\n\n    private boolean isParallelExecutionEnabled(ExtensionContext context) {\n        return findTestcontainers(context).map(Testcontainers::parallel).orElse(false);\n    }\n\n    @Override\n    public void afterEach(ExtensionContext context) {\n        signalAfterTestToContainersFor(LOCAL_LIFECYCLE_AWARE_CONTAINERS, context);\n    }\n\n    private void signalBeforeTestToContainers(\n        List<TestLifecycleAware> lifecycleAwareContainers,\n        TestDescription testDescription\n    ) {\n        lifecycleAwareContainers.forEach(container -> container.beforeTest(testDescription));\n    }\n\n    private void signalAfterTestToContainersFor(String storeKey, ExtensionContext context) {\n        List<TestLifecycleAware> lifecycleAwareContainers = (List<TestLifecycleAware>) context\n            .getStore(NAMESPACE)\n            .get(storeKey);\n        if (lifecycleAwareContainers != null) {\n            TestDescription description = testDescriptionFrom(context);\n            Optional<Throwable> throwable = context.getExecutionException();\n            lifecycleAwareContainers.forEach(container -> container.afterTest(description, throwable));\n        }\n    }\n\n    private TestDescription testDescriptionFrom(ExtensionContext context) {\n        return new TestcontainersTestDescription(\n            context.getUniqueId(),\n            FilesystemFriendlyNameGenerator.filesystemFriendlyNameOf(context)\n        );\n    }\n\n    private boolean isTestLifecycleAware(StoreAdapter adapter) {\n        return adapter.container instanceof TestLifecycleAware;\n    }\n\n    @Override\n    public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {\n        return findTestcontainers(context)\n            .map(this::evaluate)\n            .orElseThrow(() -> new ExtensionConfigurationException(\"@Testcontainers not found\"));\n    }\n\n    private Optional<Testcontainers> findTestcontainers(ExtensionContext context) {\n        Optional<ExtensionContext> current = Optional.of(context);\n        while (current.isPresent()) {\n            Optional<Testcontainers> testcontainers = AnnotationSupport.findAnnotation(\n                current.get().getRequiredTestClass(),\n                Testcontainers.class\n            );\n            if (testcontainers.isPresent()) {\n                return testcontainers;\n            }\n            current = current.get().getParent();\n        }\n        return Optional.empty();\n    }\n\n    private ConditionEvaluationResult evaluate(Testcontainers testcontainers) {\n        if (testcontainers.disabledWithoutDocker()) {\n            if (isDockerAvailable()) {\n                return ConditionEvaluationResult.enabled(\"Docker is available\");\n            }\n            return ConditionEvaluationResult.disabled(\"disabledWithoutDocker is true and Docker is not available\");\n        }\n        return ConditionEvaluationResult.enabled(\"disabledWithoutDocker is false\");\n    }\n\n    boolean isDockerAvailable() {\n        return this.dockerDetector.isDockerAvailable();\n    }\n\n    private Set<Object> collectParentTestInstances(final ExtensionContext context) {\n        List<Object> allInstances = new ArrayList<>(context.getRequiredTestInstances().getAllInstances());\n        Collections.reverse(allInstances);\n        return new LinkedHashSet<>(allInstances);\n    }\n\n    private List<StoreAdapter> findSharedContainers(Class<?> testClass) {\n        return ReflectionSupport\n            .findFields(testClass, isSharedContainer(), HierarchyTraversalMode.TOP_DOWN)\n            .stream()\n            .map(f -> getContainerInstance(null, f))\n            .collect(Collectors.toList());\n    }\n\n    private Predicate<Field> isSharedContainer() {\n        return isContainer().and(ModifierSupport::isStatic);\n    }\n\n    private Stream<StoreAdapter> findRestartContainers(Object testInstance) {\n        return ReflectionSupport\n            .findFields(testInstance.getClass(), isRestartContainer(), HierarchyTraversalMode.TOP_DOWN)\n            .stream()\n            .map(f -> getContainerInstance(testInstance, f));\n    }\n\n    private Predicate<Field> isRestartContainer() {\n        return isContainer().and(ModifierSupport::isNotStatic);\n    }\n\n    private static Predicate<Field> isContainer() {\n        return field -> {\n            boolean isAnnotatedWithContainer = AnnotationSupport.isAnnotated(field, Container.class);\n            if (isAnnotatedWithContainer) {\n                boolean isStartable = Startable.class.isAssignableFrom(field.getType());\n\n                if (!isStartable) {\n                    throw new ExtensionConfigurationException(\n                        String.format(\"FieldName: %s does not implement Startable\", field.getName())\n                    );\n                }\n                return true;\n            }\n            return false;\n        };\n    }\n\n    private static StoreAdapter getContainerInstance(final Object testInstance, final Field field) {\n        try {\n            field.setAccessible(true);\n            Startable containerInstance = (Startable) field.get(testInstance);\n            if (containerInstance == null) {\n                throw new ExtensionConfigurationException(\"Container \" + field.getName() + \" needs to be initialized\");\n            }\n            return new StoreAdapter(field.getDeclaringClass(), field.getName(), containerInstance);\n        } catch (IllegalAccessException e) {\n            throw new ExtensionConfigurationException(\"Can not access container defined in field \" + field.getName());\n        }\n    }\n\n    /**\n     * An adapter for {@link Startable} that implement {@link CloseableResource}\n     * thereby letting the JUnit automatically stop containers once the current\n     * {@link ExtensionContext} is closed.\n     */\n    private static class StoreAdapter implements CloseableResource, AutoCloseable {\n\n        @Getter\n        private String key;\n\n        private Startable container;\n\n        private StoreAdapter(Class<?> declaringClass, String fieldName, Startable container) {\n            this.key = declaringClass.getName() + \".\" + fieldName;\n            this.container = container;\n        }\n\n        private StoreAdapter start() {\n            container.start();\n            return this;\n        }\n\n        @Override\n        public void close() {\n            container.stop();\n        }\n    }\n}\n"
  },
  {
    "path": "modules/junit-jupiter/src/main/java/org/testcontainers/junit/jupiter/TestcontainersTestDescription.java",
    "content": "package org.testcontainers.junit.jupiter;\n\nimport lombok.Value;\nimport org.testcontainers.lifecycle.TestDescription;\n\n@Value\nclass TestcontainersTestDescription implements TestDescription {\n\n    String testId;\n\n    String filesystemFriendlyName;\n}\n"
  },
  {
    "path": "modules/junit-jupiter/src/test/java/org/testcontainers/junit/jupiter/ComposeContainerTests.java",
    "content": "package org.testcontainers.junit.jupiter;\n\nimport org.apache.http.HttpResponse;\nimport org.apache.http.client.HttpClient;\nimport org.apache.http.client.methods.HttpGet;\nimport org.apache.http.impl.client.HttpClientBuilder;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.ComposeContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\n\nimport java.io.File;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n@Testcontainers\nclass ComposeContainerTests {\n\n    @Container\n    private ComposeContainer composeContainer = new ComposeContainer(new File(\"src/test/resources/docker-compose.yml\"))\n        .withExposedService(\"whoami-1\", 80, Wait.forHttp(\"/\"));\n\n    @Test\n    void running_compose_defined_container_is_accessible_on_configured_port() throws Exception {\n        HttpClient client = HttpClientBuilder.create().build();\n\n        String host = composeContainer.getServiceHost(\"whoami-1\", 80);\n        int port = composeContainer.getServicePort(\"whoami-1\", 80);\n\n        HttpResponse response = client.execute(new HttpGet(\"http://\" + host + \":\" + port));\n\n        assertThat(response.getStatusLine().getStatusCode()).isEqualTo(200);\n    }\n}\n"
  },
  {
    "path": "modules/junit-jupiter/src/test/java/org/testcontainers/junit/jupiter/DockerComposeContainerTests.java",
    "content": "package org.testcontainers.junit.jupiter;\n\nimport org.apache.http.HttpResponse;\nimport org.apache.http.client.HttpClient;\nimport org.apache.http.client.methods.HttpGet;\nimport org.apache.http.impl.client.HttpClientBuilder;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.DockerComposeContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.File;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n@Testcontainers\nclass DockerComposeContainerTests {\n\n    @Container\n    private DockerComposeContainer composeContainer = new DockerComposeContainer(\n        DockerImageName.parse(\"docker/compose:1.29.2\"),\n        new File(\"src/test/resources/docker-compose.yml\")\n    )\n        .withExposedService(\"whoami_1\", 80, Wait.forHttp(\"/\"));\n\n    @Test\n    void running_compose_defined_container_is_accessible_on_configured_port() throws Exception {\n        HttpClient client = HttpClientBuilder.create().build();\n\n        String host = composeContainer.getServiceHost(\"whoami_1\", 80);\n        int port = composeContainer.getServicePort(\"whoami_1\", 80);\n\n        HttpResponse response = client.execute(new HttpGet(\"http://\" + host + \":\" + port));\n\n        assertThat(response.getStatusLine().getStatusCode()).isEqualTo(200);\n    }\n}\n"
  },
  {
    "path": "modules/junit-jupiter/src/test/java/org/testcontainers/junit/jupiter/EnabledIfDockerAvailableTests.java",
    "content": "package org.testcontainers.junit.jupiter;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ConditionEvaluationResult;\nimport org.junit.jupiter.api.extension.ExtensionContext;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\npublic class EnabledIfDockerAvailableTests {\n\n    @Test\n    void whenDockerIsAvailableTestsAreEnabled() {\n        ConditionEvaluationResult result = new TestEnabledIfDockerAvailableCondition(true)\n            .evaluateExecutionCondition(extensionContext(DisabledWithoutDocker.class));\n        assertThat(result.isDisabled()).isFalse();\n    }\n\n    @Test\n    void whenDockerIsUnavailableTestsAreDisabled() {\n        ConditionEvaluationResult result = new TestEnabledIfDockerAvailableCondition(false)\n            .evaluateExecutionCondition(extensionContext(DisabledWithoutDocker.class));\n        assertThat(result.isDisabled()).isTrue();\n    }\n\n    private ExtensionContext extensionContext(Class clazz) {\n        ExtensionContext extensionContext = mock(ExtensionContext.class);\n        when(extensionContext.getRequiredTestClass()).thenReturn(clazz);\n        return extensionContext;\n    }\n\n    @EnabledIfDockerAvailable\n    static final class DisabledWithoutDocker {}\n\n    static final class TestEnabledIfDockerAvailableCondition extends EnabledIfDockerAvailableCondition {\n\n        private final boolean dockerAvailable;\n\n        private TestEnabledIfDockerAvailableCondition(boolean dockerAvailable) {\n            this.dockerAvailable = dockerAvailable;\n        }\n\n        boolean isDockerAvailable() {\n            return dockerAvailable;\n        }\n    }\n}\n"
  },
  {
    "path": "modules/junit-jupiter/src/test/java/org/testcontainers/junit/jupiter/FilesystemFriendlyNameGeneratorTest.java",
    "content": "package org.testcontainers.junit.jupiter;\n\nimport org.junit.jupiter.api.extension.ExtensionContext;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport java.util.stream.Stream;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.doReturn;\nimport static org.mockito.Mockito.mock;\n\nclass FilesystemFriendlyNameGeneratorTest {\n\n    @ParameterizedTest\n    @MethodSource(\"provideDisplayNamesAndFilesystemFriendlyNames\")\n    void should_generate_filesystem_friendly_name(String displayName, String expectedName) {\n        ExtensionContext context = mock(ExtensionContext.class);\n        doReturn(displayName).when(context).getUniqueId();\n\n        String filesystemFriendlyName = FilesystemFriendlyNameGenerator.filesystemFriendlyNameOf(context);\n\n        assertThat(filesystemFriendlyName).isEqualTo(expectedName);\n    }\n\n    private static Stream<Arguments> provideDisplayNamesAndFilesystemFriendlyNames() {\n        return Stream.of(\n            Arguments.of(\"\", \"unknown\"),\n            Arguments.of(\"  \", \"unknown\"),\n            Arguments.of(\"not blank\", \"not+blank\"),\n            Arguments.of(\"abc ABC 1234567890\", \"abc+ABC+1234567890\"),\n            Arguments.of(\n                \"no_umlauts_äöüÄÖÜéáíó\",\n                \"no_umlauts_%C3%A4%C3%B6%C3%BC%C3%84%C3%96%C3%9C%C3%A9%C3%A1%C3%AD%C3%B3\"\n            ),\n            Arguments.of(\n                \"[engine:junit-jupiter]/[class:com.example.MyTest]/[test-factory:parameterizedTest()]/[dynamic-test:#3]\",\n                \"%5Bengine%3Ajunit-jupiter%5D%2F%5Bclass%3Acom.example.MyTest%5D%2F%5Btest-factory%3AparameterizedTest%28%29%5D%2F%5Bdynamic-test%3A%233%5D\"\n            )\n        );\n    }\n}\n"
  },
  {
    "path": "modules/junit-jupiter/src/test/java/org/testcontainers/junit/jupiter/JUnitJupiterTestImages.java",
    "content": "package org.testcontainers.junit.jupiter;\n\nimport org.testcontainers.utility.DockerImageName;\n\npublic interface JUnitJupiterTestImages {\n    DockerImageName POSTGRES_IMAGE = DockerImageName.parse(\"postgres:9.6.12\");\n\n    DockerImageName HTTPD_IMAGE = DockerImageName.parse(\"httpd:2.4-alpine\");\n\n    DockerImageName MYSQL_IMAGE = DockerImageName.parse(\"mysql:8.0.32\");\n}\n"
  },
  {
    "path": "modules/junit-jupiter/src/test/java/org/testcontainers/junit/jupiter/MetaAnnotationTest.java",
    "content": "package org.testcontainers.junit.jupiter;\n\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.PostgreSQLContainer;\n\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n@Testcontainers\nclass MetaAnnotationTest {\n\n    @TcContainer\n    private static final PostgreSQLContainer<?> POSTGRESQL = new PostgreSQLContainer<>(\n        JUnitJupiterTestImages.POSTGRES_IMAGE\n    );\n\n    @Test\n    void test() {\n        assertThat(POSTGRESQL.isRunning()).isTrue();\n    }\n}\n\n@Container\n@Retention(RetentionPolicy.RUNTIME)\n@interface TcContainer {\n}\n"
  },
  {
    "path": "modules/junit-jupiter/src/test/java/org/testcontainers/junit/jupiter/MixedLifecycleTests.java",
    "content": "package org.testcontainers.junit.jupiter;\n\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.MySQLContainer;\nimport org.testcontainers.containers.PostgreSQLContainer;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n// testClass {\n@Testcontainers\nclass MixedLifecycleTests {\n\n    // will be shared between test methods\n    @Container\n    private static final MySQLContainer MY_SQL_CONTAINER = new MySQLContainer(\"mysql:8.0.36\");\n\n    // will be started before and stopped after each test method\n    @Container\n    private PostgreSQLContainer postgresqlContainer = new PostgreSQLContainer(\"postgres:9.6.12\")\n        .withDatabaseName(\"foo\")\n        .withUsername(\"foo\")\n        .withPassword(\"secret\");\n\n    @Test\n    void test() {\n        assertThat(MY_SQL_CONTAINER.isRunning()).isTrue();\n        assertThat(postgresqlContainer.isRunning()).isTrue();\n    }\n}\n// }\n"
  },
  {
    "path": "modules/junit-jupiter/src/test/java/org/testcontainers/junit/jupiter/ParallelExecutionTests.java",
    "content": "package org.testcontainers.junit.jupiter;\n\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.MySQLContainer;\nimport org.testcontainers.containers.PostgreSQLContainer;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n@Testcontainers(parallel = true)\npublic class ParallelExecutionTests {\n\n    @Container\n    private static final PostgreSQLContainer<?> POSTGRESQL_CONTAINER = new PostgreSQLContainer<>(\n        JUnitJupiterTestImages.POSTGRES_IMAGE\n    )\n        .withDatabaseName(\"foo\")\n        .withUsername(\"foo\")\n        .withPassword(\"secret\");\n\n    @Container\n    private MySQLContainer<?> mySQLContainer = new MySQLContainer<>(JUnitJupiterTestImages.MYSQL_IMAGE);\n\n    @Test\n    void test() {\n        assertThat(POSTGRESQL_CONTAINER.isRunning()).isTrue();\n        assertThat(mySQLContainer.isRunning()).isTrue();\n    }\n}\n"
  },
  {
    "path": "modules/junit-jupiter/src/test/java/org/testcontainers/junit/jupiter/PostgresContainerTests.java",
    "content": "package org.testcontainers.junit.jupiter;\n\nimport com.zaxxer.hikari.HikariConfig;\nimport com.zaxxer.hikari.HikariDataSource;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.PostgreSQLContainer;\n\nimport java.sql.ResultSet;\nimport java.sql.Statement;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n@Testcontainers\nclass PostgresContainerTests {\n\n    @Container\n    private static final PostgreSQLContainer<?> POSTGRE_SQL_CONTAINER = new PostgreSQLContainer<>(\n        JUnitJupiterTestImages.POSTGRES_IMAGE\n    )\n        .withDatabaseName(\"foo\")\n        .withUsername(\"foo\")\n        .withPassword(\"secret\");\n\n    @Test\n    void waits_until_postgres_accepts_jdbc_connections() throws Exception {\n        HikariConfig hikariConfig = new HikariConfig();\n        hikariConfig.setJdbcUrl(POSTGRE_SQL_CONTAINER.getJdbcUrl());\n        hikariConfig.setUsername(\"foo\");\n        hikariConfig.setPassword(\"secret\");\n\n        try (HikariDataSource ds = new HikariDataSource(hikariConfig)) {\n            Statement statement = ds.getConnection().createStatement();\n            statement.execute(\"SELECT 1\");\n            ResultSet resultSet = statement.getResultSet();\n            resultSet.next();\n\n            int resultSetInt = resultSet.getInt(1);\n            assertThat(resultSetInt).isEqualTo(1);\n        }\n    }\n}\n"
  },
  {
    "path": "modules/junit-jupiter/src/test/java/org/testcontainers/junit/jupiter/TestLifecycleAwareContainerMock.java",
    "content": "package org.testcontainers.junit.jupiter;\n\nimport org.testcontainers.lifecycle.Startable;\nimport org.testcontainers.lifecycle.TestDescription;\nimport org.testcontainers.lifecycle.TestLifecycleAware;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Optional;\n\npublic class TestLifecycleAwareContainerMock implements Startable, TestLifecycleAware {\n\n    static final String BEFORE_TEST = \"beforeTest\";\n\n    static final String AFTER_TEST = \"afterTest\";\n\n    private final List<String> lifecycleMethodCalls = new ArrayList<>();\n\n    private final List<String> lifecycleFilesystemFriendlyNames = new ArrayList<>();\n\n    private Throwable capturedThrowable;\n\n    @Override\n    public void beforeTest(TestDescription description) {\n        lifecycleMethodCalls.add(BEFORE_TEST);\n        lifecycleFilesystemFriendlyNames.add(description.getFilesystemFriendlyName());\n    }\n\n    @Override\n    public void afterTest(TestDescription description, Optional<Throwable> throwable) {\n        lifecycleMethodCalls.add(AFTER_TEST);\n        throwable.ifPresent(capturedThrowable -> this.capturedThrowable = capturedThrowable);\n    }\n\n    List<String> getLifecycleMethodCalls() {\n        return lifecycleMethodCalls;\n    }\n\n    Throwable getCapturedThrowable() {\n        return capturedThrowable;\n    }\n\n    public List<String> getLifecycleFilesystemFriendlyNames() {\n        return lifecycleFilesystemFriendlyNames;\n    }\n\n    @Override\n    public void start() {}\n\n    @Override\n    public void stop() {}\n}\n"
  },
  {
    "path": "modules/junit-jupiter/src/test/java/org/testcontainers/junit/jupiter/TestLifecycleAwareExceptionCapturingTest.java",
    "content": "package org.testcontainers.junit.jupiter;\n\nimport org.junit.jupiter.api.MethodOrderer.OrderAnnotation;\nimport org.junit.jupiter.api.Order;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.TestMethodOrder;\nimport org.opentest4j.TestAbortedException;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assumptions.assumeThat;\n\n// The order of @ExtendsWith and @Testcontainers is crucial in order for the tests\n@Testcontainers\n@TestMethodOrder(OrderAnnotation.class)\nclass TestLifecycleAwareExceptionCapturingTest {\n\n    @Container\n    private final TestLifecycleAwareContainerMock testContainer = new TestLifecycleAwareContainerMock();\n\n    private static TestLifecycleAwareContainerMock startedTestContainer;\n\n    @Test\n    @Order(1)\n    void failing_test_should_pass_throwable_to_testContainer() {\n        startedTestContainer = testContainer;\n        // Force an exception that is captured by the test container without failing the test itself\n        assumeThat(false).isTrue();\n    }\n\n    @Test\n    @Order(2)\n    void should_have_captured_thrownException() {\n        Throwable capturedThrowable = startedTestContainer.getCapturedThrowable();\n        assertThat(capturedThrowable).isInstanceOf(TestAbortedException.class);\n        assertThat(capturedThrowable.getMessage()).contains(\"Expecting value to be true but was false\");\n    }\n}\n"
  },
  {
    "path": "modules/junit-jupiter/src/test/java/org/testcontainers/junit/jupiter/TestLifecycleAwareMethodTest.java",
    "content": "package org.testcontainers.junit.jupiter;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.MethodOrderer;\nimport org.junit.jupiter.api.Order;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.TestMethodOrder;\nimport org.junit.jupiter.api.extension.AfterAllCallback;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.junit.jupiter.api.extension.ExtensionContext;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n// The order of @ExtendsWith and @Testcontainers is crucial for the tests\n@ExtendWith({ TestLifecycleAwareMethodTest.SharedContainerAfterAllTestExtension.class })\n@Testcontainers\n@TestMethodOrder(MethodOrderer.OrderAnnotation.class)\nclass TestLifecycleAwareMethodTest {\n\n    @Container\n    private final TestLifecycleAwareContainerMock testContainer = new TestLifecycleAwareContainerMock();\n\n    @Container\n    private static final TestLifecycleAwareContainerMock SHARED_CONTAINER = new TestLifecycleAwareContainerMock();\n\n    private static TestLifecycleAwareContainerMock startedTestContainer;\n\n    @BeforeAll\n    static void beforeAll() {\n        assertThat(SHARED_CONTAINER.getLifecycleMethodCalls())\n            .containsExactly(TestLifecycleAwareContainerMock.BEFORE_TEST);\n    }\n\n    @Test\n    @Order(1)\n    void should_prepare_before_and_after_test() {\n        // we can only test for a call to afterTest() after this test has been finished.\n        startedTestContainer = testContainer;\n    }\n\n    @Test\n    @Order(2)\n    void should_call_beforeTest_first_afterTest_later_with_filesystem_friendly_name() {\n        assertThat(startedTestContainer.getLifecycleMethodCalls())\n            .containsExactly(TestLifecycleAwareContainerMock.BEFORE_TEST, TestLifecycleAwareContainerMock.AFTER_TEST);\n    }\n\n    @Test\n    void should_have_a_filesystem_friendly_name_container_has_started() {\n        assertThat(startedTestContainer.getLifecycleFilesystemFriendlyNames())\n            .containsExactly(\n                \"%5Bengine%3Ajunit-jupiter%5D%2F%5Bclass%3Aorg.testcontainers.junit.jupiter.TestLifecycleAwareMethodTest%5D%2F%5Bmethod%3Ashould_prepare_before_and_after_test%28%29%5D\"\n            );\n    }\n\n    @Test\n    void static_container_should_have_a_filesystem_friendly_name_after_container_has_started() {\n        assertThat(SHARED_CONTAINER.getLifecycleFilesystemFriendlyNames())\n            .containsExactly(\n                \"%5Bengine%3Ajunit-jupiter%5D%2F%5Bclass%3Aorg.testcontainers.junit.jupiter.TestLifecycleAwareMethodTest%5D\"\n            );\n    }\n\n    static class SharedContainerAfterAllTestExtension implements AfterAllCallback {\n\n        // Unfortunately it's not possible to write a @Test that is run after all tests\n        @Override\n        public void afterAll(ExtensionContext context) {\n            assertThat(SHARED_CONTAINER.getLifecycleMethodCalls())\n                .containsExactly(\n                    TestLifecycleAwareContainerMock.BEFORE_TEST,\n                    TestLifecycleAwareContainerMock.AFTER_TEST\n                );\n        }\n    }\n}\n"
  },
  {
    "path": "modules/junit-jupiter/src/test/java/org/testcontainers/junit/jupiter/TestcontainersExtensionTests.java",
    "content": "package org.testcontainers.junit.jupiter;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ConditionEvaluationResult;\nimport org.junit.jupiter.api.extension.ExtensionContext;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\npublic class TestcontainersExtensionTests {\n\n    @Test\n    void whenDisabledWithoutDockerAndDockerIsAvailableTestsAreEnabled() {\n        ConditionEvaluationResult result = new TestTestcontainersExtension(true)\n            .evaluateExecutionCondition(extensionContext(DisabledWithoutDocker.class));\n        assertThat(result.isDisabled()).isFalse();\n    }\n\n    @Test\n    void whenDisabledWithoutDockerAndDockerIsUnavailableTestsAreDisabled() {\n        ConditionEvaluationResult result = new TestTestcontainersExtension(false)\n            .evaluateExecutionCondition(extensionContext(DisabledWithoutDocker.class));\n        assertThat(result.isDisabled()).isTrue();\n    }\n\n    @Test\n    void whenEnabledWithoutDockerAndDockerIsAvailableTestsAreEnabled() {\n        ConditionEvaluationResult result = new TestTestcontainersExtension(true)\n            .evaluateExecutionCondition(extensionContext(EnabledWithoutDocker.class));\n        assertThat(result.isDisabled()).isFalse();\n    }\n\n    @Test\n    void whenEnabledWithoutDockerAndDockerIsUnavailableTestsAreEnabled() {\n        ConditionEvaluationResult result = new TestTestcontainersExtension(false)\n            .evaluateExecutionCondition(extensionContext(EnabledWithoutDocker.class));\n        assertThat(result.isDisabled()).isFalse();\n    }\n\n    private ExtensionContext extensionContext(Class clazz) {\n        ExtensionContext extensionContext = mock(ExtensionContext.class);\n        when(extensionContext.getRequiredTestClass()).thenReturn(clazz);\n        return extensionContext;\n    }\n\n    @Testcontainers(disabledWithoutDocker = true)\n    static final class DisabledWithoutDocker {}\n\n    @Testcontainers\n    static final class EnabledWithoutDocker {}\n\n    static final class TestTestcontainersExtension extends TestcontainersExtension {\n\n        private final boolean dockerAvailable;\n\n        private TestTestcontainersExtension(boolean dockerAvailable) {\n            this.dockerAvailable = dockerAvailable;\n        }\n\n        boolean isDockerAvailable() {\n            return dockerAvailable;\n        }\n    }\n}\n"
  },
  {
    "path": "modules/junit-jupiter/src/test/java/org/testcontainers/junit/jupiter/TestcontainersNestedRestartedContainerTests.java",
    "content": "package org.testcontainers.junit.jupiter;\n\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.GenericContainer;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n// testClass {\n@Testcontainers\nclass TestcontainersNestedRestartedContainerTests {\n\n    @Container\n    private final GenericContainer<?> topLevelContainer = new GenericContainer<>(JUnitJupiterTestImages.HTTPD_IMAGE)\n        .withExposedPorts(80);\n\n    // }}\n\n    private static String topLevelContainerId;\n\n    private static String nestedContainerId;\n\n    // testClass {\n    @Test\n    void top_level_container_should_be_running() {\n        assertThat(topLevelContainer.isRunning()).isTrue();\n        // }}\n        topLevelContainerId = topLevelContainer.getContainerId();\n        // testClass {{\n    }\n\n    @Nested\n    class NestedTestCase {\n\n        @Container\n        private final GenericContainer<?> nestedContainer = new GenericContainer<>(JUnitJupiterTestImages.HTTPD_IMAGE)\n            .withExposedPorts(80);\n\n        @Test\n        void both_containers_should_be_running() {\n            // top level container is restarted for nested methods\n            assertThat(topLevelContainer.isRunning()).isTrue();\n            // nested containers are only available inside their nested class\n            assertThat(nestedContainer.isRunning()).isTrue();\n            // }}}\n            if (nestedContainerId == null) {\n                nestedContainerId = nestedContainer.getContainerId();\n            } else {\n                assertThat(nestedContainer.getContainerId()).isNotEqualTo(nestedContainerId);\n            }\n            // testClass {{\n        }\n\n        // }\n        @Test\n        void containers_should_not_be_the_same() {\n            assertThat(nestedContainer.getContainerId()).isNotEqualTo(topLevelContainer.getContainerId());\n\n            if (nestedContainerId == null) {\n                nestedContainerId = nestedContainer.getContainerId();\n            } else {\n                assertThat(nestedContainer.getContainerId()).isNotEqualTo(nestedContainerId);\n            }\n        }\n\n        @Test\n        void ids_should_not_change() {\n            assertThat(topLevelContainer.getContainerId()).isNotEqualTo(topLevelContainerId);\n\n            if (nestedContainerId == null) {\n                nestedContainerId = nestedContainer.getContainerId();\n            } else {\n                assertThat(nestedContainer.getContainerId()).isNotEqualTo(nestedContainerId);\n            }\n        }\n        // testClass {{{\n    }\n}\n// }\n"
  },
  {
    "path": "modules/junit-jupiter/src/test/java/org/testcontainers/junit/jupiter/TestcontainersNestedSharedContainerTests.java",
    "content": "package org.testcontainers.junit.jupiter;\n\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.GenericContainer;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n@Testcontainers\nclass TestcontainersNestedSharedContainerTests {\n\n    @Container\n    private static final GenericContainer<?> TOP_LEVEL_CONTAINER = new GenericContainer<>(\n        JUnitJupiterTestImages.HTTPD_IMAGE\n    )\n        .withExposedPorts(80);\n\n    private static String topLevelContainerId;\n\n    @Test\n    void top_level_container_should_be_running() {\n        assertThat(TOP_LEVEL_CONTAINER.isRunning()).isTrue();\n        topLevelContainerId = TOP_LEVEL_CONTAINER.getContainerId();\n    }\n\n    @Nested\n    class NestedTestCase {\n\n        @Test\n        void top_level_containers_should_be_running() {\n            assertThat(TOP_LEVEL_CONTAINER.isRunning()).isTrue();\n        }\n\n        @Test\n        void ids_should_not_change() {\n            assertThat(TOP_LEVEL_CONTAINER.getContainerId()).isEqualTo(topLevelContainerId);\n        }\n    }\n}\n"
  },
  {
    "path": "modules/junit-jupiter/src/test/java/org/testcontainers/junit/jupiter/TestcontainersRestartBetweenTests.java",
    "content": "package org.testcontainers.junit.jupiter;\n\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.GenericContainer;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n@Testcontainers\nclass TestcontainersRestartBetweenTests {\n\n    @Container\n    private GenericContainer<?> genericContainer = new GenericContainer<>(JUnitJupiterTestImages.HTTPD_IMAGE)\n        .withExposedPorts(80);\n\n    private static String lastContainerId;\n\n    @Test\n    void first_test() {\n        if (lastContainerId == null) {\n            lastContainerId = genericContainer.getContainerId();\n        } else {\n            assertThat(genericContainer.getContainerId()).isNotEqualTo(lastContainerId);\n        }\n    }\n\n    @Test\n    void second_test() {\n        if (lastContainerId == null) {\n            lastContainerId = genericContainer.getContainerId();\n        } else {\n            assertThat(genericContainer.getContainerId()).isNotEqualTo(lastContainerId);\n        }\n    }\n}\n"
  },
  {
    "path": "modules/junit-jupiter/src/test/java/org/testcontainers/junit/jupiter/TestcontainersSharedContainerTests.java",
    "content": "package org.testcontainers.junit.jupiter;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.GenericContainer;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n@Testcontainers\nclass TestcontainersSharedContainerTests {\n\n    @Container\n    private static final GenericContainer<?> GENERIC_CONTAINER = new GenericContainer<>(\n        JUnitJupiterTestImages.HTTPD_IMAGE\n    )\n        .withExposedPorts(80);\n\n    private static String lastContainerId;\n\n    @BeforeAll\n    static void doSomethingWithAContainer() {\n        assertThat(GENERIC_CONTAINER.isRunning()).isTrue();\n    }\n\n    @Test\n    void first_test() {\n        if (lastContainerId == null) {\n            lastContainerId = GENERIC_CONTAINER.getContainerId();\n        } else {\n            assertThat(GENERIC_CONTAINER.getContainerId()).isEqualTo(lastContainerId);\n        }\n    }\n\n    @Test\n    void second_test() {\n        if (lastContainerId == null) {\n            lastContainerId = GENERIC_CONTAINER.getContainerId();\n        } else {\n            assertThat(GENERIC_CONTAINER.getContainerId()).isEqualTo(lastContainerId);\n        }\n    }\n}\n"
  },
  {
    "path": "modules/junit-jupiter/src/test/java/org/testcontainers/junit/jupiter/WrongAnnotationUsageTests.java",
    "content": "package org.testcontainers.junit.jupiter;\n\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\n\n@Disabled\n@Testcontainers\nclass WrongAnnotationUsageTests {\n\n    @Container\n    private String notStartable = \"foobar\";\n\n    @Test\n    void extension_throws_exception() {\n        assert true;\n    }\n}\n"
  },
  {
    "path": "modules/junit-jupiter/src/test/java/org/testcontainers/junit/jupiter/inheritance/AbstractTestBase.java",
    "content": "package org.testcontainers.junit.jupiter.inheritance;\n\nimport org.testcontainers.junit.jupiter.Container;\nimport org.testcontainers.junit.jupiter.Testcontainers;\n\n@Testcontainers\nabstract class AbstractTestBase {\n\n    @Container\n    static RedisContainer redisPerClass = new RedisContainer();\n\n    @Container\n    RedisContainer redisPerTest = new RedisContainer();\n}\n"
  },
  {
    "path": "modules/junit-jupiter/src/test/java/org/testcontainers/junit/jupiter/inheritance/InheritedTests.java",
    "content": "package org.testcontainers.junit.jupiter.inheritance;\n\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.junit.jupiter.Container;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass InheritedTests extends AbstractTestBase {\n\n    @Container\n    private RedisContainer myRedis = new RedisContainer();\n\n    @Test\n    void step1() {\n        assertThat(redisPerClass.getJedis().incr(\"key\")).isEqualTo(1);\n        assertThat(redisPerTest.getJedis().incr(\"key\")).isEqualTo(1);\n        assertThat(myRedis.getJedis().incr(\"key\")).isEqualTo(1);\n    }\n\n    @Test\n    void step2() {\n        assertThat(redisPerClass.getJedis().incr(\"key\")).isEqualTo(2);\n        assertThat(redisPerTest.getJedis().incr(\"key\")).isEqualTo(1);\n        assertThat(myRedis.getJedis().incr(\"key\")).isEqualTo(1);\n    }\n}\n"
  },
  {
    "path": "modules/junit-jupiter/src/test/java/org/testcontainers/junit/jupiter/inheritance/RedisContainer.java",
    "content": "package org.testcontainers.junit.jupiter.inheritance;\n\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.utility.DockerImageName;\nimport redis.clients.jedis.Jedis;\n\npublic class RedisContainer extends GenericContainer<RedisContainer> {\n\n    public RedisContainer() {\n        super(DockerImageName.parse(\"redis:6-alpine\"));\n        withExposedPorts(6379);\n    }\n\n    public Jedis getJedis() {\n        return new Jedis(getHost(), getMappedPort(6379));\n    }\n}\n"
  },
  {
    "path": "modules/junit-jupiter/src/test/resources/docker-compose.yml",
    "content": "version: '2.1'\nservices:\n  whoami:\n    image: emilevauge/whoami\n"
  },
  {
    "path": "modules/junit-jupiter/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/k3s/build.gradle",
    "content": "description = \"Testcontainers :: K3S\"\n\ndependencies {\n    api project(\":testcontainers\")\n\n    // Synchronize with the jackson version, must match major and minor version\n    shaded 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.18.4'\n\n    testImplementation 'io.fabric8:kubernetes-client:7.4.0'\n    testImplementation 'io.kubernetes:client-java:25.0.0-legacy'\n}\n"
  },
  {
    "path": "modules/k3s/src/main/java/org/testcontainers/k3s/K3sContainer.java",
    "content": "package org.testcontainers.k3s;\n\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.node.ObjectNode;\nimport com.fasterxml.jackson.databind.node.TextNode;\nimport com.fasterxml.jackson.dataformat.yaml.YAMLFactory;\nimport com.github.dockerjava.api.command.InspectContainerResponse;\nimport lombok.SneakyThrows;\nimport org.apache.commons.io.IOUtils;\nimport org.testcontainers.containers.BindMode;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.nio.charset.StandardCharsets;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * Testcontainers implementation for K3S\n * <p>\n * Supported image: {@code rancher/k3s}\n */\npublic class K3sContainer extends GenericContainer<K3sContainer> {\n\n    public static int KUBE_SECURE_PORT = 6443;\n\n    public static int RANCHER_WEBHOOK_PORT = 8443;\n\n    private String kubeConfigYaml;\n\n    public K3sContainer(DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DockerImageName.parse(\"rancher/k3s\"));\n\n        addExposedPorts(KUBE_SECURE_PORT, RANCHER_WEBHOOK_PORT);\n        setPrivilegedMode(true);\n        withCreateContainerCmdModifier(it -> {\n            it.getHostConfig().withCgroupnsMode(\"host\");\n        });\n        addFileSystemBind(\"/sys/fs/cgroup\", \"/sys/fs/cgroup\", BindMode.READ_WRITE);\n\n        Map<String, String> tmpFsMapping = new HashMap<>();\n        tmpFsMapping.put(\"/run\", \"\");\n        tmpFsMapping.put(\"/var/run\", \"\");\n        setTmpFsMapping(tmpFsMapping);\n\n        setCommand(\"server\", \"--disable=traefik\", \"--tls-san=\" + this.getHost());\n        setWaitStrategy(Wait.forLogMessage(\".*Node controller sync successful.*\", 1));\n    }\n\n    @Override\n    protected void containerIsStarted(InspectContainerResponse containerInfo) {\n        String rawKubeConfig = copyFileFromContainer(\n            \"/etc/rancher/k3s/k3s.yaml\",\n            is -> IOUtils.toString(is, StandardCharsets.UTF_8)\n        );\n        String serverUrl = \"https://\" + this.getHost() + \":\" + this.getMappedPort(KUBE_SECURE_PORT);\n        kubeConfigYaml = kubeConfigWithServerUrl(rawKubeConfig, serverUrl);\n    }\n\n    /**\n     * Return the kubernetes client configuration to access k3s from the host machine.\n     *\n     * @return the kubeConfig yaml.\n     */\n    public String getKubeConfigYaml() {\n        return kubeConfigYaml;\n    }\n\n    /**\n     * Generate a kubernetes client configuration for use on a docker internal network. The kubeConfig can be used by\n     * another docker container running in the same network as the k3s container. For access from the host, use\n     * the {@link #getKubeConfigYaml()} method instead.\n     *\n     * @param networkAlias a valid network alias of the k3s container.\n     * @return the kubeConfig yaml.\n     */\n    public String generateInternalKubeConfigYaml(String networkAlias) {\n        if (this.getNetworkAliases().contains(networkAlias)) {\n            String serverUrl = \"https://\" + networkAlias + \":\" + KUBE_SECURE_PORT;\n            return kubeConfigWithServerUrl(kubeConfigYaml, serverUrl);\n        } else {\n            throw new IllegalArgumentException(networkAlias + \" is not a network alias for k3s container\");\n        }\n    }\n\n    @SneakyThrows\n    private String kubeConfigWithServerUrl(String kubeConfigYaml, String serverUrl) {\n        ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());\n\n        ObjectNode kubeConfigObjectNode = objectMapper.readValue(kubeConfigYaml, ObjectNode.class);\n\n        JsonNode clusterNode = kubeConfigObjectNode.at(\"/clusters/0/cluster\");\n        if (!clusterNode.isObject()) {\n            throw new IllegalStateException(\"'/clusters/0/cluster' expected to be an object\");\n        }\n        ObjectNode clusterConfig = (ObjectNode) clusterNode;\n        clusterConfig.replace(\"server\", new TextNode(serverUrl));\n\n        kubeConfigObjectNode.set(\"current-context\", new TextNode(\"default\"));\n\n        return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(kubeConfigObjectNode);\n    }\n}\n"
  },
  {
    "path": "modules/k3s/src/test/java/org/testcontainers/k3s/Fabric8K3sContainerTest.java",
    "content": "package org.testcontainers.k3s;\n\nimport io.fabric8.kubernetes.api.model.ContainerBuilder;\nimport io.fabric8.kubernetes.api.model.ContainerPortBuilder;\nimport io.fabric8.kubernetes.api.model.Node;\nimport io.fabric8.kubernetes.api.model.Pod;\nimport io.fabric8.kubernetes.api.model.PodBuilder;\nimport io.fabric8.kubernetes.api.model.PodSpec;\nimport io.fabric8.kubernetes.api.model.PodSpecBuilder;\nimport io.fabric8.kubernetes.api.model.ProbeBuilder;\nimport io.fabric8.kubernetes.client.Config;\nimport io.fabric8.kubernetes.client.DefaultKubernetesClient;\nimport io.fabric8.kubernetes.client.dsl.Resource;\nimport lombok.extern.slf4j.Slf4j;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.output.Slf4jLogConsumer;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.util.List;\nimport java.util.concurrent.TimeUnit;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n@Slf4j\nclass Fabric8K3sContainerTest {\n\n    @Test\n    void shouldStartAndHaveListableNode() {\n        try (\n            // starting_k3s {\n            K3sContainer k3s = new K3sContainer(DockerImageName.parse(\"rancher/k3s:v1.21.3-k3s1\"))\n                .withLogConsumer(new Slf4jLogConsumer(log))\n            // }\n        ) {\n            k3s.start();\n\n            // connecting_with_fabric8 {\n            // obtain a kubeconfig file which allows us to connect to k3s\n            String kubeConfigYaml = k3s.getKubeConfigYaml();\n\n            // requires io.fabric8:kubernetes-client:5.11.0 or higher\n            Config config = Config.fromKubeconfig(kubeConfigYaml);\n\n            DefaultKubernetesClient client = new DefaultKubernetesClient(config);\n\n            // interact with the running K3s server, e.g.:\n            List<Node> nodes = client.nodes().list().getItems();\n            // }\n\n            assertThat(nodes).hasSize(1);\n\n            // verify that we can start a pod\n            Pod helloworld = dummyStartablePod();\n            client.pods().create(helloworld);\n            client.pods().inNamespace(\"default\").withName(\"helloworld\").waitUntilReady(30, TimeUnit.SECONDS);\n\n            assertThat(client.pods().inNamespace(\"default\").withName(\"helloworld\"))\n                .extracting(Resource::isReady)\n                .isEqualTo(true);\n        }\n    }\n\n    private Pod dummyStartablePod() {\n        PodSpec podSpec = new PodSpecBuilder()\n            .withContainers(\n                new ContainerBuilder()\n                    .withName(\"helloworld\")\n                    .withImage(\"testcontainers/helloworld:1.1.0\")\n                    .withPorts(new ContainerPortBuilder().withContainerPort(8080).build())\n                    .withReadinessProbe(new ProbeBuilder().withNewTcpSocket().withNewPort(8080).endTcpSocket().build())\n                    .build()\n            )\n            .build();\n\n        return new PodBuilder()\n            .withNewMetadata()\n            .withName(\"helloworld\")\n            .withNamespace(\"default\")\n            .endMetadata()\n            .withSpec(podSpec)\n            .build();\n    }\n}\n"
  },
  {
    "path": "modules/k3s/src/test/java/org/testcontainers/k3s/KubectlContainerTest.java",
    "content": "package org.testcontainers.k3s;\n\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.Network;\nimport org.testcontainers.containers.startupcheck.OneShotStartupCheckStrategy;\nimport org.testcontainers.images.builder.Transferable;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.time.Duration;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\n\nclass KubectlContainerTest {\n\n    private static final Network network = Network.SHARED;\n\n    private static final K3sContainer k3s = new K3sContainer(DockerImageName.parse(\"rancher/k3s:v1.21.3-k3s1\"))\n        .withNetwork(network)\n        .withNetworkAliases(\"k3s\");\n\n    @BeforeAll\n    static void setup() {\n        k3s.start();\n    }\n\n    @AfterAll\n    static void teardown() {\n        k3s.stop();\n    }\n\n    @Test\n    public void shouldExposeKubeConfigForNetworkAlias() throws Exception {\n        String kubeConfigYaml = k3s.generateInternalKubeConfigYaml(\"k3s\");\n\n        try (\n            GenericContainer<?> kubectlContainer = new GenericContainer<>(\"rancher/kubectl:v1.23.3\")\n                .withNetwork(network)\n                .withCopyToContainer(Transferable.of(kubeConfigYaml), \"/.kube/config\")\n                .withCommand(\"get namespaces\")\n                .withStartupCheckStrategy(new OneShotStartupCheckStrategy().withTimeout(Duration.ofSeconds(30)))\n        ) {\n            kubectlContainer.start();\n\n            assertThat(kubectlContainer.getLogs()).contains(\"kube-system\");\n        }\n    }\n\n    @Test\n    public void shouldThrowAnExceptionForUnknownNetworkAlias() {\n        assertThatThrownBy(() -> k3s.generateInternalKubeConfigYaml(\"not-set-network-alias\"))\n            .isInstanceOf(IllegalArgumentException.class);\n    }\n}\n"
  },
  {
    "path": "modules/k3s/src/test/java/org/testcontainers/k3s/OfficialClientK3sContainerTest.java",
    "content": "package org.testcontainers.k3s;\n\nimport io.kubernetes.client.openapi.ApiClient;\nimport io.kubernetes.client.openapi.ApiException;\nimport io.kubernetes.client.openapi.apis.CoreV1Api;\nimport io.kubernetes.client.openapi.models.V1NodeList;\nimport io.kubernetes.client.util.Config;\nimport lombok.extern.slf4j.Slf4j;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.output.Slf4jLogConsumer;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.IOException;\nimport java.io.StringReader;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n@Slf4j\nclass OfficialClientK3sContainerTest {\n\n    @Test\n    void shouldStartAndHaveListableNode() throws IOException, ApiException {\n        runK3s(DockerImageName.parse(\"rancher/k3s:v1.21.3-k3s1\"));\n    }\n\n    @Test\n    void shouldStartAndHaveListableNodeUsingLowerVersion() throws IOException, ApiException {\n        runK3s(DockerImageName.parse(\"rancher/k3s:v1.20.15-k3s1\"));\n    }\n\n    private void runK3s(DockerImageName k3sDockerImage) throws IOException, ApiException {\n        try (K3sContainer k3s = new K3sContainer(k3sDockerImage).withLogConsumer(new Slf4jLogConsumer(log))) {\n            k3s.start();\n\n            // connecting_with_k8sio {\n            String kubeConfigYaml = k3s.getKubeConfigYaml();\n\n            ApiClient client = Config.fromConfig(new StringReader(kubeConfigYaml));\n            CoreV1Api api = new CoreV1Api(client);\n\n            // interact with the running K3s server, e.g.:\n            V1NodeList nodes = api.listNode(null, null, null, null, null, null, null, null, null, null, null);\n            // }\n\n            assertThat(nodes.getItems()).hasSize(1);\n        }\n    }\n}\n"
  },
  {
    "path": "modules/k3s/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/k6/build.gradle",
    "content": "description = \"Testcontainers :: k6\"\n\ndependencies {\n    api project(':testcontainers')\n}\n"
  },
  {
    "path": "modules/k6/src/main/java/org/testcontainers/k6/K6Container.java",
    "content": "package org.testcontainers.k6;\n\nimport org.apache.commons.io.FilenameUtils;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.MountableFile;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\npublic class K6Container extends GenericContainer<K6Container> {\n\n    /** Standard image for k6, as provided by Grafana. */\n    private static final DockerImageName K6_IMAGE = DockerImageName.parse(\"grafana/k6\");\n\n    private String testScript;\n\n    private List<String> cmdOptions = new ArrayList<>();\n\n    private Map<String, String> scriptVars = new HashMap<>();\n\n    /**\n     * Creates a new container instance based upon the provided image name.\n     */\n    public K6Container(String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    /**\n     * Creates a new container instance based upon the provided image.\n     */\n    public K6Container(DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(K6_IMAGE);\n    }\n\n    /**\n     * Specifies the test script to be executed within the container.\n     * @param testScript file to be copied into the container\n     * @return the builder\n     */\n    public K6Container withTestScript(MountableFile testScript) {\n        this.testScript = \"/home/k6/\" + FilenameUtils.getName(testScript.getResolvedPath());\n        withCopyFileToContainer(testScript, this.testScript);\n        return self();\n    }\n\n    /**\n     * Specifies additional command line options to be provided to the k6 command.\n     * @param options command line options\n     * @return the builder\n     */\n    public K6Container withCmdOptions(String... options) {\n        cmdOptions.addAll(Arrays.asList(options));\n        return self();\n    }\n\n    /**\n     * Adds a key-value pair for access within test scripts as an environment variable.\n     * @param key   unique identifier for the variable\n     * @param value value of the variable\n     * @return the builder\n     */\n    public K6Container withScriptVar(String key, String value) {\n        scriptVars.put(key, value);\n        return self();\n    }\n\n    /**\n     * {@inheritDoc}\n     */\n    @Override\n    protected void configure() {\n        List<String> commandParts = new ArrayList<>();\n        commandParts.add(\"run\");\n        commandParts.addAll(cmdOptions);\n        for (Map.Entry<String, String> entry : scriptVars.entrySet()) {\n            commandParts.add(\"--env\");\n            commandParts.add(String.format(\"%s=%s\", entry.getKey(), entry.getValue()));\n        }\n        commandParts.add(testScript);\n\n        setCommand(commandParts.toArray(new String[] {}));\n    }\n}\n"
  },
  {
    "path": "modules/k6/src/test/java/org/testcontainers/k6/K6ContainerTests.java",
    "content": "package org.testcontainers.k6;\n\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.output.WaitingConsumer;\nimport org.testcontainers.utility.MountableFile;\n\nimport java.util.concurrent.TimeUnit;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass K6ContainerTests {\n\n    @Test\n    void k6StandardTest() throws Exception {\n        try (\n            // standard_k6 {\n            K6Container container = new K6Container(\"grafana/k6:0.49.0\")\n                .withTestScript(MountableFile.forClasspathResource(\"scripts/test.js\"))\n                .withScriptVar(\"MY_SCRIPT_VAR\", \"are cool!\")\n                .withScriptVar(\"AN_UNUSED_VAR\", \"unused\")\n                .withCmdOptions(\"--quiet\", \"--no-usage-report\")\n            // }\n        ) {\n            container.start();\n\n            // wait {\n            WaitingConsumer consumer = new WaitingConsumer();\n            container.followOutput(consumer);\n\n            // Wait for test script results to be collected\n            consumer.waitUntil(\n                frame -> {\n                    return frame.getUtf8String().contains(\"iteration_duration\");\n                },\n                3,\n                TimeUnit.SECONDS\n            );\n            // }\n\n            assertThat(container.getLogs()).contains(\"k6 tests are cool!\");\n        }\n    }\n}\n"
  },
  {
    "path": "modules/k6/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/k6/src/test/resources/scripts/test.js",
    "content": "// access_script_vars {\n// The most basic of k6 scripts.\nexport default function(){\n    console.log(`k6 tests ${__ENV.MY_SCRIPT_VAR}`)\n}\n// }\n"
  },
  {
    "path": "modules/kafka/build.gradle",
    "content": "description = \"Testcontainers :: Kafka\"\n\ndependencies {\n    api project(':testcontainers')\n\n    testImplementation 'org.apache.kafka:kafka-clients:3.8.0'\n    testImplementation 'com.google.guava:guava:23.0'\n    testImplementation 'org.awaitility:awaitility:4.3.0'\n}\n"
  },
  {
    "path": "modules/kafka/src/main/java/org/testcontainers/containers/KafkaContainer.java",
    "content": "package org.testcontainers.containers;\n\nimport com.github.dockerjava.api.command.InspectContainerResponse;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.images.builder.Transferable;\nimport org.testcontainers.utility.ComparableVersion;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.Set;\nimport java.util.function.Supplier;\nimport java.util.stream.Collectors;\n\n/**\n * Testcontainers implementation for Apache Kafka.\n * Zookeeper can be optionally configured.\n * <p>\n * Supported image: {@code confluentinc/cp-kafka}\n * <p>\n * Exposed ports:\n * <ul>\n *     <li>Kafka: 9093</li>\n *     <li>Zookeeper: 2181</li>\n * </ul>\n *\n * @deprecated use {@link org.testcontainers.kafka.ConfluentKafkaContainer} or\n * {@link org.testcontainers.kafka.KafkaContainer} instead\n */\n@Deprecated\npublic class KafkaContainer extends GenericContainer<KafkaContainer> {\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"confluentinc/cp-kafka\");\n\n    private static final String DEFAULT_TAG = \"5.4.3\";\n\n    public static final int KAFKA_PORT = 9093;\n\n    public static final int ZOOKEEPER_PORT = 2181;\n\n    private static final String DEFAULT_INTERNAL_TOPIC_RF = \"1\";\n\n    private static final String STARTER_SCRIPT = \"/tmp/testcontainers_start.sh\";\n\n    // https://docs.confluent.io/platform/7.0.0/release-notes/index.html#ak-raft-kraft\n    private static final String MIN_KRAFT_TAG = \"7.0.0\";\n\n    public static final String DEFAULT_CLUSTER_ID = \"4L6g3nShT-eMCtK--X86sw\";\n\n    protected String externalZookeeperConnect = null;\n\n    private boolean kraftEnabled = false;\n\n    private static final String PROTOCOL_PREFIX = \"TC\";\n\n    /**\n     * @deprecated use {@link #KafkaContainer(DockerImageName)} instead\n     */\n    @Deprecated\n    public KafkaContainer() {\n        this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG));\n    }\n\n    /**\n     * @deprecated use {@link #KafkaContainer(DockerImageName)} instead\n     */\n    @Deprecated\n    public KafkaContainer(String confluentPlatformVersion) {\n        this(DEFAULT_IMAGE_NAME.withTag(confluentPlatformVersion));\n    }\n\n    public KafkaContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n    }\n\n    @Override\n    KafkaContainerDef createContainerDef() {\n        return new KafkaContainerDef();\n    }\n\n    @Override\n    KafkaContainerDef getContainerDef() {\n        return (KafkaContainerDef) super.getContainerDef();\n    }\n\n    public KafkaContainer withEmbeddedZookeeper() {\n        if (this.kraftEnabled) {\n            throw new IllegalStateException(\"Cannot configure Zookeeper when using Kraft mode\");\n        }\n        this.externalZookeeperConnect = null;\n        return self();\n    }\n\n    public KafkaContainer withExternalZookeeper(String connectString) {\n        if (this.kraftEnabled) {\n            throw new IllegalStateException(\"Cannot configure Zookeeper when using Kraft mode\");\n        }\n        this.externalZookeeperConnect = connectString;\n        return self();\n    }\n\n    public KafkaContainer withKraft() {\n        if (this.externalZookeeperConnect != null) {\n            throw new IllegalStateException(\"Cannot configure Kraft mode when Zookeeper configured\");\n        }\n        verifyMinKraftVersion();\n        this.kraftEnabled = true;\n        return self();\n    }\n\n    private void verifyMinKraftVersion() {\n        String actualVersion = DockerImageName.parse(getDockerImageName()).getVersionPart();\n        if (new ComparableVersion(actualVersion).isLessThan(MIN_KRAFT_TAG)) {\n            throw new IllegalArgumentException(\n                String.format(\n                    \"Provided Confluent Platform's version %s is not supported in Kraft mode (must be %s or above)\",\n                    actualVersion,\n                    MIN_KRAFT_TAG\n                )\n            );\n        }\n    }\n\n    private boolean isLessThanCP740() {\n        String actualVersion = DockerImageName.parse(getDockerImageName()).getVersionPart();\n        return new ComparableVersion(actualVersion).isLessThan(\"7.4.0\");\n    }\n\n    public KafkaContainer withClusterId(String clusterId) {\n        Objects.requireNonNull(clusterId, \"clusterId cannot be null\");\n        getContainerDef().withClusterId(clusterId);\n        return self();\n    }\n\n    public String getBootstrapServers() {\n        return String.format(\"PLAINTEXT://%s:%s\", getHost(), getMappedPort(KAFKA_PORT));\n    }\n\n    @Override\n    protected void configure() {\n        getContainerDef().resolveListeners();\n\n        if (this.kraftEnabled) {\n            configureKraft();\n        } else {\n            configureZookeeper();\n        }\n    }\n\n    protected void configureKraft() {\n        getContainerDef().withRaft();\n    }\n\n    protected void configureZookeeper() {\n        if (this.externalZookeeperConnect == null) {\n            getContainerDef().withEmbeddedZookeeper();\n        } else {\n            getContainerDef().withZookeeper(this.externalZookeeperConnect);\n        }\n    }\n\n    @Override\n    protected void containerIsStarting(InspectContainerResponse containerInfo) {\n        super.containerIsStarting(containerInfo);\n\n        List<String> advertisedListeners = new ArrayList<>();\n        advertisedListeners.add(getBootstrapServers());\n        advertisedListeners.add(brokerAdvertisedListener(containerInfo));\n\n        List<Supplier<String>> listenersToTransform = new ArrayList<>(getContainerDef().listeners);\n        for (int i = 0; i < listenersToTransform.size(); i++) {\n            Supplier<String> listenerSupplier = listenersToTransform.get(i);\n            String protocol = String.format(\"%s-%d\", PROTOCOL_PREFIX, i);\n            String listener = listenerSupplier.get();\n            String listenerProtocol = String.format(\"%s://%s\", protocol, listener);\n            advertisedListeners.add(listenerProtocol);\n        }\n\n        String kafkaAdvertisedListeners = String.join(\",\", advertisedListeners);\n\n        String command = \"#!/bin/bash\\n\";\n        // exporting KAFKA_ADVERTISED_LISTENERS with the container hostname\n        command += String.format(\"export KAFKA_ADVERTISED_LISTENERS=%s\\n\", kafkaAdvertisedListeners);\n\n        if (!this.kraftEnabled || isLessThanCP740()) {\n            // Optimization: skip the checks\n            command += \"echo '' > /etc/confluent/docker/ensure \\n\";\n        }\n\n        if (this.kraftEnabled) {\n            command += commandKraft();\n        } else if (this.externalZookeeperConnect == null) {\n            command += commandZookeeper();\n        }\n\n        // Run the original command\n        command += \"/etc/confluent/docker/run \\n\";\n        copyFileToContainer(Transferable.of(command, 0777), STARTER_SCRIPT);\n    }\n\n    protected String commandKraft() {\n        String command = \"sed -i '/KAFKA_ZOOKEEPER_CONNECT/d' /etc/confluent/docker/configure\\n\";\n        command +=\n            \"echo 'kafka-storage format --ignore-formatted -t \\\"\" +\n            getContainerDef().getEnvVars().get(\"CLUSTER_ID\") +\n            \"\\\" -c /etc/kafka/kafka.properties' >> /etc/confluent/docker/configure\\n\";\n        return command;\n    }\n\n    protected String commandZookeeper() {\n        String command = \"echo 'clientPort=\" + ZOOKEEPER_PORT + \"' > /tmp/zookeeper.properties\\n\";\n        command += \"echo 'dataDir=/var/lib/zookeeper/data' >> /tmp/zookeeper.properties\\n\";\n        command += \"echo 'dataLogDir=/var/lib/zookeeper/log' >> /tmp/zookeeper.properties\\n\";\n        command += \"zookeeper-server-start /tmp/zookeeper.properties &\\n\";\n        return command;\n    }\n\n    /**\n     * Add a {@link Supplier} that will provide a listener with format {@code host:port}.\n     * Host will be added as a network alias.\n     * <p>\n     * The listener will be added to the list of default listeners.\n     * <p>\n     * Default listeners:\n     * <ul>\n     *     <li>0.0.0.0:9092</li>\n     *     <li>0.0.0.0:9093</li>\n     * </ul>\n     * <p>\n     * Default advertised listeners:\n     * <ul>\n     *      <li>{@code container.getHost():container.getMappedPort(9093)}</li>\n     *      <li>{@code container.getConfig().getHostName():9092}</li>\n     * </ul>\n     * @param listenerSupplier a supplier that will provide a listener\n     * @return this {@link KafkaContainer} instance\n     */\n    public KafkaContainer withListener(Supplier<String> listenerSupplier) {\n        getContainerDef().withListener(listenerSupplier);\n        return this;\n    }\n\n    protected String brokerAdvertisedListener(InspectContainerResponse containerInfo) {\n        return String.format(\"BROKER://%s:%s\", containerInfo.getConfig().getHostName(), \"9092\");\n    }\n\n    private static class KafkaContainerDef extends ContainerDef {\n\n        private final Set<Supplier<String>> listeners = new HashSet<>();\n\n        private String clusterId = DEFAULT_CLUSTER_ID;\n\n        KafkaContainerDef() {\n            // Use two listeners with different names, it will force Kafka to communicate with itself via internal\n            // listener when KAFKA_INTER_BROKER_LISTENER_NAME is set, otherwise Kafka will try to use the advertised listener\n            addEnvVar(\"KAFKA_LISTENERS\", \"PLAINTEXT://0.0.0.0:\" + KAFKA_PORT + \",BROKER://0.0.0.0:9092\");\n            addEnvVar(\"KAFKA_LISTENER_SECURITY_PROTOCOL_MAP\", \"BROKER:PLAINTEXT,PLAINTEXT:PLAINTEXT\");\n            addEnvVar(\"KAFKA_INTER_BROKER_LISTENER_NAME\", \"BROKER\");\n\n            addEnvVar(\"KAFKA_BROKER_ID\", \"1\");\n            addEnvVar(\"KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR\", DEFAULT_INTERNAL_TOPIC_RF);\n            addEnvVar(\"KAFKA_OFFSETS_TOPIC_NUM_PARTITIONS\", DEFAULT_INTERNAL_TOPIC_RF);\n            addEnvVar(\"KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR\", DEFAULT_INTERNAL_TOPIC_RF);\n            addEnvVar(\"KAFKA_TRANSACTION_STATE_LOG_MIN_ISR\", DEFAULT_INTERNAL_TOPIC_RF);\n            addEnvVar(\"KAFKA_LOG_FLUSH_INTERVAL_MESSAGES\", Long.MAX_VALUE + \"\");\n            addEnvVar(\"KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS\", \"0\");\n\n            addExposedTcpPort(KAFKA_PORT);\n\n            setEntrypoint(\"sh\");\n            setCommand(\"-c\", \"while [ ! -f \" + STARTER_SCRIPT + \" ]; do sleep 0.1; done; \" + STARTER_SCRIPT);\n\n            setWaitStrategy(Wait.forLogMessage(\".*\\\\[KafkaServer id=\\\\d+\\\\] started.*\", 1));\n        }\n\n        private void resolveListeners() {\n            Set<String> listeners = Arrays\n                .stream(this.envVars.get(\"KAFKA_LISTENERS\").split(\",\"))\n                .collect(Collectors.toSet());\n            Set<String> listenerSecurityProtocolMap = Arrays\n                .stream(this.envVars.get(\"KAFKA_LISTENER_SECURITY_PROTOCOL_MAP\").split(\",\"))\n                .collect(Collectors.toSet());\n\n            List<Supplier<String>> listenersToTransform = new ArrayList<>(this.listeners);\n            for (int i = 0; i < listenersToTransform.size(); i++) {\n                Supplier<String> listenerSupplier = listenersToTransform.get(i);\n                String protocol = String.format(\"%s-%d\", PROTOCOL_PREFIX, i);\n                String listener = listenerSupplier.get();\n                String listenerPort = listener.split(\":\")[1];\n                String listenerProtocol = String.format(\"%s://0.0.0.0:%s\", protocol, listenerPort);\n                String protocolMap = String.format(\"%s:PLAINTEXT\", protocol);\n                listeners.add(listenerProtocol);\n                listenerSecurityProtocolMap.add(protocolMap);\n\n                String host = listener.split(\":\")[0];\n                addNetworkAlias(host);\n            }\n\n            String kafkaListeners = String.join(\",\", listeners);\n            String kafkaListenerSecurityProtocolMap = String.join(\",\", listenerSecurityProtocolMap);\n\n            this.envVars.put(\"KAFKA_LISTENERS\", kafkaListeners);\n            this.envVars.put(\"KAFKA_LISTENER_SECURITY_PROTOCOL_MAP\", kafkaListenerSecurityProtocolMap);\n        }\n\n        void withListener(Supplier<String> listenerSupplier) {\n            this.listeners.add(listenerSupplier);\n        }\n\n        void withEmbeddedZookeeper() {\n            addExposedTcpPort(ZOOKEEPER_PORT);\n            addEnvVar(\"KAFKA_ZOOKEEPER_CONNECT\", \"localhost:\" + ZOOKEEPER_PORT);\n        }\n\n        void withZookeeper(String connectionString) {\n            addEnvVar(\"KAFKA_ZOOKEEPER_CONNECT\", connectionString);\n        }\n\n        void withClusterId(String clusterId) {\n            this.clusterId = clusterId;\n        }\n\n        void withRaft() {\n            this.envVars.computeIfAbsent(\"CLUSTER_ID\", key -> clusterId);\n            this.envVars.computeIfAbsent(\"KAFKA_NODE_ID\", key -> getEnvVars().get(\"KAFKA_BROKER_ID\"));\n            addEnvVar(\"KAFKA_LISTENER_SECURITY_PROTOCOL_MAP\", kafkaListenerSecurityProtocolMap());\n            addEnvVar(\"KAFKA_LISTENERS\", kafkaListeners());\n            addEnvVar(\"KAFKA_PROCESS_ROLES\", \"broker,controller\");\n\n            String controllerQuorumVoters = String.format(\"%s@localhost:9094\", getEnvVars().get(\"KAFKA_NODE_ID\"));\n            this.envVars.computeIfAbsent(\"KAFKA_CONTROLLER_QUORUM_VOTERS\", key -> controllerQuorumVoters);\n            addEnvVar(\"KAFKA_CONTROLLER_LISTENER_NAMES\", \"CONTROLLER\");\n\n            setWaitStrategy(Wait.forLogMessage(\".*Transitioning from RECOVERY to RUNNING.*\", 1));\n        }\n\n        private String kafkaListenerSecurityProtocolMap() {\n            String kafkaListenerSecurityProtocolMapEnvVar = getEnvVars().get(\"KAFKA_LISTENER_SECURITY_PROTOCOL_MAP\");\n            String kafkaListenerSecurityProtocolMap = String.format(\n                \"%s,CONTROLLER:PLAINTEXT\",\n                kafkaListenerSecurityProtocolMapEnvVar\n            );\n            Set<String> listenerSecurityProtocolMap = new HashSet<>(\n                Arrays.asList(kafkaListenerSecurityProtocolMap.split(\",\"))\n            );\n            return String.join(\",\", listenerSecurityProtocolMap);\n        }\n\n        private String kafkaListeners() {\n            String kafkaListenersEnvVar = getEnvVars().get(\"KAFKA_LISTENERS\");\n            String kafkaListeners = String.format(\"%s,CONTROLLER://0.0.0.0:9094\", kafkaListenersEnvVar);\n            Set<String> listeners = new HashSet<>(Arrays.asList(kafkaListeners.split(\",\")));\n            return String.join(\",\", listeners);\n        }\n    }\n}\n"
  },
  {
    "path": "modules/kafka/src/main/java/org/testcontainers/kafka/ConfluentKafkaContainer.java",
    "content": "package org.testcontainers.kafka;\n\nimport com.github.dockerjava.api.command.InspectContainerResponse;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.images.builder.Transferable;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.util.ArrayList;\nimport java.util.LinkedHashSet;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.function.Supplier;\n\n/**\n * Testcontainers implementation for Confluent Kafka.\n * <p>\n * Supported image: {@code confluentinc/cp-kafka}\n * <p>\n * Exposed ports: 9092\n */\npublic class ConfluentKafkaContainer extends GenericContainer<ConfluentKafkaContainer> {\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"confluentinc/cp-kafka\");\n\n    private final Set<String> listeners = new LinkedHashSet<>();\n\n    private final Set<Supplier<String>> advertisedListeners = new LinkedHashSet<>();\n\n    public ConfluentKafkaContainer(String imageName) {\n        this(DockerImageName.parse(imageName));\n    }\n\n    public ConfluentKafkaContainer(DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n\n        withExposedPorts(KafkaHelper.KAFKA_PORT);\n        withEnv(KafkaHelper.envVars());\n\n        withCommand(KafkaHelper.COMMAND);\n        waitingFor(KafkaHelper.WAIT_STRATEGY);\n    }\n\n    @Override\n    protected void configure() {\n        KafkaHelper.resolveListeners(this, this.listeners);\n    }\n\n    @Override\n    protected void containerIsStarting(InspectContainerResponse containerInfo) {\n        String brokerAdvertisedListener = String.format(\n            \"BROKER://%s:%s\",\n            containerInfo.getConfig().getHostName(),\n            \"9093\"\n        );\n        List<String> advertisedListeners = new ArrayList<>();\n        advertisedListeners.add(\"PLAINTEXT://\" + getBootstrapServers());\n        advertisedListeners.add(brokerAdvertisedListener);\n\n        advertisedListeners.addAll(KafkaHelper.resolveAdvertisedListeners(this.advertisedListeners));\n        String kafkaAdvertisedListeners = String.join(\",\", advertisedListeners);\n\n        String command = \"#!/bin/bash\\n\";\n        // exporting KAFKA_ADVERTISED_LISTENERS with the container hostname\n        command += String.format(\"export KAFKA_ADVERTISED_LISTENERS=%s\\n\", kafkaAdvertisedListeners);\n\n        command += \"/etc/confluent/docker/run \\n\";\n        copyFileToContainer(Transferable.of(command, 0777), KafkaHelper.STARTER_SCRIPT);\n    }\n\n    /**\n     * Add a listener in the format {@code host:port}.\n     * Host will be included as a network alias.\n     * <p>\n     * Use it to register additional connections to the Kafka broker within the same container network.\n     * <p>\n     * The listener will be added to the list of default listeners.\n     * <p>\n     * Default listeners:\n     * <ul>\n     *     <li>0.0.0.0:9092</li>\n     *     <li>0.0.0.0:9093</li>\n     *     <li>0.0.0.0:9094</li>\n     * </ul>\n     * <p>\n     * The listener will be added to the list of default advertised listeners.\n     * <p>\n     * Default advertised listeners:\n     * <ul>\n     *      <li>{@code container.getHost():container.getMappedPort(9092)}</li>\n     *      <li>{@code containerInfo.getConfig().getHostName():9093}</li>\n     * </ul>\n     * @param listener a listener with format {@code host:port}\n     * @return this {@link ConfluentKafkaContainer} instance\n     */\n    public ConfluentKafkaContainer withListener(String listener) {\n        this.listeners.add(listener);\n        this.advertisedListeners.add(() -> listener);\n        return this;\n    }\n\n    /**\n     * Add a listener in the format {@code host:port} and a {@link Supplier} for the advertised listener.\n     * Host from listener will be included as a network alias.\n     * <p>\n     * Use it to register additional connections to the Kafka broker from outside the container network\n     * <p>\n     * The listener will be added to the list of default listeners.\n     * <p>\n     * Default listeners:\n     * <ul>\n     *     <li>0.0.0.0:9092</li>\n     *     <li>0.0.0.0:9093</li>\n     *     <li>0.0.0.0:9094</li>\n     * </ul>\n     * <p>\n     * The {@link Supplier} will be added to the list of default advertised listeners.\n     * <p>\n     * Default advertised listeners:\n     * <ul>\n     *      <li>{@code container.getHost():container.getMappedPort(9092)}</li>\n     *      <li>{@code containerInfo.getConfig().getHostName():9093}</li>\n     * </ul>\n     * @param listener a supplier that will provide a listener\n     * @param advertisedListener a supplier that will provide a listener\n     * @return this {@link ConfluentKafkaContainer} instance\n     */\n    public ConfluentKafkaContainer withListener(String listener, Supplier<String> advertisedListener) {\n        this.listeners.add(listener);\n        this.advertisedListeners.add(advertisedListener);\n        return this;\n    }\n\n    public String getBootstrapServers() {\n        return String.format(\"%s:%s\", getHost(), getMappedPort(KafkaHelper.KAFKA_PORT));\n    }\n}\n"
  },
  {
    "path": "modules/kafka/src/main/java/org/testcontainers/kafka/KafkaContainer.java",
    "content": "package org.testcontainers.kafka;\n\nimport com.github.dockerjava.api.command.InspectContainerResponse;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.images.builder.Transferable;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.util.ArrayList;\nimport java.util.LinkedHashSet;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.function.Supplier;\n\n/**\n * Testcontainers implementation for Apache Kafka.\n * <p>\n * Supported image: {@code apache/kafka}, {@code apache/kafka-native}\n * <p>\n * Exposed ports: 9092\n */\npublic class KafkaContainer extends GenericContainer<KafkaContainer> {\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"apache/kafka\");\n\n    private static final DockerImageName APACHE_KAFKA_NATIVE_IMAGE_NAME = DockerImageName.parse(\"apache/kafka-native\");\n\n    private static final int KAFKA_PORT = 9092;\n\n    private static final String STARTER_SCRIPT = \"/tmp/testcontainers_start.sh\";\n\n    private final Set<String> listeners = new LinkedHashSet<>();\n\n    private final Set<Supplier<String>> advertisedListeners = new LinkedHashSet<>();\n\n    public KafkaContainer(String imageName) {\n        this(DockerImageName.parse(imageName));\n    }\n\n    public KafkaContainer(DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME, APACHE_KAFKA_NATIVE_IMAGE_NAME);\n\n        withExposedPorts(KAFKA_PORT);\n        withEnv(KafkaHelper.envVars());\n\n        withCommand(KafkaHelper.COMMAND);\n        waitingFor(KafkaHelper.WAIT_STRATEGY);\n    }\n\n    @Override\n    protected void configure() {\n        KafkaHelper.resolveListeners(this, this.listeners);\n    }\n\n    @Override\n    protected void containerIsStarting(InspectContainerResponse containerInfo) {\n        String brokerAdvertisedListener = String.format(\n            \"BROKER://%s:%s\",\n            containerInfo.getConfig().getHostName(),\n            \"9093\"\n        );\n        List<String> advertisedListeners = new ArrayList<>();\n        advertisedListeners.add(\"PLAINTEXT://\" + getBootstrapServers());\n        advertisedListeners.add(brokerAdvertisedListener);\n\n        advertisedListeners.addAll(KafkaHelper.resolveAdvertisedListeners(this.advertisedListeners));\n        String kafkaAdvertisedListeners = String.join(\",\", advertisedListeners);\n\n        String command = \"#!/bin/bash\\n\";\n        // exporting KAFKA_ADVERTISED_LISTENERS with the container hostname\n        command += String.format(\"export KAFKA_ADVERTISED_LISTENERS=%s\\n\", kafkaAdvertisedListeners);\n\n        command += \"/etc/kafka/docker/run \\n\";\n        copyFileToContainer(Transferable.of(command, 0777), STARTER_SCRIPT);\n    }\n\n    /**\n     * Add a listener in the format {@code host:port}.\n     * Host will be included as a network alias.\n     * <p>\n     * Use it to register additional connections to the Kafka broker within the same container network.\n     * <p>\n     * The listener will be added to the list of default listeners.\n     * <p>\n     * Default listeners:\n     * <ul>\n     *     <li>0.0.0.0:9092</li>\n     *     <li>0.0.0.0:9093</li>\n     *     <li>0.0.0.0:9094</li>\n     * </ul>\n     * <p>\n     * The listener will be added to the list of default advertised listeners.\n     * <p>\n     * Default advertised listeners:\n     * <ul>\n     *      <li>{@code container.getConfig().getHostName():9092}</li>\n     *      <li>{@code container.getHost():container.getMappedPort(9093)}</li>\n     * </ul>\n     * @param listener a listener with format {@code host:port}\n     * @return this {@link KafkaContainer} instance\n     */\n    public KafkaContainer withListener(String listener) {\n        this.listeners.add(listener);\n        this.advertisedListeners.add(() -> listener);\n        return this;\n    }\n\n    /**\n     * Add a listener in the format {@code host:port} and a {@link Supplier} for the advertised listener.\n     * Host from listener will be included as a network alias.\n     * <p>\n     * Use it to register additional connections to the Kafka broker from outside the container network\n     * <p>\n     * The listener will be added to the list of default listeners.\n     * <p>\n     * Default listeners:\n     * <ul>\n     *     <li>0.0.0.0:9092</li>\n     *     <li>0.0.0.0:9093</li>\n     *     <li>0.0.0.0:9094</li>\n     * </ul>\n     * <p>\n     * The {@link Supplier} will be added to the list of default advertised listeners.\n     * <p>\n     * Default advertised listeners:\n     * <ul>\n     *      <li>{@code container.getConfig().getHostName():9092}</li>\n     *      <li>{@code container.getHost():container.getMappedPort(9093)}</li>\n     * </ul>\n     * @param listener a supplier that will provide a listener\n     * @param advertisedListener a supplier that will provide a listener\n     * @return this {@link KafkaContainer} instance\n     */\n    public KafkaContainer withListener(String listener, Supplier<String> advertisedListener) {\n        this.listeners.add(listener);\n        this.advertisedListeners.add(advertisedListener);\n        return this;\n    }\n\n    public String getBootstrapServers() {\n        return String.format(\"%s:%s\", getHost(), getMappedPort(KAFKA_PORT));\n    }\n}\n"
  },
  {
    "path": "modules/kafka/src/main/java/org/testcontainers/kafka/KafkaHelper.java",
    "content": "package org.testcontainers.kafka;\n\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.containers.wait.strategy.WaitStrategy;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.function.Supplier;\nimport java.util.stream.Collectors;\n\nclass KafkaHelper {\n\n    private static final String DEFAULT_INTERNAL_TOPIC_RF = \"1\";\n\n    private static final String DEFAULT_CLUSTER_ID = \"4L6g3nShT-eMCtK--X86sw\";\n\n    private static final String PROTOCOL_PREFIX = \"TC\";\n\n    static final int KAFKA_PORT = 9092;\n\n    static final String STARTER_SCRIPT = \"/tmp/testcontainers_start.sh\";\n\n    static final String[] COMMAND = {\n        \"sh\",\n        \"-c\",\n        \"while [ ! -f \" + STARTER_SCRIPT + \" ]; do sleep 0.1; done; \" + STARTER_SCRIPT,\n    };\n\n    static final WaitStrategy WAIT_STRATEGY = Wait.forLogMessage(\".*Transitioning from RECOVERY to RUNNING.*\", 1);\n\n    static Map<String, String> envVars() {\n        Map<String, String> envVars = new HashMap<>();\n        envVars.put(\"CLUSTER_ID\", DEFAULT_CLUSTER_ID);\n\n        envVars.put(\n            \"KAFKA_LISTENERS\",\n            \"PLAINTEXT://0.0.0.0:\" + KAFKA_PORT + \",BROKER://0.0.0.0:9093,CONTROLLER://0.0.0.0:9094\"\n        );\n        envVars.put(\n            \"KAFKA_LISTENER_SECURITY_PROTOCOL_MAP\",\n            \"BROKER:PLAINTEXT,PLAINTEXT:PLAINTEXT,CONTROLLER:PLAINTEXT\"\n        );\n        envVars.put(\"KAFKA_INTER_BROKER_LISTENER_NAME\", \"BROKER\");\n        envVars.put(\"KAFKA_PROCESS_ROLES\", \"broker,controller\");\n        envVars.put(\"KAFKA_CONTROLLER_LISTENER_NAMES\", \"CONTROLLER\");\n\n        envVars.put(\"KAFKA_NODE_ID\", \"1\");\n\n        String controllerQuorumVoters = String.format(\"%s@localhost:9094\", envVars.get(\"KAFKA_NODE_ID\"));\n        envVars.put(\"KAFKA_CONTROLLER_QUORUM_VOTERS\", controllerQuorumVoters);\n\n        envVars.put(\"KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR\", DEFAULT_INTERNAL_TOPIC_RF);\n        envVars.put(\"KAFKA_OFFSETS_TOPIC_NUM_PARTITIONS\", DEFAULT_INTERNAL_TOPIC_RF);\n        envVars.put(\"KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR\", DEFAULT_INTERNAL_TOPIC_RF);\n        envVars.put(\"KAFKA_TRANSACTION_STATE_LOG_MIN_ISR\", DEFAULT_INTERNAL_TOPIC_RF);\n        envVars.put(\"KAFKA_LOG_FLUSH_INTERVAL_MESSAGES\", Long.MAX_VALUE + \"\");\n        envVars.put(\"KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS\", \"0\");\n        return envVars;\n    }\n\n    static void resolveListeners(GenericContainer<?> kafkaContainer, Set<String> listenersSuppliers) {\n        Set<String> listeners = Arrays\n            .stream(kafkaContainer.getEnvMap().get(\"KAFKA_LISTENERS\").split(\",\"))\n            .collect(Collectors.toSet());\n        Set<String> listenerSecurityProtocolMap = Arrays\n            .stream(kafkaContainer.getEnvMap().get(\"KAFKA_LISTENER_SECURITY_PROTOCOL_MAP\").split(\",\"))\n            .collect(Collectors.toSet());\n\n        List<String> listenersToTransform = new ArrayList<>(listenersSuppliers);\n        for (int i = 0; i < listenersToTransform.size(); i++) {\n            String protocol = String.format(\"%s-%d\", PROTOCOL_PREFIX, i);\n            String listener = listenersToTransform.get(i);\n            String listenerHost = listener.split(\":\")[0];\n            String listenerPort = listener.split(\":\")[1];\n            String listenerProtocol = String.format(\"%s://%s:%s\", protocol, listenerHost, listenerPort);\n            String protocolMap = String.format(\"%s:PLAINTEXT\", protocol);\n            listeners.add(listenerProtocol);\n            listenerSecurityProtocolMap.add(protocolMap);\n\n            String host = listener.split(\":\")[0];\n            kafkaContainer.withNetworkAliases(host);\n        }\n\n        String kafkaListeners = String.join(\",\", listeners);\n        String kafkaListenerSecurityProtocolMap = String.join(\",\", listenerSecurityProtocolMap);\n\n        kafkaContainer.getEnvMap().put(\"KAFKA_LISTENERS\", kafkaListeners);\n        kafkaContainer.getEnvMap().put(\"KAFKA_LISTENER_SECURITY_PROTOCOL_MAP\", kafkaListenerSecurityProtocolMap);\n    }\n\n    static List<String> resolveAdvertisedListeners(Set<Supplier<String>> listenerSuppliers) {\n        List<String> advertisedListeners = new ArrayList<>();\n        List<Supplier<String>> listenersToTransform = new ArrayList<>(listenerSuppliers);\n        for (int i = 0; i < listenersToTransform.size(); i++) {\n            Supplier<String> listenerSupplier = listenersToTransform.get(i);\n            String protocol = String.format(\"%s-%d\", PROTOCOL_PREFIX, i);\n            String listener = listenerSupplier.get();\n            String listenerProtocol = String.format(\"%s://%s\", protocol, listener);\n            advertisedListeners.add(listenerProtocol);\n        }\n        return advertisedListeners;\n    }\n}\n"
  },
  {
    "path": "modules/kafka/src/test/java/org/testcontainers/AbstractKafka.java",
    "content": "package org.testcontainers;\n\nimport com.google.common.collect.ImmutableMap;\nimport org.apache.kafka.clients.admin.AdminClient;\nimport org.apache.kafka.clients.admin.AdminClientConfig;\nimport org.apache.kafka.clients.admin.NewTopic;\nimport org.apache.kafka.clients.consumer.ConsumerConfig;\nimport org.apache.kafka.clients.consumer.ConsumerRecord;\nimport org.apache.kafka.clients.consumer.ConsumerRecords;\nimport org.apache.kafka.clients.consumer.KafkaConsumer;\nimport org.apache.kafka.clients.producer.KafkaProducer;\nimport org.apache.kafka.clients.producer.ProducerConfig;\nimport org.apache.kafka.clients.producer.ProducerRecord;\nimport org.apache.kafka.common.config.SaslConfigs;\nimport org.apache.kafka.common.serialization.StringDeserializer;\nimport org.apache.kafka.common.serialization.StringSerializer;\nimport org.awaitility.Awaitility;\n\nimport java.time.Duration;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.Map;\nimport java.util.Properties;\nimport java.util.UUID;\nimport java.util.concurrent.TimeUnit;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.tuple;\n\npublic class AbstractKafka {\n\n    private static final ImmutableMap<String, String> PLAIN_PROPERTIES = ImmutableMap.of(\n        AdminClientConfig.SECURITY_PROTOCOL_CONFIG,\n        \"SASL_PLAINTEXT\",\n        SaslConfigs.SASL_MECHANISM,\n        \"PLAIN\",\n        SaslConfigs.SASL_JAAS_CONFIG,\n        \"org.apache.kafka.common.security.plain.PlainLoginModule required username=\\\"admin\\\" password=\\\"admin\\\";\"\n    );\n\n    private static final ImmutableMap<String, String> SCRAM_PROPERTIES = ImmutableMap.of(\n        AdminClientConfig.SECURITY_PROTOCOL_CONFIG,\n        \"SASL_PLAINTEXT\",\n        SaslConfigs.SASL_MECHANISM,\n        \"SCRAM-SHA-256\",\n        SaslConfigs.SASL_JAAS_CONFIG,\n        \"org.apache.kafka.common.security.scram.ScramLoginModule required username=\\\"admin\\\" password=\\\"admin\\\";\"\n    );\n\n    protected void testKafkaFunctionality(String bootstrapServers) throws Exception {\n        testKafkaFunctionality(bootstrapServers, false, 1, 1);\n    }\n\n    protected void testSecurePlainKafkaFunctionality(String bootstrapServers) throws Exception {\n        testKafkaFunctionality(bootstrapServers, true, PLAIN_PROPERTIES, 1, 1);\n    }\n\n    protected void testSecureScramKafkaFunctionality(String bootstrapServers) throws Exception {\n        testKafkaFunctionality(bootstrapServers, true, SCRAM_PROPERTIES, 1, 1);\n    }\n\n    protected void testKafkaFunctionality(String bootstrapServers, boolean authenticated, int partitions, int rf)\n        throws Exception {\n        testKafkaFunctionality(bootstrapServers, authenticated, Collections.emptyMap(), partitions, rf);\n    }\n\n    protected void testKafkaFunctionality(\n        String bootstrapServers,\n        boolean authenticated,\n        Map<String, String> authProperties,\n        int partitions,\n        int rf\n    ) throws Exception {\n        ImmutableMap<String, String> adminClientDefaultProperties = ImmutableMap.of(\n            AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG,\n            bootstrapServers\n        );\n        Properties adminClientProperties = new Properties();\n        adminClientProperties.putAll(adminClientDefaultProperties);\n\n        ImmutableMap<String, String> consumerDefaultProperties = ImmutableMap.of(\n            ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,\n            bootstrapServers,\n            ConsumerConfig.GROUP_ID_CONFIG,\n            \"tc-\" + UUID.randomUUID(),\n            ConsumerConfig.AUTO_OFFSET_RESET_CONFIG,\n            \"earliest\"\n        );\n        Properties consumerProperties = new Properties();\n        consumerProperties.putAll(consumerDefaultProperties);\n\n        ImmutableMap<String, String> producerDefaultProperties = ImmutableMap.of(\n            ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,\n            bootstrapServers,\n            ProducerConfig.CLIENT_ID_CONFIG,\n            UUID.randomUUID().toString()\n        );\n        Properties producerProperties = new Properties();\n        producerProperties.putAll(producerDefaultProperties);\n\n        if (authenticated) {\n            adminClientProperties.putAll(authProperties);\n            consumerProperties.putAll(authProperties);\n            producerProperties.putAll(authProperties);\n        }\n        try (\n            AdminClient adminClient = AdminClient.create(adminClientProperties);\n            KafkaProducer<String, String> producer = new KafkaProducer<>(\n                producerProperties,\n                new StringSerializer(),\n                new StringSerializer()\n            );\n            KafkaConsumer<String, String> consumer = new KafkaConsumer<>(\n                consumerProperties,\n                new StringDeserializer(),\n                new StringDeserializer()\n            );\n        ) {\n            String topicName = \"messages-\" + UUID.randomUUID();\n\n            Collection<NewTopic> topics = Collections.singletonList(new NewTopic(topicName, partitions, (short) rf));\n            adminClient.createTopics(topics).all().get(30, TimeUnit.SECONDS);\n\n            consumer.subscribe(Collections.singletonList(topicName));\n\n            producer.send(new ProducerRecord<>(topicName, \"testcontainers\", \"rulezzz\")).get();\n\n            Awaitility\n                .await()\n                .atMost(Duration.ofSeconds(10))\n                .untilAsserted(() -> {\n                    ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));\n\n                    assertThat(records)\n                        .hasSize(1)\n                        .extracting(ConsumerRecord::topic, ConsumerRecord::key, ConsumerRecord::value)\n                        .containsExactly(tuple(topicName, \"testcontainers\", \"rulezzz\"));\n                });\n\n            consumer.unsubscribe();\n        }\n    }\n\n    protected static String getJaasConfig() {\n        String jaasConfig =\n            \"org.apache.kafka.common.security.plain.PlainLoginModule required \" +\n            \"username=\\\"admin\\\" \" +\n            \"password=\\\"admin\\\" \" +\n            \"user_admin=\\\"admin\\\" \" +\n            \"user_test=\\\"secret\\\";\";\n        return jaasConfig;\n    }\n}\n"
  },
  {
    "path": "modules/kafka/src/test/java/org/testcontainers/KCatContainer.java",
    "content": "package org.testcontainers;\n\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.images.builder.Transferable;\n\npublic class KCatContainer extends GenericContainer<KCatContainer> {\n\n    public KCatContainer() {\n        super(\"confluentinc/cp-kcat:7.9.0\");\n        withCreateContainerCmdModifier(cmd -> {\n            cmd.withEntrypoint(\"sh\");\n        });\n        withCopyToContainer(Transferable.of(\"Message produced by kcat\"), \"/data/msgs.txt\");\n        withCommand(\"-c\", \"tail -f /dev/null\");\n    }\n}\n"
  },
  {
    "path": "modules/kafka/src/test/java/org/testcontainers/containers/KafkaContainerTest.java",
    "content": "package org.testcontainers.containers;\n\nimport com.google.common.collect.ImmutableMap;\nimport lombok.SneakyThrows;\nimport org.apache.kafka.clients.admin.AdminClient;\nimport org.apache.kafka.clients.admin.AdminClientConfig;\nimport org.apache.kafka.clients.admin.NewTopic;\nimport org.apache.kafka.common.config.SaslConfigs;\nimport org.apache.kafka.common.errors.SaslAuthenticationException;\nimport org.apache.kafka.common.errors.TopicAuthorizationException;\nimport org.awaitility.Awaitility;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.AbstractKafka;\nimport org.testcontainers.Testcontainers;\nimport org.testcontainers.images.builder.Transferable;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.MountableFile;\n\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.UUID;\nimport java.util.concurrent.TimeUnit;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\n\nclass KafkaContainerTest extends AbstractKafka {\n\n    private static final DockerImageName KAFKA_TEST_IMAGE = DockerImageName.parse(\"confluentinc/cp-kafka:6.2.1\");\n\n    private static final DockerImageName KAFKA_KRAFT_TEST_IMAGE = DockerImageName.parse(\"confluentinc/cp-kafka:7.0.1\");\n\n    private static final DockerImageName ZOOKEEPER_TEST_IMAGE = DockerImageName.parse(\n        \"confluentinc/cp-zookeeper:4.0.0\"\n    );\n\n    @Test\n    void testUsage() throws Exception {\n        try (KafkaContainer kafka = new KafkaContainer(KAFKA_TEST_IMAGE)) {\n            kafka.start();\n            testKafkaFunctionality(kafka.getBootstrapServers());\n        }\n    }\n\n    @Test\n    void testUsageWithSpecificImage() throws Exception {\n        try (\n            // constructorWithVersion {\n            KafkaContainer kafka = new KafkaContainer(DockerImageName.parse(\"confluentinc/cp-kafka:6.2.1\"))\n            // }\n        ) {\n            kafka.start();\n            testKafkaFunctionality(\n                // getBootstrapServers {\n                kafka.getBootstrapServers()\n                // }\n            );\n        }\n    }\n\n    @Test\n    void testUsageWithVersion() throws Exception {\n        try (KafkaContainer kafka = new KafkaContainer(\"6.2.1\")) {\n            kafka.start();\n            testKafkaFunctionality(kafka.getBootstrapServers());\n        }\n    }\n\n    @Test\n    void testExternalZookeeperWithExternalNetwork() throws Exception {\n        try (\n            Network network = Network.newNetwork();\n            // withExternalZookeeper {\n            KafkaContainer kafka = new KafkaContainer(DockerImageName.parse(\"confluentinc/cp-kafka:6.2.1\"))\n                .withNetwork(network)\n                .withExternalZookeeper(\"zookeeper:2181\");\n            // }\n\n            GenericContainer<?> zookeeper = new GenericContainer<>(ZOOKEEPER_TEST_IMAGE)\n                .withNetwork(network)\n                .withNetworkAliases(\"zookeeper\")\n                .withEnv(\"ZOOKEEPER_CLIENT_PORT\", \"2181\");\n        ) {\n            zookeeper.start();\n            kafka.start();\n\n            testKafkaFunctionality(kafka.getBootstrapServers());\n        }\n    }\n\n    @Test\n    void testConfluentPlatformVersion7() throws Exception {\n        try (KafkaContainer kafka = new KafkaContainer(DockerImageName.parse(\"confluentinc/cp-kafka:7.2.2\"))) {\n            kafka.start();\n            testKafkaFunctionality(kafka.getBootstrapServers());\n        }\n    }\n\n    @Test\n    void testConfluentPlatformVersion5() throws Exception {\n        try (KafkaContainer kafka = new KafkaContainer(DockerImageName.parse(\"confluentinc/cp-kafka:5.4.3\"))) {\n            kafka.start();\n            testKafkaFunctionality(kafka.getBootstrapServers());\n        }\n    }\n\n    @Test\n    void testWithHostExposedPort() throws Exception {\n        Testcontainers.exposeHostPorts(12345);\n        try (KafkaContainer kafka = new KafkaContainer(KAFKA_TEST_IMAGE)) {\n            kafka.start();\n            testKafkaFunctionality(kafka.getBootstrapServers());\n        }\n    }\n\n    @Test\n    void testWithHostExposedPortAndExternalNetwork() throws Exception {\n        Testcontainers.exposeHostPorts(12345);\n        try (KafkaContainer kafka = new KafkaContainer(KAFKA_TEST_IMAGE).withNetwork(Network.newNetwork())) {\n            kafka.start();\n            testKafkaFunctionality(kafka.getBootstrapServers());\n        }\n    }\n\n    @Test\n    void testUsageKraftBeforeConfluentPlatformVersion74() throws Exception {\n        try (\n            KafkaContainer kafka = new KafkaContainer(DockerImageName.parse(\"confluentinc/cp-kafka:7.0.1\")).withKraft()\n        ) {\n            kafka.start();\n            testKafkaFunctionality(kafka.getBootstrapServers());\n        }\n    }\n\n    @Test\n    void testUsageKraftAfterConfluentPlatformVersion74() throws Exception {\n        try (\n            // withKraftMode {\n            KafkaContainer kafka = new KafkaContainer(DockerImageName.parse(\"confluentinc/cp-kafka:7.4.0\")).withKraft()\n            // }\n        ) {\n            kafka.start();\n            testKafkaFunctionality(kafka.getBootstrapServers());\n        }\n    }\n\n    @Test\n    void testNotSupportedKraftVersion() {\n        try (\n            KafkaContainer kafka = new KafkaContainer(DockerImageName.parse(\"confluentinc/cp-kafka:6.2.1\")).withKraft()\n        ) {} catch (IllegalArgumentException e) {\n            assertThat(e.getMessage())\n                .isEqualTo(\n                    \"Provided Confluent Platform's version 6.2.1 is not supported in Kraft mode (must be 7.0.0 or above)\"\n                );\n        }\n    }\n\n    @Test\n    void testKraftZookeeperMutualExclusion() {\n        try (\n            KafkaContainer kafka = new KafkaContainer(KAFKA_KRAFT_TEST_IMAGE).withKraft().withExternalZookeeper(\"\")\n        ) {} catch (IllegalStateException e) {\n            assertThat(e.getMessage()).isEqualTo(\"Cannot configure Zookeeper when using Kraft mode\");\n        }\n\n        try (\n            KafkaContainer kafka = new KafkaContainer(KAFKA_KRAFT_TEST_IMAGE).withExternalZookeeper(\"\").withKraft()\n        ) {} catch (IllegalStateException e) {\n            assertThat(e.getMessage()).isEqualTo(\"Cannot configure Kraft mode when Zookeeper configured\");\n        }\n\n        try (\n            KafkaContainer kafka = new KafkaContainer(KAFKA_KRAFT_TEST_IMAGE).withKraft().withEmbeddedZookeeper()\n        ) {} catch (IllegalStateException e) {\n            assertThat(e.getMessage()).isEqualTo(\"Cannot configure Zookeeper when using Kraft mode\");\n        }\n    }\n\n    @Test\n    void testKraftPrecedenceOverEmbeddedZookeeper() throws Exception {\n        try (KafkaContainer kafka = new KafkaContainer(KAFKA_KRAFT_TEST_IMAGE).withEmbeddedZookeeper().withKraft()) {\n            kafka.start();\n            testKafkaFunctionality(kafka.getBootstrapServers());\n        }\n    }\n\n    @Test\n    void testUsageWithListener() throws Exception {\n        try (\n            Network network = Network.newNetwork();\n            KafkaContainer kafka = new KafkaContainer(KAFKA_KRAFT_TEST_IMAGE)\n                .withListener(() -> \"kafka:19092\")\n                .withNetwork(network);\n            // createKCatContainer {\n            GenericContainer<?> kcat = new GenericContainer<>(\"confluentinc/cp-kcat:7.9.0\")\n                .withCreateContainerCmdModifier(cmd -> {\n                    cmd.withEntrypoint(\"sh\");\n                })\n                .withCopyToContainer(Transferable.of(\"Message produced by kcat\"), \"/data/msgs.txt\")\n                .withNetwork(network)\n                .withCommand(\"-c\", \"tail -f /dev/null\")\n            // }\n        ) {\n            kafka.start();\n            kcat.start();\n            // produceConsumeMessage {\n            kcat.execInContainer(\"kcat\", \"-b\", \"kafka:19092\", \"-t\", \"msgs\", \"-P\", \"-l\", \"/data/msgs.txt\");\n            String stdout = kcat\n                .execInContainer(\"kcat\", \"-b\", \"kafka:19092\", \"-C\", \"-t\", \"msgs\", \"-c\", \"1\")\n                .getStdout();\n            // }\n            assertThat(stdout).contains(\"Message produced by kcat\");\n        }\n    }\n\n    @SneakyThrows\n    @Test\n    void shouldConfigureAuthenticationWithSaslUsingJaas() {\n        try (\n            KafkaContainer kafka = new KafkaContainer(DockerImageName.parse(\"confluentinc/cp-kafka:6.2.1\"))\n                .withEnv(\"KAFKA_LISTENER_SECURITY_PROTOCOL_MAP\", \"PLAINTEXT:SASL_PLAINTEXT,BROKER:SASL_PLAINTEXT\")\n                .withEnv(\"KAFKA_SASL_MECHANISM_INTER_BROKER_PROTOCOL\", \"PLAIN\")\n                .withEnv(\"KAFKA_LISTENER_NAME_PLAINTEXT_SASL_ENABLED_MECHANISMS\", \"PLAIN\")\n                .withEnv(\"KAFKA_LISTENER_NAME_BROKER_SASL_ENABLED_MECHANISMS\", \"PLAIN\")\n                .withEnv(\"KAFKA_LISTENER_NAME_BROKER_PLAIN_SASL_JAAS_CONFIG\", getJaasConfig())\n                .withEnv(\"KAFKA_LISTENER_NAME_PLAINTEXT_PLAIN_SASL_JAAS_CONFIG\", getJaasConfig())\n        ) {\n            kafka.start();\n\n            testSecurePlainKafkaFunctionality(kafka.getBootstrapServers());\n        }\n    }\n\n    @SneakyThrows\n    @Test\n    void shouldConfigureAuthenticationWithSaslScramUsingJaas() {\n        try (\n            KafkaContainer kafka = new KafkaContainer(DockerImageName.parse(\"confluentinc/cp-kafka:7.7.0\")) {\n                protected String commandKraft() {\n                    String command = \"sed -i '/KAFKA_ZOOKEEPER_CONNECT/d' /etc/confluent/docker/configure\\n\";\n                    command +=\n                        \"echo 'kafka-storage format --ignore-formatted -t \\\"\" +\n                        \"$CLUSTER_ID\" +\n                        \"\\\" --add-scram SCRAM-SHA-256=[name=admin,password=admin] -c /etc/kafka/kafka.properties' >> /etc/confluent/docker/configure\\n\";\n                    return command;\n                }\n            }\n                .withKraft()\n                .withEnv(\"KAFKA_LISTENER_SECURITY_PROTOCOL_MAP\", \"PLAINTEXT:SASL_PLAINTEXT,BROKER:SASL_PLAINTEXT\")\n                .withEnv(\"KAFKA_LISTENER_NAME_PLAINTEXT_SASL_ENABLED_MECHANISMS\", \"SCRAM-SHA-256\")\n                .withEnv(\"KAFKA_SASL_MECHANISM_INTER_BROKER_PROTOCOL\", \"SCRAM-SHA-256\")\n                .withEnv(\"KAFKA_SASL_ENABLED_MECHANISMS\", \"SCRAM-SHA-256\")\n                .withEnv(\"KAFKA_OPTS\", \"-Djava.security.auth.login.config=/etc/kafka/secrets/kafka_server_jaas.conf\")\n                .withCopyFileToContainer(\n                    MountableFile.forClasspathResource(\"kafka_server_jaas.conf\"),\n                    \"/etc/kafka/secrets/kafka_server_jaas.conf\"\n                )\n        ) {\n            kafka.start();\n\n            testSecureScramKafkaFunctionality(kafka.getBootstrapServers());\n        }\n    }\n\n    @SneakyThrows\n    @Test\n    void enableSaslWithUnsuccessfulTopicCreation() {\n        try (\n            KafkaContainer kafka = new KafkaContainer(DockerImageName.parse(\"confluentinc/cp-kafka:6.2.1\"))\n                .withEnv(\"KAFKA_LISTENER_SECURITY_PROTOCOL_MAP\", \"PLAINTEXT:SASL_PLAINTEXT,BROKER:SASL_PLAINTEXT\")\n                .withEnv(\"KAFKA_SASL_MECHANISM_INTER_BROKER_PROTOCOL\", \"PLAIN\")\n                .withEnv(\"KAFKA_LISTENER_NAME_PLAINTEXT_SASL_ENABLED_MECHANISMS\", \"PLAIN\")\n                .withEnv(\"KAFKA_LISTENER_NAME_BROKER_SASL_ENABLED_MECHANISMS\", \"PLAIN\")\n                .withEnv(\"KAFKA_LISTENER_NAME_BROKER_PLAIN_SASL_JAAS_CONFIG\", getJaasConfig())\n                .withEnv(\"KAFKA_LISTENER_NAME_PLAINTEXT_PLAIN_SASL_JAAS_CONFIG\", getJaasConfig())\n                .withEnv(\"KAFKA_AUTHORIZER_CLASS_NAME\", \"kafka.security.authorizer.AclAuthorizer\")\n                .withEnv(\"KAFKA_SUPER_USERS\", \"User:admin\")\n        ) {\n            kafka.start();\n\n            AdminClient adminClient = AdminClient.create(\n                ImmutableMap.of(\n                    AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG,\n                    kafka.getBootstrapServers(),\n                    AdminClientConfig.SECURITY_PROTOCOL_CONFIG,\n                    \"SASL_PLAINTEXT\",\n                    SaslConfigs.SASL_MECHANISM,\n                    \"PLAIN\",\n                    SaslConfigs.SASL_JAAS_CONFIG,\n                    \"org.apache.kafka.common.security.plain.PlainLoginModule required username=\\\"test\\\" password=\\\"secret\\\";\"\n                )\n            );\n\n            String topicName = \"messages-\" + UUID.randomUUID();\n            Collection<NewTopic> topics = Collections.singletonList(new NewTopic(topicName, 1, (short) 1));\n\n            Awaitility\n                .await()\n                .untilAsserted(() -> {\n                    assertThatThrownBy(() -> adminClient.createTopics(topics).all().get(30, TimeUnit.SECONDS))\n                        .hasCauseInstanceOf(TopicAuthorizationException.class);\n                });\n        }\n    }\n\n    @SneakyThrows\n    @Test\n    void enableSaslAndWithAuthenticationError() {\n        String jaasConfig = getJaasConfig();\n        try (\n            KafkaContainer kafka = new KafkaContainer(DockerImageName.parse(\"confluentinc/cp-kafka:6.2.1\"))\n                .withEnv(\"KAFKA_LISTENER_SECURITY_PROTOCOL_MAP\", \"PLAINTEXT:SASL_PLAINTEXT,BROKER:SASL_PLAINTEXT\")\n                .withEnv(\"KAFKA_SASL_MECHANISM_INTER_BROKER_PROTOCOL\", \"PLAIN\")\n                .withEnv(\"KAFKA_LISTENER_NAME_PLAINTEXT_SASL_ENABLED_MECHANISMS\", \"PLAIN\")\n                .withEnv(\"KAFKA_LISTENER_NAME_BROKER_SASL_ENABLED_MECHANISMS\", \"PLAIN\")\n                .withEnv(\"KAFKA_LISTENER_NAME_BROKER_PLAIN_SASL_JAAS_CONFIG\", jaasConfig)\n                .withEnv(\"KAFKA_LISTENER_NAME_PLAINTEXT_PLAIN_SASL_JAAS_CONFIG\", jaasConfig)\n        ) {\n            kafka.start();\n\n            AdminClient adminClient = AdminClient.create(\n                ImmutableMap.of(\n                    AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG,\n                    kafka.getBootstrapServers(),\n                    AdminClientConfig.SECURITY_PROTOCOL_CONFIG,\n                    \"SASL_PLAINTEXT\",\n                    SaslConfigs.SASL_MECHANISM,\n                    \"PLAIN\",\n                    SaslConfigs.SASL_JAAS_CONFIG,\n                    \"org.apache.kafka.common.security.plain.PlainLoginModule required username=\\\"test\\\" password=\\\"secretx\\\";\"\n                )\n            );\n\n            String topicName = \"messages-\" + UUID.randomUUID();\n            Collection<NewTopic> topics = Collections.singletonList(new NewTopic(topicName, 1, (short) 1));\n\n            Awaitility\n                .await()\n                .untilAsserted(() -> {\n                    assertThatThrownBy(() -> adminClient.createTopics(topics).all().get(30, TimeUnit.SECONDS))\n                        .hasCauseInstanceOf(SaslAuthenticationException.class);\n                });\n        }\n    }\n}\n"
  },
  {
    "path": "modules/kafka/src/test/java/org/testcontainers/kafka/CompatibleApacheKafkaImageTest.java",
    "content": "package org.testcontainers.kafka;\n\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.testcontainers.AbstractKafka;\n\npublic class CompatibleApacheKafkaImageTest extends AbstractKafka {\n\n    public static String[] params() {\n        return new String[] { \"apache/kafka:3.8.0\", \"apache/kafka-native:3.8.0\" };\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"params\")\n    public void testUsage(String imageName) throws Exception {\n        try (KafkaContainer kafka = new KafkaContainer(imageName)) {\n            kafka.start();\n            testKafkaFunctionality(kafka.getBootstrapServers());\n        }\n    }\n}\n"
  },
  {
    "path": "modules/kafka/src/test/java/org/testcontainers/kafka/ConfluentKafkaContainerTest.java",
    "content": "package org.testcontainers.kafka;\n\nimport com.github.dockerjava.api.command.InspectContainerResponse;\nimport lombok.SneakyThrows;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.AbstractKafka;\nimport org.testcontainers.KCatContainer;\nimport org.testcontainers.containers.Network;\nimport org.testcontainers.containers.SocatContainer;\nimport org.testcontainers.utility.MountableFile;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass ConfluentKafkaContainerTest extends AbstractKafka {\n\n    @Test\n    void testUsage() throws Exception {\n        try ( // constructorWithVersion {\n            ConfluentKafkaContainer kafka = new ConfluentKafkaContainer(\"confluentinc/cp-kafka:7.4.0\")\n            // }\n        ) {\n            kafka.start();\n            testKafkaFunctionality(kafka.getBootstrapServers());\n        }\n    }\n\n    @Test\n    void testUsageWithListener() throws Exception {\n        try (\n            Network network = Network.newNetwork();\n            // registerListener {\n            ConfluentKafkaContainer kafka = new ConfluentKafkaContainer(\"confluentinc/cp-kafka:7.4.0\")\n                .withListener(\"kafka:19092\")\n                .withNetwork(network);\n            // }\n            KCatContainer kcat = new KCatContainer().withNetwork(network)\n        ) {\n            kafka.start();\n            kcat.start();\n\n            kcat.execInContainer(\"kcat\", \"-b\", \"kafka:19092\", \"-t\", \"msgs\", \"-P\", \"-l\", \"/data/msgs.txt\");\n            String stdout = kcat\n                .execInContainer(\"kcat\", \"-b\", \"kafka:19092\", \"-C\", \"-t\", \"msgs\", \"-c\", \"1\")\n                .getStdout();\n\n            assertThat(stdout).contains(\"Message produced by kcat\");\n        }\n    }\n\n    @Test\n    void testUsageWithListenerFromProxy() throws Exception {\n        try (\n            Network network = Network.newNetwork();\n            // registerListenerFromProxy {\n            SocatContainer socat = new SocatContainer().withNetwork(network).withTarget(2000, \"kafka\", 19092);\n            ConfluentKafkaContainer kafka = new ConfluentKafkaContainer(\"confluentinc/cp-kafka:7.4.0\")\n                .withListener(\"kafka:19092\", () -> socat.getHost() + \":\" + socat.getMappedPort(2000))\n                .withNetwork(network)\n            // }\n        ) {\n            socat.start();\n            kafka.start();\n\n            String bootstrapServers = String.format(\"%s:%s\", socat.getHost(), socat.getMappedPort(2000));\n            testKafkaFunctionality(bootstrapServers);\n        }\n    }\n\n    @SneakyThrows\n    @Test\n    void shouldConfigureAuthenticationWithSaslUsingJaas() {\n        try (\n            ConfluentKafkaContainer kafka = new ConfluentKafkaContainer(\"confluentinc/cp-kafka:7.7.0\")\n                .withEnv(\n                    \"KAFKA_LISTENER_SECURITY_PROTOCOL_MAP\",\n                    \"PLAINTEXT:SASL_PLAINTEXT,BROKER:SASL_PLAINTEXT,CONTROLLER:PLAINTEXT\"\n                )\n                .withEnv(\"KAFKA_SASL_MECHANISM_INTER_BROKER_PROTOCOL\", \"PLAIN\")\n                .withEnv(\"KAFKA_LISTENER_NAME_PLAINTEXT_SASL_ENABLED_MECHANISMS\", \"PLAIN\")\n                .withEnv(\"KAFKA_LISTENER_NAME_BROKER_SASL_ENABLED_MECHANISMS\", \"PLAIN\")\n                .withEnv(\"KAFKA_LISTENER_NAME_BROKER_PLAIN_SASL_JAAS_CONFIG\", getJaasConfig())\n                .withEnv(\"KAFKA_LISTENER_NAME_PLAINTEXT_PLAIN_SASL_JAAS_CONFIG\", getJaasConfig())\n        ) {\n            kafka.start();\n\n            testSecurePlainKafkaFunctionality(kafka.getBootstrapServers());\n        }\n    }\n\n    @SneakyThrows\n    @Test\n    void shouldConfigureAuthenticationWithSaslScramUsingJaas() {\n        try (\n            ConfluentKafkaContainer kafka = new ConfluentKafkaContainer(\"confluentinc/cp-kafka:7.7.0\") {\n                @SneakyThrows\n                @Override\n                protected void containerIsStarting(InspectContainerResponse containerInfo) {\n                    String command =\n                        \"echo 'kafka-storage format --ignore-formatted -t \\\"\" +\n                        \"$CLUSTER_ID\" +\n                        \"\\\" --add-scram SCRAM-SHA-256=[name=admin,password=admin] -c /etc/kafka/kafka.properties' >> /etc/confluent/docker/configure\";\n                    execInContainer(\"bash\", \"-c\", command);\n                    super.containerIsStarting(containerInfo);\n                }\n            }\n                .withEnv(\n                    \"KAFKA_LISTENER_SECURITY_PROTOCOL_MAP\",\n                    \"PLAINTEXT:SASL_PLAINTEXT,BROKER:SASL_PLAINTEXT,CONTROLLER:PLAINTEXT\"\n                )\n                .withEnv(\"KAFKA_LISTENER_NAME_PLAINTEXT_SASL_ENABLED_MECHANISMS\", \"SCRAM-SHA-256\")\n                .withEnv(\"KAFKA_SASL_MECHANISM_INTER_BROKER_PROTOCOL\", \"SCRAM-SHA-256\")\n                .withEnv(\"KAFKA_SASL_ENABLED_MECHANISMS\", \"SCRAM-SHA-256\")\n                .withEnv(\"KAFKA_OPTS\", \"-Djava.security.auth.login.config=/etc/kafka/secrets/kafka_server_jaas.conf\")\n                .withCopyFileToContainer(\n                    MountableFile.forClasspathResource(\"kafka_server_jaas.conf\"),\n                    \"/etc/kafka/secrets/kafka_server_jaas.conf\"\n                )\n        ) {\n            kafka.start();\n\n            testSecureScramKafkaFunctionality(kafka.getBootstrapServers());\n        }\n    }\n}\n"
  },
  {
    "path": "modules/kafka/src/test/java/org/testcontainers/kafka/KafkaContainerTest.java",
    "content": "package org.testcontainers.kafka;\n\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.AbstractKafka;\nimport org.testcontainers.KCatContainer;\nimport org.testcontainers.containers.Network;\nimport org.testcontainers.containers.SocatContainer;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass KafkaContainerTest extends AbstractKafka {\n\n    @Test\n    void testUsage() throws Exception {\n        try ( // constructorWithVersion {\n            KafkaContainer kafka = new KafkaContainer(\"apache/kafka-native:3.8.0\")\n            // }\n        ) {\n            kafka.start();\n            testKafkaFunctionality(kafka.getBootstrapServers());\n        }\n    }\n\n    @Test\n    void testUsageWithListener() throws Exception {\n        try (\n            Network network = Network.newNetwork();\n            // registerListener {\n            KafkaContainer kafka = new KafkaContainer(\"apache/kafka-native:3.8.0\")\n                .withListener(\"kafka:19092\")\n                .withNetwork(network);\n            // }\n            KCatContainer kcat = new KCatContainer().withNetwork(network)\n        ) {\n            kafka.start();\n            kcat.start();\n\n            kcat.execInContainer(\"kcat\", \"-b\", \"kafka:19092\", \"-t\", \"msgs\", \"-P\", \"-l\", \"/data/msgs.txt\");\n            String stdout = kcat\n                .execInContainer(\"kcat\", \"-b\", \"kafka:19092\", \"-C\", \"-t\", \"msgs\", \"-c\", \"1\")\n                .getStdout();\n\n            assertThat(stdout).contains(\"Message produced by kcat\");\n        }\n    }\n\n    @Test\n    void testUsageWithListenerFromProxy() throws Exception {\n        try (\n            Network network = Network.newNetwork();\n            SocatContainer socat = new SocatContainer().withNetwork(network).withTarget(2000, \"kafka\", 19092);\n            KafkaContainer kafka = new KafkaContainer(\"apache/kafka-native:3.8.0\")\n                .withListener(\"kafka:19092\", () -> socat.getHost() + \":\" + socat.getMappedPort(2000))\n                .withNetwork(network)\n        ) {\n            socat.start();\n            kafka.start();\n\n            String bootstrapServers = String.format(\"%s:%s\", socat.getHost(), socat.getMappedPort(2000));\n            testKafkaFunctionality(bootstrapServers);\n        }\n    }\n}\n"
  },
  {
    "path": "modules/kafka/src/test/resources/kafka_server_jaas.conf",
    "content": "KafkaServer {\n  org.apache.kafka.common.security.scram.ScramLoginModule required\n  username=\"admin\"\n  password=\"admin\";\n};\n"
  },
  {
    "path": "modules/kafka/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/ldap/build.gradle",
    "content": "description = \"Testcontainers :: LDAP\"\n\ndependencies {\n    api project(':testcontainers')\n\n    testImplementation 'com.unboundid:unboundid-ldapsdk:7.0.4'\n}\n"
  },
  {
    "path": "modules/ldap/src/main/java/org/testcontainers/ldap/LLdapContainer.java",
    "content": "package org.testcontainers.ldap;\n\nimport com.github.dockerjava.api.command.InspectContainerResponse;\nimport lombok.extern.slf4j.Slf4j;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.utility.DockerImageName;\n\n/**\n * Testcontainers implementation for LLDAP.\n * <p>\n * Supported image: {@code lldap/lldap}\n * <p>\n * Exposed ports:\n * <ul>\n *     <li>LDAP: 3890</li>\n *     <li>UI: 17170</li>\n * </ul>\n */\n@Slf4j\npublic class LLdapContainer extends GenericContainer<LLdapContainer> {\n\n    private static final String IMAGE_VERSION = \"lldap/lldap\";\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(IMAGE_VERSION);\n\n    private static final int LDAP_PORT = 3890;\n\n    private static final int LDAPS_PORT = 6360;\n\n    private static final int UI_PORT = 17170;\n\n    public LLdapContainer(String image) {\n        this(DockerImageName.parse(image));\n    }\n\n    public LLdapContainer(DockerImageName image) {\n        super(image);\n        image.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n        addExposedPorts(LDAP_PORT, UI_PORT);\n\n        waitingFor(Wait.forHttp(\"/health\").forPort(UI_PORT).forStatusCode(200));\n    }\n\n    @Override\n    protected void containerIsStarted(InspectContainerResponse containerInfo) {\n        log.info(\"LLDAP container is ready! UI available at http://{}:{}\", getHost(), getMappedPort(UI_PORT));\n    }\n\n    public LLdapContainer withBaseDn(String baseDn) {\n        withEnv(\"LLDAP_LDAP_BASE_DN\", baseDn);\n        return this;\n    }\n\n    public LLdapContainer withUserPass(String userPass) {\n        withEnv(\"LLDAP_LDAP_USER_PASS\", userPass);\n        return this;\n    }\n\n    public int getLdapPort() {\n        int port = getEnvMap().getOrDefault(\"LLDAP_LDAPS_OPTIONS__ENABLED\", \"false\").equals(\"true\")\n            ? LDAPS_PORT\n            : LDAP_PORT;\n        return getMappedPort(port);\n    }\n\n    public String getLdapUrl() {\n        String protocol = getEnvMap().getOrDefault(\"LLDAP_LDAPS_OPTIONS__ENABLED\", \"false\").equals(\"true\")\n            ? \"ldaps\"\n            : \"ldap\";\n        return String.format(\"%s://%s:%d\", protocol, getHost(), getLdapPort());\n    }\n\n    public String getBaseDn() {\n        return getEnvMap().getOrDefault(\"LLDAP_LDAP_BASE_DN\", \"dc=example,dc=com\");\n    }\n\n    public String getUser() {\n        return String.format(\"cn=admin,ou=people,%s\", getBaseDn());\n    }\n\n    @Deprecated\n    public String getUserPass() {\n        return getEnvMap().getOrDefault(\"LLDAP_LDAP_USER_PASS\", \"password\");\n    }\n\n    public String getPassword() {\n        return getEnvMap().getOrDefault(\"LLDAP_LDAP_USER_PASS\", \"password\");\n    }\n}\n"
  },
  {
    "path": "modules/ldap/src/test/java/org/testcontainers/ldap/LLdapContainerTest.java",
    "content": "package org.testcontainers.ldap;\n\nimport com.unboundid.ldap.sdk.BindResult;\nimport com.unboundid.ldap.sdk.LDAPConnection;\nimport com.unboundid.ldap.sdk.LDAPException;\nimport com.unboundid.ldap.sdk.LDAPURL;\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass LLdapContainerTest {\n\n    @Test\n    void test() throws LDAPException {\n        try ( // container {\n            LLdapContainer lldap = new LLdapContainer(\"lldap/lldap:v0.6.1-alpine\")\n            // }\n        ) {\n            lldap.start();\n            LDAPConnection connection = new LDAPConnection(lldap.getHost(), lldap.getLdapPort());\n            BindResult result = connection.bind(lldap.getUser(), lldap.getPassword());\n            assertThat(result).isNotNull();\n        }\n    }\n\n    @Test\n    void testUsingLdapUrl() throws LDAPException {\n        try (LLdapContainer lldap = new LLdapContainer(\"lldap/lldap:v0.6.1-alpine\")) {\n            lldap.start();\n\n            LDAPURL ldapUrl = new LDAPURL(lldap.getLdapUrl());\n            LDAPConnection connection = new LDAPConnection(ldapUrl.getHost(), ldapUrl.getPort());\n            BindResult result = connection.bind(lldap.getUser(), lldap.getPassword());\n            assertThat(result).isNotNull();\n        }\n    }\n\n    @Test\n    void testWithCustomBaseDn() throws LDAPException {\n        try (\n            LLdapContainer lldap = new LLdapContainer(\"lldap/lldap:v0.6.1-alpine\")\n                .withBaseDn(\"dc=testcontainers,dc=org\")\n        ) {\n            lldap.start();\n\n            assertThat(lldap.getBaseDn()).isEqualTo(\"dc=testcontainers,dc=org\");\n\n            LDAPURL ldapUrl = new LDAPURL(lldap.getLdapUrl());\n            LDAPConnection connection = new LDAPConnection(ldapUrl.getHost(), ldapUrl.getPort());\n            BindResult result = connection.bind(lldap.getUser(), lldap.getPassword());\n            assertThat(result).isNotNull();\n        }\n    }\n\n    @Test\n    void testWithCustomUserPass() throws LDAPException {\n        try (LLdapContainer lldap = new LLdapContainer(\"lldap/lldap:v0.6.1-alpine\").withUserPass(\"adminPas$word\")) {\n            lldap.start();\n\n            LDAPURL ldapUrl = new LDAPURL(lldap.getLdapUrl());\n            LDAPConnection connection = new LDAPConnection(ldapUrl.getHost(), ldapUrl.getPort());\n            BindResult result = connection.bind(lldap.getUser(), lldap.getPassword());\n            assertThat(result).isNotNull();\n        }\n    }\n}\n"
  },
  {
    "path": "modules/ldap/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/localstack/build.gradle",
    "content": "description = \"Testcontainers :: Localstack\"\n\ndependencies {\n    api project(':testcontainers')\n\n    testImplementation platform(\"software.amazon.awssdk:bom:2.40.4\")\n    testImplementation 'software.amazon.awssdk:s3'\n    testImplementation 'software.amazon.awssdk:sqs'\n    testImplementation 'software.amazon.awssdk:cloudwatchlogs'\n    testImplementation 'software.amazon.awssdk:lambda'\n    testImplementation 'software.amazon.awssdk:kms'\n}\n"
  },
  {
    "path": "modules/localstack/src/main/java/org/testcontainers/containers/localstack/LocalStackContainer.java",
    "content": "package org.testcontainers.containers.localstack;\n\nimport com.github.dockerjava.api.command.InspectContainerResponse;\nimport lombok.Getter;\nimport lombok.RequiredArgsConstructor;\nimport lombok.experimental.FieldDefaults;\nimport lombok.extern.slf4j.Slf4j;\nimport org.rnorth.ducttape.Preconditions;\nimport org.testcontainers.DockerClientFactory;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.images.builder.Transferable;\nimport org.testcontainers.utility.ComparableVersion;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.net.InetAddress;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.net.UnknownHostException;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\n/**\n * Testcontainers implementation for LocalStack.\n * <p>\n * Supported images: {@code localstack/localstack}, {@code localstack/localstack-pro}\n * <p>\n * Exposed ports: 4566\n *\n * @deprecated use {@link org.testcontainers.localstack.LocalStackContainer} instead.\n */\n@Slf4j\n@Deprecated\npublic class LocalStackContainer extends GenericContainer<LocalStackContainer> {\n\n    static final int PORT = 4566;\n\n    @Deprecated\n    private static final String HOSTNAME_EXTERNAL_ENV_VAR = \"HOSTNAME_EXTERNAL\";\n\n    private static final String LOCALSTACK_HOST_ENV_VAR = \"LOCALSTACK_HOST\";\n\n    private final List<EnabledService> services = new ArrayList<>();\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"localstack/localstack\");\n\n    private static final DockerImageName LOCALSTACK_PRO_IMAGE_NAME = DockerImageName.parse(\"localstack/localstack-pro\");\n\n    private static final String DEFAULT_TAG = \"0.11.2\";\n\n    private static final String DEFAULT_REGION = \"us-east-1\";\n\n    private static final String DEFAULT_AWS_ACCESS_KEY_ID = \"test\";\n\n    private static final String DEFAULT_AWS_SECRET_ACCESS_KEY = \"test\";\n\n    private static final String STARTER_SCRIPT = \"/testcontainers_start.sh\";\n\n    @Deprecated\n    public static final String VERSION = DEFAULT_TAG;\n\n    /**\n     * Whether or to assume that all APIs run on different ports (when <code>true</code>) or are\n     * exposed on a single port (<code>false</code>). From the Localstack README:\n     *\n     * <blockquote>Note: Starting with version 0.11.0, all APIs are exposed via a single edge\n     * service [...] The API-specific endpoints below are still left for backward-compatibility but\n     * may get removed in a future release - please reconfigure your client SDKs to start using the\n     * single edge endpoint URL!</blockquote>\n     * <p>\n     * Testcontainers will use the tag of the docker image to infer whether or not the used version\n     * of Localstack supports this feature.\n     */\n    private final boolean legacyMode;\n\n    /**\n     * Starting with version 0.13.0, setting services list on Localstack is not required. When <code>false</code>,\n     * containers are started lazily. When <code>true</code>, container fails to start if services list is not provided.\n     *\n     * Testcontainers will use the tag of the docker image to infer whether or not the used version\n     * of Localstack required services list.\n     */\n    private final boolean servicesEnvVarRequired;\n\n    private final boolean isVersion2;\n\n    /**\n     * @deprecated use {@link #LocalStackContainer(DockerImageName)} instead\n     */\n    @Deprecated\n    public LocalStackContainer() {\n        this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG));\n    }\n\n    /**\n     * @deprecated use {@link #LocalStackContainer(DockerImageName)} instead\n     */\n    @Deprecated\n    public LocalStackContainer(String version) {\n        this(DEFAULT_IMAGE_NAME.withTag(version));\n    }\n\n    /**\n     * @param dockerImageName    image name to use for Localstack\n     */\n    public LocalStackContainer(final DockerImageName dockerImageName) {\n        this(dockerImageName, shouldRunInLegacyMode(dockerImageName.getVersionPart()));\n    }\n\n    /**\n     * @param dockerImageName    image name to use for Localstack\n     * @param useLegacyMode      if true, each AWS service is exposed on a different port\n     * @deprecated use {@link #LocalStackContainer(DockerImageName)} instead\n     */\n    @Deprecated\n    public LocalStackContainer(final DockerImageName dockerImageName, boolean useLegacyMode) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME, LOCALSTACK_PRO_IMAGE_NAME);\n\n        this.legacyMode = useLegacyMode;\n        String version = dockerImageName.getVersionPart();\n        this.servicesEnvVarRequired = isServicesEnvVarRequired(version);\n        this.isVersion2 = isVersion2(version);\n\n        withFileSystemBind(DockerClientFactory.instance().getRemoteDockerUnixSocketPath(), \"/var/run/docker.sock\");\n        waitingFor(Wait.forLogMessage(\".*Ready\\\\.\\n\", 1));\n        withCreateContainerCmdModifier(cmd -> {\n            cmd.withEntrypoint(\n                \"sh\",\n                \"-c\",\n                \"while [ ! -f \" + STARTER_SCRIPT + \" ]; do sleep 0.1; done; \" + STARTER_SCRIPT\n            );\n        });\n    }\n\n    private static boolean isVersion2(String version) {\n        if (version.equals(\"latest\")) {\n            return true;\n        }\n\n        ComparableVersion comparableVersion = new ComparableVersion(version);\n        return comparableVersion.isGreaterThanOrEqualTo(\"2.0.0\");\n    }\n\n    private static boolean isServicesEnvVarRequired(String version) {\n        if (version.equals(\"latest\")) {\n            return false;\n        }\n\n        ComparableVersion comparableVersion = new ComparableVersion(version);\n        if (comparableVersion.isSemanticVersion()) {\n            return comparableVersion.isLessThan(\"0.13\");\n        }\n\n        log.warn(\"Version {} is not a semantic version, services list is required.\", version);\n\n        return true;\n    }\n\n    static boolean shouldRunInLegacyMode(String version) {\n        // assume that the latest images are up-to-date\n        // also consider images with extra packages (like latest-bigdata) and service-specific images (like s3-latest)\n        if (version.equals(\"latest\") || version.startsWith(\"latest-\") || version.endsWith(\"-latest\")) {\n            return false;\n        }\n\n        ComparableVersion comparableVersion = new ComparableVersion(version);\n        if (comparableVersion.isSemanticVersion()) {\n            boolean versionRequiresLegacyMode = comparableVersion.isLessThan(\"0.11\");\n            return versionRequiresLegacyMode;\n        }\n\n        log.warn(\"Version {} is not a semantic version, LocalStack will run in legacy mode.\", version);\n        log.warn(\n            \"Consider using \\\"LocalStackContainer(DockerImageName dockerImageName, boolean legacyMode)\\\" constructor if you want to disable legacy mode.\"\n        );\n        return true;\n    }\n\n    @Override\n    protected void configure() {\n        super.configure();\n\n        if (this.servicesEnvVarRequired) {\n            Preconditions.check(\"services list must not be empty\", !services.isEmpty());\n        }\n\n        if (!services.isEmpty()) {\n            withEnv(\"SERVICES\", services.stream().map(EnabledService::getName).collect(Collectors.joining(\",\")));\n            if (this.servicesEnvVarRequired) {\n                withEnv(\"EAGER_SERVICE_LOADING\", \"1\");\n            }\n        }\n\n        if (this.isVersion2) {\n            resolveHostname(LOCALSTACK_HOST_ENV_VAR);\n        } else {\n            resolveHostname(HOSTNAME_EXTERNAL_ENV_VAR);\n        }\n\n        exposePorts();\n    }\n\n    @Override\n    protected void containerIsStarting(InspectContainerResponse containerInfo) {\n        String command = \"#!/bin/bash\\n\";\n        command += \"export LAMBDA_DOCKER_FLAGS=\" + configureServiceContainerLabels(\"LAMBDA_DOCKER_FLAGS\") + \"\\n\";\n        command += \"export ECS_DOCKER_FLAGS=\" + configureServiceContainerLabels(\"ECS_DOCKER_FLAGS\") + \"\\n\";\n        command += \"export EC2_DOCKER_FLAGS=\" + configureServiceContainerLabels(\"EC2_DOCKER_FLAGS\") + \"\\n\";\n        command += \"export BATCH_DOCKER_FLAGS=\" + configureServiceContainerLabels(\"BATCH_DOCKER_FLAGS\") + \"\\n\";\n        command += \"/usr/local/bin/docker-entrypoint.sh\\n\";\n        copyFileToContainer(Transferable.of(command, 0777), STARTER_SCRIPT);\n    }\n\n    /**\n     * Configure the LocalStack container to include the default testcontainers labels on all spawned lambda containers\n     * Necessary to properly clean up lambda containers even if the LocalStack container is killed before it gets the\n     * chance.\n     * @return the lambda container labels as a string\n     */\n    private String configureServiceContainerLabels(String existingEnvFlagKey) {\n        String internalMarkerFlags = internalMarkerLabels();\n        String existingFlags = getEnvMap().get(existingEnvFlagKey);\n        if (existingFlags != null) {\n            internalMarkerFlags = existingFlags + \" \" + internalMarkerFlags;\n        }\n        return \"\\\"\" + internalMarkerFlags + \"\\\"\";\n    }\n\n    /**\n     * Provides a docker argument string including all default labels set on testcontainers containers (excluding reuse labels)\n     * @return Argument string in the format `-l key1=value1 -l key2=value2`\n     */\n    private String internalMarkerLabels() {\n        return getContainerInfo()\n            .getConfig()\n            .getLabels()\n            .entrySet()\n            .stream()\n            .filter(entry -> entry.getKey().startsWith(DockerClientFactory.TESTCONTAINERS_LABEL))\n            .filter(entry -> {\n                return (\n                    !entry.getKey().equals(\"org.testcontainers.hash\") &&\n                    !entry.getKey().equals(\"org.testcontainers.copied_files.hash\")\n                );\n            })\n            .map(entry -> String.format(\"-l %s=%s\", entry.getKey(), entry.getValue()))\n            .collect(Collectors.joining(\" \"));\n    }\n\n    private void resolveHostname(String envVar) {\n        String hostnameExternalReason;\n        if (getEnvMap().containsKey(envVar)) {\n            // do nothing\n            hostnameExternalReason = \"explicitly as environment variable\";\n        } else if (getNetwork() != null && getNetworkAliases() != null && getNetworkAliases().size() >= 1) {\n            withEnv(envVar, getNetworkAliases().get(getNetworkAliases().size() - 1)); // use the last network alias set\n            hostnameExternalReason = \"to match last network alias on container with non-default network\";\n        } else {\n            withEnv(envVar, getHost());\n            hostnameExternalReason = \"to match host-routable address for container\";\n        }\n\n        logger()\n            .info(\"{} environment variable set to {} ({})\", envVar, getEnvMap().get(envVar), hostnameExternalReason);\n    }\n\n    private void exposePorts() {\n        if (legacyMode) {\n            services.stream().map(this::getServicePort).distinct().forEach(this::addExposedPort);\n        } else {\n            this.addExposedPort(PORT);\n        }\n    }\n\n    public LocalStackContainer withServices(Service... services) {\n        this.services.addAll(Arrays.asList(services));\n        return self();\n    }\n\n    /**\n     * Declare a set of simulated AWS services that should be launched by this container.\n     * @param services one or more service names\n     * @return this container object\n     */\n    public LocalStackContainer withServices(EnabledService... services) {\n        this.services.addAll(Arrays.asList(services));\n        return self();\n    }\n\n    public URI getEndpointOverride(Service service) {\n        return getEndpointOverride((EnabledService) service);\n    }\n\n    /**\n     * Provides an endpoint override that is preconfigured to communicate with a given simulated service.\n     * The provided endpoint override should be set in the AWS Java SDK v2 when building a client, e.g.:\n     * <pre><code>S3Client s3 = S3Client\n             .builder()\n             .endpointOverride(localstack.getEndpointOverride(LocalStackContainer.Service.S3))\n             .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(\n             localstack.getAccessKey(), localstack.getSecretKey()\n             )))\n             .region(Region.of(localstack.getRegion()))\n             .build()\n             </code></pre>\n     * <p><strong>Please note that this method is only intended to be used for configuring AWS SDK clients\n     * that are running on the test host. If other containers need to call this one, they should be configured\n     * specifically to do so using a Docker network and appropriate addressing.</strong></p>\n     *\n     * @param service the service that is to be accessed\n     * @return an {@link URI} endpoint override\n     */\n    public URI getEndpointOverride(EnabledService service) {\n        try {\n            final String address = getHost();\n            String ipAddress = address;\n            // resolve IP address and use that as the endpoint so that path-style access is automatically used for S3\n            ipAddress = InetAddress.getByName(address).getHostAddress();\n            return new URI(\"http://\" + ipAddress + \":\" + getMappedPort(getServicePort(service)));\n        } catch (UnknownHostException | URISyntaxException e) {\n            throw new IllegalStateException(\"Cannot obtain endpoint URL\", e);\n        }\n    }\n\n    /**\n     * Provides an endpoint to communicate with LocalStack service.\n     * The provided endpoint should be set in the AWS Java SDK v2 when building a client, e.g.:\n     * <pre><code>S3Client s3 = S3Client\n             .builder()\n             .endpointOverride(localstack.getEndpoint())\n             .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(\n             localstack.getAccessKey(), localstack.getSecretKey()\n             )))\n             .region(Region.of(localstack.getRegion()))\n             .build()\n             </code></pre>\n     * <p><strong>Please note that this method is only intended to be used for configuring AWS SDK clients\n     * that are running on the test host. If other containers need to call this one, they should be configured\n     * specifically to do so using a Docker network and appropriate addressing.</strong></p>\n     *\n     * @return an {@link URI} endpoint\n     */\n    public URI getEndpoint() {\n        try {\n            final String address = getHost();\n            // resolve IP address and use that as the endpoint so that path-style access is automatically used for S3\n            String ipAddress = InetAddress.getByName(address).getHostAddress();\n            return new URI(\"http://\" + ipAddress + \":\" + getMappedPort(PORT));\n        } catch (UnknownHostException | URISyntaxException e) {\n            throw new IllegalStateException(\"Cannot obtain endpoint URL\", e);\n        }\n    }\n\n    private int getServicePort(EnabledService service) {\n        return legacyMode ? service.getPort() : PORT;\n    }\n\n    /**\n     * Provides a default access key that is preconfigured to communicate with a given simulated service.\n     * <a href=\"https://github.com/localstack/localstack/blob/master/doc/interaction/README.md?plain=1#L32\">AWS Access Key</a>\n     * The access key can be used to construct AWS SDK v2 clients:\n     * <pre><code>S3Client s3 = S3Client\n             .builder()\n             .endpointOverride(localstack.getEndpointOverride(LocalStackContainer.Service.S3))\n             .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(\n             localstack.getAccessKey(), localstack.getSecretKey()\n             )))\n             .region(Region.of(localstack.getRegion()))\n             .build()\n     </code></pre>\n     * @return a default access key\n     */\n    public String getAccessKey() {\n        return this.getEnvMap().getOrDefault(\"AWS_ACCESS_KEY_ID\", DEFAULT_AWS_ACCESS_KEY_ID);\n    }\n\n    /**\n     * Provides a default secret key that is preconfigured to communicate with a given simulated service.\n     * <a href=\"https://github.com/localstack/localstack/blob/master/doc/interaction/README.md?plain=1#L32\">AWS Secret Key</a>\n     * The secret key can be used to construct AWS SDK v2 clients:\n     * <pre><code>S3Client s3 = S3Client\n             .builder()\n             .endpointOverride(localstack.getEndpointOverride(LocalStackContainer.Service.S3))\n             .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(\n             localstack.getAccessKey(), localstack.getSecretKey()\n             )))\n             .region(Region.of(localstack.getRegion()))\n             .build()\n     </code></pre>\n     * @return a default secret key\n     */\n    public String getSecretKey() {\n        return this.getEnvMap().getOrDefault(\"AWS_SECRET_ACCESS_KEY\", DEFAULT_AWS_SECRET_ACCESS_KEY);\n    }\n\n    /**\n     * Provides a default region that is preconfigured to communicate with a given simulated service.\n     * The region can be used to construct AWS SDK v2 clients:\n     * <pre><code>S3Client s3 = S3Client\n             .builder()\n             .endpointOverride(localstack.getEndpointOverride(LocalStackContainer.Service.S3))\n             .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(\n             localstack.getAccessKey(), localstack.getSecretKey()\n             )))\n             .region(Region.of(localstack.getRegion()))\n             .build()\n     </code></pre>\n     * @return a default region\n     */\n    public String getRegion() {\n        return this.getEnvMap().getOrDefault(\"DEFAULT_REGION\", DEFAULT_REGION);\n    }\n\n    public interface EnabledService {\n        static EnabledService named(String name) {\n            return () -> name;\n        }\n\n        String getName();\n\n        default int getPort() {\n            return PORT;\n        }\n    }\n\n    @RequiredArgsConstructor\n    @Getter\n    @FieldDefaults(makeFinal = true)\n    public enum Service implements EnabledService {\n        API_GATEWAY(\"apigateway\", 4567),\n        EC2(\"ec2\", 4597),\n        KINESIS(\"kinesis\", 4568),\n        DYNAMODB(\"dynamodb\", 4569),\n        DYNAMODB_STREAMS(\"dynamodbstreams\", 4570),\n        // TODO: Clarify usage for ELASTICSEARCH and ELASTICSEARCH_SERVICE\n        //        ELASTICSEARCH(\"es\",           4571),\n        S3(\"s3\", 4572),\n        FIREHOSE(\"firehose\", 4573),\n        LAMBDA(\"lambda\", 4574),\n        SNS(\"sns\", 4575),\n        SQS(\"sqs\", 4576),\n        REDSHIFT(\"redshift\", 4577),\n        //        ELASTICSEARCH_SERVICE(\"\",   4578),\n        SES(\"ses\", 4579),\n        ROUTE53(\"route53\", 4580),\n        CLOUDFORMATION(\"cloudformation\", 4581),\n        CLOUDWATCH(\"cloudwatch\", 4582),\n        SSM(\"ssm\", 4583),\n        SECRETSMANAGER(\"secretsmanager\", 4584),\n        STEPFUNCTIONS(\"stepfunctions\", 4585),\n        CLOUDWATCHLOGS(\"logs\", 4586),\n        STS(\"sts\", 4592),\n        IAM(\"iam\", 4593),\n        KMS(\"kms\", 4599);\n\n        String localStackName;\n\n        int port;\n\n        @Override\n        public String getName() {\n            return localStackName;\n        }\n\n        @Deprecated\n        /*\n            Since version 0.11, LocalStack exposes all services on a single (4566) port.\n         */\n        public int getPort() {\n            return port;\n        }\n    }\n}\n"
  },
  {
    "path": "modules/localstack/src/main/java/org/testcontainers/localstack/LocalStackContainer.java",
    "content": "package org.testcontainers.localstack;\n\nimport com.github.dockerjava.api.command.InspectContainerResponse;\nimport lombok.extern.slf4j.Slf4j;\nimport org.testcontainers.DockerClientFactory;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.images.builder.Transferable;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.net.InetAddress;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.net.UnknownHostException;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\n/**\n * Testcontainers implementation for LocalStack.\n * <p>\n * Supported images: {@code localstack/localstack}, {@code localstack/localstack-pro}\n * <p>\n * Exposed ports: 4566\n */\n@Slf4j\npublic class LocalStackContainer extends GenericContainer<LocalStackContainer> {\n\n    static final int PORT = 4566;\n\n    private final List<String> services = new ArrayList<>();\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"localstack/localstack\");\n\n    private static final DockerImageName LOCALSTACK_PRO_IMAGE_NAME = DockerImageName.parse(\"localstack/localstack-pro\");\n\n    private static final String DEFAULT_REGION = \"us-east-1\";\n\n    private static final String DEFAULT_AWS_ACCESS_KEY_ID = \"test\";\n\n    private static final String DEFAULT_AWS_SECRET_ACCESS_KEY = \"test\";\n\n    private static final String STARTER_SCRIPT = \"/testcontainers_start.sh\";\n\n    /**\n     * @param dockerImageName    image name to use for Localstack\n     */\n    public LocalStackContainer(final String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    /**\n     * @param dockerImageName    image name to use for Localstack\n     */\n    public LocalStackContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME, LOCALSTACK_PRO_IMAGE_NAME);\n\n        withExposedPorts(PORT);\n        withFileSystemBind(DockerClientFactory.instance().getRemoteDockerUnixSocketPath(), \"/var/run/docker.sock\");\n        waitingFor(Wait.forLogMessage(\".*Ready\\\\.\\n\", 1));\n        withCreateContainerCmdModifier(cmd -> {\n            cmd.withEntrypoint(\n                \"sh\",\n                \"-c\",\n                \"while [ ! -f \" + STARTER_SCRIPT + \" ]; do sleep 0.1; done; \" + STARTER_SCRIPT\n            );\n        });\n    }\n\n    @Override\n    protected void configure() {\n        if (!services.isEmpty()) {\n            withEnv(\"SERVICES\", String.join(\",\", this.services));\n        }\n    }\n\n    @Override\n    protected void containerIsStarting(InspectContainerResponse containerInfo) {\n        String command = \"#!/bin/bash\\n\";\n        command += \"export LAMBDA_DOCKER_FLAGS=\" + configureServiceContainerLabels(\"LAMBDA_DOCKER_FLAGS\") + \"\\n\";\n        command += \"export ECS_DOCKER_FLAGS=\" + configureServiceContainerLabels(\"ECS_DOCKER_FLAGS\") + \"\\n\";\n        command += \"export EC2_DOCKER_FLAGS=\" + configureServiceContainerLabels(\"EC2_DOCKER_FLAGS\") + \"\\n\";\n        command += \"export BATCH_DOCKER_FLAGS=\" + configureServiceContainerLabels(\"BATCH_DOCKER_FLAGS\") + \"\\n\";\n        command += \"/usr/local/bin/docker-entrypoint.sh\\n\";\n        copyFileToContainer(Transferable.of(command, 0777), STARTER_SCRIPT);\n    }\n\n    /**\n     * Configure the LocalStack container to include the default testcontainers labels on all spawned lambda containers\n     * Necessary to properly clean up lambda containers even if the LocalStack container is killed before it gets the\n     * chance.\n     * @return the lambda container labels as a string\n     */\n    private String configureServiceContainerLabels(String existingEnvFlagKey) {\n        String internalMarkerFlags = internalMarkerLabels();\n        String existingFlags = getEnvMap().get(existingEnvFlagKey);\n        if (existingFlags != null) {\n            internalMarkerFlags = existingFlags + \" \" + internalMarkerFlags;\n        }\n        return \"\\\"\" + internalMarkerFlags + \"\\\"\";\n    }\n\n    /**\n     * Provides a docker argument string including all default labels set on testcontainers containers (excluding reuse labels)\n     * @return Argument string in the format `-l key1=value1 -l key2=value2`\n     */\n    private String internalMarkerLabels() {\n        return getContainerInfo()\n            .getConfig()\n            .getLabels()\n            .entrySet()\n            .stream()\n            .filter(entry -> entry.getKey().startsWith(DockerClientFactory.TESTCONTAINERS_LABEL))\n            .filter(entry -> {\n                return (\n                    !entry.getKey().equals(\"org.testcontainers.hash\") &&\n                    !entry.getKey().equals(\"org.testcontainers.copied_files.hash\")\n                );\n            })\n            .map(entry -> String.format(\"-l %s=%s\", entry.getKey(), entry.getValue()))\n            .collect(Collectors.joining(\" \"));\n    }\n\n    /**\n     * Declare a set of simulated AWS services that should be launched by this container.\n     * @param services one or more service names\n     * @return this container object\n     */\n    public LocalStackContainer withServices(String... services) {\n        this.services.addAll(Arrays.asList(services));\n        return self();\n    }\n\n    /**\n     * Provides an endpoint to communicate with LocalStack service.\n     * The provided endpoint should be set in the AWS Java SDK v2 when building a client, e.g.:\n     * <pre><code>S3Client s3 = S3Client\n             .builder()\n             .endpointOverride(localstack.getEndpoint())\n             .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(\n             localstack.getAccessKey(), localstack.getSecretKey()\n             )))\n             .region(Region.of(localstack.getRegion()))\n             .build()\n             </code></pre>\n     * <p><strong>Please note that this method is only intended to be used for configuring AWS SDK clients\n     * that are running on the test host. If other containers need to call this one, they should be configured\n     * specifically to do so using a Docker network and appropriate addressing.</strong></p>\n     *\n     * @return an {@link URI} endpoint\n     */\n    public URI getEndpoint() {\n        try {\n            final String address = getHost();\n            // resolve IP address and use that as the endpoint so that path-style access is automatically used for S3\n            String ipAddress = InetAddress.getByName(address).getHostAddress();\n            return new URI(\"http://\" + ipAddress + \":\" + getMappedPort(PORT));\n        } catch (UnknownHostException | URISyntaxException e) {\n            throw new IllegalStateException(\"Cannot obtain endpoint URL\", e);\n        }\n    }\n\n    /**\n     * Provides a default access key that is preconfigured to communicate with a given simulated service.\n     * <a href=\"https://github.com/localstack/localstack/blob/master/doc/interaction/README.md?plain=1#L32\">AWS Access Key</a>\n     * The access key can be used to construct AWS SDK v2 clients:\n     * <pre><code>S3Client s3 = S3Client\n             .builder()\n             .endpointOverride(localstack.getEndpointOverride(LocalStackContainer.Service.S3))\n             .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(\n             localstack.getAccessKey(), localstack.getSecretKey()\n             )))\n             .region(Region.of(localstack.getRegion()))\n             .build()\n     </code></pre>\n     * @return a default access key\n     */\n    public String getAccessKey() {\n        return this.getEnvMap().getOrDefault(\"AWS_ACCESS_KEY_ID\", DEFAULT_AWS_ACCESS_KEY_ID);\n    }\n\n    /**\n     * Provides a default secret key that is preconfigured to communicate with a given simulated service.\n     * <a href=\"https://github.com/localstack/localstack/blob/master/doc/interaction/README.md?plain=1#L32\">AWS Secret Key</a>\n     * The secret key can be used to construct AWS SDK v2 clients:\n     * <pre><code>S3Client s3 = S3Client\n             .builder()\n             .endpointOverride(localstack.getEndpointOverride(LocalStackContainer.Service.S3))\n             .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(\n             localstack.getAccessKey(), localstack.getSecretKey()\n             )))\n             .region(Region.of(localstack.getRegion()))\n             .build()\n     </code></pre>\n     * @return a default secret key\n     */\n    public String getSecretKey() {\n        return this.getEnvMap().getOrDefault(\"AWS_SECRET_ACCESS_KEY\", DEFAULT_AWS_SECRET_ACCESS_KEY);\n    }\n\n    /**\n     * Provides a default region that is preconfigured to communicate with a given simulated service.\n     * The region can be used to construct AWS SDK v2 clients:\n     * <pre><code>S3Client s3 = S3Client\n             .builder()\n             .endpointOverride(localstack.getEndpointOverride(LocalStackContainer.Service.S3))\n             .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(\n             localstack.getAccessKey(), localstack.getSecretKey()\n             )))\n             .region(Region.of(localstack.getRegion()))\n             .build()\n     </code></pre>\n     * @return a default region\n     */\n    public String getRegion() {\n        return this.getEnvMap().getOrDefault(\"DEFAULT_REGION\", DEFAULT_REGION);\n    }\n}\n"
  },
  {
    "path": "modules/localstack/src/test/java/org/testcontainers/containers/localstack/LegacyModeTest.java",
    "content": "package org.testcontainers.containers.localstack;\n\nimport com.github.dockerjava.api.DockerClient;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.testcontainers.DockerClientFactory;\nimport org.testcontainers.containers.localstack.LocalStackContainer.Service;\nimport org.testcontainers.images.RemoteDockerImage;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.util.stream.Stream;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass LegacyModeTest {\n\n    private static final DockerImageName LOCALSTACK_CUSTOM_TAG = DockerImageName\n        .parse(\"localstack/localstack:0.12.8\")\n        .withTag(\"custom\");\n\n    @BeforeAll\n    static void setup() {\n        DockerClient dockerClient = DockerClientFactory.instance().client();\n        dockerClient\n            .tagImageCmd(\n                new RemoteDockerImage(LocalstackTestImages.LOCALSTACK_0_12_IMAGE).get(),\n                LOCALSTACK_CUSTOM_TAG.getRepository(),\n                LOCALSTACK_CUSTOM_TAG.getVersionPart()\n            )\n            .exec();\n    }\n\n    static Stream<Arguments> localstackVersionWithLegacyOff() {\n        return Stream.of(\n            Arguments.arguments(\"0.12\", new LocalStackContainer(LocalstackTestImages.LOCALSTACK_0_12_IMAGE)),\n            Arguments.arguments(\"0.11\", new LocalStackContainer(LocalstackTestImages.LOCALSTACK_0_11_IMAGE)),\n            Arguments.arguments(\n                \"0.11 with legacy = off\",\n                new LocalStackContainer(LocalstackTestImages.LOCALSTACK_0_11_IMAGE, false)\n            )\n        );\n    }\n\n    @ParameterizedTest(name = \"{0}\")\n    @MethodSource(\"localstackVersionWithLegacyOff\")\n    void samePortIsExposedForAllServices(String description, LocalStackContainer localstack) {\n        localstack.withServices(Service.S3, Service.SQS);\n        localstack.start();\n\n        try {\n            assertThat(localstack.getExposedPorts()).as(\"A single port is exposed\").hasSize(1);\n            assertThat(localstack.getEndpointOverride(Service.SQS).toString())\n                .as(\"Endpoint overrides are different\")\n                .isEqualTo(localstack.getEndpointOverride(Service.S3).toString());\n            assertThat(localstack.getEndpointOverride(Service.SQS).toString())\n                .as(\"Endpoint configuration have different endpoints\")\n                .isEqualTo(localstack.getEndpointOverride(Service.S3).toString());\n        } finally {\n            localstack.stop();\n        }\n    }\n\n    public static Stream<Arguments> localstackVersionWithLegacyOn() {\n        return Stream.of(\n            Arguments.arguments(\"0.10\", new LocalStackContainer(LocalstackTestImages.LOCALSTACK_0_10_IMAGE)),\n            Arguments.arguments(\"custom\", new LocalStackContainer(LOCALSTACK_CUSTOM_TAG)),\n            Arguments.arguments(\n                \"0.11 with legacy = on\",\n                new LocalStackContainer(LocalstackTestImages.LOCALSTACK_0_11_IMAGE, true)\n            )\n        );\n    }\n\n    @ParameterizedTest(name = \"{0}\")\n    @MethodSource(\"localstackVersionWithLegacyOn\")\n    void differentPortsAreExposed(String description, LocalStackContainer localstack) {\n        localstack.withServices(Service.S3, Service.SQS);\n        localstack.start();\n\n        try {\n            assertThat(localstack.getExposedPorts()).as(\"Multiple ports are exposed\").hasSizeGreaterThan(1);\n            assertThat(localstack.getEndpointOverride(Service.SQS).toString())\n                .as(\"Endpoint overrides are different\")\n                .isNotEqualTo(localstack.getEndpointOverride(Service.S3).toString());\n            assertThat(localstack.getEndpointOverride(Service.SQS).toString())\n                .as(\"Endpoint configuration have different endpoints\")\n                .isNotEqualTo(localstack.getEndpointOverride(Service.S3).toString());\n        } finally {\n            localstack.stop();\n        }\n    }\n\n    public static Stream<Arguments> constructors() {\n        return Stream.of(\n            Arguments.arguments(\"latest\", false),\n            Arguments.arguments(\"s3-latest\", false),\n            Arguments.arguments(\"latest-bigdata\", false),\n            Arguments.arguments(\"3.4.0-bigdata\", false),\n            Arguments.arguments(\"3.4.0@sha256:54fcf172f6ff70909e1e26652c3bb4587282890aff0d02c20aa7695469476ac0\", false),\n            Arguments.arguments(\"1.4@sha256:7badf31c550f81151c485980e17542592942d7f05acc09723c5f276d41b5927d\", false),\n            Arguments.arguments(\"3.4.0\", false),\n            Arguments.arguments(\"0.12\", false),\n            Arguments.arguments(\"0.11\", false),\n            Arguments.arguments(\"sha256:8bf0d744fea26603f2b11ef7206edb38375ef954258afaeda96532a6c9c1ab8b\", false),\n            Arguments.arguments(\"0.10.7@sha256:45ef287e29af7285c6e4013fafea1e3567c167cd22d12282f0a5f9c7894b1c5f\", true),\n            Arguments.arguments(\"0.10.7\", true),\n            Arguments.arguments(\"0.9.6\", true)\n        );\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"constructors\")\n    void testLegacyMode(String version, boolean shouldUseLegacyMode) {\n        assertThat(LocalStackContainer.shouldRunInLegacyMode(version)).isEqualTo(shouldUseLegacyMode);\n    }\n}\n"
  },
  {
    "path": "modules/localstack/src/test/java/org/testcontainers/containers/localstack/LocalstackTestImages.java",
    "content": "package org.testcontainers.containers.localstack;\n\nimport org.testcontainers.utility.DockerImageName;\n\npublic interface LocalstackTestImages {\n    DockerImageName LOCALSTACK_IMAGE = DockerImageName.parse(\"localstack/localstack:4.9.2\");\n\n    DockerImageName LOCALSTACK_0_10_IMAGE = LOCALSTACK_IMAGE.withTag(\"0.10.7\");\n\n    DockerImageName LOCALSTACK_0_11_IMAGE = LOCALSTACK_IMAGE.withTag(\"0.11.3\");\n\n    DockerImageName LOCALSTACK_0_12_IMAGE = LOCALSTACK_IMAGE.withTag(\"0.12.8\");\n\n    DockerImageName AWS_CLI_IMAGE = DockerImageName.parse(\"amazon/aws-cli:2.7.27\");\n}\n"
  },
  {
    "path": "modules/localstack/src/test/java/org/testcontainers/localstack/LocalStackContainerTest.java",
    "content": "package org.testcontainers.localstack;\n\nimport com.github.dockerjava.api.DockerClient;\nimport lombok.extern.slf4j.Slf4j;\nimport org.apache.commons.io.IOUtils;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.DockerClientFactory;\nimport org.testcontainers.containers.Container;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.Network;\nimport org.testcontainers.containers.localstack.LocalstackTestImages;\nimport software.amazon.awssdk.auth.credentials.AwsBasicCredentials;\nimport software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;\nimport software.amazon.awssdk.core.SdkBytes;\nimport software.amazon.awssdk.regions.Region;\nimport software.amazon.awssdk.services.cloudwatchlogs.CloudWatchLogsClient;\nimport software.amazon.awssdk.services.cloudwatchlogs.model.CreateLogGroupRequest;\nimport software.amazon.awssdk.services.cloudwatchlogs.model.DescribeLogGroupsResponse;\nimport software.amazon.awssdk.services.kms.KmsClient;\nimport software.amazon.awssdk.services.kms.model.CreateKeyRequest;\nimport software.amazon.awssdk.services.kms.model.CreateKeyResponse;\nimport software.amazon.awssdk.services.kms.model.Tag;\nimport software.amazon.awssdk.services.lambda.LambdaClient;\nimport software.amazon.awssdk.services.lambda.model.CreateFunctionRequest;\nimport software.amazon.awssdk.services.lambda.model.CreateFunctionResponse;\nimport software.amazon.awssdk.services.lambda.model.FunctionCode;\nimport software.amazon.awssdk.services.lambda.model.GetFunctionConfigurationRequest;\nimport software.amazon.awssdk.services.lambda.model.InvokeRequest;\nimport software.amazon.awssdk.services.lambda.model.InvokeResponse;\nimport software.amazon.awssdk.services.lambda.model.Runtime;\nimport software.amazon.awssdk.services.lambda.waiters.LambdaWaiter;\nimport software.amazon.awssdk.services.s3.S3Client;\nimport software.amazon.awssdk.services.s3.model.Bucket;\nimport software.amazon.awssdk.services.s3.model.CreateBucketRequest;\nimport software.amazon.awssdk.services.s3.model.GetObjectRequest;\nimport software.amazon.awssdk.services.s3.model.ListObjectsV2Request;\nimport software.amazon.awssdk.services.s3.model.ListObjectsV2Response;\nimport software.amazon.awssdk.services.s3.model.PutObjectRequest;\nimport software.amazon.awssdk.services.s3.presigner.S3Presigner;\nimport software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest;\nimport software.amazon.awssdk.services.sqs.SqsClient;\nimport software.amazon.awssdk.services.sqs.model.CreateQueueRequest;\nimport software.amazon.awssdk.services.sqs.model.CreateQueueResponse;\nimport software.amazon.awssdk.services.sqs.model.ReceiveMessageRequest;\nimport software.amazon.awssdk.services.sqs.model.SendMessageRequest;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.net.URL;\nimport java.nio.charset.StandardCharsets;\nimport java.time.Duration;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.zip.ZipEntry;\nimport java.util.zip.ZipOutputStream;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n@Slf4j\nclass LocalStackContainerTest {\n\n    @Nested\n    class WithoutNetwork {\n\n        @Test\n        void s3TestOverBridgeNetwork() {\n            try (\n                // container {\n                LocalStackContainer localstack = new LocalStackContainer(LocalstackTestImages.LOCALSTACK_IMAGE)\n                    .withServices(\"s3\")\n                // }\n            ) {\n                localstack.start();\n\n                // with_aws_sdk_v2 {\n                S3Client s3 = S3Client\n                    .builder()\n                    .endpointOverride(localstack.getEndpoint())\n                    .credentialsProvider(\n                        StaticCredentialsProvider.create(\n                            AwsBasicCredentials.create(localstack.getAccessKey(), localstack.getSecretKey())\n                        )\n                    )\n                    .region(Region.of(localstack.getRegion()))\n                    .build();\n                // }\n\n                final String bucketName = \"foo\";\n                s3.createBucket(CreateBucketRequest.builder().bucket(bucketName).build());\n                s3.putObject(\n                    PutObjectRequest.builder().bucket(bucketName).key(\"bar\").build(),\n                    software.amazon.awssdk.core.sync.RequestBody.fromString(\"baz\")\n                );\n\n                final List<Bucket> buckets = s3.listBuckets().buckets();\n                final Optional<Bucket> maybeBucket = buckets\n                    .stream()\n                    .filter(b -> b.name().equals(bucketName))\n                    .findFirst();\n                assertThat(maybeBucket).as(\"The created bucket is present\").isPresent();\n                final Bucket bucket = maybeBucket.get();\n\n                assertThat(bucket.name()).as(\"The created bucket has the right name\").isEqualTo(bucketName);\n\n                final ListObjectsV2Response objectListing = s3.listObjectsV2(\n                    ListObjectsV2Request.builder().bucket(bucketName).build()\n                );\n                assertThat(objectListing.contents()).as(\"The created bucket has 1 item in it\").hasSize(1);\n\n                final String content = s3\n                    .getObjectAsBytes(GetObjectRequest.builder().bucket(bucketName).key(\"bar\").build())\n                    .asString(StandardCharsets.UTF_8);\n                assertThat(content).as(\"The object can be retrieved\").isEqualTo(\"baz\");\n            }\n        }\n\n        @Test\n        void sqsTestOverBridgeNetwork() {\n            try (\n                LocalStackContainer localstack = new LocalStackContainer(LocalstackTestImages.LOCALSTACK_IMAGE)\n                    .withEnv(\"SQS_ENDPOINT_STRATEGY\", \"dynamic\")\n                    .withServices(\"sqs\")\n            ) {\n                localstack.start();\n\n                SqsClient sqs = SqsClient\n                    .builder()\n                    .endpointOverride(localstack.getEndpoint())\n                    .credentialsProvider(\n                        StaticCredentialsProvider.create(\n                            AwsBasicCredentials.create(localstack.getAccessKey(), localstack.getSecretKey())\n                        )\n                    )\n                    .region(Region.of(localstack.getRegion()))\n                    .build();\n\n                CreateQueueResponse queueResult = sqs.createQueue(\n                    CreateQueueRequest.builder().queueName(\"baz\").build()\n                );\n                String fooQueueUrl = queueResult.queueUrl();\n\n                sqs.sendMessage(SendMessageRequest.builder().queueUrl(fooQueueUrl).messageBody(\"test\").build());\n                final long messageCount = sqs\n                    .receiveMessage(ReceiveMessageRequest.builder().queueUrl(fooQueueUrl).build())\n                    .messages()\n                    .stream()\n                    .filter(message -> message.body().equals(\"test\"))\n                    .count();\n                assertThat(messageCount).as(\"the sent message can be received\").isEqualTo(1L);\n            }\n        }\n\n        @Test\n        void cloudWatchLogsTestOverBridgeNetwork() {\n            try (\n                LocalStackContainer localstack = new LocalStackContainer(LocalstackTestImages.LOCALSTACK_IMAGE)\n                    .withServices(\"logs\")\n            ) {\n                localstack.start();\n\n                CloudWatchLogsClient logs = CloudWatchLogsClient\n                    .builder()\n                    .endpointOverride(localstack.getEndpoint())\n                    .credentialsProvider(\n                        StaticCredentialsProvider.create(\n                            AwsBasicCredentials.create(localstack.getAccessKey(), localstack.getSecretKey())\n                        )\n                    )\n                    .region(Region.of(localstack.getRegion()))\n                    .build();\n\n                logs.createLogGroup(CreateLogGroupRequest.builder().logGroupName(\"foo\").build());\n\n                DescribeLogGroupsResponse response = logs.describeLogGroups();\n                assertThat(response.logGroups()).as(\"One log group should be created\").hasSize(1);\n                assertThat(response.logGroups().get(0).logGroupName())\n                    .as(\"Name of created log group is [foo]\")\n                    .isEqualTo(\"foo\");\n            }\n        }\n\n        @Test\n        void kmsKeyCreationTest() {\n            try (\n                LocalStackContainer localstack = new LocalStackContainer(LocalstackTestImages.LOCALSTACK_IMAGE)\n                    .withServices(\"kms\")\n            ) {\n                localstack.start();\n                KmsClient kms = KmsClient\n                    .builder()\n                    .endpointOverride(localstack.getEndpoint())\n                    .credentialsProvider(\n                        StaticCredentialsProvider.create(\n                            AwsBasicCredentials.create(localstack.getAccessKey(), localstack.getSecretKey())\n                        )\n                    )\n                    .region(Region.of(localstack.getRegion()))\n                    .build();\n\n                String desc = \"AWS CMK Description\";\n                Tag createdByTag = Tag.builder().tagKey(\"CreatedBy\").tagValue(\"StorageService\").build();\n                CreateKeyRequest req = CreateKeyRequest.builder().description(desc).tags(createdByTag).build();\n                CreateKeyResponse key = kms.createKey(req);\n\n                assertThat(desc)\n                    .as(\"AWS KMS Customer Managed Key should be created \")\n                    .isEqualTo(key.keyMetadata().description());\n            }\n        }\n\n        @Test\n        void samePortIsExposedForAllServices() {\n            try (LocalStackContainer localstack = new LocalStackContainer(LocalstackTestImages.LOCALSTACK_IMAGE)) {\n                localstack.start();\n\n                assertThat(localstack.getExposedPorts()).as(\"A single port is exposed\").hasSize(1);\n                assertThat(localstack.getEndpoint().toString())\n                    .as(\"Endpoint overrides are different\")\n                    .isEqualTo(localstack.getEndpoint().toString());\n            }\n        }\n    }\n\n    @Nested\n    class WithNetwork {\n\n        // with_network {\n        Network network = Network.newNetwork();\n\n        LocalStackContainer localstackInDockerNetwork = new LocalStackContainer(LocalstackTestImages.LOCALSTACK_IMAGE)\n            .withNetwork(network)\n            .withNetworkAliases(\"localstack\")\n            .withServices(\"s3\", \"sqs\", \"logs\");\n        // }\n\n        GenericContainer<?> awsCliInDockerNetwork = new GenericContainer<>(LocalstackTestImages.AWS_CLI_IMAGE)\n            .withNetwork(network)\n            .withCreateContainerCmdModifier(cmd -> cmd.withEntrypoint(\"tail\"))\n            .withCommand(\" -f /dev/null\")\n            .withEnv(\"AWS_ACCESS_KEY_ID\", \"accesskey\")\n            .withEnv(\"AWS_SECRET_ACCESS_KEY\", \"secretkey\")\n            .withEnv(\"AWS_REGION\", \"eu-west-1\");\n\n        @BeforeEach\n        void setup() {\n            localstackInDockerNetwork.start();\n            awsCliInDockerNetwork.start();\n        }\n\n        @AfterEach\n        void tearDown() {\n            awsCliInDockerNetwork.stop();\n            localstackInDockerNetwork.stop();\n        }\n\n        @Test\n        void s3TestOverDockerNetwork() throws Exception {\n            runAwsCliAgainstDockerNetworkContainer(\n                \"s3api create-bucket --bucket foo --create-bucket-configuration LocationConstraint=eu-west-1\"\n            );\n            runAwsCliAgainstDockerNetworkContainer(\"s3api list-buckets\");\n            runAwsCliAgainstDockerNetworkContainer(\"s3 ls s3://foo\");\n        }\n\n        @Test\n        void sqsTestOverDockerNetwork() throws Exception {\n            final String queueCreationResponse = runAwsCliAgainstDockerNetworkContainer(\n                \"sqs create-queue --queue-name baz\"\n            );\n\n            runAwsCliAgainstDockerNetworkContainer(\n                String.format(\n                    \"sqs send-message --endpoint http://localstack:%d --queue-url http://sqs.eu-west-1.localhost.localstack.cloud:%d/000000000000/baz --message-body test\",\n                    LocalStackContainer.PORT,\n                    LocalStackContainer.PORT\n                )\n            );\n            final String message = runAwsCliAgainstDockerNetworkContainer(\n                String.format(\n                    \"sqs receive-message --endpoint http://localstack:%d --queue-url http://sqs.eu-west-1.localhost.localstack.cloud:%d/000000000000/baz\",\n                    LocalStackContainer.PORT,\n                    LocalStackContainer.PORT\n                )\n            );\n\n            assertThat(message).as(\"the sent message can be received\").contains(\"\\\"Body\\\": \\\"test\\\"\");\n        }\n\n        @Test\n        void cloudWatchLogsTestOverDockerNetwork() throws Exception {\n            runAwsCliAgainstDockerNetworkContainer(\"logs create-log-group --log-group-name foo\");\n        }\n\n        private String runAwsCliAgainstDockerNetworkContainer(String command) throws Exception {\n            final String[] commandParts = String\n                .format(\n                    \"/usr/local/bin/aws --region eu-west-1 %s --endpoint-url http://localstack:%d --no-verify-ssl\",\n                    command,\n                    LocalStackContainer.PORT\n                )\n                .split(\" \");\n            final Container.ExecResult execResult = awsCliInDockerNetwork.execInContainer(commandParts);\n            assertThat(execResult.getExitCode()).isEqualTo(0);\n\n            final String logs = execResult.getStdout() + execResult.getStderr();\n            log.info(logs);\n            return logs;\n        }\n    }\n\n    @Nested\n    class WithRegion {\n\n        @Test\n        void s3EndpointHasProperRegion() {\n            try (\n                // with_region {\n                LocalStackContainer localstack = new LocalStackContainer(LocalstackTestImages.LOCALSTACK_IMAGE)\n                    .withEnv(\"DEFAULT_REGION\", \"eu-west-1\")\n                    .withServices(\"s3\");\n                // }\n            ) {\n                localstack.start();\n                assertThat(localstack.getRegion())\n                    .as(\"The endpoint configuration has right region\")\n                    .isEqualTo(\"eu-west-1\");\n            }\n        }\n    }\n\n    @Nested\n    class WithoutServices {\n\n        @Test\n        void s3ServiceStartLazily() {\n            try (LocalStackContainer localstack = new LocalStackContainer(LocalstackTestImages.LOCALSTACK_IMAGE);) {\n                localstack.start();\n\n                S3Client s3 = S3Client\n                    .builder()\n                    .endpointOverride(localstack.getEndpoint())\n                    .credentialsProvider(\n                        StaticCredentialsProvider.create(\n                            AwsBasicCredentials.create(localstack.getAccessKey(), localstack.getSecretKey())\n                        )\n                    )\n                    .region(Region.of(localstack.getRegion()))\n                    .build();\n                assertThat(s3.listBuckets().buckets()).as(\"S3 Service is started lazily\").isEmpty();\n            }\n        }\n    }\n\n    @Nested\n    class S3SkipSignatureValidation {\n\n        @Test\n        void shouldBeAccessibleWithCredentials() throws IOException {\n            try (\n                LocalStackContainer localstack = new LocalStackContainer(LocalstackTestImages.LOCALSTACK_IMAGE)\n                    .withEnv(\"S3_SKIP_SIGNATURE_VALIDATION\", \"0\")\n            ) {\n                localstack.start();\n\n                S3Client s3 = S3Client\n                    .builder()\n                    .endpointOverride(localstack.getEndpoint())\n                    .credentialsProvider(\n                        StaticCredentialsProvider.create(\n                            AwsBasicCredentials.create(localstack.getAccessKey(), localstack.getSecretKey())\n                        )\n                    )\n                    .region(Region.of(localstack.getRegion()))\n                    .build();\n\n                final String bucketName = \"foo\";\n\n                s3.createBucket(CreateBucketRequest.builder().bucket(bucketName).build());\n\n                s3.putObject(\n                    PutObjectRequest.builder().bucket(bucketName).key(\"bar\").build(),\n                    software.amazon.awssdk.core.sync.RequestBody.fromString(\"baz\")\n                );\n\n                final List<Bucket> buckets = s3.listBuckets().buckets();\n                final Optional<Bucket> maybeBucket = buckets\n                    .stream()\n                    .filter(b -> b.name().equals(bucketName))\n                    .findFirst();\n                assertThat(maybeBucket).as(\"The created bucket is present\").isPresent();\n\n                S3Presigner presigner = S3Presigner\n                    .builder()\n                    .endpointOverride(localstack.getEndpoint())\n                    .credentialsProvider(\n                        StaticCredentialsProvider.create(\n                            AwsBasicCredentials.create(localstack.getAccessKey(), localstack.getSecretKey())\n                        )\n                    )\n                    .region(Region.of(localstack.getRegion()))\n                    .build();\n\n                GetObjectPresignRequest presignRequest = GetObjectPresignRequest\n                    .builder()\n                    .signatureDuration(Duration.ofMinutes(5))\n                    .getObjectRequest(GetObjectRequest.builder().bucket(bucketName).key(\"bar\").build())\n                    .build();\n\n                URL presignedUrl = presigner.presignGetObject(presignRequest).url();\n\n                assertThat(presignedUrl).as(\"The presigned url is valid\").isNotNull();\n                final String content = IOUtils.toString(presignedUrl, StandardCharsets.UTF_8);\n                assertThat(content).as(\"The object can be retrieved\").isEqualTo(\"baz\");\n            }\n        }\n    }\n\n    @Nested\n    class LambdaContainerLabels {\n\n        private byte[] createLambdaHandlerZipFile() throws IOException {\n            StringBuilder sb = new StringBuilder();\n            sb.append(\"def handler(event, context):\\n\");\n            sb.append(\"    return event\");\n\n            ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();\n            ZipOutputStream out = new ZipOutputStream(byteOutput);\n            ZipEntry e = new ZipEntry(\"handler.py\");\n            out.putNextEntry(e);\n\n            byte[] data = sb.toString().getBytes();\n            out.write(data, 0, data.length);\n            out.closeEntry();\n            out.close();\n            return byteOutput.toByteArray();\n        }\n\n        @Test\n        void shouldLabelLambdaContainers() throws IOException {\n            try (LocalStackContainer localstack = new LocalStackContainer(LocalstackTestImages.LOCALSTACK_IMAGE)) {\n                localstack.start();\n\n                LambdaClient lambda = LambdaClient\n                    .builder()\n                    .endpointOverride(localstack.getEndpoint())\n                    .credentialsProvider(\n                        StaticCredentialsProvider.create(\n                            AwsBasicCredentials.create(localstack.getAccessKey(), localstack.getSecretKey())\n                        )\n                    )\n                    .region(Region.of(localstack.getRegion()))\n                    .build();\n\n                // create function\n                byte[] handlerFile = createLambdaHandlerZipFile();\n                CreateFunctionRequest createFunctionRequest = CreateFunctionRequest\n                    .builder()\n                    .functionName(\"test-function\")\n                    .runtime(Runtime.PYTHON3_11)\n                    .handler(\"handler.handler\")\n                    .role(\"arn:aws:iam::000000000000:role/test-role\")\n                    .code(FunctionCode.builder().zipFile(SdkBytes.fromByteArray(handlerFile)).build())\n                    .build();\n                CreateFunctionResponse createFunctionResult = lambda.createFunction(createFunctionRequest);\n\n                try (LambdaWaiter waiter = lambda.waiter()) {\n                    waiter.waitUntilFunctionActive(\n                        GetFunctionConfigurationRequest\n                            .builder()\n                            .functionName(createFunctionResult.functionName())\n                            .build()\n                    );\n                }\n\n                // invoke function once\n                String payload = \"{\\\"test\\\": \\\"payload\\\"}\";\n                InvokeRequest invokeRequest = InvokeRequest\n                    .builder()\n                    .functionName(createFunctionResult.functionName())\n                    .payload(SdkBytes.fromUtf8String(payload))\n                    .build();\n                InvokeResponse invokeResult = lambda.invoke(invokeRequest);\n                assertThat(invokeResult.payload().asUtf8String())\n                    .as(\"Invoke result not matching expected output\")\n                    .isEqualTo(payload);\n\n                // assert that the spawned lambda containers has the testcontainers labels set\n                DockerClient dockerClient = DockerClientFactory.instance().client();\n                Collection<String> nameFilter = Collections.singleton(localstack.getContainerName().replace(\"_\", \"-\"));\n                com.github.dockerjava.api.model.Container lambdaContainer = dockerClient\n                    .listContainersCmd()\n                    .withNameFilter(nameFilter)\n                    .exec()\n                    .stream()\n                    .findFirst()\n                    .orElse(null);\n                assertThat(lambdaContainer).as(\"Lambda container not found\").isNotNull();\n                Map<String, String> labels = lambdaContainer.getLabels();\n                assertThat(labels.get(\"org.testcontainers\")).as(\"TestContainers label not present\").isEqualTo(\"true\");\n                assertThat(labels.get(\"org.testcontainers.sessionId\"))\n                    .as(\"TestContainers session id not present\")\n                    .isNotNull();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "modules/localstack/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/mariadb/build.gradle",
    "content": "description = \"Testcontainers :: JDBC :: MariaDB\"\n\ndependencies {\n    api project(':testcontainers-jdbc')\n\n    compileOnly project(':testcontainers-r2dbc')\n    compileOnly 'org.mariadb:r2dbc-mariadb:1.0.3'\n\n    testImplementation project(':testcontainers-jdbc-test')\n    testImplementation 'org.mariadb.jdbc:mariadb-java-client:3.5.6'\n\n    testImplementation testFixtures(project(':testcontainers-r2dbc'))\n    testRuntimeOnly 'org.mariadb:r2dbc-mariadb:1.0.3'\n}\n"
  },
  {
    "path": "modules/mariadb/src/main/java/org/testcontainers/containers/MariaDBContainer.java",
    "content": "package org.testcontainers.containers;\n\nimport com.google.common.collect.Sets;\nimport org.testcontainers.images.builder.Transferable;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.util.Set;\n\n/**\n * Testcontainers implementation for MariaDB.\n * <p>\n * Supported image: {@code mariadb}\n * <p>\n * Exposed ports: 3306\n *\n * @deprecated use {@link org.testcontainers.mariadb.MariaDBContainer} instead.\n */\n@Deprecated\npublic class MariaDBContainer<SELF extends MariaDBContainer<SELF>> extends JdbcDatabaseContainer<SELF> {\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"mariadb\");\n\n    @Deprecated\n    public static final String DEFAULT_TAG = \"10.3.6\";\n\n    public static final String NAME = \"mariadb\";\n\n    @Deprecated\n    public static final String IMAGE = DEFAULT_IMAGE_NAME.getUnversionedPart();\n\n    static final String DEFAULT_USER = \"test\";\n\n    static final String DEFAULT_PASSWORD = \"test\";\n\n    static final Integer MARIADB_PORT = 3306;\n\n    private String databaseName = \"test\";\n\n    private String username = DEFAULT_USER;\n\n    private String password = DEFAULT_PASSWORD;\n\n    private static final String MARIADB_ROOT_USER = \"root\";\n\n    private static final String MY_CNF_CONFIG_OVERRIDE_PARAM_NAME = \"TC_MY_CNF\";\n\n    public MariaDBContainer(String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    public MariaDBContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n\n        addExposedPort(MARIADB_PORT);\n    }\n\n    @Override\n    public Set<Integer> getLivenessCheckPortNumbers() {\n        return Sets.newHashSet(MARIADB_PORT);\n    }\n\n    @Override\n    protected void configure() {\n        optionallyMapResourceParameterAsVolume(\n            MY_CNF_CONFIG_OVERRIDE_PARAM_NAME,\n            \"/etc/mysql/conf.d\",\n            \"mariadb-default-conf\",\n            Transferable.DEFAULT_DIR_MODE\n        );\n\n        addEnv(\"MYSQL_DATABASE\", databaseName);\n\n        if (!MARIADB_ROOT_USER.equalsIgnoreCase(this.username)) {\n            addEnv(\"MYSQL_USER\", username);\n        }\n        if (password != null && !password.isEmpty()) {\n            addEnv(\"MYSQL_PASSWORD\", password);\n            addEnv(\"MYSQL_ROOT_PASSWORD\", password);\n        } else if (MARIADB_ROOT_USER.equalsIgnoreCase(username)) {\n            addEnv(\"MYSQL_ALLOW_EMPTY_PASSWORD\", \"yes\");\n        } else {\n            throw new ContainerLaunchException(\"Empty password can be used only with the root user\");\n        }\n        setStartupAttempts(3);\n    }\n\n    @Override\n    public String getDriverClassName() {\n        return \"org.mariadb.jdbc.Driver\";\n    }\n\n    @Override\n    public String getJdbcUrl() {\n        String additionalUrlParams = constructUrlParameters(\"?\", \"&\");\n        return (\n            \"jdbc:mariadb://\" + getHost() + \":\" + getMappedPort(MARIADB_PORT) + \"/\" + databaseName + additionalUrlParams\n        );\n    }\n\n    @Override\n    public String getDatabaseName() {\n        return databaseName;\n    }\n\n    @Override\n    public String getUsername() {\n        return username;\n    }\n\n    @Override\n    public String getPassword() {\n        return password;\n    }\n\n    @Override\n    public String getTestQueryString() {\n        return \"SELECT 1\";\n    }\n\n    public SELF withConfigurationOverride(String s) {\n        parameters.put(MY_CNF_CONFIG_OVERRIDE_PARAM_NAME, s);\n        return self();\n    }\n\n    @Override\n    public SELF withDatabaseName(final String databaseName) {\n        this.databaseName = databaseName;\n        return self();\n    }\n\n    @Override\n    public SELF withUsername(final String username) {\n        this.username = username;\n        return self();\n    }\n\n    @Override\n    public SELF withPassword(final String password) {\n        this.password = password;\n        return self();\n    }\n}\n"
  },
  {
    "path": "modules/mariadb/src/main/java/org/testcontainers/containers/MariaDBContainerProvider.java",
    "content": "package org.testcontainers.containers;\n\nimport org.testcontainers.jdbc.ConnectionUrl;\nimport org.testcontainers.utility.DockerImageName;\n\n/**\n * Factory for MariaDB org.testcontainers.containers.\n */\npublic class MariaDBContainerProvider extends JdbcDatabaseContainerProvider {\n\n    private static final String USER_PARAM = \"user\";\n\n    private static final String PASSWORD_PARAM = \"password\";\n\n    @Override\n    public boolean supports(String databaseType) {\n        return databaseType.equals(MariaDBContainer.NAME);\n    }\n\n    @Override\n    public JdbcDatabaseContainer newInstance() {\n        return newInstance(MariaDBContainer.DEFAULT_TAG);\n    }\n\n    @Override\n    public JdbcDatabaseContainer newInstance(String tag) {\n        return new MariaDBContainer(DockerImageName.parse(MariaDBContainer.IMAGE).withTag(tag));\n    }\n\n    @Override\n    public JdbcDatabaseContainer newInstance(ConnectionUrl connectionUrl) {\n        return newInstanceFromConnectionUrl(connectionUrl, USER_PARAM, PASSWORD_PARAM);\n    }\n}\n"
  },
  {
    "path": "modules/mariadb/src/main/java/org/testcontainers/containers/MariaDBR2DBCDatabaseContainer.java",
    "content": "package org.testcontainers.containers;\n\nimport io.r2dbc.spi.ConnectionFactoryOptions;\nimport lombok.RequiredArgsConstructor;\nimport lombok.experimental.Delegate;\nimport org.testcontainers.lifecycle.Startable;\nimport org.testcontainers.r2dbc.R2DBCDatabaseContainer;\n\n@RequiredArgsConstructor\npublic class MariaDBR2DBCDatabaseContainer implements R2DBCDatabaseContainer {\n\n    @Delegate(types = Startable.class)\n    private final MariaDBContainer<?> container;\n\n    public static ConnectionFactoryOptions getOptions(MariaDBContainer<?> container) {\n        ConnectionFactoryOptions options = ConnectionFactoryOptions\n            .builder()\n            .option(ConnectionFactoryOptions.DRIVER, MariaDBR2DBCDatabaseContainerProvider.DRIVER)\n            .build();\n\n        return new MariaDBR2DBCDatabaseContainer(container).configure(options);\n    }\n\n    @Override\n    public ConnectionFactoryOptions configure(ConnectionFactoryOptions options) {\n        return options\n            .mutate()\n            .option(ConnectionFactoryOptions.HOST, container.getHost())\n            .option(ConnectionFactoryOptions.PORT, container.getMappedPort(MariaDBContainer.MARIADB_PORT))\n            .option(ConnectionFactoryOptions.DATABASE, container.getDatabaseName())\n            .option(ConnectionFactoryOptions.USER, container.getUsername())\n            .option(ConnectionFactoryOptions.PASSWORD, container.getPassword())\n            .build();\n    }\n}\n"
  },
  {
    "path": "modules/mariadb/src/main/java/org/testcontainers/containers/MariaDBR2DBCDatabaseContainerProvider.java",
    "content": "package org.testcontainers.containers;\n\nimport io.r2dbc.spi.ConnectionFactoryMetadata;\nimport io.r2dbc.spi.ConnectionFactoryOptions;\nimport org.mariadb.r2dbc.MariadbConnectionFactoryProvider;\nimport org.testcontainers.r2dbc.R2DBCDatabaseContainer;\nimport org.testcontainers.r2dbc.R2DBCDatabaseContainerProvider;\n\nimport javax.annotation.Nullable;\n\npublic class MariaDBR2DBCDatabaseContainerProvider implements R2DBCDatabaseContainerProvider {\n\n    static final String DRIVER = MariadbConnectionFactoryProvider.MARIADB_DRIVER;\n\n    @Override\n    public boolean supports(ConnectionFactoryOptions options) {\n        return DRIVER.equals(options.getRequiredValue(ConnectionFactoryOptions.DRIVER));\n    }\n\n    @Override\n    public R2DBCDatabaseContainer createContainer(ConnectionFactoryOptions options) {\n        String image = MariaDBContainer.IMAGE + \":\" + options.getRequiredValue(IMAGE_TAG_OPTION);\n        MariaDBContainer<?> container = new MariaDBContainer<>(image)\n            .withDatabaseName((String) options.getRequiredValue(ConnectionFactoryOptions.DATABASE));\n\n        if (Boolean.TRUE.equals(options.getValue(REUSABLE_OPTION))) {\n            container.withReuse(true);\n        }\n        return new MariaDBR2DBCDatabaseContainer(container);\n    }\n\n    @Nullable\n    @Override\n    public ConnectionFactoryMetadata getMetadata(ConnectionFactoryOptions options) {\n        ConnectionFactoryOptions.Builder builder = options.mutate();\n        if (!options.hasOption(ConnectionFactoryOptions.USER)) {\n            builder.option(ConnectionFactoryOptions.USER, MariaDBContainer.DEFAULT_USER);\n        }\n        if (!options.hasOption(ConnectionFactoryOptions.PASSWORD)) {\n            builder.option(ConnectionFactoryOptions.PASSWORD, MariaDBContainer.DEFAULT_PASSWORD);\n        }\n        return R2DBCDatabaseContainerProvider.super.getMetadata(builder.build());\n    }\n}\n"
  },
  {
    "path": "modules/mariadb/src/main/java/org/testcontainers/mariadb/MariaDBContainer.java",
    "content": "package org.testcontainers.mariadb;\n\nimport com.google.common.collect.Sets;\nimport org.testcontainers.containers.ContainerLaunchException;\nimport org.testcontainers.containers.JdbcDatabaseContainer;\nimport org.testcontainers.images.builder.Transferable;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.util.Set;\n\n/**\n * Testcontainers implementation for MariaDB.\n * <p>\n * Supported image: {@code mariadb}\n * <p>\n * Exposed ports: 3306\n */\npublic class MariaDBContainer extends JdbcDatabaseContainer<MariaDBContainer> {\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"mariadb\");\n\n    public static final String NAME = \"mariadb\";\n\n    static final String DEFAULT_USER = \"test\";\n\n    static final String DEFAULT_PASSWORD = \"test\";\n\n    static final Integer MARIADB_PORT = 3306;\n\n    private String databaseName = \"test\";\n\n    private String username = DEFAULT_USER;\n\n    private String password = DEFAULT_PASSWORD;\n\n    private static final String MARIADB_ROOT_USER = \"root\";\n\n    private static final String MY_CNF_CONFIG_OVERRIDE_PARAM_NAME = \"TC_MY_CNF\";\n\n    public MariaDBContainer(String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    public MariaDBContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n\n        addExposedPort(MARIADB_PORT);\n    }\n\n    @Override\n    public Set<Integer> getLivenessCheckPortNumbers() {\n        return Sets.newHashSet(MARIADB_PORT);\n    }\n\n    @Override\n    protected void configure() {\n        optionallyMapResourceParameterAsVolume(\n            MY_CNF_CONFIG_OVERRIDE_PARAM_NAME,\n            \"/etc/mysql/conf.d\",\n            null,\n            Transferable.DEFAULT_DIR_MODE\n        );\n\n        addEnv(\"MYSQL_DATABASE\", databaseName);\n\n        if (!MARIADB_ROOT_USER.equalsIgnoreCase(this.username)) {\n            addEnv(\"MYSQL_USER\", username);\n        }\n        if (password != null && !password.isEmpty()) {\n            addEnv(\"MYSQL_PASSWORD\", password);\n            addEnv(\"MYSQL_ROOT_PASSWORD\", password);\n        } else if (MARIADB_ROOT_USER.equalsIgnoreCase(username)) {\n            addEnv(\"MYSQL_ALLOW_EMPTY_PASSWORD\", \"yes\");\n        } else {\n            throw new ContainerLaunchException(\"Empty password can be used only with the root user\");\n        }\n        setStartupAttempts(3);\n    }\n\n    @Override\n    public String getDriverClassName() {\n        return \"org.mariadb.jdbc.Driver\";\n    }\n\n    @Override\n    public String getJdbcUrl() {\n        String additionalUrlParams = constructUrlParameters(\"?\", \"&\");\n        return (\n            \"jdbc:mariadb://\" + getHost() + \":\" + getMappedPort(MARIADB_PORT) + \"/\" + databaseName + additionalUrlParams\n        );\n    }\n\n    @Override\n    public String getDatabaseName() {\n        return databaseName;\n    }\n\n    @Override\n    public String getUsername() {\n        return username;\n    }\n\n    @Override\n    public String getPassword() {\n        return password;\n    }\n\n    @Override\n    public String getTestQueryString() {\n        return \"SELECT 1\";\n    }\n\n    public MariaDBContainer withConfigurationOverride(String s) {\n        parameters.put(MY_CNF_CONFIG_OVERRIDE_PARAM_NAME, s);\n        return self();\n    }\n\n    @Override\n    public MariaDBContainer withDatabaseName(final String databaseName) {\n        this.databaseName = databaseName;\n        return self();\n    }\n\n    @Override\n    public MariaDBContainer withUsername(final String username) {\n        this.username = username;\n        return self();\n    }\n\n    @Override\n    public MariaDBContainer withPassword(final String password) {\n        this.password = password;\n        return self();\n    }\n}\n"
  },
  {
    "path": "modules/mariadb/src/main/java/org/testcontainers/mariadb/MariaDBR2DBCDatabaseContainer.java",
    "content": "package org.testcontainers.mariadb;\n\nimport io.r2dbc.spi.ConnectionFactoryOptions;\nimport org.mariadb.r2dbc.MariadbConnectionFactoryProvider;\nimport org.testcontainers.lifecycle.Startable;\nimport org.testcontainers.r2dbc.R2DBCDatabaseContainer;\n\nimport java.util.Set;\n\npublic class MariaDBR2DBCDatabaseContainer implements R2DBCDatabaseContainer {\n\n    private final MariaDBContainer container;\n\n    public MariaDBR2DBCDatabaseContainer(MariaDBContainer container) {\n        this.container = container;\n    }\n\n    public static ConnectionFactoryOptions getOptions(MariaDBContainer container) {\n        ConnectionFactoryOptions options = ConnectionFactoryOptions\n            .builder()\n            .option(ConnectionFactoryOptions.DRIVER, MariadbConnectionFactoryProvider.MARIADB_DRIVER)\n            .build();\n\n        return new MariaDBR2DBCDatabaseContainer(container).configure(options);\n    }\n\n    @Override\n    public ConnectionFactoryOptions configure(ConnectionFactoryOptions options) {\n        return options\n            .mutate()\n            .option(ConnectionFactoryOptions.HOST, container.getHost())\n            .option(ConnectionFactoryOptions.PORT, container.getMappedPort(MariaDBContainer.MARIADB_PORT))\n            .option(ConnectionFactoryOptions.DATABASE, container.getDatabaseName())\n            .option(ConnectionFactoryOptions.USER, container.getUsername())\n            .option(ConnectionFactoryOptions.PASSWORD, container.getPassword())\n            .build();\n    }\n\n    @Override\n    public Set<Startable> getDependencies() {\n        return this.container.getDependencies();\n    }\n\n    @Override\n    public void start() {\n        this.container.start();\n    }\n\n    @Override\n    public void stop() {\n        this.container.stop();\n    }\n\n    @Override\n    public void close() {\n        this.container.close();\n    }\n}\n"
  },
  {
    "path": "modules/mariadb/src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider",
    "content": "org.testcontainers.containers.MariaDBContainerProvider"
  },
  {
    "path": "modules/mariadb/src/main/resources/META-INF/services/org.testcontainers.r2dbc.R2DBCDatabaseContainerProvider",
    "content": "org.testcontainers.containers.MariaDBR2DBCDatabaseContainerProvider\n"
  },
  {
    "path": "modules/mariadb/src/main/resources/mariadb-default-conf/my.cnf",
    "content": "[mysqld]\nport   \t\t= 3306\n#socket \t\t= /tmp/mysql.sock\nskip-external-locking\nkey_buffer_size = 16K\nmax_allowed_packet = 1M\ntable_open_cache = 4\nsort_buffer_size = 64K\nread_buffer_size = 256K\nread_rnd_buffer_size = 256K\nnet_buffer_length = 2K\nthread_stack = 512K\nskip-host-cache\nskip-name-resolve\n\n# Don't listen on a TCP/IP port at all. This can be a security enhancement,\n# if all processes that need to connect to mysqld run on the same host.\n# All interaction with mysqld must be made via Unix sockets or named pipes.\n# Note that using this option without enabling named pipes on Windows\n# (using the \"enable-named-pipe\" option) will render mysqld useless!\n#\n#skip-networking\n#server-id      \t= 1\n\n# Uncomment the following if you want to log updates\n#log-bin=mysql-bin\n\n# binary logging format - mixed recommended\n#binlog_format=mixed\n\n# Causes updates to non-transactional engines using statement format to be\n# written directly to binary log. Before using this option make sure that\n# there are no dependencies between transactional and non-transactional\n# tables such as in the statement INSERT INTO t_myisam SELECT * FROM\n# t_innodb; otherwise, slaves may diverge from the master.\n#binlog_direct_non_transactional_updates=TRUE\n\n# Uncomment the following if you are using InnoDB tables\ninnodb_data_file_path = ibdata1:10M:autoextend\n# You can set .._buffer_pool_size up to 50 - 80 %\n# of RAM but beware of setting memory usage too high\ninnodb_buffer_pool_size = 16M\n#innodb_additional_mem_pool_size = 2M\n# Set .._log_file_size to 25 % of buffer pool size\ninnodb_log_file_size = 5M\ninnodb_log_buffer_size = 8M\ninnodb_flush_log_at_trx_commit = 1\ninnodb_lock_wait_timeout = 50\n"
  },
  {
    "path": "modules/mariadb/src/test/java/org/testcontainers/MariaDBTestImages.java",
    "content": "package org.testcontainers;\n\nimport org.testcontainers.utility.DockerImageName;\n\npublic interface MariaDBTestImages {\n    DockerImageName MARIADB_IMAGE = DockerImageName.parse(\"mariadb:10.3.39\");\n}\n"
  },
  {
    "path": "modules/mariadb/src/test/java/org/testcontainers/containers/MariaDBR2DBCDatabaseContainerTest.java",
    "content": "package org.testcontainers.containers;\n\nimport io.r2dbc.spi.ConnectionFactoryOptions;\nimport org.testcontainers.r2dbc.AbstractR2DBCDatabaseContainerTest;\nimport org.testcontainers.utility.DockerImageName;\n\npublic class MariaDBR2DBCDatabaseContainerTest extends AbstractR2DBCDatabaseContainerTest<MariaDBContainer<?>> {\n\n    @Override\n    protected ConnectionFactoryOptions getOptions(MariaDBContainer<?> container) {\n        return MariaDBR2DBCDatabaseContainer.getOptions(container);\n    }\n\n    @Override\n    protected String createR2DBCUrl() {\n        return \"r2dbc:tc:mariadb:///db?TC_IMAGE_TAG=10.3.39\";\n    }\n\n    @Override\n    protected MariaDBContainer<?> createContainer() {\n        return new MariaDBContainer<>(DockerImageName.parse(\"mariadb:10.3.39\"));\n    }\n}\n"
  },
  {
    "path": "modules/mariadb/src/test/java/org/testcontainers/jdbc/mariadb/MariaDBJDBCDriverTest.java",
    "content": "package org.testcontainers.jdbc.mariadb;\n\nimport org.testcontainers.jdbc.AbstractJDBCDriverTest;\n\nimport java.util.Arrays;\nimport java.util.EnumSet;\n\nclass MariaDBJDBCDriverTest extends AbstractJDBCDriverTest {\n\n    public static Iterable<Object[]> data() {\n        return Arrays.asList(\n            new Object[][] {\n                { \"jdbc:tc:mariadb://hostname/databasename\", EnumSet.noneOf(Options.class) },\n                {\n                    \"jdbc:tc:mariadb://hostname/databasename?user=someuser&TC_INITSCRIPT=somepath/init_mariadb.sql\",\n                    EnumSet.of(Options.ScriptedSchema, Options.JDBCParams),\n                },\n                { \"jdbc:tc:mariadb:10.3.39://hostname/databasename\", EnumSet.noneOf(Options.class) },\n                {\n                    \"jdbc:tc:mariadb:10.3.39://hostname/databasename?TC_INITSCRIPT=somepath/init_unicode_mariadb.sql&useUnicode=yes&characterEncoding=utf8\",\n                    EnumSet.of(Options.CharacterSet),\n                },\n                {\n                    \"jdbc:tc:mariadb:10.3.39://hostname/databasename?user=someuser&TC_INITSCRIPT=somepath/init_mariadb.sql\",\n                    EnumSet.of(Options.ScriptedSchema, Options.JDBCParams),\n                },\n                {\n                    \"jdbc:tc:mariadb:10.3.39://hostname/databasename?user=someuser&TC_INITFUNCTION=org.testcontainers.jdbc.AbstractJDBCDriverTest::sampleInitFunction\",\n                    EnumSet.of(Options.ScriptedSchema, Options.JDBCParams),\n                },\n                {\n                    \"jdbc:tc:mariadb:10.3.39://hostname/databasename?user=someuser&password=somepwd&TC_INITSCRIPT=somepath/init_mariadb.sql\",\n                    EnumSet.of(Options.ScriptedSchema, Options.JDBCParams),\n                },\n                {\n                    \"jdbc:tc:mariadb:10.3.39://hostname/databasename?user=someuser&password=somepwd&TC_INITFUNCTION=org.testcontainers.jdbc.AbstractJDBCDriverTest::sampleInitFunction\",\n                    EnumSet.of(Options.ScriptedSchema, Options.JDBCParams),\n                },\n                {\n                    \"jdbc:tc:mariadb:10.3.39://hostname/databasename?TC_MY_CNF=somepath/mariadb_conf_override\",\n                    EnumSet.of(Options.CustomIniFile),\n                },\n            }\n        );\n    }\n}\n"
  },
  {
    "path": "modules/mariadb/src/test/java/org/testcontainers/mariadb/MariaDBContainerTest.java",
    "content": "package org.testcontainers.mariadb;\n\nimport org.apache.commons.lang3.SystemUtils;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.MariaDBTestImages;\nimport org.testcontainers.db.AbstractContainerDatabaseTest;\n\nimport java.io.File;\nimport java.net.URL;\nimport java.nio.file.FileSystems;\nimport java.nio.file.Files;\nimport java.nio.file.attribute.PosixFilePermission;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.util.Arrays;\nimport java.util.HashSet;\nimport java.util.Set;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assumptions.assumeThat;\n\nclass MariaDBContainerTest extends AbstractContainerDatabaseTest {\n\n    @Test\n    void testSimple() throws SQLException {\n        try ( // container {\n            MariaDBContainer mariadb = new MariaDBContainer(\"mariadb:10.3.39\")\n            // }\n        ) {\n            mariadb.start();\n\n            ResultSet resultSet = performQuery(mariadb, \"SELECT 1\");\n            int resultSetInt = resultSet.getInt(1);\n\n            assertThat(resultSetInt).as(\"A basic SELECT query succeeds\").isEqualTo(1);\n        }\n    }\n\n    @Test\n    void testSpecificVersion() throws SQLException {\n        try (\n            MariaDBContainer mariadbOldVersion = new MariaDBContainer(\n                MariaDBTestImages.MARIADB_IMAGE.withTag(\"10.3.39\")\n            )\n        ) {\n            mariadbOldVersion.start();\n\n            ResultSet resultSet = performQuery(mariadbOldVersion, \"SELECT VERSION()\");\n            String resultSetString = resultSet.getString(1);\n\n            assertThat(resultSetString)\n                .as(\"The database version can be set using a container rule parameter\")\n                .startsWith(\"10.3.39\");\n        }\n    }\n\n    @Test\n    void testMariaDBWithCustomIniFile() throws SQLException {\n        assumeThat(SystemUtils.IS_OS_WINDOWS).isFalse();\n\n        try (\n            MariaDBContainer mariadbCustomConfig = new MariaDBContainer(\n                MariaDBTestImages.MARIADB_IMAGE.withTag(\"10.3.39\")\n            )\n                .withConfigurationOverride(\"somepath/mariadb_conf_override\")\n        ) {\n            mariadbCustomConfig.start();\n\n            assertThatCustomIniFileWasUsed(mariadbCustomConfig);\n        }\n    }\n\n    @Test\n    void testMariaDBWithCommandOverride() throws SQLException {\n        try (\n            MariaDBContainer mariadbCustomConfig = new MariaDBContainer(MariaDBTestImages.MARIADB_IMAGE)\n                .withCommand(\"mysqld --auto_increment_increment=10\")\n        ) {\n            mariadbCustomConfig.start();\n            ResultSet resultSet = performQuery(mariadbCustomConfig, \"show variables like 'auto_increment_increment'\");\n            String result = resultSet.getString(\"Value\");\n\n            assertThat(result).as(\"Auto increment increment should be overridden by command line\").isEqualTo(\"10\");\n        }\n    }\n\n    @Test\n    void testWithAdditionalUrlParamInJdbcUrl() {\n        MariaDBContainer mariaDBContainer = new MariaDBContainer(MariaDBTestImages.MARIADB_IMAGE)\n            .withUrlParam(\"connectTimeout\", \"40000\")\n            .withUrlParam(\"rewriteBatchedStatements\", \"true\");\n\n        try {\n            mariaDBContainer.start();\n            String jdbcUrl = mariaDBContainer.getJdbcUrl();\n            assertThat(jdbcUrl).contains(\"?\");\n            assertThat(jdbcUrl).contains(\"&\");\n            assertThat(jdbcUrl).contains(\"rewriteBatchedStatements=true\");\n            assertThat(jdbcUrl).contains(\"connectTimeout=40000\");\n        } finally {\n            mariaDBContainer.stop();\n        }\n    }\n\n    @Test\n    void testWithOnlyUserReadableCustomIniFile() throws Exception {\n        assumeThat(FileSystems.getDefault().supportedFileAttributeViews().contains(\"posix\")).isTrue();\n\n        try (\n            MariaDBContainer mariadbCustomConfig = new MariaDBContainer(\n                MariaDBTestImages.MARIADB_IMAGE.withTag(\"10.3.39\")\n            )\n                .withConfigurationOverride(\"somepath/mariadb_conf_override\")\n        ) {\n            URL resource = this.getClass().getClassLoader().getResource(\"somepath/mariadb_conf_override\");\n\n            File file = new File(resource.toURI());\n            assertThat(file.isDirectory()).isTrue();\n\n            Set<PosixFilePermission> permissions = new HashSet<>(\n                Arrays.asList(\n                    PosixFilePermission.OWNER_READ,\n                    PosixFilePermission.OWNER_WRITE,\n                    PosixFilePermission.OWNER_EXECUTE\n                )\n            );\n\n            Files.setPosixFilePermissions(file.toPath(), permissions);\n\n            mariadbCustomConfig.start();\n\n            assertThatCustomIniFileWasUsed(mariadbCustomConfig);\n        }\n    }\n\n    @Test\n    void testEmptyPasswordWithRootUser() throws SQLException {\n        try (MariaDBContainer mysql = new MariaDBContainer(\"mariadb:11.2.4\").withUsername(\"root\")) {\n            mysql.start();\n\n            ResultSet resultSet = performQuery(mysql, \"SELECT 1\");\n            int resultSetInt = resultSet.getInt(1);\n\n            assertThat(resultSetInt).isEqualTo(1);\n        }\n    }\n\n    private void assertThatCustomIniFileWasUsed(MariaDBContainer mariadb) throws SQLException {\n        try (ResultSet resultSet = performQuery(mariadb, \"SELECT @@GLOBAL.innodb_max_undo_log_size\")) {\n            long result = resultSet.getLong(1);\n            assertThat(result)\n                .as(\"The InnoDB max undo log size has been set by the ini file content\")\n                .isEqualTo(20000000);\n        }\n    }\n}\n"
  },
  {
    "path": "modules/mariadb/src/test/java/org/testcontainers/mariadb/MariaDBR2DBCDatabaseContainerTest.java",
    "content": "package org.testcontainers.mariadb;\n\nimport io.r2dbc.spi.ConnectionFactoryOptions;\nimport org.testcontainers.r2dbc.AbstractR2DBCDatabaseContainerTest;\nimport org.testcontainers.utility.DockerImageName;\n\npublic class MariaDBR2DBCDatabaseContainerTest extends AbstractR2DBCDatabaseContainerTest<MariaDBContainer> {\n\n    @Override\n    protected ConnectionFactoryOptions getOptions(MariaDBContainer container) {\n        return MariaDBR2DBCDatabaseContainer.getOptions(container);\n    }\n\n    @Override\n    protected String createR2DBCUrl() {\n        return \"r2dbc:tc:mariadb:///db?TC_IMAGE_TAG=10.3.39\";\n    }\n\n    @Override\n    protected MariaDBContainer createContainer() {\n        return new MariaDBContainer(DockerImageName.parse(\"mariadb:10.3.39\"));\n    }\n}\n"
  },
  {
    "path": "modules/mariadb/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/mariadb/src/test/resources/somepath/init_mariadb.sql",
    "content": "CREATE TABLE bar (\n  foo VARCHAR(255)\n);\n\nINSERT INTO bar (foo) VALUES ('hello world');"
  },
  {
    "path": "modules/mariadb/src/test/resources/somepath/init_unicode_mariadb.sql",
    "content": "CREATE TABLE bar (\n  foo varchar(255) character set utf8\n);\n\nINSERT INTO bar (foo) VALUES ('привет мир');\n"
  },
  {
    "path": "modules/mariadb/src/test/resources/somepath/mariadb_conf_override/my.cnf",
    "content": "[mysqld]\nport   \t\t= 3306\n#socket \t\t= /tmp/mysql.sock\nskip-external-locking\nkey_buffer_size = 16K\nmax_allowed_packet = 1M\ntable_open_cache = 4\nsort_buffer_size = 64K\nread_buffer_size = 256K\nread_rnd_buffer_size = 256K\nnet_buffer_length = 2K\nthread_stack = 512K\nskip-host-cache\nskip-name-resolve\n\n# This configuration is custom to test whether config override works\ninnodb_max_undo_log_size = 20000000\n\n# Don't listen on a TCP/IP port at all. This can be a security enhancement,\n# if all processes that need to connect to mysqld run on the same host.\n# All interaction with mysqld must be made via Unix sockets or named pipes.\n# Note that using this option without enabling named pipes on Windows\n# (using the \"enable-named-pipe\" option) will render mysqld useless!\n#\n#skip-networking\n#server-id      \t= 1\n\n# Uncomment the following if you want to log updates\n#log-bin=mysql-bin\n\n# binary logging format - mixed recommended\n#binlog_format=mixed\n\n# Causes updates to non-transactional engines using statement format to be\n# written directly to binary log. Before using this option make sure that\n# there are no dependencies between transactional and non-transactional\n# tables such as in the statement INSERT INTO t_myisam SELECT * FROM\n# t_innodb; otherwise, slaves may diverge from the master.\n#binlog_direct_non_transactional_updates=TRUE\n\n# Uncomment the following if you are using InnoDB tables\ninnodb_data_file_path = ibdata1:10M:autoextend\n# You can set .._buffer_pool_size up to 50 - 80 %\n# of RAM but beware of setting memory usage too high\ninnodb_buffer_pool_size = 16M\n#innodb_additional_mem_pool_size = 2M\n# Set .._log_file_size to 25 % of buffer pool size\ninnodb_log_file_size = 5M\ninnodb_log_buffer_size = 8M\ninnodb_flush_log_at_trx_commit = 1\ninnodb_lock_wait_timeout = 50\n"
  },
  {
    "path": "modules/milvus/build.gradle",
    "content": "description = \"Testcontainers :: Milvus\"\n\ndependencies {\n    api project(':testcontainers')\n\n    testImplementation 'io.milvus:milvus-sdk-java:2.6.10'\n}\n"
  },
  {
    "path": "modules/milvus/src/main/java/org/testcontainers/milvus/MilvusContainer.java",
    "content": "package org.testcontainers.milvus;\n\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.MountableFile;\n\n/**\n * Testcontainers implementation for Milvus.\n * <p>\n * Supported image: {@code milvusdb/milvus}\n * <p>\n * Exposed ports:\n * <ul>\n *     <li>Management port: 9091</li>\n *     <li>HTTP: 19530</li>\n * </ul>\n */\npublic class MilvusContainer extends GenericContainer<MilvusContainer> {\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"milvusdb/milvus\");\n\n    private String etcdEndpoint;\n\n    public MilvusContainer(String image) {\n        this(DockerImageName.parse(image));\n    }\n\n    public MilvusContainer(DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n        withExposedPorts(9091, 19530);\n        waitingFor(Wait.forHttp(\"/healthz\").forPort(9091));\n        withCommand(\"milvus\", \"run\", \"standalone\");\n        withCopyFileToContainer(\n            MountableFile.forClasspathResource(\"testcontainers/embedEtcd.yaml\"),\n            \"/milvus/configs/embedEtcd.yaml\"\n        );\n        withEnv(\"COMMON_STORAGETYPE\", \"local\");\n    }\n\n    @Override\n    protected void configure() {\n        if (this.etcdEndpoint == null) {\n            withEnv(\"ETCD_USE_EMBED\", \"true\");\n            withEnv(\"ETCD_DATA_DIR\", \"/var/lib/milvus/etcd\");\n            withEnv(\"ETCD_CONFIG_PATH\", \"/milvus/configs/embedEtcd.yaml\");\n        } else {\n            withEnv(\"ETCD_ENDPOINTS\", this.etcdEndpoint);\n        }\n    }\n\n    public MilvusContainer withEtcdEndpoint(String etcdEndpoint) {\n        this.etcdEndpoint = etcdEndpoint;\n        return this;\n    }\n\n    public String getEndpoint() {\n        return \"http://\" + getHost() + \":\" + getMappedPort(19530);\n    }\n}\n"
  },
  {
    "path": "modules/milvus/src/main/resources/testcontainers/embedEtcd.yaml",
    "content": "listen-client-urls: http://0.0.0.0:2379\nadvertise-client-urls: http://0.0.0.0:2379\n"
  },
  {
    "path": "modules/milvus/src/test/java/org/testcontainers/milvus/MilvusContainerTest.java",
    "content": "package org.testcontainers.milvus;\n\nimport io.milvus.client.MilvusServiceClient;\nimport io.milvus.param.ConnectParam;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.Network;\nimport org.testcontainers.containers.wait.strategy.Wait;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass MilvusContainerTest {\n\n    @Test\n    void withDefaultConfig() {\n        try (\n            // milvusContainer {\n            MilvusContainer milvus = new MilvusContainer(\"milvusdb/milvus:v2.3.9\")\n            // }\n        ) {\n            milvus.start();\n\n            assertThat(milvus.getEnvMap()).doesNotContainKey(\"ETCD_ENDPOINTS\");\n            assertMilvusVersion(milvus);\n        }\n    }\n\n    @Test\n    void withExternalEtcd() {\n        try (\n            // externalEtcd {\n            Network network = Network.newNetwork();\n            GenericContainer<?> etcd = new GenericContainer<>(\"quay.io/coreos/etcd:v3.5.5\")\n                .withNetwork(network)\n                .withNetworkAliases(\"etcd\")\n                .withCommand(\n                    \"etcd\",\n                    \"-advertise-client-urls=http://127.0.0.1:2379\",\n                    \"-listen-client-urls=http://0.0.0.0:2379\",\n                    \"--data-dir=/etcd\"\n                )\n                .withEnv(\"ETCD_AUTO_COMPACTION_MODE\", \"revision\")\n                .withEnv(\"ETCD_AUTO_COMPACTION_RETENTION\", \"1000\")\n                .withEnv(\"ETCD_QUOTA_BACKEND_BYTES\", \"4294967296\")\n                .withEnv(\"ETCD_SNAPSHOT_COUNT\", \"50000\")\n                .waitingFor(Wait.forLogMessage(\".*ready to serve client requests.*\", 1));\n            MilvusContainer milvus = new MilvusContainer(\"milvusdb/milvus:v2.3.9\")\n                .withNetwork(network)\n                .withEtcdEndpoint(\"etcd:2379\")\n                .dependsOn(etcd)\n            // }\n        ) {\n            milvus.start();\n\n            assertThat(milvus.getEnvMap()).doesNotContainKey(\"ETCD_USE_EMBED\");\n            assertMilvusVersion(milvus);\n        }\n    }\n\n    private static void assertMilvusVersion(MilvusContainer milvus) {\n        MilvusServiceClient milvusClient = new MilvusServiceClient(\n            ConnectParam.newBuilder().withUri(milvus.getEndpoint()).build()\n        );\n        assertThat(milvusClient.getVersion().getData().getVersion()).isEqualTo(\"v2.3.9\");\n    }\n}\n"
  },
  {
    "path": "modules/milvus/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/minio/build.gradle",
    "content": "description = \"Testcontainers :: MinIO\"\n\ndependencies {\n    api project(':testcontainers')\n\n    testImplementation(\"io.minio:minio:8.6.0\")\n}\n"
  },
  {
    "path": "modules/minio/src/main/java/org/testcontainers/containers/MinIOContainer.java",
    "content": "package org.testcontainers.containers;\n\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.time.Duration;\nimport java.time.temporal.ChronoUnit;\n\n/**\n * Testcontainers implementation for MinIO.\n * <p>\n * Supported image: {@code minio/minio}\n * <p>\n * Exposed ports:\n * <ul>\n *     <li>S3: 9000</li>\n *     <li>Console: 9001</li>\n * </ul>\n */\npublic class MinIOContainer extends GenericContainer<MinIOContainer> {\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"minio/minio\");\n\n    private static final int MINIO_S3_PORT = 9000;\n\n    private static final int MINIO_UI_PORT = 9001;\n\n    private static final String DEFAULT_USER = \"minioadmin\";\n\n    private static final String DEFAULT_PASSWORD = \"minioadmin\";\n\n    private String userName;\n\n    private String password;\n\n    /**\n     * Constructs a MinIO container from the dockerImageName\n     * @param dockerImageName the full image name to use\n     */\n    public MinIOContainer(final String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    /**\n     * Constructs a MinIO container from the dockerImageName\n     * @param dockerImageName the full image name to use\n     */\n    public MinIOContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n        withExposedPorts(MINIO_S3_PORT, MINIO_UI_PORT);\n        withCommand(\"server\", \"--console-address\", \":\" + MINIO_UI_PORT, \"/data\");\n        waitingFor(\n            Wait\n                .forHttp(\"/minio/health/live\")\n                .forPort(MINIO_S3_PORT)\n                .withStartupTimeout(Duration.of(60, ChronoUnit.SECONDS))\n        );\n    }\n\n    /**\n     * Overrides the DEFAULT_USER\n     * @param userName the Root user to override\n     * @return this\n     */\n    public MinIOContainer withUserName(String userName) {\n        this.userName = userName;\n        return this;\n    }\n\n    /**\n     * Overrides the DEFAULT_PASSWORD\n     * @param password the Root user's password to override\n     * @return this\n     */\n    public MinIOContainer withPassword(String password) {\n        this.password = password;\n        return this;\n    }\n\n    /**\n     * Configures the MinIO container\n     */\n    @Override\n    public void configure() {\n        if (this.userName != null) {\n            addEnv(\"MINIO_ROOT_USER\", this.userName);\n        } else {\n            this.userName = DEFAULT_USER;\n        }\n        if (this.password != null) {\n            addEnv(\"MINIO_ROOT_PASSWORD\", this.password);\n        } else {\n            this.password = DEFAULT_PASSWORD;\n        }\n    }\n\n    /**\n     * @return the URL to upload/download objects from\n     */\n    public String getS3URL() {\n        return String.format(\"http://%s:%s\", this.getHost(), getMappedPort(MINIO_S3_PORT));\n    }\n\n    /**\n     * @return the Username for the Root user\n     */\n    public String getUserName() {\n        return this.userName;\n    }\n\n    /**\n     * @return the password for the Root user\n     */\n    public String getPassword() {\n        return this.password;\n    }\n}\n"
  },
  {
    "path": "modules/minio/src/test/java/org/testcontainers/containers/MinIOContainerTest.java",
    "content": "package org.testcontainers.containers;\n\nimport io.minio.BucketExistsArgs;\nimport io.minio.MakeBucketArgs;\nimport io.minio.MinioClient;\nimport io.minio.StatObjectArgs;\nimport io.minio.StatObjectResponse;\nimport io.minio.UploadObjectArgs;\nimport org.junit.jupiter.api.Test;\n\nimport java.net.URL;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass MinIOContainerTest {\n\n    @Test\n    void testBasicUsage() throws Exception {\n        try (\n            // minioContainer {\n            MinIOContainer container = new MinIOContainer(\"minio/minio:RELEASE.2023-09-04T19-57-37Z\");\n            // }\n        ) {\n            container.start();\n\n            // configuringClient {\n            MinioClient minioClient = MinioClient\n                .builder()\n                .endpoint(container.getS3URL())\n                .credentials(container.getUserName(), container.getPassword())\n                .build();\n\n            // }\n            minioClient.makeBucket(MakeBucketArgs.builder().bucket(\"test-bucket\").region(\"us-west-2\").build());\n\n            BucketExistsArgs existsArgs = BucketExistsArgs.builder().bucket(\"test-bucket\").build();\n\n            assertThat(minioClient.bucketExists(existsArgs)).isTrue();\n\n            URL file = this.getClass().getResource(\"/object_to_upload.txt\");\n            assertThat(file).isNotNull();\n            minioClient.uploadObject(\n                UploadObjectArgs\n                    .builder()\n                    .bucket(\"test-bucket\")\n                    .object(\"my-objectname\")\n                    .filename(file.getPath())\n                    .build()\n            );\n\n            StatObjectResponse objectStat = minioClient.statObject(\n                StatObjectArgs.builder().bucket(\"test-bucket\").object(\"my-objectname\").build()\n            );\n\n            assertThat(objectStat.object()).isEqualTo(\"my-objectname\");\n        }\n    }\n\n    @Test\n    void testDefaultUserPassword() {\n        try (MinIOContainer container = new MinIOContainer(\"minio/minio:RELEASE.2023-09-04T19-57-37Z\")) {\n            container.start();\n            assertThat(container.getUserName()).isEqualTo(\"minioadmin\");\n            assertThat(container.getPassword()).isEqualTo(\"minioadmin\");\n        }\n    }\n\n    @Test\n    void testOverwriteUserPassword() {\n        try (\n            // minioOverrides {\n            MinIOContainer container = new MinIOContainer(\"minio/minio:RELEASE.2023-09-04T19-57-37Z\")\n                .withUserName(\"testuser\")\n                .withPassword(\"testpassword\");\n            // }\n        ) {\n            container.start();\n            assertThat(container.getUserName()).isEqualTo(\"testuser\");\n            assertThat(container.getPassword()).isEqualTo(\"testpassword\");\n        }\n    }\n}\n"
  },
  {
    "path": "modules/minio/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/minio/src/test/resources/object_to_upload.txt",
    "content": "This is a file\n"
  },
  {
    "path": "modules/mockserver/build.gradle",
    "content": "description = \"Testcontainers :: MockServer\"\n\ndependencies {\n    api project(':testcontainers')\n\n    testImplementation 'org.mock-server:mockserver-client-java:5.15.0'\n    testImplementation 'io.rest-assured:rest-assured:5.5.6'\n}\n"
  },
  {
    "path": "modules/mockserver/src/main/java/org/testcontainers/containers/MockServerContainer.java",
    "content": "package org.testcontainers.containers;\n\nimport lombok.extern.slf4j.Slf4j;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.utility.DockerImageName;\n\n/**\n * @deprecated use {@link org.testcontainers.mockserver.MockServerContainer} instead.\n */\n@Slf4j\n@Deprecated\npublic class MockServerContainer extends GenericContainer<MockServerContainer> {\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"jamesdbloom/mockserver\");\n\n    private static final String DEFAULT_TAG = \"mockserver-5.5.4\";\n\n    @Deprecated\n    public static final String VERSION = DEFAULT_TAG;\n\n    public static final int PORT = 1080;\n\n    /**\n     * @deprecated use {@link #MockServerContainer(DockerImageName)} instead\n     */\n    @Deprecated\n    public MockServerContainer() {\n        this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG));\n    }\n\n    /**\n     * @deprecated use {@link #MockServerContainer(DockerImageName)} instead\n     */\n    @Deprecated\n    public MockServerContainer(String version) {\n        this(DEFAULT_IMAGE_NAME.withTag(\"mockserver-\" + version));\n    }\n\n    public MockServerContainer(DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME, DockerImageName.parse(\"mockserver/mockserver\"));\n\n        waitingFor(Wait.forLogMessage(\".*started on port: \" + PORT + \".*\", 1));\n\n        withCommand(\"-serverPort \" + PORT);\n        addExposedPorts(PORT);\n    }\n\n    public String getEndpoint() {\n        return String.format(\"http://%s:%d\", getHost(), getMappedPort(PORT));\n    }\n\n    public String getSecureEndpoint() {\n        return String.format(\"https://%s:%d\", getHost(), getMappedPort(PORT));\n    }\n\n    public Integer getServerPort() {\n        return getMappedPort(PORT);\n    }\n}\n"
  },
  {
    "path": "modules/mockserver/src/main/java/org/testcontainers/mockserver/MockServerContainer.java",
    "content": "package org.testcontainers.mockserver;\n\nimport lombok.extern.slf4j.Slf4j;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.utility.DockerImageName;\n\n@Slf4j\npublic class MockServerContainer extends GenericContainer<MockServerContainer> {\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"jamesdbloom/mockserver\");\n\n    private static final String DEFAULT_TAG = \"mockserver-5.5.4\";\n\n    @Deprecated\n    public static final String VERSION = DEFAULT_TAG;\n\n    public static final int PORT = 1080;\n\n    /**\n     * @deprecated use {@link #MockServerContainer(DockerImageName)} instead\n     */\n    @Deprecated\n    public MockServerContainer(String version) {\n        this(DEFAULT_IMAGE_NAME.withTag(\"mockserver-\" + version));\n    }\n\n    public MockServerContainer(DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME, DockerImageName.parse(\"mockserver/mockserver\"));\n\n        waitingFor(Wait.forLogMessage(\".*started on port: \" + PORT + \".*\", 1));\n\n        withCommand(\"-serverPort \" + PORT);\n        addExposedPorts(PORT);\n    }\n\n    public String getEndpoint() {\n        return String.format(\"http://%s:%d\", getHost(), getMappedPort(PORT));\n    }\n\n    public String getSecureEndpoint() {\n        return String.format(\"https://%s:%d\", getHost(), getMappedPort(PORT));\n    }\n\n    public Integer getServerPort() {\n        return getMappedPort(PORT);\n    }\n}\n"
  },
  {
    "path": "modules/mockserver/src/test/java/org/testcontainers/mockserver/MockServerContainerTest.java",
    "content": "package org.testcontainers.mockserver;\n\nimport io.restassured.config.RestAssuredConfig;\nimport io.restassured.config.SSLConfig;\nimport org.apache.http.conn.ssl.SSLSocketFactory;\nimport org.junit.jupiter.api.Test;\nimport org.mockserver.client.MockServerClient;\nimport org.mockserver.configuration.Configuration;\nimport org.mockserver.logging.MockServerLogger;\nimport org.mockserver.socket.tls.KeyStoreFactory;\nimport org.testcontainers.utility.DockerImageName;\n\nimport static io.restassured.RestAssured.given;\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.mockserver.model.HttpRequest.request;\nimport static org.mockserver.model.HttpResponse.response;\n\nclass MockServerContainerTest {\n\n    public static final DockerImageName MOCKSERVER_IMAGE = DockerImageName\n        .parse(\"mockserver/mockserver\")\n        .withTag(\"mockserver-\" + MockServerClient.class.getPackage().getImplementationVersion());\n\n    @Test\n    void shouldCallActualMockserverVersion() {\n        try ( // creatingProxy {\n            MockServerContainer mockServer = new MockServerContainer(MOCKSERVER_IMAGE)\n            // }\n        ) {\n            mockServer.start();\n\n            String expectedBody = \"Hello World!\";\n\n            try (MockServerClient client = new MockServerClient(mockServer.getHost(), mockServer.getServerPort())) {\n                assertThat(client.hasStarted()).as(\"Mockserver running\").isTrue();\n\n                client.when(request().withPath(\"/hello\")).respond(response().withBody(expectedBody));\n\n                assertThat(given().when().get(mockServer.getEndpoint() + \"/hello\").then().extract().body().asString())\n                    .as(\"MockServer returns correct result\")\n                    .isEqualTo(expectedBody);\n            }\n        }\n    }\n\n    @Test\n    void shouldCallMockserverUsingTlsProtocol() {\n        try (MockServerContainer mockServer = new MockServerContainer(MOCKSERVER_IMAGE)) {\n            mockServer.start();\n\n            String expectedBody = \"Hello World!\";\n\n            try (\n                MockServerClient client = new MockServerClient(mockServer.getHost(), mockServer.getServerPort())\n                    .withSecure(true)\n            ) {\n                assertThat(client.hasStarted()).as(\"Mockserver running\").isTrue();\n\n                client.when(request().withPath(\"/hello\")).respond(response().withBody(expectedBody));\n\n                assertThat(secureResponseFromMockserver(mockServer))\n                    .as(\"MockServer returns correct result\")\n                    .isEqualTo(expectedBody);\n            }\n        }\n    }\n\n    @Test\n    void shouldCallMockserverUsingMutualTlsProtocol() {\n        try (\n            MockServerContainer mockServer = new MockServerContainer(MOCKSERVER_IMAGE)\n                .withEnv(\"MOCKSERVER_TLS_MUTUAL_AUTHENTICATION_REQUIRED\", \"true\")\n        ) {\n            mockServer.start();\n\n            String expectedBody = \"Hello World!\";\n\n            try (\n                MockServerClient client = new MockServerClient(mockServer.getHost(), mockServer.getServerPort())\n                    .withSecure(true)\n            ) {\n                assertThat(client.hasStarted()).as(\"Mockserver running\").isTrue();\n\n                client.when(request().withPath(\"/hello\")).respond(response().withBody(expectedBody));\n\n                assertThat(secureResponseFromMockserver(mockServer))\n                    .as(\"MockServer returns correct result\")\n                    .isEqualTo(expectedBody);\n            }\n        }\n    }\n\n    @Test\n    void newVersionStartsWithDefaultWaitStrategy() {\n        try (MockServerContainer mockServer = new MockServerContainer(MOCKSERVER_IMAGE)) {\n            mockServer.start();\n        }\n    }\n\n    private static String secureResponseFromMockserver(MockServerContainer mockServer) {\n        return given()\n            .config(\n                RestAssuredConfig\n                    .config()\n                    .sslConfig(\n                        SSLConfig\n                            .sslConfig()\n                            .sslSocketFactory(\n                                new SSLSocketFactory(\n                                    new KeyStoreFactory(Configuration.configuration(), new MockServerLogger())\n                                        .sslContext()\n                                )\n                            )\n                    )\n            )\n            .baseUri(mockServer.getSecureEndpoint())\n            .get(\"/hello\")\n            .body()\n            .asString();\n    }\n}\n"
  },
  {
    "path": "modules/mockserver/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/mongodb/build.gradle",
    "content": "description = \"Testcontainers :: MongoDB\"\n\ndependencies {\n    api project(':testcontainers')\n\n    testImplementation(\"org.mongodb:mongodb-driver-sync:5.1.4\")\n}\n"
  },
  {
    "path": "modules/mongodb/src/main/java/org/testcontainers/containers/MongoDBContainer.java",
    "content": "package org.testcontainers.containers;\n\nimport com.github.dockerjava.api.command.InspectContainerResponse;\nimport lombok.NonNull;\nimport lombok.SneakyThrows;\nimport lombok.extern.slf4j.Slf4j;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.MountableFile;\n\nimport java.io.IOException;\n\n/**\n * Testcontainers implementation for MongoDB.\n * <p>\n * Supported images: {@code mongo}, {@code mongodb/mongodb-community-server}, {@code mongodb/mongodb-enterprise-server}\n * <p>\n * Exposed ports: 27017\n *\n * @deprecated use {@link org.testcontainers.mongodb.MongoDBContainer} instead.\n */\n@Slf4j\n@Deprecated\npublic class MongoDBContainer extends GenericContainer<MongoDBContainer> {\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"mongo\");\n\n    private static final DockerImageName COMMUNITY_SERVER_IMAGE = DockerImageName.parse(\n        \"mongodb/mongodb-community-server\"\n    );\n\n    private static final DockerImageName ENTERPRISE_SERVER_IMAGE = DockerImageName.parse(\n        \"mongodb/mongodb-enterprise-server\"\n    );\n\n    private static final String DEFAULT_TAG = \"4.0.10\";\n\n    private static final int CONTAINER_EXIT_CODE_OK = 0;\n\n    private static final int AWAIT_INIT_REPLICA_SET_ATTEMPTS = 60;\n\n    private static final String MONGODB_DATABASE_NAME_DEFAULT = \"test\";\n\n    private static final String STARTER_SCRIPT = \"/testcontainers_start.sh\";\n\n    private boolean shardingEnabled;\n\n    /**\n     * @deprecated use {@link #MongoDBContainer(DockerImageName)} instead\n     */\n    @Deprecated\n    public MongoDBContainer() {\n        this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG));\n    }\n\n    public MongoDBContainer(@NonNull final String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    public MongoDBContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME, COMMUNITY_SERVER_IMAGE, ENTERPRISE_SERVER_IMAGE);\n    }\n\n    @Override\n    MongoDBContainerDef createContainerDef() {\n        return new MongoDBContainerDef();\n    }\n\n    @Override\n    MongoDBContainerDef getContainerDef() {\n        return (MongoDBContainerDef) super.getContainerDef();\n    }\n\n    @Override\n    protected void containerIsStarting(InspectContainerResponse containerInfo) {\n        if (this.shardingEnabled) {\n            copyFileToContainer(MountableFile.forClasspathResource(\"/sharding.sh\", 0777), STARTER_SCRIPT);\n        }\n    }\n\n    /**\n     * Enables sharding on the cluster\n     *\n     * @return this\n     */\n    public MongoDBContainer withSharding() {\n        this.shardingEnabled = true;\n        getContainerDef().withSharding();\n        return this;\n    }\n\n    @Override\n    protected void containerIsStarted(InspectContainerResponse containerInfo, boolean reused) {\n        if (!this.shardingEnabled) {\n            initReplicaSet(reused);\n        }\n    }\n\n    /**\n     * Gets a connection string url, unlike {@link #getReplicaSetUrl} this does not point to a\n     * database\n     * @return a connection url pointing to a mongodb instance\n     */\n    public String getConnectionString() {\n        return String.format(\"mongodb://%s:%d\", getHost(), getMappedPort(MongoDBContainerDef.MONGODB_INTERNAL_PORT));\n    }\n\n    /**\n     * Gets a replica set url for the default {@value #MONGODB_DATABASE_NAME_DEFAULT} database.\n     *\n     * @return a replica set url.\n     */\n    public String getReplicaSetUrl() {\n        return getReplicaSetUrl(MONGODB_DATABASE_NAME_DEFAULT);\n    }\n\n    /**\n     * Gets a replica set url for a provided <code>databaseName</code>.\n     *\n     * @param databaseName a database name.\n     * @return a replica set url.\n     */\n    public String getReplicaSetUrl(final String databaseName) {\n        if (!isRunning()) {\n            throw new IllegalStateException(\"MongoDBContainer should be started first\");\n        }\n        return getConnectionString() + \"/\" + databaseName;\n    }\n\n    private String[] buildMongoEvalCommand(final String command) {\n        return new String[] {\n            \"sh\",\n            \"-c\",\n            \"mongosh mongo --eval \\\"\" + command + \"\\\"  || mongo --eval \\\"\" + command + \"\\\"\",\n        };\n    }\n\n    private void checkMongoNodeExitCode(final Container.ExecResult execResult) {\n        if (execResult.getExitCode() != CONTAINER_EXIT_CODE_OK) {\n            final String errorMessage = String.format(\"An error occurred: %s\", execResult.getStdout());\n            log.error(errorMessage);\n            throw new ReplicaSetInitializationException(errorMessage);\n        }\n    }\n\n    private String buildMongoWaitCommand() {\n        return String.format(\n            \"var attempt = 0; \" +\n            \"while\" +\n            \"(%s) \" +\n            \"{ \" +\n            \"if (attempt > %d) {quit(1);} \" +\n            \"print('%s ' + attempt); sleep(100);  attempt++; \" +\n            \" }\",\n            \"db.runCommand( { isMaster: 1 } ).ismaster==false\",\n            AWAIT_INIT_REPLICA_SET_ATTEMPTS,\n            \"An attempt to await for a single node replica set initialization:\"\n        );\n    }\n\n    private void checkMongoNodeExitCodeAfterWaiting(final Container.ExecResult execResultWaitForMaster) {\n        if (execResultWaitForMaster.getExitCode() != CONTAINER_EXIT_CODE_OK) {\n            final String errorMessage = String.format(\n                \"A single node replica set was not initialized in a set timeout: %d attempts\",\n                AWAIT_INIT_REPLICA_SET_ATTEMPTS\n            );\n            log.error(errorMessage);\n            throw new ReplicaSetInitializationException(errorMessage);\n        }\n    }\n\n    @SneakyThrows(value = { IOException.class, InterruptedException.class })\n    private void initReplicaSet(boolean reused) {\n        if (reused && isReplicationSetAlreadyInitialized()) {\n            log.debug(\"Replica set already initialized.\");\n        } else {\n            log.debug(\"Initializing a single node node replica set...\");\n            final ExecResult execResultInitRs = execInContainer(buildMongoEvalCommand(\"rs.initiate();\"));\n            log.debug(execResultInitRs.getStdout());\n            checkMongoNodeExitCode(execResultInitRs);\n\n            log.debug(\n                \"Awaiting for a single node replica set initialization up to {} attempts\",\n                AWAIT_INIT_REPLICA_SET_ATTEMPTS\n            );\n            final ExecResult execResultWaitForMaster = execInContainer(buildMongoEvalCommand(buildMongoWaitCommand()));\n            log.debug(execResultWaitForMaster.getStdout());\n\n            checkMongoNodeExitCodeAfterWaiting(execResultWaitForMaster);\n        }\n    }\n\n    public static class ReplicaSetInitializationException extends RuntimeException {\n\n        ReplicaSetInitializationException(final String errorMessage) {\n            super(errorMessage);\n        }\n    }\n\n    @SneakyThrows\n    private boolean isReplicationSetAlreadyInitialized() {\n        // since we are creating a replica set with one node, this node must be primary (state = 1)\n        final ExecResult execCheckRsInit = execInContainer(\n            buildMongoEvalCommand(\"if(db.adminCommand({replSetGetStatus: 1})['myState'] != 1) quit(900)\")\n        );\n        return execCheckRsInit.getExitCode() == CONTAINER_EXIT_CODE_OK;\n    }\n\n    private static class MongoDBContainerDef extends ContainerDef {\n\n        private static final int MONGODB_INTERNAL_PORT = 27017;\n\n        MongoDBContainerDef() {\n            addExposedTcpPort(MONGODB_INTERNAL_PORT);\n            setCommand(\"--replSet\", \"docker-rs\");\n            setWaitStrategy(Wait.forLogMessage(\"(?i).*waiting for connections.*\", 1));\n        }\n\n        void withSharding() {\n            setCommand(\"-c\", \"while [ ! -f \" + STARTER_SCRIPT + \" ]; do sleep 0.1; done; \" + STARTER_SCRIPT);\n            setWaitStrategy(Wait.forLogMessage(\"(?i).*mongos ready.*\", 1));\n            setEntrypoint(\"sh\");\n        }\n    }\n}\n"
  },
  {
    "path": "modules/mongodb/src/main/java/org/testcontainers/mongodb/MongoDBAtlasLocalContainer.java",
    "content": "package org.testcontainers.mongodb;\n\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.utility.DockerImageName;\n\n/**\n * Testcontainers implementation for MongoDB Atlas.\n * <p>\n * Supported images: {@code mongodb/mongodb-atlas-local}\n * <p>\n * Exposed ports: 27017\n */\npublic class MongoDBAtlasLocalContainer extends GenericContainer<MongoDBAtlasLocalContainer> {\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"mongodb/mongodb-atlas-local\");\n\n    private static final int MONGODB_INTERNAL_PORT = 27017;\n\n    private static final String MONGODB_DATABASE_NAME_DEFAULT = \"test\";\n\n    private static final String DIRECT_CONNECTION = \"directConnection=true\";\n\n    public MongoDBAtlasLocalContainer(final String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    public MongoDBAtlasLocalContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n\n        withExposedPorts(MONGODB_INTERNAL_PORT);\n        waitingFor(Wait.forSuccessfulCommand(\"runner healthcheck\"));\n    }\n\n    /**\n     * Get the connection string to MongoDB.\n     */\n    public String getConnectionString() {\n        return baseConnectionString() + \"/?\" + DIRECT_CONNECTION;\n    }\n\n    private String baseConnectionString() {\n        return String.format(\"mongodb://%s:%d\", getHost(), getMappedPort(MONGODB_INTERNAL_PORT));\n    }\n\n    /**\n     * Gets a database specific connection string for the default {@value #MONGODB_DATABASE_NAME_DEFAULT} database.\n     *\n     * @return a database specific connection string.\n     */\n    public String getDatabaseConnectionString() {\n        return getDatabaseConnectionString(MONGODB_DATABASE_NAME_DEFAULT);\n    }\n\n    /**\n     * Gets a database specific connection string for a provided <code>databaseName</code>.\n     *\n     * @param databaseName a database name.\n     * @return a database specific connection string.\n     */\n    public String getDatabaseConnectionString(final String databaseName) {\n        if (!isRunning()) {\n            throw new IllegalStateException(\"MongoDBContainer should be started first\");\n        }\n        return baseConnectionString() + \"/\" + databaseName + \"?\" + DIRECT_CONNECTION;\n    }\n}\n"
  },
  {
    "path": "modules/mongodb/src/main/java/org/testcontainers/mongodb/MongoDBContainer.java",
    "content": "package org.testcontainers.mongodb;\n\nimport com.github.dockerjava.api.command.InspectContainerResponse;\nimport lombok.NonNull;\nimport lombok.SneakyThrows;\nimport lombok.extern.slf4j.Slf4j;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.MountableFile;\n\nimport java.io.IOException;\n\n/**\n * Testcontainers implementation for MongoDB.\n * <p>\n * Supported images: {@code mongo}, {@code mongodb/mongodb-community-server}, {@code mongodb/mongodb-enterprise-server}\n * <p>\n * Exposed ports: 27017\n */\n@Slf4j\npublic class MongoDBContainer extends GenericContainer<MongoDBContainer> {\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"mongo\");\n\n    private static final DockerImageName COMMUNITY_SERVER_IMAGE = DockerImageName.parse(\n        \"mongodb/mongodb-community-server\"\n    );\n\n    private static final DockerImageName ENTERPRISE_SERVER_IMAGE = DockerImageName.parse(\n        \"mongodb/mongodb-enterprise-server\"\n    );\n\n    private static final int MONGODB_INTERNAL_PORT = 27017;\n\n    private static final int CONTAINER_EXIT_CODE_OK = 0;\n\n    private static final int AWAIT_INIT_REPLICA_SET_ATTEMPTS = 60;\n\n    private static final String MONGODB_DATABASE_NAME_DEFAULT = \"test\";\n\n    private static final String STARTER_SCRIPT = \"/testcontainers_start.sh\";\n\n    private boolean shardingEnabled;\n\n    private boolean rsEnabled;\n\n    public MongoDBContainer(@NonNull String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    public MongoDBContainer(DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME, COMMUNITY_SERVER_IMAGE, ENTERPRISE_SERVER_IMAGE);\n\n        withExposedPorts(MONGODB_INTERNAL_PORT);\n    }\n\n    @Override\n    protected void containerIsStarting(InspectContainerResponse containerInfo) {\n        if (this.shardingEnabled) {\n            copyFileToContainer(MountableFile.forClasspathResource(\"/sharding.sh\", 0777), STARTER_SCRIPT);\n        }\n    }\n\n    @Override\n    protected void containerIsStarted(InspectContainerResponse containerInfo, boolean reused) {\n        if (this.rsEnabled) {\n            initReplicaSet(reused);\n        }\n    }\n\n    private String[] buildMongoEvalCommand(String command) {\n        return new String[] {\n            \"sh\",\n            \"-c\",\n            \"mongosh mongo --eval \\\"\" + command + \"\\\"  || mongo --eval \\\"\" + command + \"\\\"\",\n        };\n    }\n\n    private void checkMongoNodeExitCode(ExecResult execResult) {\n        if (execResult.getExitCode() != CONTAINER_EXIT_CODE_OK) {\n            String errorMessage = String.format(\"An error occurred: %s\", execResult.getStdout());\n            log.error(errorMessage);\n            throw new ReplicaSetInitializationException(errorMessage);\n        }\n    }\n\n    private String buildMongoWaitCommand() {\n        return String.format(\n            \"var attempt = 0; \" +\n            \"while\" +\n            \"(%s) \" +\n            \"{ \" +\n            \"if (attempt > %d) {quit(1);} \" +\n            \"print('%s ' + attempt); sleep(100);  attempt++; \" +\n            \" }\",\n            \"db.runCommand( { isMaster: 1 } ).ismaster==false\",\n            AWAIT_INIT_REPLICA_SET_ATTEMPTS,\n            \"An attempt to await for a single node replica set initialization:\"\n        );\n    }\n\n    private void checkMongoNodeExitCodeAfterWaiting(ExecResult execResultWaitForMaster) {\n        if (execResultWaitForMaster.getExitCode() != CONTAINER_EXIT_CODE_OK) {\n            String errorMessage = String.format(\n                \"A single node replica set was not initialized in a set timeout: %d attempts\",\n                AWAIT_INIT_REPLICA_SET_ATTEMPTS\n            );\n            log.error(errorMessage);\n            throw new ReplicaSetInitializationException(errorMessage);\n        }\n    }\n\n    @SneakyThrows(value = { IOException.class, InterruptedException.class })\n    private void initReplicaSet(boolean reused) {\n        if (reused && isReplicationSetAlreadyInitialized()) {\n            log.debug(\"Replica set already initialized.\");\n        } else {\n            log.debug(\"Initializing a single node node replica set...\");\n            ExecResult execResultInitRs = execInContainer(buildMongoEvalCommand(\"rs.initiate();\"));\n            log.debug(execResultInitRs.getStdout());\n            checkMongoNodeExitCode(execResultInitRs);\n\n            log.debug(\n                \"Awaiting for a single node replica set initialization up to {} attempts\",\n                AWAIT_INIT_REPLICA_SET_ATTEMPTS\n            );\n            ExecResult execResultWaitForMaster = execInContainer(buildMongoEvalCommand(buildMongoWaitCommand()));\n            log.debug(execResultWaitForMaster.getStdout());\n\n            checkMongoNodeExitCodeAfterWaiting(execResultWaitForMaster);\n        }\n    }\n\n    public static class ReplicaSetInitializationException extends RuntimeException {\n\n        ReplicaSetInitializationException(String errorMessage) {\n            super(errorMessage);\n        }\n    }\n\n    @SneakyThrows\n    private boolean isReplicationSetAlreadyInitialized() {\n        // since we are creating a replica set with one node, this node must be primary (state = 1)\n        ExecResult execCheckRsInit = execInContainer(\n            buildMongoEvalCommand(\"if(db.adminCommand({replSetGetStatus: 1})['myState'] != 1) quit(900)\")\n        );\n        return execCheckRsInit.getExitCode() == CONTAINER_EXIT_CODE_OK;\n    }\n\n    /**\n     * Enables replica set on the cluster\n     *\n     * @return this\n     */\n    public MongoDBContainer withReplicaSet() {\n        this.rsEnabled = true;\n        withCommand(\"--replSet\", \"docker-rs\");\n        waitingFor(Wait.forLogMessage(\"(?i).*waiting for connections.*\", 1));\n        return this;\n    }\n\n    /**\n     * Enables sharding on the cluster\n     *\n     * @return this\n     */\n    public MongoDBContainer withSharding() {\n        this.shardingEnabled = true;\n        withCommand(\"-c\", \"while [ ! -f \" + STARTER_SCRIPT + \" ]; do sleep 0.1; done; \" + STARTER_SCRIPT);\n        waitingFor(Wait.forLogMessage(\"(?i).*mongos ready.*\", 1));\n        withCreateContainerCmdModifier(cmd -> cmd.withEntrypoint(\"sh\"));\n        return this;\n    }\n\n    /**\n     * Gets a connection string url, unlike {@link #getReplicaSetUrl} this does not point to a\n     * database\n     * @return a connection url pointing to a mongodb instance\n     */\n    public String getConnectionString() {\n        return String.format(\"mongodb://%s:%d\", getHost(), getMappedPort(MONGODB_INTERNAL_PORT));\n    }\n\n    /**\n     * Gets a replica set url for the default {@value #MONGODB_DATABASE_NAME_DEFAULT} database.\n     *\n     * @return a replica set url.\n     */\n    public String getReplicaSetUrl() {\n        return getReplicaSetUrl(MONGODB_DATABASE_NAME_DEFAULT);\n    }\n\n    /**\n     * Gets a replica set url for a provided <code>databaseName</code>.\n     *\n     * @param databaseName a database name.\n     * @return a replica set url.\n     */\n    public String getReplicaSetUrl(String databaseName) {\n        if (!isRunning()) {\n            throw new IllegalStateException(\"MongoDBContainer should be started first\");\n        }\n        return getConnectionString() + \"/\" + databaseName;\n    }\n}\n"
  },
  {
    "path": "modules/mongodb/src/main/resources/sharding.sh",
    "content": "#!/bin/bash\n\nCONFIGSVR=/tmp/mongod/configsvr\nSHARDSVR=/tmp/mongod/shardsvr\n\nfunction retry() {\n    COUNT=${COUNT:-0}\n    if [ $COUNT == 5 ]\n    then\n        echo Failed $COUNT attempts\n        exit 1\n    fi\n\n    sleep $COUNT\n    echo \"Attempt #$[ $COUNT  + 1 ] '$*' \"\n    eval $*\n    if [ $? -ne 0 ]\n    then\n        COUNT=$[ $COUNT + 1 ]\n        retry $*\n    fi\n    unset COUNT\n}\n\nfunction initReplSet() {\n    PORT=$1\n    COUNT=${2:-1}\n\n    CMD=\"mongosh --quiet --port $PORT --eval \\\"if(db.adminCommand({replSetGetStatus: 1})['myState'] != 1) quit(900)\\\"\"\n    eval $CMD\n    retVal=$?\n    if [ $retVal -ne 0 -a $COUNT -ne 5 ]\n    then\n        echo \"Initiating replSet (attempt $COUNT)\"\n        mongosh --quiet --port $PORT --eval 'rs.initiate();'\n        if [ $? -ne 0 ]\n        then\n            sleep $COUNT\n            initReplSet $PORT $[ $COUNT + 1 ]\n        fi\n    fi\n    unset COUNT\n}\n\nrm -rf $CONFIGSVR $SHARDSVR\nmkdir -p $CONFIGSVR\nmkdir -p $SHARDSVR\n\necho \"Starting configsvr\"\nmongod --bind_ip_all --configsvr --port 27019 --replSet configsvr-rs --dbpath $CONFIGSVR --logpath /tmp/configsvr.log &\necho \"Initiating configsvr replSet\"\ninitReplSet 27019\n\necho \"Starting shardsvr\"\nmongod --bind_ip_all --shardsvr --port 27018 --replSet shardsvr-rs --dbpath $SHARDSVR --logpath /tmp/shardsvr.log &\n\necho \"Initiating shardsvr replSet\"\ninitReplSet 27018\n\necho \"Starting mongos\"\nmongos --bind_ip_all --configdb configsvr-rs/localhost:27019 --logpath /tmp/mongos.log &\n\necho \"Adding a shard\"\nretry \"mongosh --eval 'sh.addShard(\\\"shardsvr-rs/`hostname`:27018\\\");'\"\n\necho \"mongos ready\"\nmongosh --quiet tctest --eval \"db.testcollection.insertOne({});\"\nsleep 36000\n"
  },
  {
    "path": "modules/mongodb/src/test/java/org/testcontainers/mongodb/AbstractMongo.java",
    "content": "package org.testcontainers.mongodb;\n\nimport com.mongodb.ReadConcern;\nimport com.mongodb.ReadPreference;\nimport com.mongodb.TransactionOptions;\nimport com.mongodb.WriteConcern;\nimport com.mongodb.client.ClientSession;\nimport com.mongodb.client.MongoClient;\nimport com.mongodb.client.MongoClients;\nimport com.mongodb.client.MongoCollection;\nimport com.mongodb.client.TransactionBody;\nimport org.bson.Document;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\npublic class AbstractMongo {\n\n    protected void executeTx(MongoDBContainer mongoDBContainer) {\n        final MongoClient mongoSyncClientBase = MongoClients.create(mongoDBContainer.getConnectionString());\n        final MongoClient mongoSyncClient = MongoClients.create(mongoDBContainer.getReplicaSetUrl());\n        mongoSyncClient\n            .getDatabase(\"mydb1\")\n            .getCollection(\"foo\")\n            .withWriteConcern(WriteConcern.MAJORITY)\n            .insertOne(new Document(\"abc\", 0));\n        mongoSyncClient\n            .getDatabase(\"mydb2\")\n            .getCollection(\"bar\")\n            .withWriteConcern(WriteConcern.MAJORITY)\n            .insertOne(new Document(\"xyz\", 0));\n        mongoSyncClientBase\n            .getDatabase(\"mydb3\")\n            .getCollection(\"baz\")\n            .withWriteConcern(WriteConcern.MAJORITY)\n            .insertOne(new Document(\"def\", 0));\n\n        final ClientSession clientSession = mongoSyncClient.startSession();\n        final TransactionOptions txnOptions = TransactionOptions\n            .builder()\n            .readPreference(ReadPreference.primary())\n            .readConcern(ReadConcern.LOCAL)\n            .writeConcern(WriteConcern.MAJORITY)\n            .build();\n\n        final String trxResult = \"Inserted into collections in different databases\";\n\n        TransactionBody<String> txnBody = () -> {\n            final MongoCollection<Document> coll1 = mongoSyncClient.getDatabase(\"mydb1\").getCollection(\"foo\");\n            final MongoCollection<Document> coll2 = mongoSyncClient.getDatabase(\"mydb2\").getCollection(\"bar\");\n\n            coll1.insertOne(clientSession, new Document(\"abc\", 1));\n            coll2.insertOne(clientSession, new Document(\"xyz\", 999));\n            return trxResult;\n        };\n\n        try {\n            final String trxResultActual = clientSession.withTransaction(txnBody, txnOptions);\n            assertThat(trxResultActual).isEqualTo(trxResult);\n        } catch (RuntimeException re) {\n            throw new IllegalStateException(re.getMessage(), re);\n        } finally {\n            clientSession.close();\n            mongoSyncClient.close();\n        }\n    }\n}\n"
  },
  {
    "path": "modules/mongodb/src/test/java/org/testcontainers/mongodb/AtlasLocalDataAccess.java",
    "content": "package org.testcontainers.mongodb;\n\nimport com.mongodb.ConnectionString;\nimport com.mongodb.MongoClientSettings;\nimport com.mongodb.client.ListSearchIndexesIterable;\nimport com.mongodb.client.MongoClient;\nimport com.mongodb.client.MongoClients;\nimport com.mongodb.client.MongoCollection;\nimport com.mongodb.client.MongoDatabase;\nimport com.mongodb.client.model.Aggregates;\nimport com.mongodb.client.model.search.SearchOperator;\nimport com.mongodb.client.model.search.SearchOptions;\nimport com.mongodb.client.model.search.SearchPath;\nimport org.bson.BsonDocument;\nimport org.bson.Document;\nimport org.bson.codecs.configuration.CodecRegistries;\nimport org.bson.codecs.configuration.CodecRegistry;\nimport org.bson.codecs.pojo.PojoCodecProvider;\nimport org.bson.conversions.Bson;\nimport org.bson.json.JsonWriterSettings;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\nimport java.time.Instant;\nimport java.time.temporal.ChronoUnit;\nimport java.util.Collections;\nimport java.util.concurrent.TimeUnit;\n\nimport static org.awaitility.Awaitility.await;\n\npublic class AtlasLocalDataAccess implements AutoCloseable {\n\n    private static final Logger log = LoggerFactory.getLogger(AtlasLocalDataAccess.class);\n\n    private final MongoClient mongoClient;\n\n    private final MongoDatabase testDB;\n\n    private final MongoCollection<TestData> testCollection;\n\n    private final String collectionName;\n\n    public AtlasLocalDataAccess(String connectionString, String databaseName, String collectionName) {\n        this.collectionName = collectionName;\n        log.info(\"DataAccess connecting to {}\", connectionString);\n\n        CodecRegistry pojoCodecRegistry = CodecRegistries.fromProviders(\n            PojoCodecProvider.builder().automatic(true).build()\n        );\n        CodecRegistry codecRegistry = CodecRegistries.fromRegistries(\n            MongoClientSettings.getDefaultCodecRegistry(),\n            pojoCodecRegistry\n        );\n        MongoClientSettings clientSettings = MongoClientSettings\n            .builder()\n            .applyConnectionString(new ConnectionString(connectionString))\n            .codecRegistry(codecRegistry)\n            .build();\n        mongoClient = MongoClients.create(clientSettings);\n        testDB = mongoClient.getDatabase(databaseName);\n        testCollection = testDB.getCollection(collectionName, TestData.class);\n    }\n\n    @Override\n    public void close() {\n        mongoClient.close();\n    }\n\n    public void initAtlasSearchIndex() throws URISyntaxException, IOException, InterruptedException {\n        //Create the collection (if it doesn't exist). Required because unlike other database operations, createSearchIndex will fail if the collection doesn't exist yet\n        testDB.createCollection(collectionName);\n\n        //Read the atlas search index JSON from a resource file\n        String atlasSearchIndexJson = new String(\n            Files.readAllBytes(Paths.get(getClass().getResource(\"/atlas-local-index.json\").toURI())),\n            StandardCharsets.UTF_8\n        );\n        log.info(\n            \"Creating Atlas Search index AtlasSearchIndex on collection {}:\\n{}\",\n            collectionName,\n            atlasSearchIndexJson\n        );\n        testCollection.createSearchIndex(\"AtlasSearchIndex\", BsonDocument.parse(atlasSearchIndexJson));\n\n        //wait for the atlas search index to be ready\n        Instant start = Instant.now();\n        await()\n            .atMost(5, TimeUnit.SECONDS)\n            .pollInterval(10, TimeUnit.MILLISECONDS)\n            .pollInSameThread()\n            .until(this::getIndexStatus, \"READY\"::equalsIgnoreCase);\n\n        log.info(\n            \"Atlas Search index AtlasSearchIndex on collection {} is ready (took {} milliseconds) to create.\",\n            collectionName,\n            start.until(Instant.now(), ChronoUnit.MILLIS)\n        );\n    }\n\n    private String getIndexStatus() {\n        ListSearchIndexesIterable<Document> searchIndexes = testCollection.listSearchIndexes();\n        for (Document searchIndex : searchIndexes) {\n            if (searchIndex.get(\"name\").equals(\"AtlasSearchIndex\")) {\n                return searchIndex.getString(\"status\");\n            }\n        }\n        return null;\n    }\n\n    public void insertData(TestData data) {\n        log.info(\"Inserting document {}\", data);\n        testCollection.insertOne(data);\n    }\n\n    public TestData findAtlasSearch(String test) {\n        Bson searchClause = Aggregates.search(\n            SearchOperator.of(SearchOperator.text(SearchPath.fieldPath(\"test\"), test).fuzzy()),\n            SearchOptions.searchOptions().index(\"AtlasSearchIndex\")\n        );\n        log.trace(\n            \"Searching for document using Atlas Search:\\n{}\",\n            searchClause.toBsonDocument().toJson(JsonWriterSettings.builder().indent(true).build())\n        );\n        return testCollection.aggregate(Collections.singletonList(searchClause)).first();\n    }\n\n    public static class TestData {\n\n        String test;\n\n        int test2;\n\n        boolean test3;\n\n        public TestData() {}\n\n        public TestData(String test, int test2, boolean test3) {\n            this.test = test;\n            this.test2 = test2;\n            this.test3 = test3;\n        }\n\n        public String getTest() {\n            return test;\n        }\n\n        public void setTest(String test) {\n            this.test = test;\n        }\n\n        public int getTest2() {\n            return test2;\n        }\n\n        public void setTest2(int test2) {\n            this.test2 = test2;\n        }\n\n        public boolean isTest3() {\n            return test3;\n        }\n\n        public void setTest3(boolean test3) {\n            this.test3 = test3;\n        }\n\n        @Override\n        public String toString() {\n            return \"TestData{\" + \"test='\" + test + '\\'' + \", test2=\" + test2 + \", test3=\" + test3 + '}';\n        }\n    }\n}\n"
  },
  {
    "path": "modules/mongodb/src/test/java/org/testcontainers/mongodb/CompatibleImageTest.java",
    "content": "package org.testcontainers.mongodb;\n\nimport com.mongodb.client.MongoClient;\nimport com.mongodb.client.MongoClients;\nimport org.bson.Document;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass CompatibleImageTest extends AbstractMongo {\n\n    static String[] image() {\n        return new String[] {\n            \"mongo:7\",\n            \"mongodb/mongodb-community-server:7.0.2-ubi8\",\n            \"mongodb/mongodb-enterprise-server:7.0.0-ubi8\",\n        };\n    }\n\n    @Test\n    void shouldExecuteTransactions() {\n        try (\n            // creatingMongoDBContainer {\n            MongoDBContainer mongoDBContainer = new MongoDBContainer(\"mongo:4.0.10\").withReplicaSet()\n            // }\n        ) {\n            // startingMongoDBContainer {\n            mongoDBContainer.start();\n            // }\n            executeTx(mongoDBContainer);\n        }\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"image\")\n    void shouldSupportSharding(String image) {\n        try (MongoDBContainer mongoDBContainer = new MongoDBContainer(image).withSharding()) {\n            mongoDBContainer.start();\n            final MongoClient mongoClient = MongoClients.create(mongoDBContainer.getReplicaSetUrl());\n\n            mongoClient.getDatabase(\"mydb1\").getCollection(\"foo\").insertOne(new Document(\"abc\", 0));\n\n            Document shards = mongoClient.getDatabase(\"config\").getCollection(\"shards\").find().first();\n            assertThat(shards).isNotNull();\n            assertThat(shards).isNotEmpty();\n            assertThat(isReplicaSet(mongoClient)).isFalse();\n        }\n    }\n\n    private boolean isReplicaSet(MongoClient mongoClient) {\n        return runIsMaster(mongoClient).get(\"setName\") != null;\n    }\n\n    private Document runIsMaster(MongoClient mongoClient) {\n        return mongoClient.getDatabase(\"admin\").runCommand(new Document(\"ismaster\", 1));\n    }\n}\n"
  },
  {
    "path": "modules/mongodb/src/test/java/org/testcontainers/mongodb/MongoDBAtlasLocalContainerTest.java",
    "content": "package org.testcontainers.mongodb;\n\nimport org.junit.jupiter.api.Test;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.time.Instant;\nimport java.time.temporal.ChronoUnit;\nimport java.util.Objects;\nimport java.util.concurrent.TimeUnit;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\n\nclass MongoDBAtlasLocalContainerTest {\n\n    private static final Logger log = LoggerFactory.getLogger(MongoDBAtlasLocalContainerTest.class);\n\n    @Test\n    void getConnectionString() {\n        try (\n            MongoDBAtlasLocalContainer container = new MongoDBAtlasLocalContainer(\"mongodb/mongodb-atlas-local:7.0.9\")\n        ) {\n            container.start();\n            String connectionString = container.getConnectionString();\n            assertThat(connectionString).isNotNull();\n            assertThat(connectionString).startsWith(\"mongodb://\");\n            assertThat(connectionString)\n                .isEqualTo(\n                    String.format(\n                        \"mongodb://%s:%d/?directConnection=true\",\n                        container.getHost(),\n                        container.getFirstMappedPort()\n                    )\n                );\n        }\n    }\n\n    @Test\n    void getDatabaseConnectionString() {\n        try (\n            MongoDBAtlasLocalContainer container = new MongoDBAtlasLocalContainer(\"mongodb/mongodb-atlas-local:7.0.9\")\n        ) {\n            container.start();\n            String databaseConnectionString = container.getDatabaseConnectionString();\n            assertThat(databaseConnectionString).isNotNull();\n            assertThat(databaseConnectionString).startsWith(\"mongodb://\");\n            assertThat(databaseConnectionString)\n                .isEqualTo(\n                    String.format(\n                        \"mongodb://%s:%d/test?directConnection=true\",\n                        container.getHost(),\n                        container.getFirstMappedPort()\n                    )\n                );\n        }\n    }\n\n    @Test\n    void createAtlasIndexAndSearchIt() throws Exception {\n        try (\n            // creatingAtlasLocalContainer {\n            MongoDBAtlasLocalContainer atlasLocalContainer = new MongoDBAtlasLocalContainer(\n                \"mongodb/mongodb-atlas-local:7.0.9\"\n            );\n            // }\n        ) {\n            // startingAtlasLocalContainer {\n            atlasLocalContainer.start();\n            // }\n\n            // getConnectionStringAtlasLocalContainer {\n            String connectionString = atlasLocalContainer.getConnectionString();\n            // }\n\n            try (\n                AtlasLocalDataAccess atlasLocalDataAccess = new AtlasLocalDataAccess(connectionString, \"test\", \"test\")\n            ) {\n                atlasLocalDataAccess.initAtlasSearchIndex();\n\n                atlasLocalDataAccess.insertData(new AtlasLocalDataAccess.TestData(\"tests\", 123, true));\n\n                Instant start = Instant.now();\n                log.info(\n                    \"Waiting for Atlas Search to index the data by polling atlas search query (Atlas Search is eventually consistent)\"\n                );\n                await()\n                    .atMost(5, TimeUnit.SECONDS)\n                    .pollInterval(10, TimeUnit.MILLISECONDS)\n                    .pollInSameThread()\n                    .until(() -> atlasLocalDataAccess.findAtlasSearch(\"test\"), Objects::nonNull);\n                log.info(\n                    \"Atlas Search indexed the new data and was searchable after {}ms.\",\n                    start.until(Instant.now(), ChronoUnit.MILLIS)\n                );\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "modules/mongodb/src/test/java/org/testcontainers/mongodb/MongoDBContainerTest.java",
    "content": "package org.testcontainers.mongodb;\n\nimport org.junit.jupiter.api.Test;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass MongoDBContainerTest extends AbstractMongo {\n\n    /**\n     * Taken from <a href=\"https://docs.mongodb.com/manual/core/transactions/\">https://docs.mongodb.com</a>\n     */\n    @Test\n    void shouldExecuteTransactions() {\n        try (\n            // creatingMongoDBContainer {\n            MongoDBContainer mongoDBContainer = new MongoDBContainer(\"mongo:4.0.10\").withReplicaSet()\n            // }\n        ) {\n            // startingMongoDBContainer {\n            mongoDBContainer.start();\n            // }\n            executeTx(mongoDBContainer);\n        }\n    }\n\n    @Test\n    void supportsMongoDB_7_0() {\n        try (MongoDBContainer mongoDBContainer = new MongoDBContainer(\"mongo:7.0\")) {\n            mongoDBContainer.start();\n        }\n    }\n\n    @Test\n    void shouldTestDatabaseName() {\n        try (MongoDBContainer mongoDBContainer = new MongoDBContainer(\"mongo:4.0.10\")) {\n            mongoDBContainer.start();\n            final String databaseName = \"my-db\";\n            assertThat(mongoDBContainer.getReplicaSetUrl(databaseName)).endsWith(databaseName);\n        }\n    }\n}\n"
  },
  {
    "path": "modules/mongodb/src/test/resources/atlas-local-index.json",
    "content": "{\n    \"mappings\": {\n        \"dynamic\": false,\n        \"fields\": {\n            \"test\": {\n                \"type\": \"string\"\n            },\n            \"test2\": {\n                \"type\": \"number\",\n                \"representation\": \"int64\",\n                \"indexDoubles\": false\n            },\n            \"test3\": {\n                \"type\": \"boolean\"\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "modules/mongodb/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/mssqlserver/AUTHORS",
    "content": "Stefan Hufschmidt <Stefan.Hufschmidt@gdata.de>"
  },
  {
    "path": "modules/mssqlserver/LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2017 - 2019 G DATA Software AG and other authors.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
  },
  {
    "path": "modules/mssqlserver/build.gradle",
    "content": "description = \"Testcontainers :: MS SQL Server\"\n\ndependencies {\n    api project(':testcontainers-jdbc')\n\n    compileOnly project(':testcontainers-r2dbc')\n    compileOnly 'io.r2dbc:r2dbc-mssql:1.0.3.RELEASE'\n\n    testImplementation project(':testcontainers-jdbc-test')\n    testImplementation 'com.microsoft.sqlserver:mssql-jdbc:13.3.0.jre8-preview'\n\n    testImplementation project(':testcontainers-r2dbc')\n    testRuntimeOnly 'io.r2dbc:r2dbc-mssql:1.0.3.RELEASE'\n\n    // MSSQL's wait strategy requires the JDBC driver\n    testImplementation testFixtures(project(':testcontainers-r2dbc'))\n}\n"
  },
  {
    "path": "modules/mssqlserver/src/main/java/org/testcontainers/containers/MSSQLR2DBCDatabaseContainer.java",
    "content": "package org.testcontainers.containers;\n\nimport io.r2dbc.spi.ConnectionFactoryOptions;\nimport lombok.RequiredArgsConstructor;\nimport lombok.experimental.Delegate;\nimport org.testcontainers.lifecycle.Startable;\nimport org.testcontainers.r2dbc.R2DBCDatabaseContainer;\n\n@RequiredArgsConstructor\npublic class MSSQLR2DBCDatabaseContainer implements R2DBCDatabaseContainer {\n\n    @Delegate(types = Startable.class)\n    private final MSSQLServerContainer<?> container;\n\n    public static ConnectionFactoryOptions getOptions(MSSQLServerContainer<?> container) {\n        ConnectionFactoryOptions options = ConnectionFactoryOptions\n            .builder()\n            .option(ConnectionFactoryOptions.DRIVER, MSSQLR2DBCDatabaseContainerProvider.DRIVER)\n            .build();\n\n        return new MSSQLR2DBCDatabaseContainer(container).configure(options);\n    }\n\n    @Override\n    public ConnectionFactoryOptions configure(ConnectionFactoryOptions options) {\n        return options\n            .mutate()\n            .option(ConnectionFactoryOptions.HOST, container.getHost())\n            .option(ConnectionFactoryOptions.PORT, container.getMappedPort(MSSQLServerContainer.MS_SQL_SERVER_PORT))\n            // TODO enable if/when MSSQLServerContainer adds support for customizing the DB name\n            // .option(ConnectionFactoryOptions.DATABASE, container.getDatabasseName())\n            .option(ConnectionFactoryOptions.USER, container.getUsername())\n            .option(ConnectionFactoryOptions.PASSWORD, container.getPassword())\n            .build();\n    }\n}\n"
  },
  {
    "path": "modules/mssqlserver/src/main/java/org/testcontainers/containers/MSSQLR2DBCDatabaseContainerProvider.java",
    "content": "package org.testcontainers.containers;\n\nimport io.r2dbc.mssql.MssqlConnectionFactoryProvider;\nimport io.r2dbc.spi.ConnectionFactoryMetadata;\nimport io.r2dbc.spi.ConnectionFactoryOptions;\nimport org.testcontainers.r2dbc.R2DBCDatabaseContainer;\nimport org.testcontainers.r2dbc.R2DBCDatabaseContainerProvider;\n\nimport javax.annotation.Nullable;\n\npublic class MSSQLR2DBCDatabaseContainerProvider implements R2DBCDatabaseContainerProvider {\n\n    static final String DRIVER = MssqlConnectionFactoryProvider.MSSQL_DRIVER;\n\n    @Override\n    public boolean supports(ConnectionFactoryOptions options) {\n        return DRIVER.equals(options.getRequiredValue(ConnectionFactoryOptions.DRIVER));\n    }\n\n    @Override\n    public R2DBCDatabaseContainer createContainer(ConnectionFactoryOptions options) {\n        // TODO work out how best to do this if these constants become private\n        String image = MSSQLServerContainer.IMAGE + \":\" + options.getRequiredValue(IMAGE_TAG_OPTION);\n        MSSQLServerContainer<?> container = new MSSQLServerContainer<>(image);\n\n        if (Boolean.TRUE.equals(options.getValue(REUSABLE_OPTION))) {\n            container.withReuse(true);\n        }\n        return new MSSQLR2DBCDatabaseContainer(container);\n    }\n\n    @Nullable\n    @Override\n    public ConnectionFactoryMetadata getMetadata(ConnectionFactoryOptions options) {\n        ConnectionFactoryOptions.Builder builder = options.mutate();\n        if (!options.hasOption(ConnectionFactoryOptions.USER)) {\n            builder.option(ConnectionFactoryOptions.USER, MSSQLServerContainer.DEFAULT_USER);\n        }\n        if (!options.hasOption(ConnectionFactoryOptions.PASSWORD)) {\n            builder.option(ConnectionFactoryOptions.PASSWORD, MSSQLServerContainer.DEFAULT_PASSWORD);\n        }\n        return R2DBCDatabaseContainerProvider.super.getMetadata(builder.build());\n    }\n}\n"
  },
  {
    "path": "modules/mssqlserver/src/main/java/org/testcontainers/containers/MSSQLServerContainer.java",
    "content": "package org.testcontainers.containers;\n\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.LicenseAcceptance;\n\nimport java.util.Set;\nimport java.util.regex.Pattern;\nimport java.util.stream.Stream;\n\n/**\n * Testcontainers implementation for Microsoft SQL Server.\n * <p>\n * Supported image: {@code mcr.microsoft.com/mssql/server}\n * <p>\n * Exposed ports: 1433\n *\n * @deprecated use {@link org.testcontainers.mssqlserver.MSSQLServerContainer} instead.\n */\n@Deprecated\npublic class MSSQLServerContainer<SELF extends MSSQLServerContainer<SELF>> extends JdbcDatabaseContainer<SELF> {\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"mcr.microsoft.com/mssql/server\");\n\n    @Deprecated\n    public static final String DEFAULT_TAG = \"2017-CU12\";\n\n    public static final String NAME = \"sqlserver\";\n\n    public static final String IMAGE = DEFAULT_IMAGE_NAME.getUnversionedPart();\n\n    public static final Integer MS_SQL_SERVER_PORT = 1433;\n\n    static final String DEFAULT_USER = \"sa\";\n\n    static final String DEFAULT_PASSWORD = \"A_Str0ng_Required_Password\";\n\n    private String password = DEFAULT_PASSWORD;\n\n    private static final int DEFAULT_STARTUP_TIMEOUT_SECONDS = 240;\n\n    private static final int DEFAULT_CONNECT_TIMEOUT_SECONDS = 240;\n\n    private static final Pattern[] PASSWORD_CATEGORY_VALIDATION_PATTERNS = new Pattern[] {\n        Pattern.compile(\"[A-Z]+\"),\n        Pattern.compile(\"[a-z]+\"),\n        Pattern.compile(\"[0-9]+\"),\n        Pattern.compile(\"[^a-zA-Z0-9]+\", Pattern.CASE_INSENSITIVE),\n    };\n\n    /**\n     * @deprecated use {@link #MSSQLServerContainer(DockerImageName)} instead\n     */\n    @Deprecated\n    public MSSQLServerContainer() {\n        this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG));\n    }\n\n    public MSSQLServerContainer(final String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    public MSSQLServerContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n\n        withStartupTimeoutSeconds(DEFAULT_STARTUP_TIMEOUT_SECONDS);\n        withConnectTimeoutSeconds(DEFAULT_CONNECT_TIMEOUT_SECONDS);\n        addExposedPort(MS_SQL_SERVER_PORT);\n    }\n\n    @Override\n    public Set<Integer> getLivenessCheckPortNumbers() {\n        return super.getLivenessCheckPortNumbers();\n    }\n\n    @Override\n    protected void configure() {\n        // If license was not accepted programmatically, check if it was accepted via resource file\n        if (!getEnvMap().containsKey(\"ACCEPT_EULA\")) {\n            LicenseAcceptance.assertLicenseAccepted(this.getDockerImageName());\n            acceptLicense();\n        }\n\n        addEnv(\"MSSQL_SA_PASSWORD\", password);\n    }\n\n    /**\n     * Accepts the license for the SQLServer container by setting the ACCEPT_EULA=Y\n     * variable as described at <a href=\"https://hub.docker.com/_/microsoft-mssql-server\">https://hub.docker.com/_/microsoft-mssql-server</a>\n     */\n    public SELF acceptLicense() {\n        addEnv(\"ACCEPT_EULA\", \"Y\");\n        return self();\n    }\n\n    @Override\n    public String getDriverClassName() {\n        return \"com.microsoft.sqlserver.jdbc.SQLServerDriver\";\n    }\n\n    @Override\n    protected String constructUrlForConnection(String queryString) {\n        // The JDBC driver of MS SQL Server enables encryption by default for versions > 10.1.0.\n        // We need to disable it by default to be able to use the container without having to pass extra params.\n        // See https://github.com/microsoft/mssql-jdbc/releases/tag/v10.1.0\n        if (urlParameters.keySet().stream().map(String::toLowerCase).noneMatch(\"encrypt\"::equals)) {\n            urlParameters.put(\"encrypt\", \"false\");\n        }\n        return super.constructUrlForConnection(queryString);\n    }\n\n    @Override\n    public String getJdbcUrl() {\n        String additionalUrlParams = constructUrlParameters(\";\", \";\");\n        return \"jdbc:sqlserver://\" + getHost() + \":\" + getMappedPort(MS_SQL_SERVER_PORT) + additionalUrlParams;\n    }\n\n    @Override\n    public String getUsername() {\n        return DEFAULT_USER;\n    }\n\n    @Override\n    public String getPassword() {\n        return password;\n    }\n\n    @Override\n    public String getTestQueryString() {\n        return \"SELECT 1\";\n    }\n\n    @Override\n    public SELF withPassword(final String password) {\n        checkPasswordStrength(password);\n        this.password = password;\n        return self();\n    }\n\n    private void checkPasswordStrength(String password) {\n        if (password == null) {\n            throw new IllegalArgumentException(\"Null password is not allowed\");\n        }\n\n        if (password.length() < 8) {\n            throw new IllegalArgumentException(\"Password should be at least 8 characters long\");\n        }\n\n        if (password.length() > 128) {\n            throw new IllegalArgumentException(\"Password can be up to 128 characters long\");\n        }\n\n        long satisfiedCategories = Stream\n            .of(PASSWORD_CATEGORY_VALIDATION_PATTERNS)\n            .filter(p -> p.matcher(password).find())\n            .count();\n\n        if (satisfiedCategories < 3) {\n            throw new IllegalArgumentException(\n                \"Password must contain characters from three of the following four categories:\\n\" +\n                \" - Latin uppercase letters (A through Z)\\n\" +\n                \" - Latin lowercase letters (a through z)\\n\" +\n                \" - Base 10 digits (0 through 9)\\n\" +\n                \" - Non-alphanumeric characters such as: exclamation point (!), dollar sign ($), number sign (#), \" +\n                \"or percent (%).\"\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "modules/mssqlserver/src/main/java/org/testcontainers/containers/MSSQLServerContainerProvider.java",
    "content": "package org.testcontainers.containers;\n\nimport org.testcontainers.utility.DockerImageName;\n\n/**\n * Factory for MS SQL Server containers.\n */\npublic class MSSQLServerContainerProvider extends JdbcDatabaseContainerProvider {\n\n    @Override\n    public boolean supports(String databaseType) {\n        return databaseType.equals(MSSQLServerContainer.NAME);\n    }\n\n    @Override\n    public JdbcDatabaseContainer newInstance() {\n        return newInstance(MSSQLServerContainer.DEFAULT_TAG);\n    }\n\n    @Override\n    public JdbcDatabaseContainer newInstance(String tag) {\n        return new MSSQLServerContainer(DockerImageName.parse(MSSQLServerContainer.IMAGE).withTag(tag));\n    }\n}\n"
  },
  {
    "path": "modules/mssqlserver/src/main/java/org/testcontainers/mssqlserver/MSSQLR2DBCDatabaseContainer.java",
    "content": "package org.testcontainers.mssqlserver;\n\nimport io.r2dbc.mssql.MssqlConnectionFactoryProvider;\nimport io.r2dbc.spi.ConnectionFactoryOptions;\nimport org.testcontainers.lifecycle.Startable;\nimport org.testcontainers.r2dbc.R2DBCDatabaseContainer;\n\nimport java.util.Set;\n\npublic class MSSQLR2DBCDatabaseContainer implements R2DBCDatabaseContainer {\n\n    private final MSSQLServerContainer container;\n\n    public MSSQLR2DBCDatabaseContainer(MSSQLServerContainer container) {\n        this.container = container;\n    }\n\n    public static ConnectionFactoryOptions getOptions(MSSQLServerContainer container) {\n        ConnectionFactoryOptions options = ConnectionFactoryOptions\n            .builder()\n            .option(ConnectionFactoryOptions.DRIVER, MssqlConnectionFactoryProvider.MSSQL_DRIVER)\n            .build();\n\n        return new MSSQLR2DBCDatabaseContainer(container).configure(options);\n    }\n\n    @Override\n    public ConnectionFactoryOptions configure(ConnectionFactoryOptions options) {\n        return options\n            .mutate()\n            .option(ConnectionFactoryOptions.HOST, container.getHost())\n            .option(ConnectionFactoryOptions.PORT, container.getMappedPort(MSSQLServerContainer.MS_SQL_SERVER_PORT))\n            // TODO enable if/when MSSQLServerContainer adds support for customizing the DB name\n            // .option(ConnectionFactoryOptions.DATABASE, container.getDatabasseName())\n            .option(ConnectionFactoryOptions.USER, container.getUsername())\n            .option(ConnectionFactoryOptions.PASSWORD, container.getPassword())\n            .build();\n    }\n\n    @Override\n    public Set<Startable> getDependencies() {\n        return this.container.getDependencies();\n    }\n\n    @Override\n    public void start() {\n        this.container.start();\n    }\n\n    @Override\n    public void stop() {\n        this.container.stop();\n    }\n\n    @Override\n    public void close() {\n        this.container.close();\n    }\n}\n"
  },
  {
    "path": "modules/mssqlserver/src/main/java/org/testcontainers/mssqlserver/MSSQLServerContainer.java",
    "content": "package org.testcontainers.mssqlserver;\n\nimport org.testcontainers.containers.JdbcDatabaseContainer;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.LicenseAcceptance;\n\nimport java.util.Set;\nimport java.util.regex.Pattern;\nimport java.util.stream.Stream;\n\n/**\n * Testcontainers implementation for Microsoft SQL Server.\n * <p>\n * Supported image: {@code mcr.microsoft.com/mssql/server}\n * <p>\n * Exposed ports: 1433\n */\npublic class MSSQLServerContainer extends JdbcDatabaseContainer<MSSQLServerContainer> {\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"mcr.microsoft.com/mssql/server\");\n\n    public static final String NAME = \"sqlserver\";\n\n    public static final String IMAGE = DEFAULT_IMAGE_NAME.getUnversionedPart();\n\n    public static final Integer MS_SQL_SERVER_PORT = 1433;\n\n    static final String DEFAULT_USER = \"sa\";\n\n    static final String DEFAULT_PASSWORD = \"A_Str0ng_Required_Password\";\n\n    private String password = DEFAULT_PASSWORD;\n\n    private static final int DEFAULT_STARTUP_TIMEOUT_SECONDS = 240;\n\n    private static final int DEFAULT_CONNECT_TIMEOUT_SECONDS = 240;\n\n    private static final Pattern[] PASSWORD_CATEGORY_VALIDATION_PATTERNS = new Pattern[] {\n        Pattern.compile(\"[A-Z]+\"),\n        Pattern.compile(\"[a-z]+\"),\n        Pattern.compile(\"[0-9]+\"),\n        Pattern.compile(\"[^a-zA-Z0-9]+\", Pattern.CASE_INSENSITIVE),\n    };\n\n    public MSSQLServerContainer(final String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    public MSSQLServerContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n\n        withStartupTimeoutSeconds(DEFAULT_STARTUP_TIMEOUT_SECONDS);\n        withConnectTimeoutSeconds(DEFAULT_CONNECT_TIMEOUT_SECONDS);\n        addExposedPort(MS_SQL_SERVER_PORT);\n    }\n\n    @Override\n    public Set<Integer> getLivenessCheckPortNumbers() {\n        return super.getLivenessCheckPortNumbers();\n    }\n\n    @Override\n    protected void configure() {\n        // If license was not accepted programmatically, check if it was accepted via resource file\n        if (!getEnvMap().containsKey(\"ACCEPT_EULA\")) {\n            LicenseAcceptance.assertLicenseAccepted(this.getDockerImageName());\n            acceptLicense();\n        }\n\n        addEnv(\"MSSQL_SA_PASSWORD\", password);\n    }\n\n    /**\n     * Accepts the license for the SQLServer container by setting the ACCEPT_EULA=Y\n     * variable as described at <a href=\"https://hub.docker.com/_/microsoft-mssql-server\">https://hub.docker.com/_/microsoft-mssql-server</a>\n     */\n    public MSSQLServerContainer acceptLicense() {\n        addEnv(\"ACCEPT_EULA\", \"Y\");\n        return self();\n    }\n\n    @Override\n    public String getDriverClassName() {\n        return \"com.microsoft.sqlserver.jdbc.SQLServerDriver\";\n    }\n\n    @Override\n    protected String constructUrlForConnection(String queryString) {\n        // The JDBC driver of MS SQL Server enables encryption by default for versions > 10.1.0.\n        // We need to disable it by default to be able to use the container without having to pass extra params.\n        // See https://github.com/microsoft/mssql-jdbc/releases/tag/v10.1.0\n        if (urlParameters.keySet().stream().map(String::toLowerCase).noneMatch(\"encrypt\"::equals)) {\n            urlParameters.put(\"encrypt\", \"false\");\n        }\n        return super.constructUrlForConnection(queryString);\n    }\n\n    @Override\n    public String getJdbcUrl() {\n        String additionalUrlParams = constructUrlParameters(\";\", \";\");\n        return \"jdbc:sqlserver://\" + getHost() + \":\" + getMappedPort(MS_SQL_SERVER_PORT) + additionalUrlParams;\n    }\n\n    @Override\n    public String getUsername() {\n        return DEFAULT_USER;\n    }\n\n    @Override\n    public String getPassword() {\n        return password;\n    }\n\n    @Override\n    public String getTestQueryString() {\n        return \"SELECT 1\";\n    }\n\n    @Override\n    public MSSQLServerContainer withPassword(final String password) {\n        checkPasswordStrength(password);\n        this.password = password;\n        return self();\n    }\n\n    private void checkPasswordStrength(String password) {\n        if (password == null) {\n            throw new IllegalArgumentException(\"Null password is not allowed\");\n        }\n\n        if (password.length() < 8) {\n            throw new IllegalArgumentException(\"Password should be at least 8 characters long\");\n        }\n\n        if (password.length() > 128) {\n            throw new IllegalArgumentException(\"Password can be up to 128 characters long\");\n        }\n\n        long satisfiedCategories = Stream\n            .of(PASSWORD_CATEGORY_VALIDATION_PATTERNS)\n            .filter(p -> p.matcher(password).find())\n            .count();\n\n        if (satisfiedCategories < 3) {\n            throw new IllegalArgumentException(\n                \"Password must contain characters from three of the following four categories:\\n\" +\n                \" - Latin uppercase letters (A through Z)\\n\" +\n                \" - Latin lowercase letters (a through z)\\n\" +\n                \" - Base 10 digits (0 through 9)\\n\" +\n                \" - Non-alphanumeric characters such as: exclamation point (!), dollar sign ($), number sign (#), \" +\n                \"or percent (%).\"\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "modules/mssqlserver/src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider",
    "content": "org.testcontainers.containers.MSSQLServerContainerProvider"
  },
  {
    "path": "modules/mssqlserver/src/main/resources/META-INF/services/org.testcontainers.r2dbc.R2DBCDatabaseContainerProvider",
    "content": "org.testcontainers.containers.MSSQLR2DBCDatabaseContainerProvider\n"
  },
  {
    "path": "modules/mssqlserver/src/test/java/org/testcontainers/MSSQLServerTestImages.java",
    "content": "package org.testcontainers;\n\nimport org.testcontainers.utility.DockerImageName;\n\npublic interface MSSQLServerTestImages {\n    DockerImageName MSSQL_SERVER_IMAGE = DockerImageName.parse(\"mcr.microsoft.com/mssql/server:2022-CU14-ubuntu-22.04\");\n}\n"
  },
  {
    "path": "modules/mssqlserver/src/test/java/org/testcontainers/containers/MSSQLR2DBCDatabaseContainerTest.java",
    "content": "package org.testcontainers.containers;\n\nimport io.r2dbc.spi.ConnectionFactoryOptions;\nimport org.testcontainers.MSSQLServerTestImages;\nimport org.testcontainers.r2dbc.AbstractR2DBCDatabaseContainerTest;\n\npublic class MSSQLR2DBCDatabaseContainerTest extends AbstractR2DBCDatabaseContainerTest<MSSQLServerContainer<?>> {\n\n    @Override\n    protected ConnectionFactoryOptions getOptions(MSSQLServerContainer<?> container) {\n        return MSSQLR2DBCDatabaseContainer.getOptions(container);\n    }\n\n    @Override\n    protected String createR2DBCUrl() {\n        return \"r2dbc:tc:sqlserver:///?TC_IMAGE_TAG=2022-CU14-ubuntu-22.04\";\n    }\n\n    @Override\n    protected MSSQLServerContainer<?> createContainer() {\n        return new MSSQLServerContainer<>(MSSQLServerTestImages.MSSQL_SERVER_IMAGE);\n    }\n}\n"
  },
  {
    "path": "modules/mssqlserver/src/test/java/org/testcontainers/jdbc/mssqlserver/MSSQLServerJDBCDriverTest.java",
    "content": "package org.testcontainers.jdbc.mssqlserver;\n\nimport org.testcontainers.jdbc.AbstractJDBCDriverTest;\n\nimport java.util.Arrays;\nimport java.util.EnumSet;\n\nclass MSSQLServerJDBCDriverTest extends AbstractJDBCDriverTest {\n\n    public static Iterable<Object[]> data() {\n        return Arrays.asList(\n            new Object[][] {\n                {\n                    \"jdbc:tc:sqlserver:2022-CU14-ubuntu-22.04://hostname:hostport;databaseName=databasename\",\n                    EnumSet.noneOf(Options.class),\n                },\n            }\n        );\n    }\n}\n"
  },
  {
    "path": "modules/mssqlserver/src/test/java/org/testcontainers/mssqlserver/CustomPasswordMSSQLServerTest.java",
    "content": "package org.testcontainers.mssqlserver;\n\nimport org.apache.commons.lang3.RandomStringUtils;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.Arguments;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.testcontainers.MSSQLServerTestImages;\nimport org.testcontainers.containers.MSSQLServerContainer;\n\nimport java.util.stream.Stream;\n\nimport static org.assertj.core.api.Assertions.fail;\n\n/**\n * Tests if the password passed to the container satisfied the password policy described at\n * https://docs.microsoft.com/en-us/sql/relational-databases/security/password-policy?view=sql-server-2017\n */\npublic class CustomPasswordMSSQLServerTest {\n\n    private static String UPPER_CASE_LETTERS = \"ABCDE\";\n\n    private static String LOWER_CASE_LETTERS = \"abcde\";\n\n    private static String NUMBERS = \"12345\";\n\n    private static String SPECIAL_CHARS = \"_(!)_\";\n\n    public static Stream<Arguments> data() {\n        return Stream.of(\n            Arguments.arguments(null, false),\n            // too short\n            Arguments.arguments(\"abc123\", false),\n            // too long\n            Arguments.arguments(RandomStringUtils.randomAlphabetic(129), false),\n            // only 2 categories\n            Arguments.arguments(UPPER_CASE_LETTERS + NUMBERS, false),\n            Arguments.arguments(UPPER_CASE_LETTERS + SPECIAL_CHARS, false),\n            Arguments.arguments(LOWER_CASE_LETTERS + NUMBERS, false),\n            Arguments.arguments(LOWER_CASE_LETTERS + SPECIAL_CHARS, false),\n            Arguments.arguments(NUMBERS + SPECIAL_CHARS, false),\n            // 3 categories\n            Arguments.arguments(UPPER_CASE_LETTERS + LOWER_CASE_LETTERS + NUMBERS, true),\n            Arguments.arguments(UPPER_CASE_LETTERS + LOWER_CASE_LETTERS + SPECIAL_CHARS, true),\n            Arguments.arguments(UPPER_CASE_LETTERS + NUMBERS + SPECIAL_CHARS, true),\n            Arguments.arguments(LOWER_CASE_LETTERS + NUMBERS + SPECIAL_CHARS, true),\n            // 4 categories\n            Arguments.arguments(UPPER_CASE_LETTERS + LOWER_CASE_LETTERS + NUMBERS + SPECIAL_CHARS, true)\n        );\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"data\")\n    public void runPasswordTests(String password, boolean valid) {\n        try {\n            new MSSQLServerContainer<>(MSSQLServerTestImages.MSSQL_SERVER_IMAGE).withPassword(password);\n            if (!valid) {\n                fail(\"Password \" + password + \" is not valid. Expected exception\");\n            }\n        } catch (IllegalArgumentException e) {\n            if (valid) {\n                fail(\"Password \" + password + \" should have been validated\");\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "modules/mssqlserver/src/test/java/org/testcontainers/mssqlserver/MSSQLR2DBCDatabaseContainerTest.java",
    "content": "package org.testcontainers.mssqlserver;\n\nimport io.r2dbc.spi.ConnectionFactoryOptions;\nimport org.testcontainers.MSSQLServerTestImages;\nimport org.testcontainers.r2dbc.AbstractR2DBCDatabaseContainerTest;\n\nclass MSSQLR2DBCDatabaseContainerTest extends AbstractR2DBCDatabaseContainerTest<MSSQLServerContainer> {\n\n    @Override\n    protected ConnectionFactoryOptions getOptions(MSSQLServerContainer container) {\n        return MSSQLR2DBCDatabaseContainer.getOptions(container);\n    }\n\n    @Override\n    protected String createR2DBCUrl() {\n        return \"r2dbc:tc:sqlserver:///?TC_IMAGE_TAG=2022-CU14-ubuntu-22.04\";\n    }\n\n    @Override\n    protected MSSQLServerContainer createContainer() {\n        return new MSSQLServerContainer(MSSQLServerTestImages.MSSQL_SERVER_IMAGE);\n    }\n}\n"
  },
  {
    "path": "modules/mssqlserver/src/test/java/org/testcontainers/mssqlserver/MSSQLServerContainerTest.java",
    "content": "package org.testcontainers.mssqlserver;\n\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.MSSQLServerTestImages;\nimport org.testcontainers.db.AbstractContainerDatabaseTest;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\n\nimport javax.sql.DataSource;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass MSSQLServerContainerTest extends AbstractContainerDatabaseTest {\n\n    @Test\n    void testSimple() throws SQLException {\n        try ( // container {\n            MSSQLServerContainer mssqlServer = new MSSQLServerContainer(\n                \"mcr.microsoft.com/mssql/server:2022-CU20-ubuntu-22.04\"\n            )\n                .acceptLicense()\n            // }\n        ) {\n            mssqlServer.start();\n            ResultSet resultSet = performQuery(mssqlServer, \"SELECT 1\");\n\n            int resultSetInt = resultSet.getInt(1);\n            assertThat(resultSetInt).as(\"A basic SELECT query succeeds\").isEqualTo(1);\n            assertHasCorrectExposedAndLivenessCheckPorts(mssqlServer);\n        }\n    }\n\n    @Test\n    void testWithAdditionalUrlParamInJdbcUrl() {\n        try (\n            MSSQLServerContainer mssqlServer = new MSSQLServerContainer(MSSQLServerTestImages.MSSQL_SERVER_IMAGE)\n                .withUrlParam(\"integratedSecurity\", \"false\")\n                .withUrlParam(\"applicationName\", \"MyApp\")\n        ) {\n            mssqlServer.start();\n\n            String jdbcUrl = mssqlServer.getJdbcUrl();\n            assertThat(jdbcUrl).contains(\";integratedSecurity=false;applicationName=MyApp\");\n        }\n    }\n\n    @Test\n    void testSetupDatabase() throws SQLException {\n        try (MSSQLServerContainer mssqlServer = new MSSQLServerContainer(MSSQLServerTestImages.MSSQL_SERVER_IMAGE)) {\n            mssqlServer.start();\n            DataSource ds = getDataSource(mssqlServer);\n            Statement statement = ds.getConnection().createStatement();\n            statement.executeUpdate(\"CREATE DATABASE [test];\");\n            statement = ds.getConnection().createStatement();\n            statement.executeUpdate(\"CREATE TABLE [test].[dbo].[Foo](ID INT PRIMARY KEY);\");\n            statement = ds.getConnection().createStatement();\n            statement.executeUpdate(\"INSERT INTO [test].[dbo].[Foo] (ID) VALUES (3);\");\n            statement = ds.getConnection().createStatement();\n            statement.execute(\"SELECT * FROM [test].[dbo].[Foo];\");\n            ResultSet resultSet = statement.getResultSet();\n\n            resultSet.next();\n            int resultSetInt = resultSet.getInt(\"ID\");\n            assertThat(resultSetInt).as(\"A basic SELECT query succeeds\").isEqualTo(3);\n        }\n    }\n\n    @Test\n    void testSqlServerConnection() throws SQLException {\n        try (\n            MSSQLServerContainer mssqlServerContainer = new MSSQLServerContainer(\n                DockerImageName.parse(\"mcr.microsoft.com/mssql/server:2022-CU14-ubuntu-22.04\")\n            )\n                .withPassword(\"myStrong(!)Password\")\n        ) {\n            mssqlServerContainer.start();\n\n            ResultSet resultSet = performQuery(mssqlServerContainer, mssqlServerContainer.getTestQueryString());\n            int resultSetInt = resultSet.getInt(1);\n            assertThat(resultSetInt).as(\"A basic SELECT query succeeds\").isEqualTo(1);\n        }\n    }\n\n    private void assertHasCorrectExposedAndLivenessCheckPorts(MSSQLServerContainer mssqlServer) {\n        assertThat(mssqlServer.getExposedPorts()).containsExactly(MSSQLServerContainer.MS_SQL_SERVER_PORT);\n        assertThat(mssqlServer.getLivenessCheckPortNumbers())\n            .containsExactly(mssqlServer.getMappedPort(MSSQLServerContainer.MS_SQL_SERVER_PORT));\n    }\n}\n"
  },
  {
    "path": "modules/mssqlserver/src/test/resources/container-license-acceptance.txt",
    "content": "mcr.microsoft.com/mssql/server:2022-CU14-ubuntu-22.04\n"
  },
  {
    "path": "modules/mssqlserver/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/mysql/build.gradle",
    "content": "description = \"Testcontainers :: JDBC :: MySQL\"\n\ndependencies {\n    api project(':testcontainers-jdbc')\n\n    compileOnly project(':testcontainers-r2dbc')\n    compileOnly 'io.asyncer:r2dbc-mysql:1.4.1'\n\n    testImplementation project(':testcontainers-jdbc-test')\n    testRuntimeOnly 'com.mysql:mysql-connector-j:9.5.0'\n\n    testImplementation testFixtures(project(':testcontainers-r2dbc'))\n    testRuntimeOnly 'io.asyncer:r2dbc-mysql:1.4.1'\n\n    compileOnly 'org.jetbrains:annotations:26.0.2-1'\n}\n"
  },
  {
    "path": "modules/mysql/sql/init_mysql.sql",
    "content": "CREATE TABLE bar (\n  foo VARCHAR(255)\n);\n\nINSERT INTO bar (foo) VALUES ('hello world');"
  },
  {
    "path": "modules/mysql/src/main/java/org/testcontainers/containers/MySQLContainer.java",
    "content": "package org.testcontainers.containers;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.testcontainers.images.builder.Transferable;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.util.Set;\n\n/**\n * Testcontainers implementation for MySQL.\n * <p>\n * Supported image: {@code mysql}\n * <p>\n * Exposed ports: 3306\n *\n * @deprecated use {@link org.testcontainers.mysql.MySQLContainer} instead.\n */\n@Deprecated\npublic class MySQLContainer<SELF extends MySQLContainer<SELF>> extends JdbcDatabaseContainer<SELF> {\n\n    public static final String NAME = \"mysql\";\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"mysql\");\n\n    @Deprecated\n    public static final String DEFAULT_TAG = \"5.7.34\";\n\n    @Deprecated\n    public static final String IMAGE = DEFAULT_IMAGE_NAME.getUnversionedPart();\n\n    static final String DEFAULT_USER = \"test\";\n\n    static final String DEFAULT_PASSWORD = \"test\";\n\n    private static final String MY_CNF_CONFIG_OVERRIDE_PARAM_NAME = \"TC_MY_CNF\";\n\n    public static final Integer MYSQL_PORT = 3306;\n\n    private String databaseName = \"test\";\n\n    private String username = DEFAULT_USER;\n\n    private String password = DEFAULT_PASSWORD;\n\n    private static final String MYSQL_ROOT_USER = \"root\";\n\n    /**\n     * @deprecated use {@link #MySQLContainer(DockerImageName)} instead\n     */\n    @Deprecated\n    public MySQLContainer() {\n        this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG));\n    }\n\n    public MySQLContainer(String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    public MySQLContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n\n        addExposedPort(MYSQL_PORT);\n    }\n\n    /**\n     * @return the ports on which to check if the container is ready\n     * @deprecated use {@link #getLivenessCheckPortNumbers()} instead\n     */\n    @NotNull\n    @Override\n    @Deprecated\n    protected Set<Integer> getLivenessCheckPorts() {\n        return super.getLivenessCheckPorts();\n    }\n\n    @Override\n    protected void configure() {\n        optionallyMapResourceParameterAsVolume(\n            MY_CNF_CONFIG_OVERRIDE_PARAM_NAME,\n            \"/etc/mysql/conf.d\",\n            \"mysql-default-conf\",\n            Transferable.DEFAULT_DIR_MODE\n        );\n\n        addEnv(\"MYSQL_DATABASE\", databaseName);\n        if (!MYSQL_ROOT_USER.equalsIgnoreCase(username)) {\n            addEnv(\"MYSQL_USER\", username);\n        }\n        if (password != null && !password.isEmpty()) {\n            addEnv(\"MYSQL_PASSWORD\", password);\n            addEnv(\"MYSQL_ROOT_PASSWORD\", password);\n        } else if (MYSQL_ROOT_USER.equalsIgnoreCase(username)) {\n            addEnv(\"MYSQL_ALLOW_EMPTY_PASSWORD\", \"yes\");\n        } else {\n            throw new ContainerLaunchException(\"Empty password can be used only with the root user\");\n        }\n        setStartupAttempts(3);\n    }\n\n    @Override\n    public String getDriverClassName() {\n        try {\n            Class.forName(\"com.mysql.cj.jdbc.Driver\");\n            return \"com.mysql.cj.jdbc.Driver\";\n        } catch (ClassNotFoundException e) {\n            return \"com.mysql.jdbc.Driver\";\n        }\n    }\n\n    @Override\n    public String getJdbcUrl() {\n        String additionalUrlParams = constructUrlParameters(\"?\", \"&\");\n        return \"jdbc:mysql://\" + getHost() + \":\" + getMappedPort(MYSQL_PORT) + \"/\" + databaseName + additionalUrlParams;\n    }\n\n    @Override\n    protected String constructUrlForConnection(String queryString) {\n        String url = super.constructUrlForConnection(queryString);\n\n        if (!url.contains(\"useSSL=\")) {\n            String separator = url.contains(\"?\") ? \"&\" : \"?\";\n            url = url + separator + \"useSSL=false\";\n        }\n\n        if (!url.contains(\"allowPublicKeyRetrieval=\")) {\n            url = url + \"&allowPublicKeyRetrieval=true\";\n        }\n\n        return url;\n    }\n\n    @Override\n    public String getDatabaseName() {\n        return databaseName;\n    }\n\n    @Override\n    public String getUsername() {\n        return username;\n    }\n\n    @Override\n    public String getPassword() {\n        return password;\n    }\n\n    @Override\n    public String getTestQueryString() {\n        return \"SELECT 1\";\n    }\n\n    public SELF withConfigurationOverride(String s) {\n        parameters.put(MY_CNF_CONFIG_OVERRIDE_PARAM_NAME, s);\n        return self();\n    }\n\n    @Override\n    public SELF withDatabaseName(final String databaseName) {\n        this.databaseName = databaseName;\n        return self();\n    }\n\n    @Override\n    public SELF withUsername(final String username) {\n        this.username = username;\n        return self();\n    }\n\n    @Override\n    public SELF withPassword(final String password) {\n        this.password = password;\n        return self();\n    }\n}\n"
  },
  {
    "path": "modules/mysql/src/main/java/org/testcontainers/containers/MySQLContainerProvider.java",
    "content": "package org.testcontainers.containers;\n\nimport org.testcontainers.jdbc.ConnectionUrl;\nimport org.testcontainers.utility.DockerImageName;\n\n/**\n * Factory for MySQL containers.\n */\npublic class MySQLContainerProvider extends JdbcDatabaseContainerProvider {\n\n    private static final String USER_PARAM = \"user\";\n\n    private static final String PASSWORD_PARAM = \"password\";\n\n    @Override\n    public boolean supports(String databaseType) {\n        return databaseType.equals(MySQLContainer.NAME);\n    }\n\n    @Override\n    public JdbcDatabaseContainer newInstance() {\n        return newInstance(MySQLContainer.DEFAULT_TAG);\n    }\n\n    @Override\n    public JdbcDatabaseContainer newInstance(String tag) {\n        if (tag != null) {\n            return new MySQLContainer(DockerImageName.parse(MySQLContainer.IMAGE).withTag(tag));\n        } else {\n            return newInstance();\n        }\n    }\n\n    @Override\n    public JdbcDatabaseContainer newInstance(ConnectionUrl connectionUrl) {\n        return newInstanceFromConnectionUrl(connectionUrl, USER_PARAM, PASSWORD_PARAM);\n    }\n}\n"
  },
  {
    "path": "modules/mysql/src/main/java/org/testcontainers/containers/MySQLR2DBCDatabaseContainer.java",
    "content": "package org.testcontainers.containers;\n\nimport io.r2dbc.spi.ConnectionFactoryOptions;\nimport lombok.RequiredArgsConstructor;\nimport lombok.experimental.Delegate;\nimport org.testcontainers.lifecycle.Startable;\nimport org.testcontainers.r2dbc.R2DBCDatabaseContainer;\n\n@RequiredArgsConstructor\npublic class MySQLR2DBCDatabaseContainer implements R2DBCDatabaseContainer {\n\n    @Delegate(types = Startable.class)\n    private final MySQLContainer<?> container;\n\n    public static ConnectionFactoryOptions getOptions(MySQLContainer<?> container) {\n        ConnectionFactoryOptions options = ConnectionFactoryOptions\n            .builder()\n            .option(ConnectionFactoryOptions.DRIVER, MySQLR2DBCDatabaseContainerProvider.DRIVER)\n            .build();\n\n        return new MySQLR2DBCDatabaseContainer(container).configure(options);\n    }\n\n    @Override\n    public ConnectionFactoryOptions configure(ConnectionFactoryOptions options) {\n        return options\n            .mutate()\n            .option(ConnectionFactoryOptions.HOST, container.getHost())\n            .option(ConnectionFactoryOptions.PORT, container.getMappedPort(MySQLContainer.MYSQL_PORT))\n            .option(ConnectionFactoryOptions.DATABASE, container.getDatabaseName())\n            .option(ConnectionFactoryOptions.USER, container.getUsername())\n            .option(ConnectionFactoryOptions.PASSWORD, container.getPassword())\n            .build();\n    }\n}\n"
  },
  {
    "path": "modules/mysql/src/main/java/org/testcontainers/containers/MySQLR2DBCDatabaseContainerProvider.java",
    "content": "package org.testcontainers.containers;\n\nimport io.asyncer.r2dbc.mysql.MySqlConnectionFactoryProvider;\nimport io.r2dbc.spi.ConnectionFactoryMetadata;\nimport io.r2dbc.spi.ConnectionFactoryOptions;\nimport org.testcontainers.r2dbc.R2DBCDatabaseContainer;\nimport org.testcontainers.r2dbc.R2DBCDatabaseContainerProvider;\n\nimport javax.annotation.Nullable;\n\npublic class MySQLR2DBCDatabaseContainerProvider implements R2DBCDatabaseContainerProvider {\n\n    static final String DRIVER = MySqlConnectionFactoryProvider.MYSQL_DRIVER;\n\n    @Override\n    public boolean supports(ConnectionFactoryOptions options) {\n        return DRIVER.equals(options.getRequiredValue(ConnectionFactoryOptions.DRIVER));\n    }\n\n    @Override\n    public R2DBCDatabaseContainer createContainer(ConnectionFactoryOptions options) {\n        String image = MySQLContainer.IMAGE + \":\" + options.getRequiredValue(IMAGE_TAG_OPTION);\n        MySQLContainer<?> container = new MySQLContainer<>(image)\n            .withDatabaseName((String) options.getRequiredValue(ConnectionFactoryOptions.DATABASE));\n\n        if (Boolean.TRUE.equals(options.getValue(REUSABLE_OPTION))) {\n            container.withReuse(true);\n        }\n        return new MySQLR2DBCDatabaseContainer(container);\n    }\n\n    @Nullable\n    @Override\n    public ConnectionFactoryMetadata getMetadata(ConnectionFactoryOptions options) {\n        ConnectionFactoryOptions.Builder builder = options.mutate();\n        if (!options.hasOption(ConnectionFactoryOptions.USER)) {\n            builder.option(ConnectionFactoryOptions.USER, MySQLContainer.DEFAULT_USER);\n        }\n        if (!options.hasOption(ConnectionFactoryOptions.PASSWORD)) {\n            builder.option(ConnectionFactoryOptions.PASSWORD, MySQLContainer.DEFAULT_PASSWORD);\n        }\n        return R2DBCDatabaseContainerProvider.super.getMetadata(builder.build());\n    }\n}\n"
  },
  {
    "path": "modules/mysql/src/main/java/org/testcontainers/mysql/MySQLContainer.java",
    "content": "package org.testcontainers.mysql;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.testcontainers.containers.ContainerLaunchException;\nimport org.testcontainers.containers.JdbcDatabaseContainer;\nimport org.testcontainers.images.builder.Transferable;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.util.Set;\n\n/**\n * Testcontainers implementation for MySQL.\n * <p>\n * Supported image: {@code mysql}\n * <p>\n * Exposed ports: 3306\n */\npublic class MySQLContainer extends JdbcDatabaseContainer<MySQLContainer> {\n\n    public static final String NAME = \"mysql\";\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"mysql\");\n\n    static final String DEFAULT_USER = \"test\";\n\n    static final String DEFAULT_PASSWORD = \"test\";\n\n    private static final String MY_CNF_CONFIG_OVERRIDE_PARAM_NAME = \"TC_MY_CNF\";\n\n    public static final Integer MYSQL_PORT = 3306;\n\n    private String databaseName = \"test\";\n\n    private String username = DEFAULT_USER;\n\n    private String password = DEFAULT_PASSWORD;\n\n    private static final String MYSQL_ROOT_USER = \"root\";\n\n    public MySQLContainer(String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    public MySQLContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n\n        addExposedPort(MYSQL_PORT);\n    }\n\n    /**\n     * @return the ports on which to check if the container is ready\n     * @deprecated use {@link #getLivenessCheckPortNumbers()} instead\n     */\n    @NotNull\n    @Override\n    @Deprecated\n    protected Set<Integer> getLivenessCheckPorts() {\n        return super.getLivenessCheckPorts();\n    }\n\n    @Override\n    protected void configure() {\n        optionallyMapResourceParameterAsVolume(\n            MY_CNF_CONFIG_OVERRIDE_PARAM_NAME,\n            \"/etc/mysql/conf.d\",\n            null,\n            Transferable.DEFAULT_DIR_MODE\n        );\n\n        addEnv(\"MYSQL_DATABASE\", databaseName);\n        if (!MYSQL_ROOT_USER.equalsIgnoreCase(username)) {\n            addEnv(\"MYSQL_USER\", username);\n        }\n        if (password != null && !password.isEmpty()) {\n            addEnv(\"MYSQL_PASSWORD\", password);\n            addEnv(\"MYSQL_ROOT_PASSWORD\", password);\n        } else if (MYSQL_ROOT_USER.equalsIgnoreCase(username)) {\n            addEnv(\"MYSQL_ALLOW_EMPTY_PASSWORD\", \"yes\");\n        } else {\n            throw new ContainerLaunchException(\"Empty password can be used only with the root user\");\n        }\n        setStartupAttempts(3);\n    }\n\n    @Override\n    public String getDriverClassName() {\n        try {\n            Class.forName(\"com.mysql.cj.jdbc.Driver\");\n            return \"com.mysql.cj.jdbc.Driver\";\n        } catch (ClassNotFoundException e) {\n            return \"com.mysql.jdbc.Driver\";\n        }\n    }\n\n    @Override\n    public String getJdbcUrl() {\n        String additionalUrlParams = constructUrlParameters(\"?\", \"&\");\n        return \"jdbc:mysql://\" + getHost() + \":\" + getMappedPort(MYSQL_PORT) + \"/\" + databaseName + additionalUrlParams;\n    }\n\n    @Override\n    protected String constructUrlForConnection(String queryString) {\n        String url = super.constructUrlForConnection(queryString);\n\n        if (!url.contains(\"useSSL=\")) {\n            String separator = url.contains(\"?\") ? \"&\" : \"?\";\n            url = url + separator + \"useSSL=false\";\n        }\n\n        if (!url.contains(\"allowPublicKeyRetrieval=\")) {\n            url = url + \"&allowPublicKeyRetrieval=true\";\n        }\n\n        return url;\n    }\n\n    @Override\n    public String getDatabaseName() {\n        return databaseName;\n    }\n\n    @Override\n    public String getUsername() {\n        return username;\n    }\n\n    @Override\n    public String getPassword() {\n        return password;\n    }\n\n    @Override\n    public String getTestQueryString() {\n        return \"SELECT 1\";\n    }\n\n    public MySQLContainer withConfigurationOverride(String s) {\n        parameters.put(MY_CNF_CONFIG_OVERRIDE_PARAM_NAME, s);\n        return self();\n    }\n\n    @Override\n    public MySQLContainer withDatabaseName(final String databaseName) {\n        this.databaseName = databaseName;\n        return self();\n    }\n\n    @Override\n    public MySQLContainer withUsername(final String username) {\n        this.username = username;\n        return self();\n    }\n\n    @Override\n    public MySQLContainer withPassword(final String password) {\n        this.password = password;\n        return self();\n    }\n}\n"
  },
  {
    "path": "modules/mysql/src/main/java/org/testcontainers/mysql/MySQLR2DBCDatabaseContainer.java",
    "content": "package org.testcontainers.mysql;\n\nimport io.asyncer.r2dbc.mysql.MySqlConnectionFactoryProvider;\nimport io.r2dbc.spi.ConnectionFactoryOptions;\nimport org.testcontainers.lifecycle.Startable;\nimport org.testcontainers.r2dbc.R2DBCDatabaseContainer;\n\nimport java.util.Set;\n\npublic class MySQLR2DBCDatabaseContainer implements R2DBCDatabaseContainer {\n\n    private final MySQLContainer container;\n\n    public MySQLR2DBCDatabaseContainer(MySQLContainer container) {\n        this.container = container;\n    }\n\n    public static ConnectionFactoryOptions getOptions(MySQLContainer container) {\n        ConnectionFactoryOptions options = ConnectionFactoryOptions\n            .builder()\n            .option(ConnectionFactoryOptions.DRIVER, MySqlConnectionFactoryProvider.MYSQL_DRIVER)\n            .build();\n\n        return new MySQLR2DBCDatabaseContainer(container).configure(options);\n    }\n\n    @Override\n    public ConnectionFactoryOptions configure(ConnectionFactoryOptions options) {\n        return options\n            .mutate()\n            .option(ConnectionFactoryOptions.HOST, container.getHost())\n            .option(ConnectionFactoryOptions.PORT, container.getMappedPort(MySQLContainer.MYSQL_PORT))\n            .option(ConnectionFactoryOptions.DATABASE, container.getDatabaseName())\n            .option(ConnectionFactoryOptions.USER, container.getUsername())\n            .option(ConnectionFactoryOptions.PASSWORD, container.getPassword())\n            .build();\n    }\n\n    @Override\n    public Set<Startable> getDependencies() {\n        return this.container.getDependencies();\n    }\n\n    @Override\n    public void start() {\n        this.container.start();\n    }\n\n    @Override\n    public void stop() {\n        this.container.stop();\n    }\n\n    @Override\n    public void close() {\n        this.container.close();\n    }\n}\n"
  },
  {
    "path": "modules/mysql/src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider",
    "content": "org.testcontainers.containers.MySQLContainerProvider"
  },
  {
    "path": "modules/mysql/src/main/resources/META-INF/services/org.testcontainers.r2dbc.R2DBCDatabaseContainerProvider",
    "content": "org.testcontainers.containers.MySQLR2DBCDatabaseContainerProvider\n"
  },
  {
    "path": "modules/mysql/src/main/resources/mysql-default-conf/my.cnf",
    "content": "[mysqld]\nuser = mysql\ndatadir = /var/lib/mysql\nport   \t\t= 3306\n#socket \t\t= /tmp/mysql.sock\nskip-external-locking\nkey_buffer_size = 16K\nmax_allowed_packet = 1M\ntable_open_cache = 4\nsort_buffer_size = 64K\nread_buffer_size = 256K\nread_rnd_buffer_size = 256K\nnet_buffer_length = 2K\nhost_cache_size = 0\nskip-name-resolve\n\n# Don't listen on a TCP/IP port at all. This can be a security enhancement,\n# if all processes that need to connect to mysqld run on the same host.\n# All interaction with mysqld must be made via Unix sockets or named pipes.\n# Note that using this option without enabling named pipes on Windows\n# (using the \"enable-named-pipe\" option) will render mysqld useless!\n#\n#skip-networking\n#server-id      \t= 1\n\n# Uncomment the following if you want to log updates\n#log-bin=mysql-bin\n\n# binary logging format - mixed recommended\n#binlog_format=mixed\n\n# Causes updates to non-transactional engines using statement format to be\n# written directly to binary log. Before using this option make sure that\n# there are no dependencies between transactional and non-transactional\n# tables such as in the statement INSERT INTO t_myisam SELECT * FROM\n# t_innodb; otherwise, slaves may diverge from the master.\n#binlog_direct_non_transactional_updates=TRUE\n\n# Uncomment the following if you are using InnoDB tables\ninnodb_data_file_path = ibdata1:10M:autoextend\n# You can set .._buffer_pool_size up to 50 - 80 %\n# of RAM but beware of setting memory usage too high\ninnodb_buffer_pool_size = 16M\n#innodb_additional_mem_pool_size = 2M\n# Set .._log_file_size to 25 % of buffer pool size\ninnodb_log_buffer_size = 8M\ninnodb_flush_log_at_trx_commit = 1\ninnodb_lock_wait_timeout = 50\n"
  },
  {
    "path": "modules/mysql/src/test/java/org/testcontainers/MySQLTestImages.java",
    "content": "package org.testcontainers;\n\nimport org.testcontainers.utility.DockerImageName;\n\npublic class MySQLTestImages {\n\n    public static final DockerImageName MYSQL_57_IMAGE = DockerImageName.parse(\"mysql:5.7.44\");\n\n    public static final DockerImageName MYSQL_80_IMAGE = DockerImageName.parse(\"mysql:8.0.36\");\n\n    public static final DockerImageName MYSQL_INNOVATION_IMAGE = DockerImageName.parse(\"mysql:8.3.0\");\n\n    public static final DockerImageName MYSQL_93_IMAGE = DockerImageName.parse(\"mysql:9.3.0\");\n}\n"
  },
  {
    "path": "modules/mysql/src/test/java/org/testcontainers/containers/MySQLR2DBCDatabaseContainerTest.java",
    "content": "package org.testcontainers.containers;\n\nimport io.r2dbc.spi.ConnectionFactoryOptions;\nimport org.testcontainers.MySQLTestImages;\nimport org.testcontainers.r2dbc.AbstractR2DBCDatabaseContainerTest;\n\npublic class MySQLR2DBCDatabaseContainerTest extends AbstractR2DBCDatabaseContainerTest<MySQLContainer<?>> {\n\n    @Override\n    protected ConnectionFactoryOptions getOptions(MySQLContainer<?> container) {\n        return MySQLR2DBCDatabaseContainer.getOptions(container);\n    }\n\n    @Override\n    protected String createR2DBCUrl() {\n        return \"r2dbc:tc:mysql:///db?TC_IMAGE_TAG=\" + MySQLTestImages.MYSQL_80_IMAGE.getVersionPart();\n    }\n\n    @Override\n    protected MySQLContainer<?> createContainer() {\n        return new MySQLContainer<>(MySQLTestImages.MYSQL_80_IMAGE);\n    }\n}\n"
  },
  {
    "path": "modules/mysql/src/test/java/org/testcontainers/containers/MySQLRootAccountTest.java",
    "content": "package org.testcontainers.containers;\n\nimport lombok.extern.slf4j.Slf4j;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.testcontainers.MySQLTestImages;\nimport org.testcontainers.containers.output.Slf4jLogConsumer;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.sql.Connection;\nimport java.sql.DriverManager;\nimport java.sql.SQLException;\n\n@Slf4j\nclass MySQLRootAccountTest {\n\n    public static DockerImageName[] params() {\n        return new DockerImageName[] {\n            MySQLTestImages.MYSQL_57_IMAGE,\n            MySQLTestImages.MYSQL_80_IMAGE,\n            MySQLTestImages.MYSQL_INNOVATION_IMAGE,\n            MySQLTestImages.MYSQL_93_IMAGE,\n        };\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"params\")\n    void testRootAccountUsageWithDefaultPassword(DockerImageName image) throws SQLException {\n        testWithDB(new MySQLContainer<>(image).withUsername(\"root\"));\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"params\")\n    void testRootAccountUsageWithEmptyPassword(DockerImageName image) throws SQLException {\n        testWithDB(new MySQLContainer<>(image).withUsername(\"root\").withPassword(\"\"));\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"params\")\n    void testRootAccountUsageWithCustomPassword(DockerImageName image) throws SQLException {\n        testWithDB(new MySQLContainer<>(image).withUsername(\"root\").withPassword(\"not-default\"));\n    }\n\n    private void testWithDB(MySQLContainer<?> db) throws SQLException {\n        try {\n            db.withLogConsumer(new Slf4jLogConsumer(log)).start();\n            Connection connection = DriverManager.getConnection(db.getJdbcUrl(), db.getUsername(), db.getPassword());\n            connection.createStatement().execute(\"SELECT 1\");\n            connection.createStatement().execute(\"set sql_log_bin=0\"); // requires root\n        } finally {\n            db.close();\n        }\n    }\n}\n"
  },
  {
    "path": "modules/mysql/src/test/java/org/testcontainers/jdbc/mysql/JDBCDriverWithPoolTest.java",
    "content": "package org.testcontainers.jdbc.mysql;\n\nimport com.zaxxer.hikari.HikariConfig;\nimport com.zaxxer.hikari.HikariDataSource;\nimport org.apache.commons.dbutils.QueryRunner;\nimport org.apache.commons.dbutils.ResultSetHandler;\nimport org.apache.tomcat.jdbc.pool.PoolProperties;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.testcontainers.jdbc.ContainerDatabaseDriver;\nimport org.vibur.dbcp.ViburDBCPDataSource;\n\nimport java.sql.Connection;\nimport java.sql.SQLException;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.Supplier;\nimport java.util.stream.Stream;\n\nimport javax.sql.DataSource;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * This test belongs in the jdbc module, as it is focused on testing the behaviour of {@link org.testcontainers.containers.JdbcDatabaseContainer}.\n * However, the need to use the {@link org.testcontainers.containers.MySQLContainerProvider} (due to the jdbc:tc:mysql) URL forces it to live here in\n * the mysql module, to avoid circular dependencies.\n * TODO: Move to the jdbc module and either (a) implement a barebones {@link org.testcontainers.containers.JdbcDatabaseContainerProvider} for testing, or (b) refactor into a unit test.\n */\n@ParameterizedClass\n@MethodSource(\"dataSourceSuppliers\")\npublic class JDBCDriverWithPoolTest {\n\n    public static final String URL =\n        \"jdbc:tc:mysql:8.0.36://hostname/databasename?TC_INITFUNCTION=org.testcontainers.jdbc.mysql.JDBCDriverWithPoolTest::sampleInitFunction\";\n\n    private final DataSource dataSource;\n\n    public static Stream<Supplier<DataSource>> dataSourceSuppliers() {\n        return Stream.of(\n            JDBCDriverWithPoolTest::getTomcatDataSourceWithDriverClassName,\n            JDBCDriverWithPoolTest::getTomcatDataSource,\n            JDBCDriverWithPoolTest::getHikariDataSourceWithDriverClassName,\n            JDBCDriverWithPoolTest::getHikariDataSource,\n            JDBCDriverWithPoolTest::getViburDataSourceWithDriverClassName,\n            JDBCDriverWithPoolTest::getViburDataSource\n        );\n    }\n\n    public JDBCDriverWithPoolTest(Supplier<DataSource> dataSourceSupplier) {\n        this.dataSource = dataSourceSupplier.get();\n    }\n\n    private ExecutorService executorService = Executors.newFixedThreadPool(5);\n\n    @Test\n    void testMySQLWithConnectionPoolUsingSameContainer() throws SQLException, InterruptedException {\n        // Populate the database with some data in multiple threads, so that multiple connections from the pool will be used\n        for (int i = 0; i < 100; i++) {\n            executorService.submit(() -> {\n                try {\n                    new QueryRunner(dataSource)\n                        .insert(\"INSERT INTO my_counter (n) VALUES (5)\", (ResultSetHandler<Object>) rs -> true);\n                } catch (SQLException e) {\n                    e.printStackTrace();\n                }\n            });\n        }\n\n        // Complete population of the database\n        executorService.shutdown();\n        executorService.awaitTermination(5, TimeUnit.MINUTES);\n\n        // compare to expected results\n        int count = new QueryRunner(dataSource)\n            .query(\n                \"SELECT COUNT(1) FROM my_counter\",\n                rs -> {\n                    rs.next();\n                    return rs.getInt(1);\n                }\n            );\n        assertThat(count).as(\"Reuse of a datasource points to the same DB container\").isEqualTo(100);\n\n        int sum = new QueryRunner(dataSource)\n            .query(\n                \"SELECT SUM(n) FROM my_counter\",\n                rs -> {\n                    rs.next();\n                    return rs.getInt(1);\n                }\n            );\n        // 100 records * 5 = 500 expected\n        assertThat(sum).as(\"Reuse of a datasource points to the same DB container\").isEqualTo(500);\n    }\n\n    private static DataSource getTomcatDataSourceWithDriverClassName() {\n        PoolProperties poolProperties = new PoolProperties();\n        poolProperties.setUrl(URL + \";TEST=TOMCAT_WITH_CLASSNAME\"); // append a dummy URL element to ensure different DB per test\n        poolProperties.setValidationQuery(\"SELECT 1\");\n        poolProperties.setMinIdle(3);\n        poolProperties.setMaxActive(10);\n        poolProperties.setDriverClassName(ContainerDatabaseDriver.class.getName());\n\n        return new org.apache.tomcat.jdbc.pool.DataSource(poolProperties);\n    }\n\n    private static DataSource getTomcatDataSource() {\n        PoolProperties poolProperties = new PoolProperties();\n        poolProperties.setUrl(URL + \";TEST=TOMCAT\"); // append a dummy URL element to ensure different DB per test\n        poolProperties.setValidationQuery(\"SELECT 1\");\n        poolProperties.setInitialSize(3);\n        poolProperties.setMaxActive(10);\n\n        return new org.apache.tomcat.jdbc.pool.DataSource(poolProperties);\n    }\n\n    private static HikariDataSource getHikariDataSourceWithDriverClassName() {\n        HikariConfig hikariConfig = new HikariConfig();\n        hikariConfig.setJdbcUrl(URL + \";TEST=HIKARI_WITH_CLASSNAME\"); // append a dummy URL element to ensure different DB per test\n        hikariConfig.setConnectionTestQuery(\"SELECT 1\");\n        hikariConfig.setMinimumIdle(3);\n        hikariConfig.setMaximumPoolSize(10);\n        hikariConfig.setDriverClassName(ContainerDatabaseDriver.class.getName());\n\n        return new HikariDataSource(hikariConfig);\n    }\n\n    private static HikariDataSource getHikariDataSource() {\n        HikariConfig hikariConfig = new HikariConfig();\n        hikariConfig.setJdbcUrl(URL + \";TEST=HIKARI\"); // append a dummy URL element to ensure different DB per test\n        hikariConfig.setConnectionTestQuery(\"SELECT 1\");\n        hikariConfig.setMinimumIdle(3);\n        hikariConfig.setMaximumPoolSize(10);\n\n        return new HikariDataSource(hikariConfig);\n    }\n\n    private static DataSource getViburDataSourceWithDriverClassName() {\n        ViburDBCPDataSource ds = new ViburDBCPDataSource();\n\n        ds.setJdbcUrl(URL + \";TEST=VIBUR_WITH_CLASSNAME\");\n        ds.setUsername(\"any\"); // Recent versions of Vibur require a username, even though it will not be used\n        ds.setPassword(\"\");\n        ds.setPoolInitialSize(3);\n        ds.setPoolMaxSize(10);\n        ds.setTestConnectionQuery(\"SELECT 1\");\n        ds.setDriverClassName(ContainerDatabaseDriver.class.getName());\n\n        ds.start();\n\n        return ds;\n    }\n\n    private static DataSource getViburDataSource() {\n        ViburDBCPDataSource ds = new ViburDBCPDataSource();\n        ds.setJdbcUrl(URL + \";TEST=VIBUR\");\n        ds.setUsername(\"any\"); // Recent versions of Vibur require a username, even though it will not be used\n        ds.setPassword(\"\");\n        ds.setPoolInitialSize(3);\n        ds.setPoolMaxSize(10);\n        ds.setTestConnectionQuery(\"SELECT 1\");\n\n        ds.start();\n\n        return ds;\n    }\n\n    @SuppressWarnings(\"SqlNoDataSourceInspection\")\n    public static void sampleInitFunction(Connection connection) throws SQLException {\n        connection.createStatement().execute(\"CREATE TABLE bar (\\n\" + \"  foo VARCHAR(255)\\n\" + \");\");\n        connection.createStatement().execute(\"INSERT INTO bar (foo) VALUES ('hello world');\");\n        connection.createStatement().execute(\"CREATE TABLE my_counter (\\n\" + \"  n INT\\n\" + \");\");\n    }\n}\n"
  },
  {
    "path": "modules/mysql/src/test/java/org/testcontainers/jdbc/mysql/MySQLDatabaseContainerDriverTest.java",
    "content": "package org.testcontainers.jdbc.mysql;\n\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.jdbc.ContainerDatabaseDriver;\n\nimport java.sql.Connection;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.Properties;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass MySQLDatabaseContainerDriverTest {\n\n    @Test\n    void shouldRespectBothUrlPropertiesAndParameterProperties() throws SQLException {\n        ContainerDatabaseDriver driver = new ContainerDatabaseDriver();\n        String url = \"jdbc:tc:mysql:8.0.36://hostname/databasename?padCharsWithSpace=true\";\n        Properties properties = new Properties();\n        properties.setProperty(\"maxRows\", \"1\");\n\n        try (Connection connection = driver.connect(url, properties)) {\n            try (Statement statement = connection.createStatement()) {\n                statement.execute(\"CREATE TABLE arbitrary_table (length_5_string CHAR(5))\");\n                statement.execute(\"INSERT INTO arbitrary_table VALUES ('abc')\");\n                statement.execute(\"INSERT INTO arbitrary_table VALUES ('123')\");\n\n                // Check that maxRows is set\n                try (ResultSet resultSet = statement.executeQuery(\"SELECT * FROM arbitrary_table\")) {\n                    resultSet.next();\n                    assertThat(resultSet.isFirst()).isTrue();\n                    assertThat(resultSet.isLast()).isTrue();\n                }\n\n                // Check that pad with chars is set\n                try (ResultSet resultSet = statement.executeQuery(\"SELECT * FROM arbitrary_table\")) {\n                    assertThat(resultSet.next()).isTrue();\n                    assertThat(resultSet.getString(1)).isEqualTo(\"abc  \");\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "modules/mysql/src/test/java/org/testcontainers/jdbc/mysql/MySQLJDBCDriverTest.java",
    "content": "package org.testcontainers.jdbc.mysql;\n\nimport org.testcontainers.jdbc.AbstractJDBCDriverTest;\n\nimport java.util.Arrays;\nimport java.util.EnumSet;\n\nclass MySQLJDBCDriverTest extends AbstractJDBCDriverTest {\n\n    public static Iterable<Object[]> data() {\n        return Arrays.asList(\n            new Object[][] {\n                { \"jdbc:tc:mysql://hostname/databasename\", EnumSet.noneOf(Options.class) },\n                {\n                    \"jdbc:tc:mysql://hostname/databasename?user=someuser&TC_INITSCRIPT=somepath/init_mysql.sql\",\n                    EnumSet.of(Options.ScriptedSchema, Options.JDBCParams),\n                },\n                {\n                    \"jdbc:tc:mysql:8.0.36://hostname/databasename?user=someuser&TC_INITFUNCTION=org.testcontainers.jdbc.AbstractJDBCDriverTest::sampleInitFunction\",\n                    EnumSet.of(Options.ScriptedSchema, Options.JDBCParams),\n                },\n                {\n                    \"jdbc:tc:mysql:8.0.36://hostname/databasename?user=someuser&password=somepwd&TC_INITSCRIPT=somepath/init_mysql.sql\",\n                    EnumSet.of(Options.ScriptedSchema, Options.JDBCParams),\n                },\n                {\n                    \"jdbc:tc:mysql:8.0.36://hostname/databasename?user=someuser&password=somepwd&TC_INITSCRIPT=file:sql/init_mysql.sql\",\n                    EnumSet.of(Options.ScriptedSchema, Options.JDBCParams),\n                },\n                {\n                    \"jdbc:tc:mysql:8.0.36://hostname/databasename?user=someuser&password=somepwd&TC_INITFUNCTION=org.testcontainers.jdbc.AbstractJDBCDriverTest::sampleInitFunction\",\n                    EnumSet.of(Options.ScriptedSchema, Options.JDBCParams),\n                },\n                {\n                    \"jdbc:tc:mysql:8.0.36://hostname/databasename?TC_INITSCRIPT=somepath/init_unicode_mysql.sql&useUnicode=yes&characterEncoding=utf8\",\n                    EnumSet.of(Options.CharacterSet),\n                },\n                { \"jdbc:tc:mysql:8.0.36://hostname/databasename\", EnumSet.noneOf(Options.class) },\n                { \"jdbc:tc:mysql:8.0.36://hostname/databasename?useSSL=false\", EnumSet.noneOf(Options.class) },\n                {\n                    \"jdbc:tc:mysql:8.0.36://hostname/databasename?TC_MY_CNF=somepath/mysql_conf_override\",\n                    EnumSet.of(Options.CustomIniFile),\n                },\n            }\n        );\n    }\n}\n"
  },
  {
    "path": "modules/mysql/src/test/java/org/testcontainers/mysql/MultiVersionMySQLTest.java",
    "content": "package org.testcontainers.mysql;\n\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.testcontainers.MySQLTestImages;\nimport org.testcontainers.db.AbstractContainerDatabaseTest;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass MultiVersionMySQLTest extends AbstractContainerDatabaseTest {\n\n    public static DockerImageName[] params() {\n        return new DockerImageName[] {\n            MySQLTestImages.MYSQL_57_IMAGE,\n            MySQLTestImages.MYSQL_80_IMAGE,\n            MySQLTestImages.MYSQL_INNOVATION_IMAGE,\n            MySQLTestImages.MYSQL_93_IMAGE,\n        };\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"params\")\n    void versionCheckTest(DockerImageName dockerImageName) throws SQLException {\n        try (MySQLContainer mysql = new MySQLContainer(dockerImageName)) {\n            mysql.start();\n            final ResultSet resultSet = performQuery(mysql, \"SELECT VERSION()\");\n            final String resultSetString = resultSet.getString(1);\n\n            assertThat(resultSetString)\n                .as(\"The database version can be set using a container rule parameter\")\n                .isEqualTo(dockerImageName.getVersionPart());\n        }\n    }\n}\n"
  },
  {
    "path": "modules/mysql/src/test/java/org/testcontainers/mysql/MySQLContainerTest.java",
    "content": "package org.testcontainers.mysql;\n\nimport org.junit.jupiter.api.Test;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.testcontainers.MySQLTestImages;\nimport org.testcontainers.containers.ContainerLaunchException;\nimport org.testcontainers.containers.output.Slf4jLogConsumer;\nimport org.testcontainers.db.AbstractContainerDatabaseTest;\n\nimport java.io.File;\nimport java.net.URL;\nimport java.nio.file.FileSystems;\nimport java.nio.file.Files;\nimport java.nio.file.attribute.PosixFilePermission;\nimport java.sql.Connection;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.time.LocalDateTime;\nimport java.time.ZoneId;\nimport java.time.ZonedDateTime;\nimport java.time.temporal.ChronoUnit;\nimport java.util.Arrays;\nimport java.util.HashSet;\nimport java.util.Set;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\nimport static org.assertj.core.api.Assumptions.assumeThat;\n\nclass MySQLContainerTest extends AbstractContainerDatabaseTest {\n\n    private static final Logger logger = LoggerFactory.getLogger(MySQLContainerTest.class);\n\n    @Test\n    void testSimple() throws SQLException {\n        try ( // container {\n            MySQLContainer mysql = new MySQLContainer(\"mysql:8.0.36\")\n            // }\n        ) {\n            mysql.start();\n\n            ResultSet resultSet = performQuery(mysql, \"SELECT 1\");\n            int resultSetInt = resultSet.getInt(1);\n\n            assertThat(resultSetInt).as(\"A basic SELECT query succeeds\").isEqualTo(1);\n            assertHasCorrectExposedAndLivenessCheckPorts(mysql);\n        }\n    }\n\n    @Test\n    void testSpecificVersion() throws SQLException {\n        try (\n            MySQLContainer mysqlOldVersion = new MySQLContainer(MySQLTestImages.MYSQL_80_IMAGE)\n                .withConfigurationOverride(\"somepath/mysql_conf_override\")\n                .withLogConsumer(new Slf4jLogConsumer(logger))\n        ) {\n            mysqlOldVersion.start();\n\n            ResultSet resultSet = performQuery(mysqlOldVersion, \"SELECT VERSION()\");\n            String resultSetString = resultSet.getString(1);\n\n            assertThat(resultSetString)\n                .as(\"The database version can be set using a container rule parameter\")\n                .startsWith(\"8.0\");\n        }\n    }\n\n    @Test\n    void testMySQLWithCustomIniFile() throws SQLException {\n        try (\n            MySQLContainer mysqlCustomConfig = new MySQLContainer(MySQLTestImages.MYSQL_80_IMAGE)\n                .withConfigurationOverride(\"somepath/mysql_conf_override\")\n        ) {\n            mysqlCustomConfig.start();\n\n            assertThatCustomIniFileWasUsed(mysqlCustomConfig);\n        }\n    }\n\n    @Test\n    void testCommandOverride() throws SQLException {\n        try (\n            MySQLContainer mysqlCustomConfig = new MySQLContainer(MySQLTestImages.MYSQL_80_IMAGE)\n                .withCommand(\"mysqld --auto_increment_increment=42\")\n        ) {\n            mysqlCustomConfig.start();\n\n            ResultSet resultSet = performQuery(mysqlCustomConfig, \"show variables like 'auto_increment_increment'\");\n            String result = resultSet.getString(\"Value\");\n\n            assertThat(result).as(\"Auto increment increment should be overridden by command line\").isEqualTo(\"42\");\n        }\n    }\n\n    @Test\n    void testExplicitInitScript() throws SQLException {\n        try (\n            MySQLContainer container = new MySQLContainer(MySQLTestImages.MYSQL_80_IMAGE)\n                .withInitScript(\"somepath/init_mysql.sql\")\n                .withLogConsumer(new Slf4jLogConsumer(logger))\n        ) {\n            container.start();\n\n            ResultSet resultSet = performQuery(container, \"SELECT foo FROM bar\");\n            String firstColumnValue = resultSet.getString(1);\n\n            assertThat(firstColumnValue).as(\"Value from init script should equal real value\").isEqualTo(\"hello world\");\n        }\n    }\n\n    @Test\n    void testEmptyPasswordWithNonRootUser() {\n        try (\n            MySQLContainer container = new MySQLContainer(MySQLTestImages.MYSQL_80_IMAGE)\n                .withDatabaseName(\"TEST\")\n                .withUsername(\"test\")\n                .withPassword(\"\")\n                .withEnv(\"MYSQL_ROOT_HOST\", \"%\")\n        ) {\n            assertThatThrownBy(container::start)\n                .isInstanceOf(ContainerLaunchException.class)\n                .hasMessageStartingWith(\"Container startup failed for image mysql\");\n        }\n    }\n\n    @Test\n    void testEmptyPasswordWithRootUser() throws SQLException {\n        // Add MYSQL_ROOT_HOST environment so that we can root login from anywhere for testing purposes\n        try (\n            MySQLContainer mysql = new MySQLContainer(MySQLTestImages.MYSQL_80_IMAGE)\n                .withDatabaseName(\"foo\")\n                .withUsername(\"root\")\n                .withPassword(\"\")\n                .withEnv(\"MYSQL_ROOT_HOST\", \"%\")\n        ) {\n            mysql.start();\n\n            ResultSet resultSet = performQuery(mysql, \"SELECT 1\");\n            int resultSetInt = resultSet.getInt(1);\n\n            assertThat(resultSetInt).as(\"A basic SELECT query succeeds\").isEqualTo(1);\n        }\n    }\n\n    @Test\n    void testWithAdditionalUrlParamTimeZone() throws SQLException {\n        MySQLContainer mysql = new MySQLContainer(MySQLTestImages.MYSQL_80_IMAGE)\n            .withUrlParam(\"serverTimezone\", \"Europe/Zurich\")\n            .withEnv(\"TZ\", \"Europe/Zurich\")\n            .withLogConsumer(new Slf4jLogConsumer(logger));\n        mysql.start();\n\n        try (Connection connection = mysql.createConnection(\"\")) {\n            Statement statement = connection.createStatement();\n            statement.execute(\"SELECT NOW();\");\n            try (ResultSet resultSet = statement.getResultSet()) {\n                resultSet.next();\n\n                // checking that the time_zone MySQL is Europe/Zurich\n                LocalDateTime localDateTime = resultSet.getObject(1, LocalDateTime.class);\n                ZonedDateTime actualDateTime = localDateTime\n                    .atZone(ZoneId.of(\"Europe/Zurich\"))\n                    .truncatedTo(ChronoUnit.MINUTES);\n                ZonedDateTime expectedDateTime = ZonedDateTime\n                    .now(ZoneId.of(\"Europe/Zurich\"))\n                    .truncatedTo(ChronoUnit.MINUTES);\n\n                String message = String.format(\n                    \"MySQL time zone is not Europe/Zurich. MySQL date:%s, current date:%s\",\n                    actualDateTime,\n                    expectedDateTime\n                );\n                assertThat(actualDateTime).as(message).isEqualTo(expectedDateTime);\n            }\n        } finally {\n            mysql.stop();\n        }\n    }\n\n    @Test\n    void testWithAdditionalUrlParamMultiQueries() throws SQLException {\n        MySQLContainer mysql = new MySQLContainer(MySQLTestImages.MYSQL_80_IMAGE)\n            .withUrlParam(\"allowMultiQueries\", \"true\")\n            .withLogConsumer(new Slf4jLogConsumer(logger));\n        mysql.start();\n\n        try (Connection connection = mysql.createConnection(\"\")) {\n            Statement statement = connection.createStatement();\n            String multiQuery =\n                \"DROP TABLE IF EXISTS bar; \" +\n                \"CREATE TABLE bar (foo VARCHAR(20)); \" +\n                \"INSERT INTO bar (foo) VALUES ('hello world');\";\n            statement.execute(multiQuery);\n            statement.execute(\"SELECT foo FROM bar;\");\n            try (ResultSet resultSet = statement.getResultSet()) {\n                resultSet.next();\n                String firstColumnValue = resultSet.getString(1);\n                assertThat(firstColumnValue).as(\"Value from bar should equal real value\").isEqualTo(\"hello world\");\n            }\n        } finally {\n            mysql.stop();\n        }\n    }\n\n    @Test\n    void testWithAdditionalUrlParamInJdbcUrl() {\n        MySQLContainer mysql = new MySQLContainer(MySQLTestImages.MYSQL_80_IMAGE)\n            .withUrlParam(\"allowMultiQueries\", \"true\")\n            .withUrlParam(\"rewriteBatchedStatements\", \"true\")\n            .withLogConsumer(new Slf4jLogConsumer(logger));\n\n        try {\n            mysql.start();\n            String jdbcUrl = mysql.getJdbcUrl();\n            assertThat(jdbcUrl).contains(\"?\");\n            assertThat(jdbcUrl).contains(\"&\");\n            assertThat(jdbcUrl).contains(\"rewriteBatchedStatements=true\");\n            assertThat(jdbcUrl).contains(\"allowMultiQueries=true\");\n        } finally {\n            mysql.stop();\n        }\n    }\n\n    @Test\n    void testWithOnlyUserReadableCustomIniFile() throws Exception {\n        assumeThat(FileSystems.getDefault().supportedFileAttributeViews().contains(\"posix\")).isTrue();\n        try (\n            MySQLContainer mysql = new MySQLContainer(MySQLTestImages.MYSQL_80_IMAGE)\n                .withConfigurationOverride(\"somepath/mysql_conf_override\")\n                .withLogConsumer(new Slf4jLogConsumer(logger))\n        ) {\n            URL resource = this.getClass().getClassLoader().getResource(\"somepath/mysql_conf_override\");\n\n            File file = new File(resource.toURI());\n            assertThat(file.isDirectory()).isTrue();\n\n            Set<PosixFilePermission> permissions = new HashSet<>(\n                Arrays.asList(\n                    PosixFilePermission.OWNER_READ,\n                    PosixFilePermission.OWNER_WRITE,\n                    PosixFilePermission.OWNER_EXECUTE\n                )\n            );\n\n            Files.setPosixFilePermissions(file.toPath(), permissions);\n\n            mysql.start();\n            assertThatCustomIniFileWasUsed(mysql);\n        }\n    }\n\n    @Test\n    void testCustom() throws SQLException {\n        // Add MYSQL_ROOT_HOST environment so that we can root login from anywhere for testing purposes\n        try (\n            MySQLContainer mysql = new MySQLContainer(MySQLTestImages.MYSQL_80_IMAGE)\n                .withDatabaseName(\"foo\")\n                .withUsername(\"bar\")\n                .withPassword(\"baz\")\n                .withEnv(\"MYSQL_ROOT_HOST\", \"%\")\n        ) {\n            mysql.start();\n\n            ResultSet resultSet = performQuery(mysql, \"SELECT 1\");\n\n            int resultSetInt = resultSet.getInt(1);\n            assertThat(resultSetInt).as(\"A basic SELECT query succeeds\").isEqualTo(1);\n        }\n    }\n\n    private void assertHasCorrectExposedAndLivenessCheckPorts(MySQLContainer mysql) {\n        assertThat(mysql.getExposedPorts()).containsExactly(MySQLContainer.MYSQL_PORT);\n        assertThat(mysql.getLivenessCheckPortNumbers()).containsExactly(mysql.getMappedPort(MySQLContainer.MYSQL_PORT));\n    }\n\n    private void assertThatCustomIniFileWasUsed(MySQLContainer mysql) throws SQLException {\n        try (ResultSet resultSet = performQuery(mysql, \"SELECT @@GLOBAL.innodb_max_undo_log_size\")) {\n            long result = resultSet.getLong(1);\n            assertThat(result)\n                .as(\"The InnoDB max undo log size has been set by the ini file content\")\n                .isEqualTo(20000000);\n        }\n    }\n}\n"
  },
  {
    "path": "modules/mysql/src/test/java/org/testcontainers/mysql/MySQLR2DBCDatabaseContainerTest.java",
    "content": "package org.testcontainers.mysql;\n\nimport io.r2dbc.spi.ConnectionFactoryOptions;\nimport org.testcontainers.MySQLTestImages;\nimport org.testcontainers.r2dbc.AbstractR2DBCDatabaseContainerTest;\n\npublic class MySQLR2DBCDatabaseContainerTest extends AbstractR2DBCDatabaseContainerTest<MySQLContainer> {\n\n    @Override\n    protected ConnectionFactoryOptions getOptions(MySQLContainer container) {\n        return MySQLR2DBCDatabaseContainer.getOptions(container);\n    }\n\n    @Override\n    protected String createR2DBCUrl() {\n        return \"r2dbc:tc:mysql:///db?TC_IMAGE_TAG=\" + MySQLTestImages.MYSQL_80_IMAGE.getVersionPart();\n    }\n\n    @Override\n    protected MySQLContainer createContainer() {\n        return new MySQLContainer(MySQLTestImages.MYSQL_80_IMAGE);\n    }\n}\n"
  },
  {
    "path": "modules/mysql/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/mysql/src/test/resources/somepath/init_mysql.sql",
    "content": "CREATE TABLE bar (\n  foo VARCHAR(255)\n);\n\nDROP PROCEDURE IF EXISTS -- ;\n    count_foo;\n\nSELECT \"a /* string literal containing comment characters like -- here\";\nSELECT \"a 'quoting' \\\"scenario ` involving BEGIN keyword\\\" here\";\nSELECT * from `bar`;\n\n-- What about a line comment containing imbalanced string delimiters? \"\n\nCREATE PROCEDURE count_foo()\n  BEGIN\n\n    BEGIN\n      SELECT *\n      FROM bar;\n      SELECT 1\n      FROM dual;\n    END;\n\n    BEGIN\n      select * from bar;\n    END;\n\n    -- we can do comments\n\n    /* including block\n       comments\n     */\n\n    /* what if BEGIN appears inside a comment? */\n\n    select \"or what if BEGIN appears inside a literal?\";\n\n  END /*; */;\n\n/* or a block comment\n    containing imbalanced string delimiters?\n    ' \"\n    */\n\nINSERT INTO bar (foo) /* ; */ VALUES ('hello world');\n"
  },
  {
    "path": "modules/mysql/src/test/resources/somepath/init_unicode_mysql.sql",
    "content": "CREATE TABLE bar (\n  foo varchar(255) character set utf8\n);\n\nINSERT INTO bar (foo) VALUES ('привет мир');\n"
  },
  {
    "path": "modules/mysql/src/test/resources/somepath/mysql_conf_override/my.cnf",
    "content": "[mysqld]\nuser = mysql\ndatadir = /var/lib/mysql\nport   \t\t= 3306\n#socket \t\t= /tmp/mysql.sock\nskip-external-locking\nkey_buffer_size = 16K\nmax_allowed_packet = 1M\ntable_open_cache = 4\nsort_buffer_size = 64K\nread_buffer_size = 256K\nread_rnd_buffer_size = 256K\nnet_buffer_length = 2K\nhost_cache_size = 0\nskip-name-resolve\n\n# This configuration is custom to test whether config override works\ninnodb_max_undo_log_size = 20000000\n\n# Don't listen on a TCP/IP port at all. This can be a security enhancement,\n# if all processes that need to connect to mysqld run on the same host.\n# All interaction with mysqld must be made via Unix sockets or named pipes.\n# Note that using this option without enabling named pipes on Windows\n# (using the \"enable-named-pipe\" option) will render mysqld useless!\n#\n#skip-networking\n#server-id      \t= 1\n\n# Uncomment the following if you want to log updates\n#log-bin=mysql-bin\n\n# binary logging format - mixed recommended\n#binlog_format=mixed\n\n# Causes updates to non-transactional engines using statement format to be\n# written directly to binary log. Before using this option make sure that\n# there are no dependencies between transactional and non-transactional\n# tables such as in the statement INSERT INTO t_myisam SELECT * FROM\n# t_innodb; otherwise, slaves may diverge from the master.\n#binlog_direct_non_transactional_updates=TRUE\n\n# Uncomment the following if you are using InnoDB tables\ninnodb_data_file_path = ibdata1:10M:autoextend\n# You can set .._buffer_pool_size up to 50 - 80 %\n# of RAM but beware of setting memory usage too high\ninnodb_buffer_pool_size = 16M\n#innodb_additional_mem_pool_size = 2M\n# Set .._log_file_size to 25 % of buffer pool size\ninnodb_log_buffer_size = 8M\ninnodb_flush_log_at_trx_commit = 1\ninnodb_lock_wait_timeout = 50\n"
  },
  {
    "path": "modules/neo4j/.gitignore",
    "content": "container-license-acceptance.txt\n"
  },
  {
    "path": "modules/neo4j/build.gradle",
    "content": "description = \"Testcontainers :: Neo4j\"\n\ndef generatedResourcesDir = new File(project.buildDir, \"generated-resources\")\ndef customNeo4jPluginDestinationDir = new File(generatedResourcesDir, \"custom-plugins\")\n\nsourceSets {\n    customNeo4jPlugin {\n        java {\n            srcDir 'src/custom-neo4j-plugin'\n        }\n    }\n    test {\n        resources {\n            srcDir generatedResourcesDir\n        }\n    }\n}\n\ntask customNeo4jPluginJar(type: Jar) {\n    from sourceSets.customNeo4jPlugin.output\n    archiveFileName = \"hello-world.jar\"\n    destinationDirectory = customNeo4jPluginDestinationDir\n\n    inputs.files(sourceSets.customNeo4jPlugin.java.srcDirs)\n    outputs.cacheIf { true }\n    outputs.dir(customNeo4jPluginDestinationDir)\n}\n\nprocessTestResources.dependsOn customNeo4jPluginJar\n\ndependencies {\n    customNeo4jPluginCompileOnly \"org.neo4j:neo4j:3.5.35\"\n\n    api project(\":testcontainers\")\n\n    testImplementation 'org.neo4j.driver:neo4j-java-driver:4.4.21'\n}\n"
  },
  {
    "path": "modules/neo4j/src/custom-neo4j-plugin/java/ac/simons/neo4j/demos/plugins/HelloWorld.java",
    "content": "package ac.simons.neo4j.demos.plugins;\n\nimport org.neo4j.procedure.Description;\nimport org.neo4j.procedure.Name;\nimport org.neo4j.procedure.UserFunction;\n\npublic class HelloWorld {\n\n    @UserFunction(\"ac.simons.helloWorld\")\n    @Description(\"Simple Hello World\")\n    public String helloWorld(@Name(\"name\") String name) {\n        return \"Hello, \" + name;\n    }\n}\n"
  },
  {
    "path": "modules/neo4j/src/main/java/org/testcontainers/containers/Neo4jContainer.java",
    "content": "package org.testcontainers.containers;\n\nimport org.testcontainers.containers.wait.strategy.HttpWaitStrategy;\nimport org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy;\nimport org.testcontainers.containers.wait.strategy.WaitAllStrategy;\nimport org.testcontainers.containers.wait.strategy.WaitStrategy;\nimport org.testcontainers.utility.ComparableVersion;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.LicenseAcceptance;\nimport org.testcontainers.utility.MountableFile;\n\nimport java.net.HttpURLConnection;\nimport java.time.Duration;\nimport java.util.Arrays;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.UUID;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\n/**\n * Testcontainers implementation for Neo4j.\n * <p>\n * Supported image: {@code neo4j}\n * <p>\n * Exposed ports:\n * <ul>\n *     <li>Bolt: 7687</li>\n *     <li>HTTP: 7474</li>\n *     <li>HTTPS: 7473</li>\n * </ul>\n *\n * @deprecated use {@link org.testcontainers.neo4j.Neo4jContainer} instead.\n */\n@Deprecated\npublic class Neo4jContainer<S extends Neo4jContainer<S>> extends GenericContainer<S> {\n\n    /**\n     * The image defaults to the official Neo4j image: <a href=\"https://hub.docker.com/_/neo4j/\">Neo4j</a>.\n     */\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"neo4j\");\n\n    /**\n     * The default tag (version) to use.\n     */\n    private static final String DEFAULT_TAG = \"4.4\";\n\n    private static final String ENTERPRISE_TAG = DEFAULT_TAG + \"-enterprise\";\n\n    /**\n     * Default port for the binary Bolt protocol.\n     */\n    private static final int DEFAULT_BOLT_PORT = 7687;\n\n    /**\n     * The port of the transactional HTTPS endpoint: <a href=\"https://neo4j.com/docs/rest-docs/current/\">Neo4j REST API</a>.\n     */\n    private static final int DEFAULT_HTTPS_PORT = 7473;\n\n    /**\n     * The port of the transactional HTTP endpoint: <a href=\"https://neo4j.com/docs/rest-docs/current/\">Neo4j REST API</a>.\n     */\n    private static final int DEFAULT_HTTP_PORT = 7474;\n\n    /**\n     * The official image requires a change of password by default from \"neo4j\" to something else. This defaults to \"password\".\n     */\n    private static final String DEFAULT_ADMIN_PASSWORD = \"password\";\n\n    private static final String AUTH_FORMAT = \"neo4j/%s\";\n\n    private final boolean standardImage;\n\n    private String adminPassword = DEFAULT_ADMIN_PASSWORD;\n\n    private final Set<String> labsPlugins = new HashSet<>();\n\n    /**\n     * Default wait strategies\n     */\n    public static final WaitStrategy WAIT_FOR_BOLT = new LogMessageWaitStrategy()\n        .withRegEx(String.format(\".*Bolt enabled on .*:%d\\\\.\\n\", DEFAULT_BOLT_PORT));\n\n    private static final WaitStrategy WAIT_FOR_HTTP = new HttpWaitStrategy()\n        .forPort(DEFAULT_HTTP_PORT)\n        .forStatusCodeMatching(response -> response == HttpURLConnection.HTTP_OK);\n\n    /**\n     * Creates a Neo4jContainer using the official Neo4j docker image.\n     * @deprecated use {@link #Neo4jContainer(DockerImageName)} instead\n     */\n    @Deprecated\n    public Neo4jContainer() {\n        this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG));\n    }\n\n    /**\n     * Creates a Neo4jContainer using a specific docker image.\n     *\n     * @param dockerImageName The docker image to use.\n     */\n    public Neo4jContainer(String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    /**\n     * Creates a Neo4jContainer using a specific docker image.\n     *\n     * @param dockerImageName The docker image to use.\n     */\n    public Neo4jContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        this.standardImage = dockerImageName.getUnversionedPart().equals(DEFAULT_IMAGE_NAME.getUnversionedPart());\n\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n\n        this.waitStrategy =\n            new WaitAllStrategy()\n                .withStrategy(WAIT_FOR_BOLT)\n                .withStrategy(WAIT_FOR_HTTP)\n                .withStartupTimeout(Duration.ofMinutes(2));\n\n        addExposedPorts(DEFAULT_BOLT_PORT, DEFAULT_HTTP_PORT, DEFAULT_HTTPS_PORT);\n    }\n\n    @Override\n    public Set<Integer> getLivenessCheckPortNumbers() {\n        return Stream\n            .of(DEFAULT_BOLT_PORT, DEFAULT_HTTP_PORT, DEFAULT_HTTPS_PORT)\n            .map(this::getMappedPort)\n            .collect(Collectors.toSet());\n    }\n\n    @Override\n    protected void configure() {\n        configureAuth();\n        configureLabsPlugins();\n        configureWaitStrategy();\n    }\n\n    /**\n     * Configured via {@link Neo4jContainer#withAdminPassword(String)} or {@link Neo4jContainer#withoutAuthentication()}\n     * It is only possible to set the correct auth in the configuration call.\n     * Also, the custom methods overrule the set env parameter.\n     */\n    private void configureAuth() {\n        String neo4jAuthEnvKey = \"NEO4J_AUTH\";\n        if (!getEnvMap().containsKey(neo4jAuthEnvKey) || !DEFAULT_ADMIN_PASSWORD.equals(this.adminPassword)) {\n            boolean emptyAdminPassword = this.adminPassword == null || this.adminPassword.isEmpty();\n            String neo4jAuth = emptyAdminPassword ? \"none\" : String.format(AUTH_FORMAT, this.adminPassword);\n            addEnv(neo4jAuthEnvKey, neo4jAuth);\n        }\n    }\n\n    /**\n     * Configured via {@link Neo4jContainer#withLabsPlugins}.\n     * Configuration can only happen in the configuration call because there is no default.\n     */\n    private void configureLabsPlugins() {\n        String neo4jLabsPluginsEnvKey = \"NEO4JLABS_PLUGINS\";\n        if (!getEnv().contains(neo4jLabsPluginsEnvKey) && !this.labsPlugins.isEmpty()) {\n            String enabledPlugins =\n                this.labsPlugins.stream().map(pluginName -> \"\\\"\" + pluginName + \"\\\"\").collect(Collectors.joining(\",\"));\n\n            addEnv(neo4jLabsPluginsEnvKey, \"[\" + enabledPlugins + \"]\");\n        }\n    }\n\n    /**\n     * Update the default Neo4jContainer wait strategy based on the exposed ports.\n     * Still possible to override the startup timeout before starting the container via {@link WaitStrategy#withStartupTimeout(Duration)}.\n     */\n    private void configureWaitStrategy() {\n        List<Integer> exposedPorts = getExposedPorts();\n        boolean boltExposed = exposedPorts.contains(DEFAULT_BOLT_PORT);\n        boolean httpExposed = exposedPorts.contains(DEFAULT_HTTP_PORT);\n        boolean onlyBoltExposed = boltExposed && !httpExposed;\n        boolean onlyHttpExposed = !boltExposed && httpExposed;\n\n        if (onlyBoltExposed) {\n            this.waitStrategy =\n                new WaitAllStrategy().withStrategy(WAIT_FOR_BOLT).withStartupTimeout(Duration.ofMinutes(2));\n        } else if (onlyHttpExposed) {\n            this.waitStrategy =\n                new WaitAllStrategy().withStrategy(WAIT_FOR_HTTP).withStartupTimeout(Duration.ofMinutes(2));\n        }\n    }\n\n    /**\n     * @return Bolt URL for use with Neo4j's Java-Driver.\n     */\n    public String getBoltUrl() {\n        return String.format(\"bolt://\" + getHost() + \":\" + getMappedPort(DEFAULT_BOLT_PORT));\n    }\n\n    /**\n     * @return URL of the transactional HTTP endpoint.\n     */\n    public String getHttpUrl() {\n        return String.format(\"http://\" + getHost() + \":\" + getMappedPort(DEFAULT_HTTP_PORT));\n    }\n\n    /**\n     * @return URL of the transactional HTTPS endpoint.\n     */\n    public String getHttpsUrl() {\n        return String.format(\"https://\" + getHost() + \":\" + getMappedPort(DEFAULT_HTTPS_PORT));\n    }\n\n    /**\n     * Configures the container to use the enterprise edition of the default docker image.\n     * <br><br>\n     * Please have a look at the <a href=\"https://neo4j.com/licensing/\">Neo4j Licensing page</a>. While the Neo4j\n     * Community Edition can be used for free in your projects under the GPL v3 license, Neo4j Enterprise edition\n     * needs either a commercial, education or evaluation license.\n     *\n     * @return This container.\n     */\n    public S withEnterpriseEdition() {\n        if (!standardImage) {\n            throw new IllegalStateException(\n                String.format(\"Cannot use enterprise version with alternative image %s.\", getDockerImageName())\n            );\n        }\n\n        setDockerImageName(DEFAULT_IMAGE_NAME.withTag(ENTERPRISE_TAG).asCanonicalNameString());\n        LicenseAcceptance.assertLicenseAccepted(getDockerImageName());\n\n        addEnv(\"NEO4J_ACCEPT_LICENSE_AGREEMENT\", \"yes\");\n\n        return self();\n    }\n\n    /**\n     * Sets the admin password for the default account (which is <pre>neo4j</pre>). A null value or an empty string\n     * disables authentication.\n     *\n     * @param adminPassword The admin password for the default database account.\n     * @return This container.\n     */\n    public S withAdminPassword(final String adminPassword) {\n        if (adminPassword != null && adminPassword.length() < 8) {\n            logger().warn(\"Your provided admin password is too short and will not work with Neo4j 5.3+.\");\n        }\n        this.adminPassword = adminPassword;\n        return self();\n    }\n\n    /**\n     * Disables authentication.\n     *\n     * @return This container.\n     */\n    public S withoutAuthentication() {\n        return withAdminPassword(null);\n    }\n\n    /**\n     * Copies an existing {@code graph.db} folder into the container. This can either be a classpath resource or a\n     * host resource. Please have a look at the factory methods in {@link MountableFile}.\n     * <br>\n     * If you want to map your database into the container instead of copying them, please use {@code #withClasspathResourceMapping},\n     * but this will only work when your test does not run in a container itself.\n     * <br>\n     * Note: This method only works with Neo4j 3.5.\n     * <br>\n     * Mapping would work like this:\n     * <pre>\n     *      &#64;Container\n     *      private static final Neo4jContainer databaseServer = new Neo4jContainer&lt;&gt;()\n     *          .withClasspathResourceMapping(\"/test-graph.db\", \"/data/databases/graph.db\", BindMode.READ_WRITE);\n     * </pre>\n     *\n     * @param graphDb The graph.db folder to copy into the container\n     * @throws IllegalArgumentException If the database version is not 3.5.\n     * @return This container.\n     */\n    public S withDatabase(MountableFile graphDb) {\n        if (!isNeo4jDatabaseVersionSupportingDbCopy()) {\n            throw new IllegalArgumentException(\n                \"Copying database folder is not supported for Neo4j instances with version 4.0 or higher.\"\n            );\n        }\n        return withCopyFileToContainer(graphDb, \"/data/databases/graph.db\");\n    }\n\n    /**\n     * Adds plugins to the given directory to the container. If {@code plugins} denotes a directory, than all of that\n     * directory is mapped to Neo4j's plugins. Otherwise, single resources are copied over.\n     * <br>\n     * If you want to map your plugins into the container instead of copying them, please use {@code #withClasspathResourceMapping},\n     * but this will only work when your test does not run in a container itself.\n     *\n     * @param plugins\n     * @return This container.\n     */\n    public S withPlugins(MountableFile plugins) {\n        return withCopyFileToContainer(plugins, \"/var/lib/neo4j/plugins/\");\n    }\n\n    /**\n     * Adds Neo4j configuration properties to the container. The properties can be added as in the official Neo4j\n     * configuration, the method automatically translate them into the format required by the Neo4j container.\n     *\n     * @param key   The key to configure, i.e. {@code dbms.security.procedures.unrestricted}\n     * @param value The value to set\n     * @return This container.\n     */\n    public S withNeo4jConfig(String key, String value) {\n        addEnv(formatConfigurationKey(key), value);\n        return self();\n    }\n\n    /**\n     * @return The admin password for the <code>neo4j</code> account or literal <code>null</code> if auth is disabled.\n     */\n    public String getAdminPassword() {\n        return adminPassword;\n    }\n\n    /**\n     * Registers one or more Neo4j plugins for server startup.\n     * The plugins are listed here\n     * <ul>\n     *     <li><a href=\"https://neo4j.com/docs/operations-manual/5/configuration/plugins/\">Neo4j 5</a></li>\n     *     <li><a href=\"https://neo4j.com/docs/operations-manual/4.4/docker/operations/#docker-neo4jlabs-plugins\">Neo4j 4.4</a></li>\n     * </ul>\n     *\n     * @param plugins The Neo4j plugins that should get started with the server.\n     * @return This container.\n     */\n    public S withPlugins(String... plugins) {\n        this.labsPlugins.addAll(Arrays.asList(plugins));\n        return self();\n    }\n\n    private static String formatConfigurationKey(String plainConfigKey) {\n        final String prefix = \"NEO4J_\";\n\n        return String.format(\"%s%s\", prefix, plainConfigKey.replaceAll(\"_\", \"__\").replaceAll(\"\\\\.\", \"_\"));\n    }\n\n    private boolean isNeo4jDatabaseVersionSupportingDbCopy() {\n        String usedImageVersion = DockerImageName.parse(getDockerImageName()).getVersionPart();\n        ComparableVersion usedComparableVersion = new ComparableVersion(usedImageVersion);\n\n        boolean versionSupportingDbCopy =\n            usedComparableVersion.isLessThan(\"4.0\") && usedComparableVersion.isGreaterThanOrEqualTo(\"2\");\n\n        if (versionSupportingDbCopy) {\n            return true;\n        }\n        if (!usedComparableVersion.isSemanticVersion()) {\n            logger()\n                .warn(\n                    \"Version {} is not a semantic version. The function \\\"withDatabase\\\" will fail.\",\n                    usedImageVersion\n                );\n            logger().warn(\"Copying databases is only supported for Neo4j versions 3.5.x\");\n        }\n\n        return false;\n    }\n\n    public S withRandomPassword() {\n        return withAdminPassword(UUID.randomUUID().toString());\n    }\n}\n"
  },
  {
    "path": "modules/neo4j/src/main/java/org/testcontainers/neo4j/Neo4jContainer.java",
    "content": "package org.testcontainers.neo4j;\n\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.wait.strategy.HttpWaitStrategy;\nimport org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy;\nimport org.testcontainers.containers.wait.strategy.WaitAllStrategy;\nimport org.testcontainers.containers.wait.strategy.WaitStrategy;\nimport org.testcontainers.utility.ComparableVersion;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.MountableFile;\n\nimport java.net.HttpURLConnection;\nimport java.time.Duration;\nimport java.util.Arrays;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.UUID;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\n/**\n * Testcontainers implementation for Neo4j.\n * <p>\n * Supported image: {@code neo4j}\n * <p>\n * Exposed ports:\n * <ul>\n *     <li>Bolt: 7687</li>\n *     <li>HTTP: 7474</li>\n *     <li>HTTPS: 7473</li>\n * </ul>\n */\npublic class Neo4jContainer extends GenericContainer<Neo4jContainer> {\n\n    /**\n     * The image defaults to the official Neo4j image: <a href=\"https://hub.docker.com/_/neo4j/\">Neo4j</a>.\n     */\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"neo4j\");\n\n    /**\n     * Default port for the binary Bolt protocol.\n     */\n    private static final int DEFAULT_BOLT_PORT = 7687;\n\n    /**\n     * The port of the transactional HTTPS endpoint: <a href=\"https://neo4j.com/docs/rest-docs/current/\">Neo4j REST API</a>.\n     */\n    private static final int DEFAULT_HTTPS_PORT = 7473;\n\n    /**\n     * The port of the transactional HTTP endpoint: <a href=\"https://neo4j.com/docs/rest-docs/current/\">Neo4j REST API</a>.\n     */\n    private static final int DEFAULT_HTTP_PORT = 7474;\n\n    /**\n     * The official image requires a change of password by default from \"neo4j\" to something else. This defaults to \"password\".\n     */\n    private static final String DEFAULT_ADMIN_PASSWORD = \"password\";\n\n    private static final String AUTH_FORMAT = \"neo4j/%s\";\n\n    private String adminPassword = DEFAULT_ADMIN_PASSWORD;\n\n    private final Set<String> labsPlugins = new HashSet<>();\n\n    /**\n     * Default wait strategies\n     */\n    public static final WaitStrategy WAIT_FOR_BOLT = new LogMessageWaitStrategy()\n        .withRegEx(String.format(\".*Bolt enabled on .*:%d\\\\.\\n\", DEFAULT_BOLT_PORT));\n\n    private static final WaitStrategy WAIT_FOR_HTTP = new HttpWaitStrategy()\n        .forPort(DEFAULT_HTTP_PORT)\n        .forStatusCodeMatching(response -> response == HttpURLConnection.HTTP_OK);\n\n    /**\n     * Creates a Neo4jContainer using a specific docker image.\n     *\n     * @param dockerImageName The docker image to use.\n     */\n    public Neo4jContainer(String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    /**\n     * Creates a Neo4jContainer using a specific docker image.\n     *\n     * @param dockerImageName The docker image to use.\n     */\n    public Neo4jContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n\n        waitingFor(\n            new WaitAllStrategy()\n                .withStrategy(WAIT_FOR_BOLT)\n                .withStrategy(WAIT_FOR_HTTP)\n                .withStartupTimeout(Duration.ofMinutes(2))\n        );\n\n        addExposedPorts(DEFAULT_BOLT_PORT, DEFAULT_HTTP_PORT, DEFAULT_HTTPS_PORT);\n    }\n\n    @Override\n    public Set<Integer> getLivenessCheckPortNumbers() {\n        return Stream\n            .of(DEFAULT_BOLT_PORT, DEFAULT_HTTP_PORT, DEFAULT_HTTPS_PORT)\n            .map(this::getMappedPort)\n            .collect(Collectors.toSet());\n    }\n\n    @Override\n    protected void configure() {\n        configureAuth();\n        configureLabsPlugins();\n        configureWaitStrategy();\n    }\n\n    /**\n     * Configured via {@link Neo4jContainer#withAdminPassword(String)} or {@link Neo4jContainer#withoutAuthentication()}\n     * It is only possible to set the correct auth in the configuration call.\n     * Also, the custom methods overrule the set env parameter.\n     */\n    private void configureAuth() {\n        String neo4jAuthEnvKey = \"NEO4J_AUTH\";\n        if (!getEnvMap().containsKey(neo4jAuthEnvKey) || !DEFAULT_ADMIN_PASSWORD.equals(this.adminPassword)) {\n            boolean emptyAdminPassword = this.adminPassword == null || this.adminPassword.isEmpty();\n            String neo4jAuth = emptyAdminPassword ? \"none\" : String.format(AUTH_FORMAT, this.adminPassword);\n            addEnv(neo4jAuthEnvKey, neo4jAuth);\n        }\n    }\n\n    /**\n     * Configured via {@link Neo4jContainer#withLabsPlugins}.\n     * Configuration can only happen in the configuration call because there is no default.\n     */\n    private void configureLabsPlugins() {\n        String neo4jLabsPluginsEnvKey = \"NEO4JLABS_PLUGINS\";\n        if (!getEnv().contains(neo4jLabsPluginsEnvKey) && !this.labsPlugins.isEmpty()) {\n            String enabledPlugins =\n                this.labsPlugins.stream().map(pluginName -> \"\\\"\" + pluginName + \"\\\"\").collect(Collectors.joining(\",\"));\n\n            addEnv(neo4jLabsPluginsEnvKey, \"[\" + enabledPlugins + \"]\");\n        }\n    }\n\n    /**\n     * Update the default Neo4jContainer wait strategy based on the exposed ports.\n     * Still possible to override the startup timeout before starting the container via {@link WaitStrategy#withStartupTimeout(Duration)}.\n     */\n    private void configureWaitStrategy() {\n        List<Integer> exposedPorts = getExposedPorts();\n        boolean boltExposed = exposedPorts.contains(DEFAULT_BOLT_PORT);\n        boolean httpExposed = exposedPorts.contains(DEFAULT_HTTP_PORT);\n        boolean onlyBoltExposed = boltExposed && !httpExposed;\n        boolean onlyHttpExposed = !boltExposed && httpExposed;\n\n        if (onlyBoltExposed) {\n            waitingFor(new WaitAllStrategy().withStrategy(WAIT_FOR_BOLT).withStartupTimeout(Duration.ofMinutes(2)));\n        } else if (onlyHttpExposed) {\n            waitingFor(new WaitAllStrategy().withStrategy(WAIT_FOR_HTTP).withStartupTimeout(Duration.ofMinutes(2)));\n        }\n    }\n\n    /**\n     * @return Bolt URL for use with Neo4j's Java-Driver.\n     */\n    public String getBoltUrl() {\n        return String.format(\"bolt://\" + getHost() + \":\" + getMappedPort(DEFAULT_BOLT_PORT));\n    }\n\n    /**\n     * @return URL of the transactional HTTP endpoint.\n     */\n    public String getHttpUrl() {\n        return String.format(\"http://\" + getHost() + \":\" + getMappedPort(DEFAULT_HTTP_PORT));\n    }\n\n    /**\n     * @return URL of the transactional HTTPS endpoint.\n     */\n    public String getHttpsUrl() {\n        return String.format(\"https://\" + getHost() + \":\" + getMappedPort(DEFAULT_HTTPS_PORT));\n    }\n\n    /**\n     * Accepts the license agreement of the container.\n     *\n     * @return this\n     */\n    public Neo4jContainer acceptLicense() {\n        addEnv(\"NEO4J_ACCEPT_LICENSE_AGREEMENT\", \"yes\");\n        return self();\n    }\n\n    /**\n     * Sets the admin password for the default account (which is <pre>neo4j</pre>). A null value or an empty string\n     * disables authentication.\n     *\n     * @param adminPassword The admin password for the default database account.\n     * @return This container.\n     */\n    public Neo4jContainer withAdminPassword(final String adminPassword) {\n        if (adminPassword != null && adminPassword.length() < 8) {\n            logger().warn(\"Your provided admin password is too short and will not work with Neo4j 5.3+.\");\n        }\n        this.adminPassword = adminPassword;\n        return self();\n    }\n\n    /**\n     * Disables authentication.\n     *\n     * @return This container.\n     */\n    public Neo4jContainer withoutAuthentication() {\n        return withAdminPassword(null);\n    }\n\n    /**\n     * Copies an existing {@code graph.db} folder into the container. This can either be a classpath resource or a\n     * host resource. Please have a look at the factory methods in {@link MountableFile}.\n     * <br>\n     * If you want to map your database into the container instead of copying them, please use {@code #withClasspathResourceMapping},\n     * but this will only work when your test does not run in a container itself.\n     * <br>\n     * Note: This method only works with Neo4j 3.5.\n     * <br>\n     * Mapping would work like this:\n     * <pre>\n     *      &#64;Container\n     *      private static final Neo4jContainer databaseServer = new Neo4jContainer&lt;&gt;()\n     *          .withClasspathResourceMapping(\"/test-graph.db\", \"/data/databases/graph.db\", BindMode.READ_WRITE);\n     * </pre>\n     *\n     * @param graphDb The graph.db folder to copy into the container\n     * @throws IllegalArgumentException If the database version is not 3.5.\n     * @return This container.\n     */\n    public Neo4jContainer withDatabase(MountableFile graphDb) {\n        if (!isNeo4jDatabaseVersionSupportingDbCopy()) {\n            throw new IllegalArgumentException(\n                \"Copying database folder is not supported for Neo4j instances with version 4.0 or higher.\"\n            );\n        }\n        return withCopyFileToContainer(graphDb, \"/data/databases/graph.db\");\n    }\n\n    /**\n     * Adds plugins to the given directory to the container. If {@code plugins} denotes a directory, than all of that\n     * directory is mapped to Neo4j's plugins. Otherwise, single resources are copied over.\n     * <br>\n     * If you want to map your plugins into the container instead of copying them, please use {@code #withClasspathResourceMapping},\n     * but this will only work when your test does not run in a container itself.\n     *\n     * @param plugins\n     * @return This container.\n     */\n    public Neo4jContainer withPlugins(MountableFile plugins) {\n        return withCopyFileToContainer(plugins, \"/var/lib/neo4j/plugins/\");\n    }\n\n    /**\n     * Adds Neo4j configuration properties to the container. The properties can be added as in the official Neo4j\n     * configuration, the method automatically translate them into the format required by the Neo4j container.\n     *\n     * @param key   The key to configure, i.e. {@code dbms.security.procedures.unrestricted}\n     * @param value The value to set\n     * @return This container.\n     */\n    public Neo4jContainer withNeo4jConfig(String key, String value) {\n        addEnv(formatConfigurationKey(key), value);\n        return self();\n    }\n\n    /**\n     * @return The admin password for the <code>neo4j</code> account or literal <code>null</code> if auth is disabled.\n     */\n    public String getAdminPassword() {\n        return adminPassword;\n    }\n\n    /**\n     * Registers one or more Neo4j plugins for server startup.\n     * The plugins are listed here\n     * <ul>\n     *     <li><a href=\"https://neo4j.com/docs/operations-manual/5/configuration/plugins/\">Neo4j 5</a></li>\n     *     <li><a href=\"https://neo4j.com/docs/operations-manual/4.4/docker/operations/#docker-neo4jlabs-plugins\">Neo4j 4.4</a></li>\n     * </ul>\n     *\n     * @param plugins The Neo4j plugins that should get started with the server.\n     * @return This container.\n     */\n    public Neo4jContainer withPlugins(String... plugins) {\n        this.labsPlugins.addAll(Arrays.asList(plugins));\n        return self();\n    }\n\n    private static String formatConfigurationKey(String plainConfigKey) {\n        final String prefix = \"NEO4J_\";\n\n        return String.format(\"%s%s\", prefix, plainConfigKey.replaceAll(\"_\", \"__\").replaceAll(\"\\\\.\", \"_\"));\n    }\n\n    private boolean isNeo4jDatabaseVersionSupportingDbCopy() {\n        String usedImageVersion = DockerImageName.parse(getDockerImageName()).getVersionPart();\n        ComparableVersion usedComparableVersion = new ComparableVersion(usedImageVersion);\n\n        boolean versionSupportingDbCopy =\n            usedComparableVersion.isLessThan(\"4.0\") && usedComparableVersion.isGreaterThanOrEqualTo(\"2\");\n\n        if (versionSupportingDbCopy) {\n            return true;\n        }\n        if (!usedComparableVersion.isSemanticVersion()) {\n            logger()\n                .warn(\n                    \"Version {} is not a semantic version. The function \\\"withDatabase\\\" will fail.\",\n                    usedImageVersion\n                );\n            logger().warn(\"Copying databases is only supported for Neo4j versions 3.5.x\");\n        }\n\n        return false;\n    }\n\n    public Neo4jContainer withRandomPassword() {\n        return withAdminPassword(UUID.randomUUID().toString());\n    }\n}\n"
  },
  {
    "path": "modules/neo4j/src/test/java/org/testcontainers/neo4j/Neo4jContainerTest.java",
    "content": "package org.testcontainers.neo4j;\n\nimport ch.qos.logback.classic.Level;\nimport ch.qos.logback.classic.Logger;\nimport ch.qos.logback.classic.spi.ILoggingEvent;\nimport ch.qos.logback.core.AppenderBase;\nimport org.junit.jupiter.api.Test;\nimport org.neo4j.driver.AuthToken;\nimport org.neo4j.driver.AuthTokens;\nimport org.neo4j.driver.Driver;\nimport org.neo4j.driver.GraphDatabase;\nimport org.neo4j.driver.Record;\nimport org.neo4j.driver.Result;\nimport org.neo4j.driver.Session;\nimport org.testcontainers.DockerClientFactory;\nimport org.testcontainers.containers.wait.strategy.AbstractWaitStrategy;\nimport org.testcontainers.utility.DockerLoggerFactory;\nimport org.testcontainers.utility.MountableFile;\n\nimport java.util.Collections;\nimport java.util.UUID;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;\nimport static org.assertj.core.api.Assertions.assertThatNoException;\nimport static org.assertj.core.api.Assumptions.assumeThat;\n\nclass Neo4jContainerTest {\n\n    // See org.testcontainers.utility.LicenseAcceptance#ACCEPTANCE_FILE_NAME\n    private static final String ACCEPTANCE_FILE_LOCATION = \"/container-license-acceptance.txt\";\n\n    @Test\n    void shouldDisableAuthentication() {\n        try (\n            // spotless:off\n            // withoutAuthentication {\n            Neo4jContainer neo4jContainer = new Neo4jContainer(\"neo4j:4.4\")\n                .withoutAuthentication()\n            // }\n            // spotless:on\n        ) {\n            neo4jContainer.start();\n            try (Driver driver = getDriver(neo4jContainer); Session session = driver.session()) {\n                long one = session.run(\"RETURN 1\", Collections.emptyMap()).next().get(0).asLong();\n                assertThat(one).isEqualTo(1L);\n            }\n        }\n    }\n\n    @Test\n    void shouldCopyDatabase() {\n        // no aarch64 image available for Neo4j 3.5\n        assumeThat(DockerClientFactory.instance().getInfo().getArchitecture()).isNotEqualTo(\"aarch64\");\n        try (\n            // copyDatabase {\n            Neo4jContainer neo4jContainer = new Neo4jContainer(\"neo4j:3.5.30\")\n                .withDatabase(MountableFile.forClasspathResource(\"/test-graph.db\"))\n            // }\n        ) {\n            neo4jContainer.start();\n            try (Driver driver = getDriver(neo4jContainer); Session session = driver.session()) {\n                Result result = session.run(\"MATCH (t:Thing) RETURN t\");\n                assertThat(result.list().stream().map(r -> r.get(\"t\").get(\"name\").asString()))\n                    .containsExactlyInAnyOrder(\"Thing\", \"Thing 2\", \"Thing 3\", \"A box\");\n            }\n        }\n    }\n\n    @Test\n    void shouldFailOnCopyDatabaseForDefaultNeo4j4Image() {\n        assertThatIllegalArgumentException()\n            .isThrownBy(() -> {\n                new Neo4jContainer(\"neo4j:4.4.1\").withDatabase(MountableFile.forClasspathResource(\"/test-graph.db\"));\n            })\n            .withMessage(\"Copying database folder is not supported for Neo4j instances with version 4.0 or higher.\");\n    }\n\n    @Test\n    void shouldFailOnCopyDatabaseForCustomNeo4j4Image() {\n        assertThatIllegalArgumentException()\n            .isThrownBy(() -> {\n                new Neo4jContainer(\"neo4j:4.4.1\").withDatabase(MountableFile.forClasspathResource(\"/test-graph.db\"));\n            })\n            .withMessage(\"Copying database folder is not supported for Neo4j instances with version 4.0 or higher.\");\n    }\n\n    @Test\n    void shouldFailOnCopyDatabaseForCustomNonSemverNeo4j4Image() {\n        assertThatIllegalArgumentException()\n            .isThrownBy(() -> {\n                new Neo4jContainer(\"neo4j:latest\").withDatabase(MountableFile.forClasspathResource(\"/test-graph.db\"));\n            })\n            .withMessage(\"Copying database folder is not supported for Neo4j instances with version 4.0 or higher.\");\n    }\n\n    @Test\n    void shouldCopyPlugins() {\n        try (\n            // registerPluginsPath {\n            Neo4jContainer neo4jContainer = new Neo4jContainer(\"neo4j:4.4\")\n                .withPlugins(MountableFile.forClasspathResource(\"/custom-plugins\"))\n            // }\n        ) {\n            neo4jContainer.start();\n            try (Driver driver = getDriver(neo4jContainer); Session session = driver.session()) {\n                assertThatCustomPluginWasCopied(session);\n            }\n        }\n    }\n\n    @Test\n    void shouldCopyPlugin() {\n        try (\n            // registerPluginsJar {\n            Neo4jContainer neo4jContainer = new Neo4jContainer(\"neo4j:4.4\")\n                .withPlugins(MountableFile.forClasspathResource(\"/custom-plugins/hello-world.jar\"))\n            // }\n        ) {\n            neo4jContainer.start();\n            try (Driver driver = getDriver(neo4jContainer); Session session = driver.session()) {\n                assertThatCustomPluginWasCopied(session);\n            }\n        }\n    }\n\n    private static void assertThatCustomPluginWasCopied(Session session) {\n        Result result = session.run(\"RETURN ac.simons.helloWorld('Testcontainers') AS greeting\");\n        Record singleRecord = result.single();\n        assertThat(singleRecord).isNotNull();\n        assertThat(singleRecord.get(\"greeting\").asString()).isEqualTo(\"Hello, Testcontainers\");\n    }\n\n    @Test\n    void shouldRunEnterprise() {\n        assumeThat(Neo4jContainerTest.class.getResource(ACCEPTANCE_FILE_LOCATION)).isNotNull();\n\n        try (\n            // enterpriseEdition {\n            Neo4jContainer neo4jContainer = new Neo4jContainer(\"neo4j:4.4-enterprise\")\n                .acceptLicense()\n                // }\n                .withAdminPassword(\"Picard123\")\n        ) {\n            neo4jContainer.start();\n            try (Driver driver = getDriver(neo4jContainer); Session session = driver.session()) {\n                String edition = session\n                    .run(\"CALL dbms.components() YIELD edition RETURN edition\", Collections.emptyMap())\n                    .next()\n                    .get(0)\n                    .asString();\n                assertThat(edition).isEqualTo(\"enterprise\");\n            }\n        }\n    }\n\n    @Test\n    void shouldAddConfigToEnvironment() {\n        // neo4jConfiguration {\n        Neo4jContainer neo4jContainer = new Neo4jContainer(\"neo4j:4.4\")\n            .withNeo4jConfig(\"dbms.security.procedures.unrestricted\", \"apoc.*,algo.*\")\n            .withNeo4jConfig(\"dbms.tx_log.rotation.size\", \"42M\");\n        // }\n\n        assertThat(neo4jContainer.getEnvMap())\n            .containsEntry(\"NEO4J_dbms_security_procedures_unrestricted\", \"apoc.*,algo.*\");\n        assertThat(neo4jContainer.getEnvMap()).containsEntry(\"NEO4J_dbms_tx__log_rotation_size\", \"42M\");\n    }\n\n    @Test\n    void shouldRespectEnvironmentAuth() {\n        Neo4jContainer neo4jContainer = new Neo4jContainer(\"neo4j:4.4\").withEnv(\"NEO4J_AUTH\", \"neo4j/secret\");\n\n        neo4jContainer.configure();\n\n        assertThat(neo4jContainer.getEnvMap()).containsEntry(\"NEO4J_AUTH\", \"neo4j/secret\");\n    }\n\n    @Test\n    void shouldSetCustomPasswordCorrectly() {\n        // withAdminPassword {\n        Neo4jContainer neo4jContainer = new Neo4jContainer(\"neo4j:4.4\").withAdminPassword(\"verySecret\");\n        // }\n\n        neo4jContainer.configure();\n\n        assertThat(neo4jContainer.getEnvMap()).containsEntry(\"NEO4J_AUTH\", \"neo4j/verySecret\");\n    }\n\n    @Test\n    void containerAdminPasswordOverrulesEnvironmentAuth() {\n        Neo4jContainer neo4jContainer = new Neo4jContainer(\"neo4j:4.4\")\n            .withEnv(\"NEO4J_AUTH\", \"neo4j/secret\")\n            .withAdminPassword(\"anotherSecret\");\n\n        neo4jContainer.configure();\n\n        assertThat(neo4jContainer.getEnvMap()).containsEntry(\"NEO4J_AUTH\", \"neo4j/anotherSecret\");\n    }\n\n    @Test\n    void containerWithoutAuthenticationOverrulesEnvironmentAuth() {\n        Neo4jContainer neo4jContainer = new Neo4jContainer(\"neo4j:4.4\")\n            .withEnv(\"NEO4J_AUTH\", \"neo4j/secret\")\n            .withoutAuthentication();\n\n        neo4jContainer.configure();\n\n        assertThat(neo4jContainer.getEnvMap()).containsEntry(\"NEO4J_AUTH\", \"none\");\n    }\n\n    @Test\n    void shouldRespectAlreadyDefinedPortMappingsBolt() {\n        Neo4jContainer neo4jContainer = new Neo4jContainer(\"neo4j:4.4\").withExposedPorts(7687);\n\n        neo4jContainer.configure();\n\n        assertThat(neo4jContainer.getExposedPorts()).containsExactly(7687);\n    }\n\n    @Test\n    void shouldRespectAlreadyDefinedPortMappingsHttp() {\n        Neo4jContainer neo4jContainer = new Neo4jContainer(\"neo4j:4.4\").withExposedPorts(7474);\n\n        neo4jContainer.configure();\n\n        assertThat(neo4jContainer.getExposedPorts()).containsExactly(7474);\n    }\n\n    @Test\n    void shouldRespectAlreadyDefinedPortMappingsWithoutHttps() {\n        Neo4jContainer neo4jContainer = new Neo4jContainer(\"neo4j:4.4\").withExposedPorts(7687, 7474);\n\n        neo4jContainer.configure();\n\n        assertThat(neo4jContainer.getExposedPorts()).containsExactlyInAnyOrder(7474, 7687);\n    }\n\n    @Test\n    void shouldDefaultExportBoltHttpAndHttps() {\n        Neo4jContainer neo4jContainer = new Neo4jContainer(\"neo4j:4.4\");\n\n        neo4jContainer.configure();\n\n        assertThat(neo4jContainer.getExposedPorts()).containsExactlyInAnyOrder(7473, 7474, 7687);\n    }\n\n    @Test\n    void shouldRespectCustomWaitStrategy() {\n        Neo4jContainer neo4jContainer = new Neo4jContainer(\"neo4j:4.4\").waitingFor(new CustomDummyWaitStrategy());\n\n        neo4jContainer.configure();\n\n        assertThat(neo4jContainer).extracting(\"waitStrategy\").isInstanceOf(CustomDummyWaitStrategy.class);\n    }\n\n    @Test\n    void shouldConfigureSinglePluginByName() {\n        try (Neo4jContainer neo4jContainer = new Neo4jContainer(\"neo4j:4.4\").withPlugins(\"apoc\")) {\n            // needs to get called explicitly for setup\n            neo4jContainer.configure();\n\n            assertThat(neo4jContainer.getEnvMap()).containsEntry(\"NEO4JLABS_PLUGINS\", \"[\\\"apoc\\\"]\");\n        }\n    }\n\n    @Test\n    void shouldConfigureMultiplePluginsByName() {\n        try (\n            // configureLabsPlugins {\n            Neo4jContainer neo4jContainer = new Neo4jContainer(\"neo4j:4.4\") //\n                .withPlugins(\"apoc\", \"bloom\");\n            // }\n        ) {\n            // needs to get called explicitly for setup\n            neo4jContainer.configure();\n\n            assertThat(neo4jContainer.getEnvMap().get(\"NEO4JLABS_PLUGINS\"))\n                .containsAnyOf(\"[\\\"apoc\\\",\\\"bloom\\\"]\", \"[\\\"bloom\\\",\\\"apoc\\\"]\");\n        }\n    }\n\n    @Test\n    void shouldCreateRandomUuidBasedPasswords() {\n        try (\n            // withRandomPassword {\n            Neo4jContainer neo4jContainer = new Neo4jContainer(\"neo4j:4.4\").withRandomPassword();\n            // }\n        ) {\n            // It will throw an exception if it's not UUID parsable.\n            assertThatNoException().isThrownBy(neo4jContainer::configure);\n            // This basically is always true at if the random password is UUID-like.\n            assertThat(neo4jContainer.getAdminPassword())\n                .satisfies(password -> assertThat(UUID.fromString(password).toString()).isEqualTo(password));\n        }\n    }\n\n    @Test\n    void shouldWarnOnPasswordTooShort() {\n        try (Neo4jContainer neo4jContainer = new Neo4jContainer(\"neo4j:4.4\");) {\n            Logger logger = (Logger) DockerLoggerFactory.getLogger(\"neo4j:4.4\");\n            TestLogAppender testLogAppender = new TestLogAppender();\n            logger.addAppender(testLogAppender);\n            testLogAppender.start();\n\n            neo4jContainer.withAdminPassword(\"short\");\n\n            testLogAppender.stop();\n\n            assertThat(testLogAppender.passwordTooShortWarningAppeared).isTrue();\n        }\n    }\n\n    private static class CustomDummyWaitStrategy extends AbstractWaitStrategy {\n\n        @Override\n        protected void waitUntilReady() {\n            // ehm...ready\n        }\n    }\n\n    private static class TestLogAppender extends AppenderBase<ILoggingEvent> {\n\n        boolean passwordTooShortWarningAppeared = false;\n\n        @Override\n        protected void append(ILoggingEvent eventObject) {\n            if (eventObject.getLevel().equals(Level.WARN)) {\n                if (\n                    eventObject\n                        .getMessage()\n                        .equals(\"Your provided admin password is too short and will not work with Neo4j 5.3+.\")\n                ) {\n                    passwordTooShortWarningAppeared = true;\n                }\n            }\n        }\n    }\n\n    private static Driver getDriver(Neo4jContainer container) {\n        AuthToken authToken = AuthTokens.none();\n        if (container.getAdminPassword() != null) {\n            authToken = AuthTokens.basic(\"neo4j\", container.getAdminPassword());\n        }\n        return GraphDatabase.driver(container.getBoltUrl(), authToken);\n    }\n}\n"
  },
  {
    "path": "modules/neo4j/src/test/resources/example-container-license-acceptance.txt",
    "content": "neo4j:4.4.1-enterprise\n"
  },
  {
    "path": "modules/neo4j/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/nginx/build.gradle",
    "content": "description = \"Testcontainers :: Nginx\"\n\ndependencies {\n    api project(':testcontainers')\n    compileOnly 'org.jetbrains:annotations:26.0.2-1'\n}\n"
  },
  {
    "path": "modules/nginx/src/main/java/org/testcontainers/containers/NginxContainer.java",
    "content": "package org.testcontainers.containers;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.testcontainers.containers.traits.LinkableContainer;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.util.Set;\n\n/**\n * @deprecated use {@link org.testcontainers.nginx.NginxContainer} instead.\n */\n@Deprecated\npublic class NginxContainer<SELF extends NginxContainer<SELF>>\n    extends GenericContainer<SELF>\n    implements LinkableContainer {\n\n    private static final int NGINX_DEFAULT_PORT = 80;\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"nginx\");\n\n    private static final String DEFAULT_TAG = \"1.9.4\";\n\n    /**\n     * @deprecated use {@link #NginxContainer(DockerImageName)} instead\n     */\n    @Deprecated\n    public NginxContainer() {\n        this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG));\n    }\n\n    public NginxContainer(String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    public NginxContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n\n        addExposedPort(NGINX_DEFAULT_PORT);\n        setCommand(\"nginx\", \"-g\", \"daemon off;\");\n    }\n\n    /**\n     * @return the ports on which to check if the container is ready\n     * @deprecated use {@link #getLivenessCheckPortNumbers()} instead\n     */\n    @NotNull\n    @Override\n    @Deprecated\n    protected Set<Integer> getLivenessCheckPorts() {\n        return super.getLivenessCheckPorts();\n    }\n\n    public URL getBaseUrl(String scheme, int port) throws MalformedURLException {\n        return new URL(scheme + \"://\" + getHost() + \":\" + getMappedPort(port));\n    }\n\n    @Deprecated\n    public void setCustomContent(String htmlContentPath) {\n        addFileSystemBind(htmlContentPath, \"/usr/share/nginx/html\", BindMode.READ_ONLY);\n    }\n\n    @Deprecated\n    public SELF withCustomContent(String htmlContentPath) {\n        this.setCustomContent(htmlContentPath);\n        return self();\n    }\n}\n"
  },
  {
    "path": "modules/nginx/src/main/java/org/testcontainers/nginx/NginxContainer.java",
    "content": "package org.testcontainers.nginx;\n\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.net.MalformedURLException;\nimport java.net.URL;\n\npublic class NginxContainer extends GenericContainer<NginxContainer> {\n\n    private static final int NGINX_DEFAULT_PORT = 80;\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"nginx\");\n\n    public NginxContainer(String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    public NginxContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n\n        addExposedPort(NGINX_DEFAULT_PORT);\n        setCommand(\"nginx\", \"-g\", \"daemon off;\");\n    }\n\n    public URL getBaseUrl(String scheme, int port) throws MalformedURLException {\n        return new URL(scheme + \"://\" + getHost() + \":\" + getMappedPort(port));\n    }\n\n    public URL getBaseUrl(String scheme) throws MalformedURLException {\n        return getBaseUrl(scheme, NGINX_DEFAULT_PORT);\n    }\n}\n"
  },
  {
    "path": "modules/nginx/src/test/java/org/testcontainers/nginx/NginxContainerTest.java",
    "content": "package org.testcontainers.nginx;\n\nimport lombok.Cleanup;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.wait.strategy.HttpWaitStrategy;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.MountableFile;\n\nimport java.io.BufferedReader;\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.io.PrintStream;\nimport java.net.URL;\nimport java.net.URLConnection;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass NginxContainerTest {\n\n    private static final DockerImageName NGINX_IMAGE = DockerImageName.parse(\"nginx:1.27.0-alpine3.19-slim\");\n\n    private static String tmpDirectory = System.getProperty(\"user.home\") + \"/.tmp-test-container\";\n\n    @SuppressWarnings({ \"Duplicates\", \"ResultOfMethodCallIgnored\" })\n    @BeforeAll\n    static void setupContent() throws Exception {\n        // addCustomContent {\n        // Create a temporary dir\n        File contentFolder = new File(tmpDirectory);\n        contentFolder.mkdir();\n        contentFolder.deleteOnExit();\n\n        // And \"hello world\" HTTP file\n        File indexFile = new File(contentFolder, \"index.html\");\n        indexFile.deleteOnExit();\n        @Cleanup\n        PrintStream printStream = new PrintStream(new FileOutputStream(indexFile));\n        printStream.println(\"<html><body>Hello World!</body></html>\");\n        // }\n    }\n\n    @Test\n    void testSimple() throws Exception {\n        try (\n            // creatingContainer {\n            NginxContainer nginx = new NginxContainer(NGINX_IMAGE)\n                .withCopyFileToContainer(MountableFile.forHostPath(tmpDirectory), \"/usr/share/nginx/html\")\n                .waitingFor(new HttpWaitStrategy());\n            // }\n        ) {\n            nginx.start();\n            // getFromNginxServer {\n            URL baseUrl = nginx.getBaseUrl(\"http\", 80);\n\n            assertThat(responseFromNginx(baseUrl))\n                .as(\"An HTTP GET from the Nginx server returns the index.html from the custom content directory\")\n                .contains(\"Hello World!\");\n            // }\n            assertHasCorrectExposedAndLivenessCheckPorts(nginx);\n        }\n    }\n\n    private void assertHasCorrectExposedAndLivenessCheckPorts(NginxContainer nginxContainer) {\n        assertThat(nginxContainer.getExposedPorts()).containsExactly(80);\n        assertThat(nginxContainer.getLivenessCheckPortNumbers()).containsExactly(nginxContainer.getMappedPort(80));\n    }\n\n    private static String responseFromNginx(URL baseUrl) throws IOException {\n        URLConnection urlConnection = baseUrl.openConnection();\n        @Cleanup\n        BufferedReader reader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));\n        return reader.readLine();\n    }\n}\n"
  },
  {
    "path": "modules/nginx/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/oceanbase/build.gradle",
    "content": "description = \"Testcontainers :: JDBC :: OceanBase\"\n\ndependencies {\n    api project(':testcontainers-jdbc')\n\n    testImplementation project(':testcontainers-jdbc-test')\n    testRuntimeOnly 'com.mysql:mysql-connector-j:9.5.0'\n}\n"
  },
  {
    "path": "modules/oceanbase/src/main/java/org/testcontainers/oceanbase/OceanBaseCEContainer.java",
    "content": "package org.testcontainers.oceanbase;\n\nimport org.testcontainers.containers.JdbcDatabaseContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.utility.DockerImageName;\n\n/**\n * Testcontainers implementation for OceanBase Community Edition.\n * <p>\n * Supported image: {@code oceanbase/oceanbase-ce}\n * <p>\n * Exposed ports:\n * <ul>\n *     <li>SQL: 2881</li>\n *     <li>RPC: 2882</li>\n * </ul>\n */\npublic class OceanBaseCEContainer extends JdbcDatabaseContainer<OceanBaseCEContainer> {\n\n    static final String NAME = \"oceanbasece\";\n\n    static final String DOCKER_IMAGE_NAME = \"oceanbase/oceanbase-ce\";\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(DOCKER_IMAGE_NAME);\n\n    private static final Integer SQL_PORT = 2881;\n\n    private static final Integer RPC_PORT = 2882;\n\n    private static final String DEFAULT_TENANT_NAME = \"test\";\n\n    private static final String DEFAULT_USER = \"root\";\n\n    private static final String DEFAULT_PASSWORD = \"\";\n\n    private static final String DEFAULT_DATABASE_NAME = \"test\";\n\n    private Mode mode = Mode.SLIM;\n\n    private String tenantName = DEFAULT_TENANT_NAME;\n\n    private String password = DEFAULT_PASSWORD;\n\n    public OceanBaseCEContainer(String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    public OceanBaseCEContainer(DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n\n        addExposedPorts(SQL_PORT, RPC_PORT);\n        setWaitStrategy(Wait.forLogMessage(\".*boot success!.*\", 1));\n    }\n\n    @Override\n    protected void configure() {\n        addEnv(\"MODE\", mode.name().toLowerCase());\n\n        if (!DEFAULT_TENANT_NAME.equals(tenantName)) {\n            if (mode == Mode.SLIM) {\n                logger().warn(\"The tenant name is not configurable on slim mode, so this option will be ignored.\");\n                // reset the tenant name to ensure the constructed username is correct\n                tenantName = DEFAULT_TENANT_NAME;\n            } else {\n                addEnv(\"OB_TENANT_NAME\", tenantName);\n            }\n        }\n\n        addEnv(\"OB_TENANT_PASSWORD\", password);\n    }\n\n    @Override\n    protected void waitUntilContainerStarted() {\n        getWaitStrategy().waitUntilReady(this);\n    }\n\n    @Override\n    public String getDriverClassName() {\n        return OceanBaseJdbcUtils.getDriverClass();\n    }\n\n    @Override\n    public String getJdbcUrl() {\n        String additionalUrlParams = constructUrlParameters(\"?\", \"&\");\n        String prefix = OceanBaseJdbcUtils.isMySQLDriver(getDriverClassName()) ? \"jdbc:mysql://\" : \"jdbc:oceanbase://\";\n        return prefix + getHost() + \":\" + getMappedPort(SQL_PORT) + \"/\" + DEFAULT_DATABASE_NAME + additionalUrlParams;\n    }\n\n    @Override\n    public String getDatabaseName() {\n        return DEFAULT_DATABASE_NAME;\n    }\n\n    @Override\n    public String getUsername() {\n        return DEFAULT_USER + \"@\" + tenantName;\n    }\n\n    @Override\n    public String getPassword() {\n        return password;\n    }\n\n    @Override\n    protected String getTestQueryString() {\n        return \"SELECT 1\";\n    }\n\n    public OceanBaseCEContainer withMode(Mode mode) {\n        this.mode = mode;\n        return this;\n    }\n\n    public OceanBaseCEContainer withTenantName(String tenantName) {\n        this.tenantName = tenantName;\n        return this;\n    }\n\n    public OceanBaseCEContainer withPassword(String password) {\n        this.password = password;\n        return this;\n    }\n\n    public enum Mode {\n        /**\n         * Use as much hardware resources as possible for deployment by default,\n         * and all environment variables are available.\n         */\n        NORMAL,\n        /**\n         * Use the minimum hardware resources for deployment by default,\n         * and all environment variables are available.\n         */\n        MINI,\n        /**\n         * Use minimal hardware resources and pre-built deployment files for quick startup,\n         * and password of user tenant is the only available environment variable.\n         */\n        SLIM,\n    }\n}\n"
  },
  {
    "path": "modules/oceanbase/src/main/java/org/testcontainers/oceanbase/OceanBaseCEContainerProvider.java",
    "content": "package org.testcontainers.oceanbase;\n\nimport org.testcontainers.containers.JdbcDatabaseContainer;\nimport org.testcontainers.containers.JdbcDatabaseContainerProvider;\nimport org.testcontainers.utility.DockerImageName;\n\n/**\n * Factory for OceanBase Community Edition containers.\n */\npublic class OceanBaseCEContainerProvider extends JdbcDatabaseContainerProvider {\n\n    private static final String DEFAULT_TAG = \"4.2.1.8-108000022024072217\";\n\n    @Override\n    public boolean supports(String databaseType) {\n        return databaseType.equals(OceanBaseCEContainer.NAME);\n    }\n\n    @Override\n    public JdbcDatabaseContainer newInstance() {\n        return newInstance(DEFAULT_TAG);\n    }\n\n    @Override\n    public JdbcDatabaseContainer newInstance(String tag) {\n        if (tag != null) {\n            return new OceanBaseCEContainer(DockerImageName.parse(OceanBaseCEContainer.DOCKER_IMAGE_NAME).withTag(tag));\n        } else {\n            return newInstance();\n        }\n    }\n}\n"
  },
  {
    "path": "modules/oceanbase/src/main/java/org/testcontainers/oceanbase/OceanBaseJdbcUtils.java",
    "content": "package org.testcontainers.oceanbase;\n\nimport java.util.Arrays;\nimport java.util.List;\n\n/**\n * Utils for OceanBase Jdbc Connection.\n */\nclass OceanBaseJdbcUtils {\n\n    static final String MYSQL_JDBC_DRIVER = \"com.mysql.cj.jdbc.Driver\";\n\n    static final String MYSQL_LEGACY_JDBC_DRIVER = \"com.mysql.jdbc.Driver\";\n\n    static final String OCEANBASE_JDBC_DRIVER = \"com.oceanbase.jdbc.Driver\";\n\n    static final String OCEANBASE_LEGACY_JDBC_DRIVER = \"com.alipay.oceanbase.jdbc.Driver\";\n\n    static final List<String> SUPPORTED_DRIVERS = Arrays.asList(\n        OCEANBASE_JDBC_DRIVER,\n        OCEANBASE_LEGACY_JDBC_DRIVER,\n        MYSQL_JDBC_DRIVER,\n        MYSQL_LEGACY_JDBC_DRIVER\n    );\n\n    static String getDriverClass() {\n        for (String driverClass : SUPPORTED_DRIVERS) {\n            try {\n                Class.forName(driverClass);\n                return driverClass;\n            } catch (ClassNotFoundException e) {\n                // try to load next driver\n            }\n        }\n        throw new RuntimeException(\"Can't find valid driver class for OceanBase\");\n    }\n\n    static boolean isMySQLDriver(String driverClassName) {\n        return MYSQL_JDBC_DRIVER.equals(driverClassName) || MYSQL_LEGACY_JDBC_DRIVER.equals(driverClassName);\n    }\n\n    static boolean isOceanBaseDriver(String driverClassName) {\n        return OCEANBASE_JDBC_DRIVER.equals(driverClassName) || OCEANBASE_LEGACY_JDBC_DRIVER.equals(driverClassName);\n    }\n}\n"
  },
  {
    "path": "modules/oceanbase/src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider",
    "content": "org.testcontainers.oceanbase.OceanBaseCEContainerProvider\n"
  },
  {
    "path": "modules/oceanbase/src/test/java/org/testcontainers/oceanbase/OceanBaseJdbcDriverTest.java",
    "content": "package org.testcontainers.oceanbase;\n\nimport org.testcontainers.jdbc.AbstractJDBCDriverTest;\n\nimport java.util.Arrays;\nimport java.util.EnumSet;\n\nclass OceanBaseJdbcDriverTest extends AbstractJDBCDriverTest {\n\n    public static Iterable<Object[]> data() {\n        return Arrays.asList(\n            new Object[][] { { \"jdbc:tc:oceanbasece://hostname/databasename\", EnumSet.noneOf(Options.class) } }\n        );\n    }\n}\n"
  },
  {
    "path": "modules/oceanbase/src/test/java/org/testcontainers/oceanbase/SimpleOceanBaseCETest.java",
    "content": "package org.testcontainers.oceanbase;\n\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.db.AbstractContainerDatabaseTest;\n\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass SimpleOceanBaseCETest extends AbstractContainerDatabaseTest {\n\n    private static final String IMAGE = \"oceanbase/oceanbase-ce:4.2.1.8-108000022024072217\";\n\n    @Test\n    void testSimple() throws SQLException {\n        try ( // container {\n            OceanBaseCEContainer oceanbase = new OceanBaseCEContainer(\n                \"oceanbase/oceanbase-ce:4.2.1.8-108000022024072217\"\n            )\n            // }\n        ) {\n            oceanbase.start();\n\n            ResultSet resultSet = performQuery(oceanbase, \"SELECT 1\");\n            int resultSetInt = resultSet.getInt(1);\n            assertThat(resultSetInt).as(\"A basic SELECT query succeeds\").isEqualTo(1);\n            assertHasCorrectExposedAndLivenessCheckPorts(oceanbase);\n        }\n    }\n\n    @Test\n    void testExplicitInitScript() throws SQLException {\n        try (OceanBaseCEContainer oceanbase = new OceanBaseCEContainer(IMAGE).withInitScript(\"init.sql\")) {\n            oceanbase.start();\n\n            ResultSet resultSet = performQuery(oceanbase, \"SELECT foo FROM bar\");\n            String firstColumnValue = resultSet.getString(1);\n            assertThat(firstColumnValue).as(\"Value from init script should equal real value\").isEqualTo(\"hello world\");\n        }\n    }\n\n    @Test\n    void testWithAdditionalUrlParamInJdbcUrl() {\n        try (OceanBaseCEContainer oceanbase = new OceanBaseCEContainer(IMAGE).withUrlParam(\"useSSL\", \"false\")) {\n            oceanbase.start();\n\n            String jdbcUrl = oceanbase.getJdbcUrl();\n            assertThat(jdbcUrl).contains(\"?\");\n            assertThat(jdbcUrl).contains(\"useSSL=false\");\n        }\n    }\n\n    private void assertHasCorrectExposedAndLivenessCheckPorts(OceanBaseCEContainer oceanbase) {\n        int sqlPort = 2881;\n        int rpcPort = 2882;\n\n        assertThat(oceanbase.getExposedPorts()).containsExactlyInAnyOrder(sqlPort, rpcPort);\n        assertThat(oceanbase.getLivenessCheckPortNumbers())\n            .containsExactlyInAnyOrder(oceanbase.getMappedPort(sqlPort), oceanbase.getMappedPort(rpcPort));\n    }\n}\n"
  },
  {
    "path": "modules/oceanbase/src/test/resources/init.sql",
    "content": "CREATE TABLE bar (\n    foo VARCHAR(255)\n);\n\nDROP PROCEDURE IF EXISTS -- ;\n    count_foo;\n\nSELECT \"a /* string literal containing comment characters like -- here\";\nSELECT \"a 'quoting' \\\"scenario ` involving BEGIN keyword\\\" here\";\nSELECT * from `bar`;\n\n-- What about a line comment containing imbalanced string delimiters? \"\n\nCREATE PROCEDURE count_foo()\nBEGIN\n\n    BEGIN\n        SELECT *\n        FROM bar;\n        SELECT 1\n        FROM dual;\n    END;\n\n    BEGIN\n        select * from bar;\n    END;\n\n    -- we can do comments\n\n    /* including block\n       comments\n     */\n\n    /* what if BEGIN appears inside a comment? */\n\n    select \"or what if BEGIN appears inside a literal?\";\n\nEND /*; */;\n\n/* or a block comment\n    containing imbalanced string delimiters?\n    ' \"\n    */\n\nINSERT INTO bar (foo) /* ; */ VALUES ('hello world');\n"
  },
  {
    "path": "modules/oceanbase/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/ollama/build.gradle",
    "content": "description = \"Testcontainers :: Ollama\"\n\ndependencies {\n    api project(':testcontainers')\n\n    testImplementation 'io.rest-assured:rest-assured:5.5.6'\n}\n"
  },
  {
    "path": "modules/ollama/src/main/java/org/testcontainers/ollama/OllamaContainer.java",
    "content": "package org.testcontainers.ollama;\n\nimport com.github.dockerjava.api.DockerClient;\nimport com.github.dockerjava.api.model.DeviceRequest;\nimport com.github.dockerjava.api.model.Image;\nimport com.github.dockerjava.api.model.Info;\nimport com.github.dockerjava.api.model.RuntimeInfo;\nimport org.testcontainers.DockerClientFactory;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * Testcontainers implementation for Ollama.\n * <p>\n * Supported image: {@code ollama/ollama}\n * <p>\n * Exposed ports: 11434\n */\npublic class OllamaContainer extends GenericContainer<OllamaContainer> {\n\n    private static final DockerImageName DOCKER_IMAGE_NAME = DockerImageName.parse(\"ollama/ollama\");\n\n    private static final int OLLAMA_PORT = 11434;\n\n    public OllamaContainer(String image) {\n        this(DockerImageName.parse(image));\n    }\n\n    public OllamaContainer(DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DOCKER_IMAGE_NAME);\n\n        Info info = this.dockerClient.infoCmd().exec();\n        Map<String, RuntimeInfo> runtimes = info.getRuntimes();\n        if (runtimes != null) {\n            if (runtimes.containsKey(\"nvidia\")) {\n                withCreateContainerCmdModifier(cmd -> {\n                    cmd\n                        .getHostConfig()\n                        .withDeviceRequests(\n                            Collections.singletonList(\n                                new DeviceRequest()\n                                    .withCapabilities(Collections.singletonList(Collections.singletonList(\"gpu\")))\n                                    .withCount(-1)\n                            )\n                        );\n                });\n            }\n        }\n        withExposedPorts(OLLAMA_PORT);\n    }\n\n    /**\n     * Commits the current file system changes in the container into a new image.\n     * Should be used for creating an image that contains a loaded model.\n     * @param imageName the name of the new image\n     */\n    public void commitToImage(String imageName) {\n        DockerImageName dockerImageName = DockerImageName.parse(getDockerImageName());\n        if (!dockerImageName.equals(DockerImageName.parse(imageName))) {\n            DockerClient dockerClient = DockerClientFactory.instance().client();\n            List<Image> images = dockerClient.listImagesCmd().withReferenceFilter(imageName).exec();\n            if (images.isEmpty()) {\n                DockerImageName imageModel = DockerImageName.parse(imageName);\n                dockerClient\n                    .commitCmd(getContainerId())\n                    .withRepository(imageModel.getUnversionedPart())\n                    .withLabels(Collections.singletonMap(\"org.testcontainers.sessionId\", \"\"))\n                    .withTag(imageModel.getVersionPart())\n                    .exec();\n            }\n        }\n    }\n\n    public int getPort() {\n        return getMappedPort(OLLAMA_PORT);\n    }\n\n    public String getEndpoint() {\n        return \"http://\" + getHost() + \":\" + getPort();\n    }\n}\n"
  },
  {
    "path": "modules/ollama/src/test/java/org/testcontainers/ollama/OllamaContainerTest.java",
    "content": "package org.testcontainers.ollama;\n\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.utility.Base58;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.IOException;\n\nimport static io.restassured.RestAssured.given;\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass OllamaContainerTest {\n\n    @Test\n    void withDefaultConfig() {\n        try ( // container {\n            OllamaContainer ollama = new OllamaContainer(\"ollama/ollama:0.1.26\")\n            // }\n        ) {\n            ollama.start();\n\n            String version = given().baseUri(ollama.getEndpoint()).get(\"/api/version\").jsonPath().get(\"version\");\n            assertThat(version).isEqualTo(\"0.1.26\");\n        }\n    }\n\n    @Test\n    void downloadModelAndCommitToImage() throws IOException, InterruptedException {\n        String newImageName = \"tc-ollama-allminilm-\" + Base58.randomString(4).toLowerCase();\n        try (OllamaContainer ollama = new OllamaContainer(\"ollama/ollama:0.1.26\")) {\n            ollama.start();\n            // pullModel {\n            ollama.execInContainer(\"ollama\", \"pull\", \"all-minilm\");\n            // }\n\n            String modelName = given()\n                .baseUri(ollama.getEndpoint())\n                .get(\"/api/tags\")\n                .jsonPath()\n                .getString(\"models[0].name\");\n            assertThat(modelName).contains(\"all-minilm\");\n            // commitToImage {\n            ollama.commitToImage(newImageName);\n            // }\n        }\n        try (\n            // spotless:off\n            // substitute {\n            OllamaContainer ollama = new OllamaContainer(\n                DockerImageName.parse(newImageName)\n                    .asCompatibleSubstituteFor(\"ollama/ollama\")\n            )\n            // }\n            // spotless:on\n        ) {\n            ollama.start();\n            String modelName = given()\n                .baseUri(ollama.getEndpoint())\n                .get(\"/api/tags\")\n                .jsonPath()\n                .getString(\"models[0].name\");\n            assertThat(modelName).contains(\"all-minilm\");\n        }\n    }\n}\n"
  },
  {
    "path": "modules/ollama/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/openfga/build.gradle",
    "content": "description = \"Testcontainers :: OpenFGA\"\n\ndependencies {\n    api project(':testcontainers')\n\n    testImplementation 'dev.openfga:openfga-sdk:0.9.4'\n}\n\ntest {\n    javaLauncher = javaToolchains.launcherFor {\n        languageVersion = JavaLanguageVersion.of(17)\n    }\n}\n\ncompileTestJava {\n    javaCompiler = javaToolchains.compilerFor {\n        languageVersion = JavaLanguageVersion.of(17)\n    }\n    options.release.set(17)\n}\n"
  },
  {
    "path": "modules/openfga/src/main/java/org/testcontainers/openfga/OpenFGAContainer.java",
    "content": "package org.testcontainers.openfga;\n\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.utility.DockerImageName;\n\n/**\n * Testcontainers implementation for OpenFGA.\n * <p>\n * Supported image: {@code openfga/openfga}\n * <p>\n * Exposed ports:\n * <ul>\n *     <li>Playground: 3000</li>\n *     <li>HTTP: 8080</li>\n *     <li>gRPC: 8081</li>\n * </ul>\n */\npublic class OpenFGAContainer extends GenericContainer<OpenFGAContainer> {\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"openfga/openfga\");\n\n    public OpenFGAContainer(String image) {\n        this(DockerImageName.parse(image));\n    }\n\n    public OpenFGAContainer(DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n\n        withExposedPorts(3000, 8080, 8081);\n        withCommand(\"run\");\n        waitingFor(\n            Wait.forHttp(\"/healthz\").forPort(8080).forResponsePredicate(response -> response.contains(\"SERVING\"))\n        );\n    }\n\n    public String getHttpEndpoint() {\n        return \"http://\" + getHost() + \":\" + getMappedPort(8080);\n    }\n\n    public String getGrpcEndpoint() {\n        return \"http://\" + getHost() + \":\" + getMappedPort(8081);\n    }\n}\n"
  },
  {
    "path": "modules/openfga/src/test/java/org/testcontainers/openfga/OpenFGAContainerTest.java",
    "content": "package org.testcontainers.openfga;\n\nimport dev.openfga.sdk.api.client.OpenFgaClient;\nimport dev.openfga.sdk.api.client.model.ClientCreateStoreResponse;\nimport dev.openfga.sdk.api.configuration.ClientConfiguration;\nimport dev.openfga.sdk.api.model.CreateStoreRequest;\nimport dev.openfga.sdk.errors.FgaInvalidParameterException;\nimport org.junit.jupiter.api.Test;\n\nimport java.util.concurrent.ExecutionException;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass OpenFGAContainerTest {\n\n    @Test\n    void withDefaultConfig() throws FgaInvalidParameterException, ExecutionException, InterruptedException {\n        try ( // container {\n            OpenFGAContainer openfga = new OpenFGAContainer(\"openfga/openfga:v1.4.3\")\n            // }\n        ) {\n            openfga.start();\n\n            ClientConfiguration config = new ClientConfiguration().apiUrl(openfga.getHttpEndpoint());\n            OpenFgaClient client = new OpenFgaClient(config);\n\n            assertThat(client.listStores().get().getStores()).isEmpty();\n            ClientCreateStoreResponse store = client.createStore(new CreateStoreRequest().name(\"test\")).get();\n            assertThat(store.getId()).isNotNull();\n            assertThat(client.listStores().get().getStores()).hasSize(1);\n        }\n    }\n}\n"
  },
  {
    "path": "modules/openfga/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/oracle-free/build.gradle",
    "content": "description = \"Testcontainers :: JDBC :: Oracle Database Free\"\n\ndependencies {\n    api project(':testcontainers-jdbc')\n\n    compileOnly project(':testcontainers-r2dbc')\n    compileOnly 'com.oracle.database.r2dbc:oracle-r2dbc:1.3.0'\n\n    testImplementation project(':testcontainers-jdbc-test')\n    testImplementation 'com.oracle.database.jdbc:ojdbc11:23.26.0.0.0'\n\n    compileOnly 'org.jetbrains:annotations:26.0.2-1'\n\n    testImplementation testFixtures(project(':testcontainers-r2dbc'))\n    testRuntimeOnly 'com.oracle.database.r2dbc:oracle-r2dbc:1.3.0'\n}\n"
  },
  {
    "path": "modules/oracle-free/src/main/java/org/testcontainers/oracle/OracleContainer.java",
    "content": "package org.testcontainers.oracle;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.jetbrains.annotations.NotNull;\nimport org.testcontainers.containers.JdbcDatabaseContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.time.Duration;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Set;\n\n/**\n * Testcontainers implementation for Oracle Database Free.\n * <p>\n * Supported image: {@code gvenzl/oracle-free}\n * <p>\n * Exposed ports: 1521\n */\npublic class OracleContainer extends JdbcDatabaseContainer<OracleContainer> {\n\n    static final String NAME = \"oracle\";\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"gvenzl/oracle-free\");\n\n    static final String DEFAULT_TAG = \"slim\";\n\n    static final String IMAGE = DEFAULT_IMAGE_NAME.getUnversionedPart();\n\n    static final int ORACLE_PORT = 1521;\n\n    private static final int DEFAULT_STARTUP_TIMEOUT_SECONDS = 60;\n\n    private static final int DEFAULT_CONNECT_TIMEOUT_SECONDS = 60;\n\n    // Container defaults\n    static final String DEFAULT_DATABASE_NAME = \"freepdb1\";\n\n    static final String DEFAULT_SID = \"free\";\n\n    static final String DEFAULT_SYSTEM_USER = \"system\";\n\n    static final String DEFAULT_SYS_USER = \"sys\";\n\n    // Test container defaults\n    static final String APP_USER = \"test\";\n\n    static final String APP_USER_PASSWORD = \"test\";\n\n    // Restricted user and database names\n    private static final List<String> ORACLE_SYSTEM_USERS = Arrays.asList(DEFAULT_SYSTEM_USER, DEFAULT_SYS_USER);\n\n    private String databaseName = DEFAULT_DATABASE_NAME;\n\n    private String username = APP_USER;\n\n    private String password = APP_USER_PASSWORD;\n\n    private boolean usingSid = false;\n\n    public OracleContainer(String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    public OracleContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n        waitingFor(\n            Wait\n                .forLogMessage(\".*DATABASE IS READY TO USE!.*\\\\s\", 1)\n                .withStartupTimeout(Duration.ofSeconds(DEFAULT_STARTUP_TIMEOUT_SECONDS))\n        );\n        withConnectTimeoutSeconds(DEFAULT_CONNECT_TIMEOUT_SECONDS);\n        addExposedPorts(ORACLE_PORT);\n    }\n\n    @Override\n    protected void waitUntilContainerStarted() {\n        getWaitStrategy().waitUntilReady(this);\n    }\n\n    @NotNull\n    @Override\n    public Set<Integer> getLivenessCheckPortNumbers() {\n        return Collections.singleton(getMappedPort(ORACLE_PORT));\n    }\n\n    @Override\n    public String getDriverClassName() {\n        try {\n            Class.forName(\"oracle.jdbc.OracleDriver\");\n            return \"oracle.jdbc.OracleDriver\";\n        } catch (ClassNotFoundException e) {\n            return \"oracle.jdbc.driver.OracleDriver\";\n        }\n    }\n\n    @Override\n    public String getJdbcUrl() {\n        return isUsingSid()\n            ? \"jdbc:oracle:thin:\" + \"@\" + getHost() + \":\" + getOraclePort() + \":\" + getSid()\n            : \"jdbc:oracle:thin:\" + \"@\" + getHost() + \":\" + getOraclePort() + \"/\" + getDatabaseName();\n    }\n\n    @Override\n    public String getUsername() {\n        // An application user is tied to the database, and therefore not authenticated to connect to SID.\n        return isUsingSid() ? DEFAULT_SYSTEM_USER : username;\n    }\n\n    @Override\n    public String getPassword() {\n        return password;\n    }\n\n    @Override\n    public String getDatabaseName() {\n        return databaseName;\n    }\n\n    protected boolean isUsingSid() {\n        return usingSid;\n    }\n\n    @Override\n    public OracleContainer withUsername(String username) {\n        if (StringUtils.isEmpty(username)) {\n            throw new IllegalArgumentException(\"Username cannot be null or empty\");\n        }\n        if (ORACLE_SYSTEM_USERS.contains(username.toLowerCase())) {\n            throw new IllegalArgumentException(\"Username cannot be one of \" + ORACLE_SYSTEM_USERS);\n        }\n        this.username = username;\n        return self();\n    }\n\n    @Override\n    public OracleContainer withPassword(String password) {\n        if (StringUtils.isEmpty(password)) {\n            throw new IllegalArgumentException(\"Password cannot be null or empty\");\n        }\n        this.password = password;\n        return self();\n    }\n\n    @Override\n    public OracleContainer withDatabaseName(String databaseName) {\n        if (StringUtils.isEmpty(databaseName)) {\n            throw new IllegalArgumentException(\"Database name cannot be null or empty\");\n        }\n\n        if (DEFAULT_DATABASE_NAME.equals(databaseName.toLowerCase())) {\n            throw new IllegalArgumentException(\"Database name cannot be set to \" + DEFAULT_DATABASE_NAME);\n        }\n\n        this.databaseName = databaseName;\n        return self();\n    }\n\n    public OracleContainer usingSid() {\n        this.usingSid = true;\n        return self();\n    }\n\n    @Override\n    public OracleContainer withUrlParam(String paramName, String paramValue) {\n        throw new UnsupportedOperationException(\"The Oracle Database driver does not support this\");\n    }\n\n    @SuppressWarnings(\"SameReturnValue\")\n    public String getSid() {\n        return DEFAULT_SID;\n    }\n\n    public Integer getOraclePort() {\n        return getMappedPort(ORACLE_PORT);\n    }\n\n    @Override\n    public String getTestQueryString() {\n        return \"SELECT 1 FROM DUAL\";\n    }\n\n    @Override\n    protected void configure() {\n        withEnv(\"ORACLE_PASSWORD\", password);\n\n        // Only set ORACLE_DATABASE if different than the default.\n        if (databaseName != DEFAULT_DATABASE_NAME) {\n            withEnv(\"ORACLE_DATABASE\", databaseName);\n        }\n\n        withEnv(\"APP_USER\", username);\n        withEnv(\"APP_USER_PASSWORD\", password);\n    }\n}\n"
  },
  {
    "path": "modules/oracle-free/src/main/java/org/testcontainers/oracle/OracleContainerProvider.java",
    "content": "package org.testcontainers.oracle;\n\nimport org.testcontainers.containers.JdbcDatabaseContainer;\nimport org.testcontainers.containers.JdbcDatabaseContainerProvider;\nimport org.testcontainers.utility.DockerImageName;\n\n/**\n * Factory for Oracle containers.\n */\npublic class OracleContainerProvider extends JdbcDatabaseContainerProvider {\n\n    @Override\n    public boolean supports(String databaseType) {\n        return databaseType.equals(OracleContainer.NAME);\n    }\n\n    @Override\n    public JdbcDatabaseContainer newInstance() {\n        return newInstance(OracleContainer.DEFAULT_TAG);\n    }\n\n    @Override\n    public JdbcDatabaseContainer newInstance(String tag) {\n        if (tag != null) {\n            return new OracleContainer(DockerImageName.parse(OracleContainer.IMAGE).withTag(tag));\n        }\n        return newInstance();\n    }\n}\n"
  },
  {
    "path": "modules/oracle-free/src/main/java/org/testcontainers/oracle/OracleR2DBCDatabaseContainer.java",
    "content": "package org.testcontainers.oracle;\n\nimport io.r2dbc.spi.ConnectionFactoryOptions;\nimport org.testcontainers.lifecycle.Startable;\nimport org.testcontainers.r2dbc.R2DBCDatabaseContainer;\n\nimport java.util.Set;\n\npublic class OracleR2DBCDatabaseContainer implements R2DBCDatabaseContainer {\n\n    private final OracleContainer container;\n\n    public OracleR2DBCDatabaseContainer(OracleContainer container) {\n        this.container = container;\n    }\n\n    public static ConnectionFactoryOptions getOptions(OracleContainer container) {\n        ConnectionFactoryOptions options = ConnectionFactoryOptions\n            .builder()\n            .option(ConnectionFactoryOptions.DRIVER, OracleR2DBCDatabaseContainerProvider.DRIVER)\n            .build();\n\n        return new OracleR2DBCDatabaseContainer(container).configure(options);\n    }\n\n    @Override\n    public ConnectionFactoryOptions configure(ConnectionFactoryOptions options) {\n        return options\n            .mutate()\n            .option(ConnectionFactoryOptions.HOST, container.getHost())\n            .option(ConnectionFactoryOptions.PORT, container.getMappedPort(OracleContainer.ORACLE_PORT))\n            .option(ConnectionFactoryOptions.DATABASE, container.getDatabaseName())\n            .option(ConnectionFactoryOptions.USER, container.getUsername())\n            .option(ConnectionFactoryOptions.PASSWORD, container.getPassword())\n            .build();\n    }\n\n    @Override\n    public Set<Startable> getDependencies() {\n        return this.container.getDependencies();\n    }\n\n    @Override\n    public void start() {\n        this.container.start();\n    }\n\n    @Override\n    public void stop() {\n        this.container.stop();\n    }\n\n    @Override\n    public void close() {\n        this.container.close();\n    }\n}\n"
  },
  {
    "path": "modules/oracle-free/src/main/java/org/testcontainers/oracle/OracleR2DBCDatabaseContainerProvider.java",
    "content": "package org.testcontainers.oracle;\n\nimport io.r2dbc.spi.ConnectionFactoryMetadata;\nimport io.r2dbc.spi.ConnectionFactoryOptions;\nimport org.jetbrains.annotations.Nullable;\nimport org.testcontainers.r2dbc.R2DBCDatabaseContainer;\nimport org.testcontainers.r2dbc.R2DBCDatabaseContainerProvider;\n\npublic class OracleR2DBCDatabaseContainerProvider implements R2DBCDatabaseContainerProvider {\n\n    static final String DRIVER = \"oracle\";\n\n    @Override\n    public boolean supports(ConnectionFactoryOptions options) {\n        return DRIVER.equals(options.getRequiredValue(ConnectionFactoryOptions.DRIVER));\n    }\n\n    @Override\n    public R2DBCDatabaseContainer createContainer(ConnectionFactoryOptions options) {\n        String image = OracleContainer.IMAGE + \":\" + options.getRequiredValue(IMAGE_TAG_OPTION);\n        OracleContainer container = new OracleContainer(image)\n            .withDatabaseName((String) options.getRequiredValue(ConnectionFactoryOptions.DATABASE));\n        if (Boolean.TRUE.equals(options.getValue(REUSABLE_OPTION))) {\n            container.withReuse(true);\n        }\n        return new OracleR2DBCDatabaseContainer(container);\n    }\n\n    @Nullable\n    @Override\n    public ConnectionFactoryMetadata getMetadata(ConnectionFactoryOptions options) {\n        ConnectionFactoryOptions.Builder builder = options.mutate();\n        if (!options.hasOption(ConnectionFactoryOptions.USER)) {\n            builder.option(ConnectionFactoryOptions.USER, OracleContainer.APP_USER);\n        }\n        if (!options.hasOption(ConnectionFactoryOptions.PASSWORD)) {\n            builder.option(ConnectionFactoryOptions.PASSWORD, OracleContainer.APP_USER_PASSWORD);\n        }\n        return R2DBCDatabaseContainerProvider.super.getMetadata(builder.build());\n    }\n}\n"
  },
  {
    "path": "modules/oracle-free/src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider",
    "content": "org.testcontainers.oracle.OracleContainerProvider\n"
  },
  {
    "path": "modules/oracle-free/src/main/resources/META-INF/services/org.testcontainers.r2dbc.R2DBCDatabaseContainerProvider",
    "content": "org.testcontainers.oracle.OracleR2DBCDatabaseContainerProvider\n"
  },
  {
    "path": "modules/oracle-free/src/test/java/org/testcontainers/junit/oracle/SimpleOracleTest.java",
    "content": "package org.testcontainers.junit.oracle;\n\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.db.AbstractContainerDatabaseTest;\nimport org.testcontainers.oracle.OracleContainer;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.fail;\n\nclass SimpleOracleTest extends AbstractContainerDatabaseTest {\n\n    private static final DockerImageName ORACLE_DOCKER_IMAGE_NAME = DockerImageName.parse(\n        \"gvenzl/oracle-free:slim-faststart\"\n    );\n\n    private void runTest(OracleContainer container, String databaseName, String username, String password)\n        throws SQLException {\n        //Test config was honored\n        assertThat(container.getDatabaseName()).isEqualTo(databaseName);\n        assertThat(container.getUsername()).isEqualTo(username);\n        assertThat(container.getPassword()).isEqualTo(password);\n\n        //Test we can get a connection\n        container.start();\n        ResultSet resultSet = performQuery(container, \"SELECT 1 FROM dual\");\n        int resultSetInt = resultSet.getInt(1);\n        assertThat(resultSetInt).as(\"A basic SELECT query succeeds\").isEqualTo(1);\n    }\n\n    @Test\n    void testDefaultSettings() throws SQLException {\n        try ( // container {\n            OracleContainer oracle = new OracleContainer(\"gvenzl/oracle-free:slim-faststart\")\n            // }\n        ) {\n            runTest(oracle, \"freepdb1\", \"test\", \"test\");\n\n            // Match against the last '/'\n            String urlSuffix = oracle.getJdbcUrl().split(\"(\\\\/)(?!.*\\\\/)\", 2)[1];\n            assertThat(urlSuffix).isEqualTo(\"freepdb1\");\n        }\n    }\n\n    @Test\n    void testPluggableDatabase() throws SQLException {\n        try (OracleContainer oracle = new OracleContainer(ORACLE_DOCKER_IMAGE_NAME).withDatabaseName(\"testDB\")) {\n            runTest(oracle, \"testDB\", \"test\", \"test\");\n        }\n    }\n\n    @Test\n    void testPluggableDatabaseAndCustomUser() throws SQLException {\n        try (\n            OracleContainer oracle = new OracleContainer(\"gvenzl/oracle-free:slim-faststart\")\n                .withDatabaseName(\"testDB\")\n                .withUsername(\"testUser\")\n                .withPassword(\"testPassword\")\n        ) {\n            runTest(oracle, \"testDB\", \"testUser\", \"testPassword\");\n        }\n    }\n\n    @Test\n    void testCustomUser() throws SQLException {\n        try (\n            OracleContainer oracle = new OracleContainer(ORACLE_DOCKER_IMAGE_NAME)\n                .withUsername(\"testUser\")\n                .withPassword(\"testPassword\")\n        ) {\n            runTest(oracle, \"freepdb1\", \"testUser\", \"testPassword\");\n        }\n    }\n\n    @Test\n    void testSID() throws SQLException {\n        try (OracleContainer oracle = new OracleContainer(ORACLE_DOCKER_IMAGE_NAME).usingSid()) {\n            runTest(oracle, \"freepdb1\", \"system\", \"test\");\n\n            // Match against the last ':'\n            String urlSuffix = oracle.getJdbcUrl().split(\"(\\\\:)(?!.*\\\\:)\", 2)[1];\n            assertThat(urlSuffix).isEqualTo(\"free\");\n        }\n    }\n\n    @Test\n    void testSIDAndCustomPassword() throws SQLException {\n        try (\n            OracleContainer oracle = new OracleContainer(ORACLE_DOCKER_IMAGE_NAME)\n                .usingSid()\n                .withPassword(\"testPassword\")\n        ) {\n            runTest(oracle, \"freepdb1\", \"system\", \"testPassword\");\n        }\n    }\n\n    @Test\n    void testErrorPaths() throws SQLException {\n        try (OracleContainer oracle = new OracleContainer(ORACLE_DOCKER_IMAGE_NAME)) {\n            try {\n                oracle.withDatabaseName(\"FREEPDB1\");\n                fail(\"Should not have been able to set database name to freepdb1.\");\n            } catch (IllegalArgumentException e) {\n                //expected\n            }\n\n            try {\n                oracle.withDatabaseName(\"\");\n                fail(\"Should not have been able to set database name to nothing.\");\n            } catch (IllegalArgumentException e) {\n                //expected\n            }\n\n            try {\n                oracle.withUsername(\"SYSTEM\");\n                fail(\"Should not have been able to set username to system.\");\n            } catch (IllegalArgumentException e) {\n                //expected\n            }\n\n            try {\n                oracle.withUsername(\"SYS\");\n                fail(\"Should not have been able to set username to sys.\");\n            } catch (IllegalArgumentException e) {\n                //expected\n            }\n\n            try {\n                oracle.withUsername(\"\");\n                fail(\"Should not have been able to set username to nothing.\");\n            } catch (IllegalArgumentException e) {\n                //expected\n            }\n\n            try {\n                oracle.withPassword(\"\");\n                fail(\"Should not have been able to set password to nothing.\");\n            } catch (IllegalArgumentException e) {\n                //expected\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "modules/oracle-free/src/test/java/org/testcontainers/oracle/jdbc/OracleJDBCDriverTest.java",
    "content": "package org.testcontainers.oracle.jdbc;\n\nimport com.zaxxer.hikari.HikariConfig;\nimport com.zaxxer.hikari.HikariDataSource;\nimport org.apache.commons.dbutils.QueryRunner;\nimport org.apache.commons.dbutils.ResultSetHandler;\nimport org.junit.jupiter.api.Test;\n\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass OracleJDBCDriverTest {\n\n    @Test\n    void testOracleWithNoSpecifiedVersion() throws SQLException {\n        performSimpleTest(\"jdbc:tc:oracle://hostname/databasename\");\n    }\n\n    private void performSimpleTest(String jdbcUrl) throws SQLException {\n        HikariDataSource dataSource = getDataSource(jdbcUrl, 1);\n        new QueryRunner(dataSource)\n            .query(\n                \"SELECT 1 FROM dual\",\n                new ResultSetHandler<Object>() {\n                    @Override\n                    public Object handle(ResultSet rs) throws SQLException {\n                        rs.next();\n                        int resultSetInt = rs.getInt(1);\n                        assertThat(resultSetInt).as(\"A basic SELECT query succeeds\").isEqualTo(1);\n                        return true;\n                    }\n                }\n            );\n        dataSource.close();\n    }\n\n    private HikariDataSource getDataSource(String jdbcUrl, int poolSize) {\n        HikariConfig hikariConfig = new HikariConfig();\n        hikariConfig.setJdbcUrl(jdbcUrl);\n        hikariConfig.setConnectionTestQuery(\"SELECT 1 FROM dual\");\n        hikariConfig.setMinimumIdle(1);\n        hikariConfig.setMaximumPoolSize(poolSize);\n\n        return new HikariDataSource(hikariConfig);\n    }\n}\n"
  },
  {
    "path": "modules/oracle-free/src/test/java/org/testcontainers/oracle/r2dbc/OracleR2DBCDatabaseContainerTest.java",
    "content": "package org.testcontainers.oracle.r2dbc;\n\nimport io.r2dbc.spi.ConnectionFactoryOptions;\nimport org.testcontainers.oracle.OracleContainer;\nimport org.testcontainers.oracle.OracleR2DBCDatabaseContainer;\nimport org.testcontainers.r2dbc.AbstractR2DBCDatabaseContainerTest;\n\npublic class OracleR2DBCDatabaseContainerTest extends AbstractR2DBCDatabaseContainerTest<OracleContainer> {\n\n    @Override\n    protected OracleContainer createContainer() {\n        return new OracleContainer(\"gvenzl/oracle-free:slim-faststart\");\n    }\n\n    @Override\n    protected ConnectionFactoryOptions getOptions(OracleContainer container) {\n        ConnectionFactoryOptions options = OracleR2DBCDatabaseContainer.getOptions(container);\n\n        return options;\n    }\n\n    protected String createR2DBCUrl() {\n        return \"r2dbc:tc:oracle:///db?TC_IMAGE_TAG=slim-faststart\";\n    }\n\n    @Override\n    protected String query() {\n        return \"SELECT %s from dual\";\n    }\n}\n"
  },
  {
    "path": "modules/oracle-free/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/oracle-xe/build.gradle",
    "content": "description = \"Testcontainers :: JDBC :: Oracle XE\"\n\ndependencies {\n    api project(':testcontainers-jdbc')\n\n    compileOnly project(':testcontainers-r2dbc')\n    compileOnly 'com.oracle.database.r2dbc:oracle-r2dbc:1.3.0'\n\n    testImplementation project(':testcontainers-jdbc-test')\n    testImplementation 'com.oracle.database.jdbc:ojdbc11:23.26.0.0.0'\n\n    compileOnly 'org.jetbrains:annotations:26.0.2-1'\n\n    testImplementation testFixtures(project(':testcontainers-r2dbc'))\n    testRuntimeOnly 'com.oracle.database.r2dbc:oracle-r2dbc:1.3.0'\n}\n"
  },
  {
    "path": "modules/oracle-xe/src/main/java/org/testcontainers/containers/OracleContainer.java",
    "content": "package org.testcontainers.containers;\n\nimport org.apache.commons.lang3.StringUtils;\nimport org.jetbrains.annotations.NotNull;\nimport org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.time.Duration;\nimport java.time.temporal.ChronoUnit;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.concurrent.Future;\n\n/**\n * Testcontainers implementation for Oracle.\n * <p>\n * Supported image: {@code gvenzl/oracle-xe}\n * <p>\n * Exposed ports: 1521\n */\npublic class OracleContainer extends JdbcDatabaseContainer<OracleContainer> {\n\n    public static final String NAME = \"oracle\";\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"gvenzl/oracle-xe\");\n\n    static final String DEFAULT_TAG = \"18.4.0-slim\";\n\n    static final String IMAGE = DEFAULT_IMAGE_NAME.getUnversionedPart();\n\n    static final int ORACLE_PORT = 1521;\n\n    private static final int APEX_HTTP_PORT = 8080;\n\n    private static final int DEFAULT_STARTUP_TIMEOUT_SECONDS = 240;\n\n    private static final int DEFAULT_CONNECT_TIMEOUT_SECONDS = 120;\n\n    // Container defaults\n    static final String DEFAULT_DATABASE_NAME = \"xepdb1\";\n\n    static final String DEFAULT_SID = \"xe\";\n\n    static final String DEFAULT_SYSTEM_USER = \"system\";\n\n    static final String DEFAULT_SYS_USER = \"sys\";\n\n    // Test container defaults\n    static final String APP_USER = \"test\";\n\n    static final String APP_USER_PASSWORD = \"test\";\n\n    // Restricted user and database names\n    private static final List<String> ORACLE_SYSTEM_USERS = Arrays.asList(DEFAULT_SYSTEM_USER, DEFAULT_SYS_USER);\n\n    private String databaseName = DEFAULT_DATABASE_NAME;\n\n    private String username = APP_USER;\n\n    private String password = APP_USER_PASSWORD;\n\n    private boolean usingSid = false;\n\n    /**\n     * @deprecated use {@link #OracleContainer(DockerImageName)} instead\n     */\n    @Deprecated\n    public OracleContainer() {\n        this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG));\n    }\n\n    public OracleContainer(String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    public OracleContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n        preconfigure();\n    }\n\n    public OracleContainer(Future<String> dockerImageName) {\n        super(dockerImageName);\n        preconfigure();\n    }\n\n    private void preconfigure() {\n        this.waitStrategy =\n            new LogMessageWaitStrategy()\n                .withRegEx(\".*DATABASE IS READY TO USE!.*\\\\s\")\n                .withTimes(1)\n                .withStartupTimeout(Duration.of(DEFAULT_STARTUP_TIMEOUT_SECONDS, ChronoUnit.SECONDS));\n\n        withConnectTimeoutSeconds(DEFAULT_CONNECT_TIMEOUT_SECONDS);\n        addExposedPorts(ORACLE_PORT, APEX_HTTP_PORT);\n    }\n\n    @Override\n    protected void waitUntilContainerStarted() {\n        getWaitStrategy().waitUntilReady(this);\n    }\n\n    @NotNull\n    @Override\n    public Set<Integer> getLivenessCheckPortNumbers() {\n        return Collections.singleton(getMappedPort(ORACLE_PORT));\n    }\n\n    @Override\n    public String getDriverClassName() {\n        try {\n            Class.forName(\"oracle.jdbc.OracleDriver\");\n            return \"oracle.jdbc.OracleDriver\";\n        } catch (ClassNotFoundException e) {\n            return \"oracle.jdbc.driver.OracleDriver\";\n        }\n    }\n\n    @Override\n    public String getJdbcUrl() {\n        return isUsingSid()\n            ? \"jdbc:oracle:thin:\" + \"@\" + getHost() + \":\" + getOraclePort() + \":\" + getSid()\n            : \"jdbc:oracle:thin:\" + \"@\" + getHost() + \":\" + getOraclePort() + \"/\" + getDatabaseName();\n    }\n\n    @Override\n    public String getUsername() {\n        // An application user is tied to the database, and therefore not authenticated to connect to SID.\n        return isUsingSid() ? DEFAULT_SYSTEM_USER : username;\n    }\n\n    @Override\n    public String getPassword() {\n        return password;\n    }\n\n    @Override\n    public String getDatabaseName() {\n        return databaseName;\n    }\n\n    protected boolean isUsingSid() {\n        return usingSid;\n    }\n\n    @Override\n    public OracleContainer withUsername(String username) {\n        if (StringUtils.isEmpty(username)) {\n            throw new IllegalArgumentException(\"Username cannot be null or empty\");\n        }\n        if (ORACLE_SYSTEM_USERS.contains(username.toLowerCase())) {\n            throw new IllegalArgumentException(\"Username cannot be one of \" + ORACLE_SYSTEM_USERS);\n        }\n        this.username = username;\n        return self();\n    }\n\n    @Override\n    public OracleContainer withPassword(String password) {\n        if (StringUtils.isEmpty(password)) {\n            throw new IllegalArgumentException(\"Password cannot be null or empty\");\n        }\n        this.password = password;\n        return self();\n    }\n\n    @Override\n    public OracleContainer withDatabaseName(String databaseName) {\n        if (StringUtils.isEmpty(databaseName)) {\n            throw new IllegalArgumentException(\"Database name cannot be null or empty\");\n        }\n\n        if (DEFAULT_DATABASE_NAME.equals(databaseName.toLowerCase())) {\n            throw new IllegalArgumentException(\"Database name cannot be set to \" + DEFAULT_DATABASE_NAME);\n        }\n\n        this.databaseName = databaseName;\n        return self();\n    }\n\n    public OracleContainer usingSid() {\n        this.usingSid = true;\n        return self();\n    }\n\n    @Override\n    public OracleContainer withUrlParam(String paramName, String paramValue) {\n        throw new UnsupportedOperationException(\"The Oracle Database driver does not support this\");\n    }\n\n    @SuppressWarnings(\"SameReturnValue\")\n    public String getSid() {\n        return DEFAULT_SID;\n    }\n\n    public Integer getOraclePort() {\n        return getMappedPort(ORACLE_PORT);\n    }\n\n    @SuppressWarnings(\"unused\")\n    public Integer getWebPort() {\n        return getMappedPort(APEX_HTTP_PORT);\n    }\n\n    @Override\n    public String getTestQueryString() {\n        return \"SELECT 1 FROM DUAL\";\n    }\n\n    @Override\n    protected void configure() {\n        withEnv(\"ORACLE_PASSWORD\", password);\n\n        // Only set ORACLE_DATABASE if different than the default.\n        if (databaseName != DEFAULT_DATABASE_NAME) {\n            withEnv(\"ORACLE_DATABASE\", databaseName);\n        }\n\n        withEnv(\"APP_USER\", username);\n        withEnv(\"APP_USER_PASSWORD\", password);\n    }\n}\n"
  },
  {
    "path": "modules/oracle-xe/src/main/java/org/testcontainers/containers/OracleContainerProvider.java",
    "content": "package org.testcontainers.containers;\n\nimport org.testcontainers.utility.DockerImageName;\n\n/**\n * Factory for Oracle containers.\n */\npublic class OracleContainerProvider extends JdbcDatabaseContainerProvider {\n\n    @Override\n    public boolean supports(String databaseType) {\n        return databaseType.equals(OracleContainer.NAME);\n    }\n\n    @Override\n    public JdbcDatabaseContainer newInstance() {\n        return newInstance(OracleContainer.DEFAULT_TAG);\n    }\n\n    @Override\n    public JdbcDatabaseContainer newInstance(String tag) {\n        if (tag != null) {\n            return new OracleContainer(DockerImageName.parse(OracleContainer.IMAGE).withTag(tag));\n        }\n        return newInstance();\n    }\n}\n"
  },
  {
    "path": "modules/oracle-xe/src/main/java/org/testcontainers/containers/OracleR2DBCDatabaseContainer.java",
    "content": "package org.testcontainers.containers;\n\nimport io.r2dbc.spi.ConnectionFactoryOptions;\nimport lombok.RequiredArgsConstructor;\nimport lombok.experimental.Delegate;\nimport org.testcontainers.lifecycle.Startable;\nimport org.testcontainers.r2dbc.R2DBCDatabaseContainer;\n\n@RequiredArgsConstructor\npublic class OracleR2DBCDatabaseContainer implements R2DBCDatabaseContainer {\n\n    @Delegate(types = Startable.class)\n    private final OracleContainer container;\n\n    public static ConnectionFactoryOptions getOptions(OracleContainer container) {\n        ConnectionFactoryOptions options = ConnectionFactoryOptions\n            .builder()\n            .option(ConnectionFactoryOptions.DRIVER, OracleR2DBCDatabaseContainerProvider.DRIVER)\n            .build();\n\n        return new OracleR2DBCDatabaseContainer(container).configure(options);\n    }\n\n    @Override\n    public ConnectionFactoryOptions configure(ConnectionFactoryOptions options) {\n        return options\n            .mutate()\n            .option(ConnectionFactoryOptions.HOST, container.getHost())\n            .option(ConnectionFactoryOptions.PORT, container.getMappedPort(OracleContainer.ORACLE_PORT))\n            .option(ConnectionFactoryOptions.DATABASE, container.getDatabaseName())\n            .option(ConnectionFactoryOptions.USER, container.getUsername())\n            .option(ConnectionFactoryOptions.PASSWORD, container.getPassword())\n            .build();\n    }\n}\n"
  },
  {
    "path": "modules/oracle-xe/src/main/java/org/testcontainers/containers/OracleR2DBCDatabaseContainerProvider.java",
    "content": "package org.testcontainers.containers;\n\nimport io.r2dbc.spi.ConnectionFactoryMetadata;\nimport io.r2dbc.spi.ConnectionFactoryOptions;\nimport org.jetbrains.annotations.Nullable;\nimport org.testcontainers.r2dbc.R2DBCDatabaseContainer;\nimport org.testcontainers.r2dbc.R2DBCDatabaseContainerProvider;\n\npublic class OracleR2DBCDatabaseContainerProvider implements R2DBCDatabaseContainerProvider {\n\n    static final String DRIVER = \"oracle\";\n\n    @Override\n    public boolean supports(ConnectionFactoryOptions options) {\n        return DRIVER.equals(options.getRequiredValue(ConnectionFactoryOptions.DRIVER));\n    }\n\n    @Override\n    public R2DBCDatabaseContainer createContainer(ConnectionFactoryOptions options) {\n        String image = OracleContainer.IMAGE + \":\" + options.getRequiredValue(IMAGE_TAG_OPTION);\n        OracleContainer container = new OracleContainer(image)\n            .withDatabaseName((String) options.getRequiredValue(ConnectionFactoryOptions.DATABASE));\n        if (Boolean.TRUE.equals(options.getValue(REUSABLE_OPTION))) {\n            container.withReuse(true);\n        }\n        return new OracleR2DBCDatabaseContainer(container);\n    }\n\n    @Nullable\n    @Override\n    public ConnectionFactoryMetadata getMetadata(ConnectionFactoryOptions options) {\n        ConnectionFactoryOptions.Builder builder = options.mutate();\n        if (!options.hasOption(ConnectionFactoryOptions.USER)) {\n            builder.option(ConnectionFactoryOptions.USER, OracleContainer.APP_USER);\n        }\n        if (!options.hasOption(ConnectionFactoryOptions.PASSWORD)) {\n            builder.option(ConnectionFactoryOptions.PASSWORD, OracleContainer.APP_USER_PASSWORD);\n        }\n        return R2DBCDatabaseContainerProvider.super.getMetadata(builder.build());\n    }\n}\n"
  },
  {
    "path": "modules/oracle-xe/src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider",
    "content": "org.testcontainers.containers.OracleContainerProvider"
  },
  {
    "path": "modules/oracle-xe/src/main/resources/META-INF/services/org.testcontainers.r2dbc.R2DBCDatabaseContainerProvider",
    "content": "org.testcontainers.containers.OracleR2DBCDatabaseContainerProvider\n"
  },
  {
    "path": "modules/oracle-xe/src/test/java/org/testcontainers/containers/jdbc/OracleJDBCDriverTest.java",
    "content": "package org.testcontainers.containers.jdbc;\n\nimport com.zaxxer.hikari.HikariConfig;\nimport com.zaxxer.hikari.HikariDataSource;\nimport org.apache.commons.dbutils.QueryRunner;\nimport org.apache.commons.dbutils.ResultSetHandler;\nimport org.junit.jupiter.api.Test;\n\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass OracleJDBCDriverTest {\n\n    @Test\n    void testOracleWithNoSpecifiedVersion() throws SQLException {\n        performSimpleTest(\"jdbc:tc:oracle://hostname/databasename\");\n    }\n\n    private void performSimpleTest(String jdbcUrl) throws SQLException {\n        HikariDataSource dataSource = getDataSource(jdbcUrl, 1);\n        new QueryRunner(dataSource)\n            .query(\n                \"SELECT 1 FROM dual\",\n                new ResultSetHandler<Object>() {\n                    @Override\n                    public Object handle(ResultSet rs) throws SQLException {\n                        rs.next();\n                        int resultSetInt = rs.getInt(1);\n                        assertThat(resultSetInt).as(\"A basic SELECT query succeeds\").isEqualTo(1);\n                        return true;\n                    }\n                }\n            );\n        dataSource.close();\n    }\n\n    private HikariDataSource getDataSource(String jdbcUrl, int poolSize) {\n        HikariConfig hikariConfig = new HikariConfig();\n        hikariConfig.setJdbcUrl(jdbcUrl);\n        hikariConfig.setConnectionTestQuery(\"SELECT 1 FROM dual\");\n        hikariConfig.setMinimumIdle(1);\n        hikariConfig.setMaximumPoolSize(poolSize);\n\n        return new HikariDataSource(hikariConfig);\n    }\n}\n"
  },
  {
    "path": "modules/oracle-xe/src/test/java/org/testcontainers/containers/r2dbc/OracleR2DBCDatabaseContainerTest.java",
    "content": "package org.testcontainers.containers.r2dbc;\n\nimport io.r2dbc.spi.ConnectionFactoryOptions;\nimport org.testcontainers.containers.OracleContainer;\nimport org.testcontainers.containers.OracleR2DBCDatabaseContainer;\nimport org.testcontainers.r2dbc.AbstractR2DBCDatabaseContainerTest;\n\npublic class OracleR2DBCDatabaseContainerTest extends AbstractR2DBCDatabaseContainerTest<OracleContainer> {\n\n    @Override\n    protected OracleContainer createContainer() {\n        return new OracleContainer(\"gvenzl/oracle-xe:21-slim-faststart\");\n    }\n\n    @Override\n    protected ConnectionFactoryOptions getOptions(OracleContainer container) {\n        ConnectionFactoryOptions options = OracleR2DBCDatabaseContainer.getOptions(container);\n\n        return options;\n    }\n\n    protected String createR2DBCUrl() {\n        return \"r2dbc:tc:oracle:///db?TC_IMAGE_TAG=21-slim-faststart\";\n    }\n\n    @Override\n    protected String query() {\n        return \"SELECT %s from dual\";\n    }\n}\n"
  },
  {
    "path": "modules/oracle-xe/src/test/java/org/testcontainers/junit/oracle/SimpleOracleTest.java",
    "content": "package org.testcontainers.junit.oracle;\n\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.OracleContainer;\nimport org.testcontainers.db.AbstractContainerDatabaseTest;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.fail;\n\nclass SimpleOracleTest extends AbstractContainerDatabaseTest {\n\n    public static final DockerImageName ORACLE_DOCKER_IMAGE_NAME = DockerImageName.parse(\n        \"gvenzl/oracle-xe:21-slim-faststart\"\n    );\n\n    private void runTest(OracleContainer container, String databaseName, String username, String password)\n        throws SQLException {\n        //Test config was honored\n        assertThat(container.getDatabaseName()).isEqualTo(databaseName);\n        assertThat(container.getUsername()).isEqualTo(username);\n        assertThat(container.getPassword()).isEqualTo(password);\n\n        //Test we can get a connection\n        container.start();\n        ResultSet resultSet = performQuery(container, \"SELECT 1 FROM dual\");\n        int resultSetInt = resultSet.getInt(1);\n        assertThat(resultSetInt).as(\"A basic SELECT query succeeds\").isEqualTo(1);\n    }\n\n    @Test\n    void testDefaultSettings() throws SQLException {\n        try ( // container {\n            OracleContainer oracle = new OracleContainer(\"gvenzl/oracle-xe:21-slim-faststart\")\n            // }\n        ) {\n            runTest(oracle, \"xepdb1\", \"test\", \"test\");\n\n            // Match against the last '/'\n            String urlSuffix = oracle.getJdbcUrl().split(\"(\\\\/)(?!.*\\\\/)\", 2)[1];\n            assertThat(urlSuffix).isEqualTo(\"xepdb1\");\n        }\n    }\n\n    @Test\n    void testPluggableDatabase() throws SQLException {\n        try (OracleContainer oracle = new OracleContainer(ORACLE_DOCKER_IMAGE_NAME).withDatabaseName(\"testDB\")) {\n            runTest(oracle, \"testDB\", \"test\", \"test\");\n        }\n    }\n\n    @Test\n    void testPluggableDatabaseAndCustomUser() throws SQLException {\n        try (\n            OracleContainer oracle = new OracleContainer(\"gvenzl/oracle-xe:21-slim-faststart\")\n                .withDatabaseName(\"testDB\")\n                .withUsername(\"testUser\")\n                .withPassword(\"testPassword\")\n        ) {\n            runTest(oracle, \"testDB\", \"testUser\", \"testPassword\");\n        }\n    }\n\n    @Test\n    void testCustomUser() throws SQLException {\n        try (\n            OracleContainer oracle = new OracleContainer(ORACLE_DOCKER_IMAGE_NAME)\n                .withUsername(\"testUser\")\n                .withPassword(\"testPassword\")\n        ) {\n            runTest(oracle, \"xepdb1\", \"testUser\", \"testPassword\");\n        }\n    }\n\n    @Test\n    void testSID() throws SQLException {\n        try (OracleContainer oracle = new OracleContainer(ORACLE_DOCKER_IMAGE_NAME).usingSid();) {\n            runTest(oracle, \"xepdb1\", \"system\", \"test\");\n\n            // Match against the last ':'\n            String urlSuffix = oracle.getJdbcUrl().split(\"(\\\\:)(?!.*\\\\:)\", 2)[1];\n            assertThat(urlSuffix).isEqualTo(\"xe\");\n        }\n    }\n\n    @Test\n    void testSIDAndCustomPassword() throws SQLException {\n        try (\n            OracleContainer oracle = new OracleContainer(ORACLE_DOCKER_IMAGE_NAME)\n                .usingSid()\n                .withPassword(\"testPassword\");\n        ) {\n            runTest(oracle, \"xepdb1\", \"system\", \"testPassword\");\n        }\n    }\n\n    @Test\n    void testErrorPaths() throws SQLException {\n        try (OracleContainer oracle = new OracleContainer(ORACLE_DOCKER_IMAGE_NAME)) {\n            try {\n                oracle.withDatabaseName(\"XEPDB1\");\n                fail(\"Should not have been able to set database name to xepdb1.\");\n            } catch (IllegalArgumentException e) {\n                //expected\n            }\n\n            try {\n                oracle.withDatabaseName(\"\");\n                fail(\"Should not have been able to set database name to nothing.\");\n            } catch (IllegalArgumentException e) {\n                //expected\n            }\n\n            try {\n                oracle.withUsername(\"SYSTEM\");\n                fail(\"Should not have been able to set username to system.\");\n            } catch (IllegalArgumentException e) {\n                //expected\n            }\n\n            try {\n                oracle.withUsername(\"SYS\");\n                fail(\"Should not have been able to set username to sys.\");\n            } catch (IllegalArgumentException e) {\n                //expected\n            }\n\n            try {\n                oracle.withUsername(\"\");\n                fail(\"Should not have been able to set username to nothing.\");\n            } catch (IllegalArgumentException e) {\n                //expected\n            }\n\n            try {\n                oracle.withPassword(\"\");\n                fail(\"Should not have been able to set password to nothing.\");\n            } catch (IllegalArgumentException e) {\n                //expected\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "modules/oracle-xe/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/orientdb/build.gradle",
    "content": "description = \"Testcontainers :: Orientdb\"\n\ndependencies {\n    api project(\":testcontainers\")\n\n    api \"com.orientechnologies:orientdb-client:3.2.46\"\n\n    testImplementation 'org.apache.tinkerpop:gremlin-driver:3.8.0'\n    testImplementation \"com.orientechnologies:orientdb-gremlin:3.2.46\"\n}\n"
  },
  {
    "path": "modules/orientdb/src/main/java/org/testcontainers/containers/OrientDBContainer.java",
    "content": "package org.testcontainers.containers;\n\nimport com.github.dockerjava.api.command.InspectContainerResponse;\nimport com.orientechnologies.orient.core.db.ODatabaseSession;\nimport com.orientechnologies.orient.core.db.ODatabaseType;\nimport com.orientechnologies.orient.core.db.OrientDB;\nimport com.orientechnologies.orient.core.db.OrientDBConfig;\nimport lombok.NonNull;\nimport org.apache.commons.io.IOUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.utility.ComparableVersion;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.IOException;\nimport java.net.URL;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Arrays;\nimport java.util.Optional;\n\n/**\n * Testcontainers implementation for OrientDB.\n * <p>\n * Supported image: {@code orientdb}\n * <p>\n * Exposed ports:\n * <ul>\n *     <li>Database: 2424</li>\n *     <li>Studio: 2480</li>\n * </ul>\n *\n * @deprecated use {@link org.testcontainers.orientdb.OrientDBContainer} instead.\n */\n@Deprecated\npublic class OrientDBContainer extends GenericContainer<OrientDBContainer> {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(OrientDBContainer.class);\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"orientdb\");\n\n    private static final String DEFAULT_TAG = \"3.0.24-tp3\";\n\n    private static final String DEFAULT_USERNAME = \"admin\";\n\n    private static final String DEFAULT_PASSWORD = \"admin\";\n\n    private static final String DEFAULT_SERVER_PASSWORD = \"root\";\n\n    private static final String DEFAULT_DATABASE_NAME = \"testcontainers\";\n\n    private static final int DEFAULT_BINARY_PORT = 2424;\n\n    private static final int DEFAULT_HTTP_PORT = 2480;\n\n    private String databaseName;\n\n    private String serverPassword;\n\n    private Optional<String> scriptPath = Optional.empty();\n\n    private OrientDB orientDB;\n\n    private ODatabaseSession session;\n\n    /**\n     * @deprecated use {@link #OrientDBContainer(DockerImageName)} instead\n     */\n    @Deprecated\n    public OrientDBContainer() {\n        this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG));\n    }\n\n    public OrientDBContainer(@NonNull String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    public OrientDBContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n\n        serverPassword = DEFAULT_SERVER_PASSWORD;\n        databaseName = DEFAULT_DATABASE_NAME;\n\n        waitStrategy = Wait.forLogMessage(\".*OrientDB Studio available.*\", 1);\n\n        addExposedPorts(DEFAULT_BINARY_PORT, DEFAULT_HTTP_PORT);\n    }\n\n    @Override\n    protected void configure() {\n        addEnv(\"ORIENTDB_ROOT_PASSWORD\", serverPassword);\n    }\n\n    public String getDatabaseName() {\n        return databaseName;\n    }\n\n    public String getTestQueryString() {\n        return \"SELECT FROM V\";\n    }\n\n    public OrientDBContainer withDatabaseName(final String databaseName) {\n        this.databaseName = databaseName;\n        return self();\n    }\n\n    public OrientDBContainer withServerPassword(final String serverPassword) {\n        this.serverPassword = serverPassword;\n        return self();\n    }\n\n    public OrientDBContainer withScriptPath(String scriptPath) {\n        this.scriptPath = Optional.of(scriptPath);\n        return self();\n    }\n\n    @Override\n    protected void containerIsStarted(InspectContainerResponse containerInfo) {\n        orientDB = new OrientDB(getServerUrl(), \"root\", serverPassword, OrientDBConfig.defaultConfig());\n    }\n\n    @Deprecated\n    public OrientDB getOrientDB() {\n        return orientDB;\n    }\n\n    public String getServerUrl() {\n        return \"remote:\" + getHost() + \":\" + getMappedPort(2424);\n    }\n\n    public String getDbUrl() {\n        return getServerUrl() + \"/\" + databaseName;\n    }\n\n    @Deprecated\n    public ODatabaseSession getSession() {\n        return getSession(DEFAULT_USERNAME, DEFAULT_PASSWORD);\n    }\n\n    @Deprecated\n    public synchronized ODatabaseSession getSession(String username, String password) {\n        String orientdbVersion = Arrays\n            .stream(this.getContainerInfo().getConfig().getEnv())\n            .filter(env -> env.startsWith(\"ORIENTDB_VERSION\"))\n            .map(env -> env.split(\"=\")[1])\n            .findFirst()\n            .orElseThrow(() -> new IllegalStateException(\"no required env var\"));\n        boolean isGreaterThan32 = new ComparableVersion(orientdbVersion).isGreaterThanOrEqualTo(\"3.2.0\");\n        if (isGreaterThan32) {\n            String script = String.format(\n                \"CREATE DATABASE %s plocal users(%s identified by '%s' role admin)\",\n                databaseName,\n                username,\n                password\n            );\n            if (!orientDB.exists(databaseName)) {\n                orientDB.execute(script);\n            }\n        } else {\n            orientDB.createIfNotExists(databaseName, ODatabaseType.PLOCAL);\n        }\n        if (session == null) {\n            session = orientDB.open(databaseName, username, password);\n\n            scriptPath.ifPresent(path -> loadScript(path, session));\n        }\n        return session;\n    }\n\n    @Deprecated\n    private void loadScript(String path, ODatabaseSession session) {\n        try {\n            URL resource = getClass().getClassLoader().getResource(path);\n\n            if (resource == null) {\n                LOGGER.warn(\"Could not load classpath init script: {}\", scriptPath);\n                throw new RuntimeException(\n                    \"Could not load classpath init script: \" + scriptPath + \". Resource not found.\"\n                );\n            }\n\n            String script = IOUtils.toString(resource, StandardCharsets.UTF_8);\n\n            session.execute(\"sql\", script);\n        } catch (IOException e) {\n            LOGGER.warn(\"Could not load classpath init script: {}\", scriptPath);\n            throw new RuntimeException(\"Could not load classpath init script: \" + scriptPath, e);\n        } catch (UnsupportedOperationException e) {\n            LOGGER.error(\"Error while executing init script: {}\", scriptPath, e);\n            throw new RuntimeException(\"Error while executing init script: \" + scriptPath, e);\n        }\n    }\n}\n"
  },
  {
    "path": "modules/orientdb/src/main/java/org/testcontainers/orientdb/OrientDBContainer.java",
    "content": "package org.testcontainers.orientdb;\n\nimport com.github.dockerjava.api.command.InspectContainerResponse;\nimport lombok.NonNull;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.images.builder.Transferable;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.IOException;\n\n/**\n * Testcontainers implementation for OrientDB.\n * <p>\n * Supported image: {@code orientdb}\n * <p>\n * Exposed ports:\n * <ul>\n *     <li>Database: 2424</li>\n *     <li>Studio: 2480</li>\n * </ul>\n */\npublic class OrientDBContainer extends GenericContainer<OrientDBContainer> {\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"orientdb\");\n\n    private static final String DEFAULT_USERNAME = \"admin\";\n\n    private static final String DEFAULT_PASSWORD = \"admin\";\n\n    private static final String DEFAULT_SERVER_USER = \"root\";\n\n    private static final String DEFAULT_SERVER_PASSWORD = \"root\";\n\n    private static final String DEFAULT_DATABASE_NAME = \"testcontainers\";\n\n    private static final int DEFAULT_BINARY_PORT = 2424;\n\n    private static final int DEFAULT_HTTP_PORT = 2480;\n\n    private String databaseName;\n\n    private String serverPassword;\n\n    private Transferable scriptPath;\n\n    public OrientDBContainer(@NonNull String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    public OrientDBContainer(DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n\n        this.serverPassword = DEFAULT_SERVER_PASSWORD;\n        this.databaseName = DEFAULT_DATABASE_NAME;\n\n        waitingFor(Wait.forLogMessage(\".*OrientDB Studio available.*\", 1));\n        addExposedPorts(DEFAULT_BINARY_PORT, DEFAULT_HTTP_PORT);\n    }\n\n    @Override\n    protected void configure() {\n        addEnv(\"ORIENTDB_ROOT_PASSWORD\", serverPassword);\n    }\n\n    @Override\n    protected void containerIsStarted(InspectContainerResponse containerInfo) {\n        try {\n            String createDb = String.format(\n                \"CREATE DATABASE remote:localhost/%s %s %s plocal; CONNECT remote:localhost/%s %s %s; CREATE USER %s IDENTIFIED BY %s ROLE admin;\",\n                this.databaseName,\n                DEFAULT_SERVER_USER,\n                this.serverPassword,\n                this.databaseName,\n                DEFAULT_SERVER_USER,\n                this.serverPassword,\n                DEFAULT_USERNAME,\n                DEFAULT_PASSWORD\n            );\n            execInContainer(\"/orientdb/bin/console.sh\", createDb);\n\n            if (this.scriptPath != null) {\n                copyFileToContainer(this.scriptPath, \"/opt/testcontainers/script.osql\");\n                String loadScript = String.format(\n                    \"CONNECT remote:localhost/%s %s %s; LOAD SCRIPT /opt/testcontainers/script.osql\",\n                    this.databaseName,\n                    DEFAULT_SERVER_USER,\n                    this.serverPassword\n                );\n                execInContainer(\"/orientdb/bin/console.sh\", loadScript);\n            }\n        } catch (IOException | InterruptedException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    public String getDatabaseName() {\n        return databaseName;\n    }\n\n    public OrientDBContainer withDatabaseName(final String databaseName) {\n        this.databaseName = databaseName;\n        return self();\n    }\n\n    public OrientDBContainer withServerPassword(final String serverPassword) {\n        this.serverPassword = serverPassword;\n        return self();\n    }\n\n    public OrientDBContainer withScriptPath(Transferable scriptPath) {\n        this.scriptPath = scriptPath;\n        return self();\n    }\n\n    public String getServerUrl() {\n        return \"remote:\" + getHost() + \":\" + getMappedPort(2424);\n    }\n\n    public String getDbUrl() {\n        return getServerUrl() + \"/\" + this.databaseName;\n    }\n\n    public String getServerUser() {\n        return DEFAULT_SERVER_USER;\n    }\n\n    public String getServerPassword() {\n        return this.serverPassword;\n    }\n\n    public String getUsername() {\n        return DEFAULT_USERNAME;\n    }\n\n    public String getPassword() {\n        return DEFAULT_PASSWORD;\n    }\n}\n"
  },
  {
    "path": "modules/orientdb/src/test/java/org/testcontainers/orientdb/OrientDBContainerTest.java",
    "content": "package org.testcontainers.orientdb;\n\nimport com.orientechnologies.orient.core.db.ODatabaseSession;\nimport com.orientechnologies.orient.core.db.OrientDB;\nimport com.orientechnologies.orient.core.db.OrientDBConfig;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.MountableFile;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass OrientDBContainerTest {\n\n    private static final DockerImageName ORIENTDB_IMAGE = DockerImageName.parse(\"orientdb:3.2.0-tp3\");\n\n    @Test\n    void shouldInitializeWithCommands() {\n        try ( // container {\n            OrientDBContainer orientdb = new OrientDBContainer(\"orientdb:3.2.0-tp3\")\n            // }\n        ) {\n            orientdb.start();\n\n            OrientDB orientDB = new OrientDB(\n                orientdb.getServerUrl(),\n                orientdb.getServerUser(),\n                orientdb.getServerPassword(),\n                OrientDBConfig.defaultConfig()\n            );\n            ODatabaseSession session = orientDB.open(\n                orientdb.getDatabaseName(),\n                orientdb.getUsername(),\n                orientdb.getPassword()\n            );\n\n            session.command(\"CREATE CLASS Person EXTENDS V\");\n            session.command(\"INSERT INTO Person set name='john'\");\n            session.command(\"INSERT INTO Person set name='jane'\");\n\n            assertThat(session.query(\"SELECT FROM Person\").stream()).hasSize(2);\n        }\n    }\n\n    @Test\n    void shouldQueryWithGremlin() {\n        try (\n            OrientDBContainer orientdb = new OrientDBContainer(ORIENTDB_IMAGE)\n                .withCopyFileToContainer(\n                    MountableFile.forClasspathResource(\"orientdb-server-config.xml\"),\n                    \"/orientdb/config/orientdb-server-config.xml\"\n                )\n        ) {\n            orientdb.start();\n\n            OrientDB orientDB = new OrientDB(\n                orientdb.getServerUrl(),\n                orientdb.getServerUser(),\n                orientdb.getServerPassword(),\n                OrientDBConfig.defaultConfig()\n            );\n            ODatabaseSession session = orientDB.open(\n                orientdb.getDatabaseName(),\n                orientdb.getUsername(),\n                orientdb.getPassword()\n            );\n\n            session.command(\"CREATE CLASS Person EXTENDS V\");\n            session.command(\"INSERT INTO Person set name='john'\");\n            session.command(\"INSERT INTO Person set name='jane'\");\n\n            assertThat(session.execute(\"gremlin\", \"g.V().hasLabel('Person')\").stream()).hasSize(2);\n        }\n    }\n\n    @Test\n    void shouldInitializeDatabaseFromScript() {\n        try (\n            OrientDBContainer orientdb = new OrientDBContainer(ORIENTDB_IMAGE)\n                .withScriptPath(MountableFile.forClasspathResource(\"initscript.osql\"))\n                .withDatabaseName(\"persons\")\n        ) {\n            orientdb.start();\n\n            assertThat(orientdb.getDbUrl())\n                .isEqualTo(\"remote:\" + orientdb.getHost() + \":\" + orientdb.getMappedPort(2424) + \"/persons\");\n\n            OrientDB orientDB = new OrientDB(\n                orientdb.getServerUrl(),\n                orientdb.getServerUser(),\n                orientdb.getServerPassword(),\n                OrientDBConfig.defaultConfig()\n            );\n            ODatabaseSession session = orientDB.open(\n                orientdb.getDatabaseName(),\n                orientdb.getUsername(),\n                orientdb.getPassword()\n            );\n\n            assertThat(session.query(\"SELECT FROM Person\").stream()).hasSize(4);\n        }\n    }\n}\n"
  },
  {
    "path": "modules/orientdb/src/test/resources/initscript.osql",
    "content": "CREATE CLASS Person EXTENDS V;\n\nINSERT INTO Person set name=\"john\";\nINSERT INTO Person set name=\"paul\";\nINSERT INTO Person set name=\"luke\";\nINSERT INTO Person set name=\"albert\";\n"
  },
  {
    "path": "modules/orientdb/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n    <logger name=\"org.testcontainers.shaded\" level=\"WARN\"/>\n\n</configuration>\n"
  },
  {
    "path": "modules/orientdb/src/test/resources/orientdb-server-config.xml",
    "content": "<orient-server>\n    <handlers>\n        <!-- DISABLED-->\n        <!--<handler class=\"com.orientechnologies.orient.graph.handler.OGraphServerHandler\">-->\n        <!--<parameters>-->\n        <!--<parameter name=\"enabled\" value=\"true\"/>-->\n        <!--<parameter name=\"graph.pool.max\" value=\"50\"/>-->\n        <!--</parameters>-->\n        <!--</handler>-->\n        <!-- CLUSTER PLUGIN, TO TURN ON SET THE 'ENABLED' PARAMETER TO 'true' -->\n        <handler class=\"com.orientechnologies.orient.server.hazelcast.OHazelcastPlugin\">\n            <parameters>\n                <!-- <parameter name=\"nodeName\" value=\"europe1\" /> -->\n                <parameter name=\"enabled\" value=\"${distributed}\"/>\n                <parameter name=\"configuration.db.default\"\n                           value=\"${ORIENTDB_HOME}/config/default-distributed-db-config.json\"/>\n                <parameter name=\"configuration.hazelcast\" value=\"${ORIENTDB_HOME}/config/hazelcast.xml\"/>\n            </parameters>\n        </handler>\n        <!-- JMX SERVER, TO TURN ON SET THE 'ENABLED' PARAMETER TO 'true' -->\n        <handler class=\"com.orientechnologies.orient.server.handler.OJMXPlugin\">\n            <parameters>\n                <parameter name=\"enabled\" value=\"false\"/>\n                <parameter name=\"profilerManaged\" value=\"true\"/>\n            </parameters>\n        </handler>\n        <!-- AUTOMATIC BACKUP, TO TURN ON SET THE 'ENABLED' PARAMETER TO 'true' -->\n        <handler class=\"com.orientechnologies.orient.server.handler.OAutomaticBackup\">\n            <parameters>\n                <parameter name=\"enabled\" value=\"false\"/>\n                <!-- LOCATION OF JSON CONFIGURATION FILE -->\n                <parameter name=\"config\" value=\"${ORIENTDB_HOME}/config/automatic-backup.json\"/>\n            </parameters>\n        </handler>\n        <!-- SERVER SIDE SCRIPT INTERPRETER. WARNING, THIS CAN BE A SECURITY HOLE BECAUSE MALICIOUS CODE COULD BE INJECTED.\n            ENABLE IT ONLY IF CLIENTS ARE TRUSTED, TO TURN ON SET THE 'ENABLED' PARAMETER TO 'true' -->\n        <handler\n            class=\"com.orientechnologies.orient.server.handler.OServerSideScriptInterpreter\">\n            <parameters>\n                <parameter name=\"enabled\" value=\"true\"/>\n                <parameter name=\"allowedLanguages\" value=\"SQL,GREMLIN\"/>\n                <!--  Comma separated packages  allowed in JS scripts eg. java.math.*, java.util.ArrayList -->\n                <parameter name=\"allowedPackages\" value=\"\"/>\n            </parameters>\n        </handler>\n        <!-- CUSTOM SQL FUNCTIONS -->\n        <handler class=\"com.orientechnologies.orient.server.handler.OCustomSQLFunctionPlugin\">\n            <parameters>\n                <!-- LOCATION OF JSON CONFIGURATION FILE -->\n                <parameter name=\"config\" value=\"${ORIENTDB_HOME}/config/custom-sql-functions.json\"/>\n            </parameters>\n        </handler>\n\n    </handlers>\n    <network>\n        <sockets>\n            <socket implementation=\"com.orientechnologies.orient.server.network.OServerTLSSocketFactory\" name=\"ssl\">\n                <parameters>\n                    <parameter value=\"false\" name=\"network.ssl.clientAuth\"/>\n                    <parameter value=\"config/cert/orientdb.ks\" name=\"network.ssl.keyStore\"/>\n                    <parameter value=\"password\" name=\"network.ssl.keyStorePassword\"/>\n                    <parameter value=\"config/cert/orientdb.ks\" name=\"network.ssl.trustStore\"/>\n                    <parameter value=\"password\" name=\"network.ssl.trustStorePassword\"/>\n                </parameters>\n            </socket>\n            <socket implementation=\"com.orientechnologies.orient.server.network.OServerTLSSocketFactory\" name=\"https\">\n                <parameters>\n                    <parameter value=\"false\" name=\"network.ssl.clientAuth\"/>\n                    <parameter value=\"config/cert/orientdb.ks\" name=\"network.ssl.keyStore\"/>\n                    <parameter value=\"password\" name=\"network.ssl.keyStorePassword\"/>\n                    <parameter value=\"config/cert/orientdb.ks\" name=\"network.ssl.trustStore\"/>\n                    <parameter value=\"password\" name=\"network.ssl.trustStorePassword\"/>\n                </parameters>\n            </socket>\n        </sockets>\n        <protocols>\n            <!-- Default registered protocol. It reads commands using the HTTP protocol\n                and write data locally -->\n            <protocol name=\"binary\"\n                      implementation=\"com.orientechnologies.orient.server.network.protocol.binary.ONetworkProtocolBinary\"/>\n            <protocol name=\"http\"\n                      implementation=\"com.orientechnologies.orient.server.network.protocol.http.ONetworkProtocolHttpDb\"/>\n        </protocols>\n        <listeners>\n            <listener protocol=\"binary\" ip-address=\"0.0.0.0\" port-range=\"2424-2430\" socket=\"default\"/>\n            <listener protocol=\"http\" ip-address=\"0.0.0.0\" port-range=\"2480-2490\" socket=\"default\">\n                <parameters>\n                    <!-- Connection's custom parameters. If not specified the global configuration\n                        will be taken -->\n                    <parameter name=\"network.http.charset\" value=\"utf-8\"/>\n                    <parameter value=\"true\" name=\"network.http.jsonResponseError\"/>\n                    <parameter value=\"Content-Security-Policy: frame-ancestors 'none'\" name=\"network.http.additionalResponseHeaders\"></parameter>\n                    <!-- Define additional HTTP headers to always send as response -->\n                    <!-- Allow cross-site scripting -->\n                    <!-- parameter name=\"network.http.additionalResponseHeaders\" value=\"Access-Control-Allow-Origin:\n                        *;Access-Control-Allow-Credentials: true\" / -->\n                </parameters>\n                <commands>\n                    <command\n                        pattern=\"GET|www GET|studio/ GET| GET|*.htm GET|*.html GET|*.xml GET|*.jpeg GET|*.jpg GET|*.png GET|*.gif GET|*.js GET|*.css GET|*.swf GET|*.ico GET|*.txt GET|*.otf GET|*.pjs GET|*.svg GET|*.json GET|*.woff GET|*.woff2 GET|*.ttf GET|*.svgz\"\n                        implementation=\"com.orientechnologies.orient.server.network.protocol.http.command.get.OServerCommandGetStaticContent\">\n                        <parameters>\n                            <!-- Don't cache html resources in development mode -->\n                            <entry name=\"http.cache:*.htm *.html\"\n                                   value=\"Cache-Control: no-cache, no-store, max-age=0, must-revalidate\\r\\nPragma: no-cache\"/>\n                            <!-- Default caching -->\n                            <entry name=\"http.cache:default\" value=\"Cache-Control: max-age=120\"/>\n                        </parameters>\n                    </command>\n                    <command pattern=\"GET|gephi/*\"\n                             implementation=\"com.orientechnologies.orient.server.network.protocol.http.command.get.OServerCommandGetGephi\"/>\n\n                </commands>\n            </listener>\n        </listeners>\n        <cluster>\n        </cluster>\n    </network>\n    <storages>\n    </storages>\n    <users>\n    </users>\n    <properties>\n        <!-- PROFILER: configures the profiler as <seconds-for-snapshot>,<archive-snapshot-size>,<summary-size> -->\n        <entry name=\"profiler.enabled\" value=\"false\"/>\n        <!-- <entry name=\"profiler.config\" value=\"30,10,10\" /> -->\n    </properties>\n</orient-server>\n"
  },
  {
    "path": "modules/pinecone/build.gradle",
    "content": "description = \"Testcontainers :: Pinecone\"\n\ndependencies {\n    api project(':testcontainers')\n\n    testImplementation 'io.pinecone:pinecone-client:3.1.0'\n}\n"
  },
  {
    "path": "modules/pinecone/src/main/java/org/testcontainers/pinecone/PineconeLocalContainer.java",
    "content": "package org.testcontainers.pinecone;\n\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.utility.DockerImageName;\n\n/**\n * Testcontainers implementation for Pinecone.\n * <p>\n * Exposed port: 5080\n */\npublic class PineconeLocalContainer extends GenericContainer<PineconeLocalContainer> {\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\n        \"ghcr.io/pinecone-io/pinecone-local\"\n    );\n\n    private static final int PORT = 5080;\n\n    public PineconeLocalContainer(String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    public PineconeLocalContainer(DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n\n        withEnv(\"PORT\", String.valueOf(5080));\n        withExposedPorts(5080);\n    }\n\n    public String getEndpoint() {\n        return \"http://\" + getHost() + \":\" + getMappedPort(PORT);\n    }\n}\n"
  },
  {
    "path": "modules/pinecone/src/test/java/org/testcontainers/pinecone/PineconeLocalContainerTest.java",
    "content": "package org.testcontainers.pinecone;\n\nimport io.pinecone.clients.Pinecone;\nimport org.junit.jupiter.api.Test;\nimport org.openapitools.db_control.client.model.DeletionProtection;\nimport org.openapitools.db_control.client.model.IndexModel;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass PineconeLocalContainerTest {\n\n    @Test\n    void testSimple() {\n        try ( // container {\n            PineconeLocalContainer container = new PineconeLocalContainer(\"ghcr.io/pinecone-io/pinecone-local:v0.7.0\")\n            // }\n        ) {\n            container.start();\n\n            // client {\n            Pinecone pinecone = new Pinecone.Builder(\"pclocal\")\n                .withHost(container.getEndpoint())\n                .withTlsEnabled(false)\n                .build();\n            // }\n\n            String indexName = \"example-index\";\n            pinecone.createServerlessIndex(indexName, \"cosine\", 2, \"aws\", \"us-east-1\", DeletionProtection.DISABLED);\n            IndexModel indexModel = pinecone.describeIndex(indexName);\n            assertThat(indexModel.getDeletionProtection()).isEqualTo(DeletionProtection.DISABLED);\n        }\n    }\n}\n"
  },
  {
    "path": "modules/pinecone/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/postgresql/build.gradle",
    "content": "description = \"Testcontainers :: JDBC :: PostgreSQL\"\n\ndependencies {\n    api project(':testcontainers-jdbc')\n\n    compileOnly project(':testcontainers-r2dbc')\n    compileOnly 'io.r2dbc:r2dbc-postgresql:0.8.13.RELEASE'\n\n    testImplementation project(':testcontainers-jdbc-test')\n    testRuntimeOnly 'org.postgresql:postgresql:42.7.8'\n\n    testImplementation testFixtures(project(':testcontainers-r2dbc'))\n    testRuntimeOnly 'io.r2dbc:r2dbc-postgresql:0.8.13.RELEASE'\n\n    compileOnly 'org.jetbrains:annotations:26.0.2-1'\n}\n"
  },
  {
    "path": "modules/postgresql/src/main/java/org/testcontainers/containers/PgVectorContainerProvider.java",
    "content": "package org.testcontainers.containers;\n\nimport org.testcontainers.jdbc.ConnectionUrl;\nimport org.testcontainers.utility.DockerImageName;\n\n/**\n * Factory for PgVector containers.\n *\n * @see <a href=\"https://github.com/pgvector/pgvector\">https://github.com/pgvector/pgvector</a>\n */\npublic class PgVectorContainerProvider extends JdbcDatabaseContainerProvider {\n\n    private static final String NAME = \"pgvector\";\n\n    private static final String DEFAULT_TAG = \"pg16\";\n\n    private static final DockerImageName DEFAULT_IMAGE = DockerImageName.parse(\"pgvector/pgvector\");\n\n    public static final String USER_PARAM = \"user\";\n\n    public static final String PASSWORD_PARAM = \"password\";\n\n    @Override\n    public boolean supports(String databaseType) {\n        return databaseType.equals(NAME);\n    }\n\n    @Override\n    public JdbcDatabaseContainer newInstance() {\n        return newInstance(DEFAULT_TAG);\n    }\n\n    @Override\n    public JdbcDatabaseContainer newInstance(String tag) {\n        return new PostgreSQLContainer(DEFAULT_IMAGE.withTag(tag));\n    }\n\n    @Override\n    public JdbcDatabaseContainer newInstance(ConnectionUrl connectionUrl) {\n        return newInstanceFromConnectionUrl(connectionUrl, USER_PARAM, PASSWORD_PARAM);\n    }\n}\n"
  },
  {
    "path": "modules/postgresql/src/main/java/org/testcontainers/containers/PostgisContainerProvider.java",
    "content": "package org.testcontainers.containers;\n\nimport org.testcontainers.jdbc.ConnectionUrl;\nimport org.testcontainers.utility.DockerImageName;\n\n/**\n * Factory for PostGIS containers, which are a special flavour of PostgreSQL.\n */\npublic class PostgisContainerProvider extends JdbcDatabaseContainerProvider {\n\n    private static final String NAME = \"postgis\";\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName\n        .parse(\"postgis/postgis\")\n        .asCompatibleSubstituteFor(\"postgres\");\n\n    private static final String DEFAULT_TAG = \"12-3.0\";\n\n    public static final String USER_PARAM = \"user\";\n\n    public static final String PASSWORD_PARAM = \"password\";\n\n    @Override\n    public boolean supports(String databaseType) {\n        return databaseType.equals(NAME);\n    }\n\n    @Override\n    public JdbcDatabaseContainer newInstance() {\n        return newInstance(DEFAULT_TAG);\n    }\n\n    @Override\n    public JdbcDatabaseContainer newInstance(String tag) {\n        return new PostgreSQLContainer<>(DEFAULT_IMAGE_NAME.withTag(tag));\n    }\n\n    @Override\n    public JdbcDatabaseContainer newInstance(ConnectionUrl connectionUrl) {\n        return newInstanceFromConnectionUrl(connectionUrl, USER_PARAM, PASSWORD_PARAM);\n    }\n}\n"
  },
  {
    "path": "modules/postgresql/src/main/java/org/testcontainers/containers/PostgreSQLContainer.java",
    "content": "package org.testcontainers.containers;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.time.Duration;\nimport java.time.temporal.ChronoUnit;\nimport java.util.Set;\n\n/**\n * Testcontainers implementation for PostgreSQL.\n * <p>\n * Supported images: {@code postgres}, {@code pgvector/pgvector}\n * <p>\n * Exposed ports: 5432\n *\n * @deprecated use {@link org.testcontainers.postgresql.PostgreSQLContainer} instead.\n */\n@Deprecated\npublic class PostgreSQLContainer<SELF extends PostgreSQLContainer<SELF>> extends JdbcDatabaseContainer<SELF> {\n\n    public static final String NAME = \"postgresql\";\n\n    public static final String IMAGE = \"postgres\";\n\n    public static final String DEFAULT_TAG = \"9.6.12\";\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"postgres\");\n\n    private static final DockerImageName PGVECTOR_IMAGE_NAME = DockerImageName.parse(\"pgvector/pgvector\");\n\n    public static final Integer POSTGRESQL_PORT = 5432;\n\n    static final String DEFAULT_USER = \"test\";\n\n    static final String DEFAULT_PASSWORD = \"test\";\n\n    private String databaseName = \"test\";\n\n    private String username = \"test\";\n\n    private String password = \"test\";\n\n    private static final String FSYNC_OFF_OPTION = \"fsync=off\";\n\n    /**\n     * @deprecated use {@link #PostgreSQLContainer(DockerImageName)} or {@link #PostgreSQLContainer(String)} instead\n     */\n    @Deprecated\n    public PostgreSQLContainer() {\n        this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG));\n    }\n\n    public PostgreSQLContainer(final String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    public PostgreSQLContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME, PGVECTOR_IMAGE_NAME);\n\n        this.waitStrategy =\n            new LogMessageWaitStrategy()\n                .withRegEx(\".*database system is ready to accept connections.*\\\\s\")\n                .withTimes(2)\n                .withStartupTimeout(Duration.of(60, ChronoUnit.SECONDS));\n        this.setCommand(\"postgres\", \"-c\", FSYNC_OFF_OPTION);\n\n        addExposedPort(POSTGRESQL_PORT);\n    }\n\n    /**\n     * @return the ports on which to check if the container is ready\n     * @deprecated use {@link #getLivenessCheckPortNumbers()} instead\n     */\n    @NotNull\n    @Override\n    @Deprecated\n    protected Set<Integer> getLivenessCheckPorts() {\n        return super.getLivenessCheckPorts();\n    }\n\n    @Override\n    protected void configure() {\n        // Disable Postgres driver use of java.util.logging to reduce noise at startup time\n        withUrlParam(\"loggerLevel\", \"OFF\");\n        addEnv(\"POSTGRES_DB\", databaseName);\n        addEnv(\"POSTGRES_USER\", username);\n        addEnv(\"POSTGRES_PASSWORD\", password);\n    }\n\n    @Override\n    public String getDriverClassName() {\n        return \"org.postgresql.Driver\";\n    }\n\n    @Override\n    public String getJdbcUrl() {\n        String additionalUrlParams = constructUrlParameters(\"?\", \"&\");\n        return (\n            \"jdbc:postgresql://\" +\n            getHost() +\n            \":\" +\n            getMappedPort(POSTGRESQL_PORT) +\n            \"/\" +\n            databaseName +\n            additionalUrlParams\n        );\n    }\n\n    @Override\n    public String getDatabaseName() {\n        return databaseName;\n    }\n\n    @Override\n    public String getUsername() {\n        return username;\n    }\n\n    @Override\n    public String getPassword() {\n        return password;\n    }\n\n    @Override\n    public String getTestQueryString() {\n        return \"SELECT 1\";\n    }\n\n    @Override\n    public SELF withDatabaseName(final String databaseName) {\n        this.databaseName = databaseName;\n        return self();\n    }\n\n    @Override\n    public SELF withUsername(final String username) {\n        this.username = username;\n        return self();\n    }\n\n    @Override\n    public SELF withPassword(final String password) {\n        this.password = password;\n        return self();\n    }\n\n    @Override\n    protected void waitUntilContainerStarted() {\n        getWaitStrategy().waitUntilReady(this);\n    }\n}\n"
  },
  {
    "path": "modules/postgresql/src/main/java/org/testcontainers/containers/PostgreSQLContainerProvider.java",
    "content": "package org.testcontainers.containers;\n\nimport org.testcontainers.jdbc.ConnectionUrl;\nimport org.testcontainers.utility.DockerImageName;\n\n/**\n * Factory for PostgreSQL containers.\n */\npublic class PostgreSQLContainerProvider extends JdbcDatabaseContainerProvider {\n\n    public static final String USER_PARAM = \"user\";\n\n    public static final String PASSWORD_PARAM = \"password\";\n\n    @Override\n    public boolean supports(String databaseType) {\n        return databaseType.equals(PostgreSQLContainer.NAME);\n    }\n\n    @Override\n    public JdbcDatabaseContainer newInstance() {\n        return newInstance(PostgreSQLContainer.DEFAULT_TAG);\n    }\n\n    @Override\n    public JdbcDatabaseContainer newInstance(String tag) {\n        return new PostgreSQLContainer(DockerImageName.parse(PostgreSQLContainer.IMAGE).withTag(tag));\n    }\n\n    @Override\n    public JdbcDatabaseContainer newInstance(ConnectionUrl connectionUrl) {\n        return newInstanceFromConnectionUrl(connectionUrl, USER_PARAM, PASSWORD_PARAM);\n    }\n}\n"
  },
  {
    "path": "modules/postgresql/src/main/java/org/testcontainers/containers/PostgreSQLR2DBCDatabaseContainer.java",
    "content": "package org.testcontainers.containers;\n\nimport io.r2dbc.spi.ConnectionFactoryOptions;\nimport lombok.RequiredArgsConstructor;\nimport lombok.experimental.Delegate;\nimport org.testcontainers.lifecycle.Startable;\nimport org.testcontainers.r2dbc.R2DBCDatabaseContainer;\n\n@RequiredArgsConstructor\npublic final class PostgreSQLR2DBCDatabaseContainer implements R2DBCDatabaseContainer {\n\n    @Delegate(types = Startable.class)\n    private final PostgreSQLContainer<?> container;\n\n    public static ConnectionFactoryOptions getOptions(PostgreSQLContainer<?> container) {\n        ConnectionFactoryOptions options = ConnectionFactoryOptions\n            .builder()\n            .option(ConnectionFactoryOptions.DRIVER, PostgreSQLR2DBCDatabaseContainerProvider.DRIVER)\n            .build();\n\n        return new PostgreSQLR2DBCDatabaseContainer(container).configure(options);\n    }\n\n    @Override\n    public ConnectionFactoryOptions configure(ConnectionFactoryOptions options) {\n        return options\n            .mutate()\n            .option(ConnectionFactoryOptions.HOST, container.getHost())\n            .option(ConnectionFactoryOptions.PORT, container.getMappedPort(PostgreSQLContainer.POSTGRESQL_PORT))\n            .option(ConnectionFactoryOptions.DATABASE, container.getDatabaseName())\n            .option(ConnectionFactoryOptions.USER, container.getUsername())\n            .option(ConnectionFactoryOptions.PASSWORD, container.getPassword())\n            .build();\n    }\n}\n"
  },
  {
    "path": "modules/postgresql/src/main/java/org/testcontainers/containers/PostgreSQLR2DBCDatabaseContainerProvider.java",
    "content": "package org.testcontainers.containers;\n\nimport io.r2dbc.postgresql.PostgresqlConnectionFactoryProvider;\nimport io.r2dbc.spi.ConnectionFactoryMetadata;\nimport io.r2dbc.spi.ConnectionFactoryOptions;\nimport org.testcontainers.r2dbc.R2DBCDatabaseContainer;\nimport org.testcontainers.r2dbc.R2DBCDatabaseContainerProvider;\n\nimport javax.annotation.Nullable;\n\npublic final class PostgreSQLR2DBCDatabaseContainerProvider implements R2DBCDatabaseContainerProvider {\n\n    static final String DRIVER = PostgresqlConnectionFactoryProvider.POSTGRESQL_DRIVER;\n\n    @Override\n    public boolean supports(ConnectionFactoryOptions options) {\n        return DRIVER.equals(options.getRequiredValue(ConnectionFactoryOptions.DRIVER));\n    }\n\n    @Override\n    public R2DBCDatabaseContainer createContainer(ConnectionFactoryOptions options) {\n        String image = PostgreSQLContainer.IMAGE + \":\" + options.getRequiredValue(IMAGE_TAG_OPTION);\n        PostgreSQLContainer<?> container = new PostgreSQLContainer<>(image)\n            .withDatabaseName((String) options.getRequiredValue(ConnectionFactoryOptions.DATABASE));\n\n        if (Boolean.TRUE.equals(options.getValue(REUSABLE_OPTION))) {\n            container.withReuse(true);\n        }\n        return new PostgreSQLR2DBCDatabaseContainer(container);\n    }\n\n    @Nullable\n    @Override\n    public ConnectionFactoryMetadata getMetadata(ConnectionFactoryOptions options) {\n        ConnectionFactoryOptions.Builder builder = options.mutate();\n        if (!options.hasOption(ConnectionFactoryOptions.USER)) {\n            builder.option(ConnectionFactoryOptions.USER, PostgreSQLContainer.DEFAULT_USER);\n        }\n        if (!options.hasOption(ConnectionFactoryOptions.PASSWORD)) {\n            builder.option(ConnectionFactoryOptions.PASSWORD, PostgreSQLContainer.DEFAULT_PASSWORD);\n        }\n        return R2DBCDatabaseContainerProvider.super.getMetadata(builder.build());\n    }\n}\n"
  },
  {
    "path": "modules/postgresql/src/main/java/org/testcontainers/containers/TimescaleDBContainerProvider.java",
    "content": "package org.testcontainers.containers;\n\nimport org.testcontainers.jdbc.ConnectionUrl;\nimport org.testcontainers.utility.DockerImageName;\n\n/**\n * Factory for TimescaleDB containers, which are a special flavour of PostgreSQL.\n *\n * @see <a href=\"https://docs.timescale.com/latest/introduction\">https://docs.timescale.com/latest/introduction</a>\n */\npublic class TimescaleDBContainerProvider extends JdbcDatabaseContainerProvider {\n\n    private static final String NAME = \"timescaledb\";\n\n    private static final String DEFAULT_TAG = \"2.1.0-pg11\";\n\n    private static final DockerImageName DEFAULT_IMAGE = DockerImageName\n        .parse(\"timescale/timescaledb\")\n        .asCompatibleSubstituteFor(\"postgres\");\n\n    public static final String USER_PARAM = \"user\";\n\n    public static final String PASSWORD_PARAM = \"password\";\n\n    @Override\n    public boolean supports(String databaseType) {\n        return databaseType.equals(NAME);\n    }\n\n    @Override\n    public JdbcDatabaseContainer newInstance() {\n        return newInstance(DEFAULT_TAG);\n    }\n\n    @Override\n    public JdbcDatabaseContainer newInstance(String tag) {\n        return new PostgreSQLContainer(DEFAULT_IMAGE.withTag(tag));\n    }\n\n    @Override\n    public JdbcDatabaseContainer newInstance(ConnectionUrl connectionUrl) {\n        return newInstanceFromConnectionUrl(connectionUrl, USER_PARAM, PASSWORD_PARAM);\n    }\n}\n"
  },
  {
    "path": "modules/postgresql/src/main/java/org/testcontainers/postgresql/PostgreSQLContainer.java",
    "content": "package org.testcontainers.postgresql;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.testcontainers.containers.JdbcDatabaseContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.time.Duration;\nimport java.time.temporal.ChronoUnit;\nimport java.util.Set;\n\n/**\n * Testcontainers implementation for PostgreSQL.\n * <p>\n * Supported images: {@code postgres}, {@code pgvector/pgvector}\n * <p>\n * Exposed ports: 5432\n */\npublic class PostgreSQLContainer extends JdbcDatabaseContainer<PostgreSQLContainer> {\n\n    public static final String NAME = \"postgresql\";\n\n    public static final String IMAGE = \"postgres\";\n\n    public static final String DEFAULT_TAG = \"9.6.12\";\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"postgres\");\n\n    private static final DockerImageName PGVECTOR_IMAGE_NAME = DockerImageName.parse(\"pgvector/pgvector\");\n\n    public static final Integer POSTGRESQL_PORT = 5432;\n\n    static final String DEFAULT_USER = \"test\";\n\n    static final String DEFAULT_PASSWORD = \"test\";\n\n    private String databaseName = \"test\";\n\n    private String username = \"test\";\n\n    private String password = \"test\";\n\n    private static final String FSYNC_OFF_OPTION = \"fsync=off\";\n\n    public PostgreSQLContainer(final String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    public PostgreSQLContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME, PGVECTOR_IMAGE_NAME);\n\n        waitingFor(\n            Wait\n                .forLogMessage(\".*database system is ready to accept connections.*\\\\s\", 2)\n                .withStartupTimeout(Duration.of(60, ChronoUnit.SECONDS))\n        );\n        setCommand(\"postgres\", \"-c\", FSYNC_OFF_OPTION);\n\n        addExposedPort(POSTGRESQL_PORT);\n    }\n\n    /**\n     * @return the ports on which to check if the container is ready\n     * @deprecated use {@link #getLivenessCheckPortNumbers()} instead\n     */\n    @NotNull\n    @Override\n    @Deprecated\n    protected Set<Integer> getLivenessCheckPorts() {\n        return super.getLivenessCheckPorts();\n    }\n\n    @Override\n    protected void configure() {\n        // Disable Postgres driver use of java.util.logging to reduce noise at startup time\n        withUrlParam(\"loggerLevel\", \"OFF\");\n        addEnv(\"POSTGRES_DB\", databaseName);\n        addEnv(\"POSTGRES_USER\", username);\n        addEnv(\"POSTGRES_PASSWORD\", password);\n    }\n\n    @Override\n    public String getDriverClassName() {\n        return \"org.postgresql.Driver\";\n    }\n\n    @Override\n    public String getJdbcUrl() {\n        String additionalUrlParams = constructUrlParameters(\"?\", \"&\");\n        return (\n            \"jdbc:postgresql://\" +\n            getHost() +\n            \":\" +\n            getMappedPort(POSTGRESQL_PORT) +\n            \"/\" +\n            databaseName +\n            additionalUrlParams\n        );\n    }\n\n    @Override\n    public String getDatabaseName() {\n        return databaseName;\n    }\n\n    @Override\n    public String getUsername() {\n        return username;\n    }\n\n    @Override\n    public String getPassword() {\n        return password;\n    }\n\n    @Override\n    public String getTestQueryString() {\n        return \"SELECT 1\";\n    }\n\n    @Override\n    public PostgreSQLContainer withDatabaseName(final String databaseName) {\n        this.databaseName = databaseName;\n        return self();\n    }\n\n    @Override\n    public PostgreSQLContainer withUsername(final String username) {\n        this.username = username;\n        return self();\n    }\n\n    @Override\n    public PostgreSQLContainer withPassword(final String password) {\n        this.password = password;\n        return self();\n    }\n\n    @Override\n    protected void waitUntilContainerStarted() {\n        getWaitStrategy().waitUntilReady(this);\n    }\n}\n"
  },
  {
    "path": "modules/postgresql/src/main/java/org/testcontainers/postgresql/PostgreSQLR2DBCDatabaseContainer.java",
    "content": "package org.testcontainers.postgresql;\n\nimport io.r2dbc.postgresql.PostgresqlConnectionFactoryProvider;\nimport io.r2dbc.spi.ConnectionFactoryOptions;\nimport org.testcontainers.lifecycle.Startable;\nimport org.testcontainers.r2dbc.R2DBCDatabaseContainer;\n\nimport java.util.Set;\n\npublic final class PostgreSQLR2DBCDatabaseContainer implements R2DBCDatabaseContainer {\n\n    private final PostgreSQLContainer container;\n\n    public PostgreSQLR2DBCDatabaseContainer(PostgreSQLContainer container) {\n        this.container = container;\n    }\n\n    public static ConnectionFactoryOptions getOptions(PostgreSQLContainer container) {\n        ConnectionFactoryOptions options = ConnectionFactoryOptions\n            .builder()\n            .option(ConnectionFactoryOptions.DRIVER, PostgresqlConnectionFactoryProvider.POSTGRESQL_DRIVER)\n            .build();\n\n        return new PostgreSQLR2DBCDatabaseContainer(container).configure(options);\n    }\n\n    @Override\n    public ConnectionFactoryOptions configure(ConnectionFactoryOptions options) {\n        return options\n            .mutate()\n            .option(ConnectionFactoryOptions.HOST, container.getHost())\n            .option(ConnectionFactoryOptions.PORT, container.getMappedPort(PostgreSQLContainer.POSTGRESQL_PORT))\n            .option(ConnectionFactoryOptions.DATABASE, container.getDatabaseName())\n            .option(ConnectionFactoryOptions.USER, container.getUsername())\n            .option(ConnectionFactoryOptions.PASSWORD, container.getPassword())\n            .build();\n    }\n\n    @Override\n    public Set<Startable> getDependencies() {\n        return this.container.getDependencies();\n    }\n\n    @Override\n    public void start() {\n        this.container.start();\n    }\n\n    @Override\n    public void stop() {\n        this.container.stop();\n    }\n\n    @Override\n    public void close() {\n        this.container.close();\n    }\n}\n"
  },
  {
    "path": "modules/postgresql/src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider",
    "content": "org.testcontainers.containers.PostgreSQLContainerProvider\norg.testcontainers.containers.PostgisContainerProvider\norg.testcontainers.containers.TimescaleDBContainerProvider\norg.testcontainers.containers.PgVectorContainerProvider\n"
  },
  {
    "path": "modules/postgresql/src/main/resources/META-INF/services/org.testcontainers.r2dbc.R2DBCDatabaseContainerProvider",
    "content": "org.testcontainers.containers.PostgreSQLR2DBCDatabaseContainerProvider\n"
  },
  {
    "path": "modules/postgresql/src/test/java/org/testcontainers/PostgreSQLTestImages.java",
    "content": "package org.testcontainers;\n\nimport org.testcontainers.utility.DockerImageName;\n\npublic interface PostgreSQLTestImages {\n    DockerImageName POSTGRES_TEST_IMAGE = DockerImageName.parse(\"postgres:9.6.12\");\n}\n"
  },
  {
    "path": "modules/postgresql/src/test/java/org/testcontainers/containers/PostgreSQLConnectionURLTest.java",
    "content": "package org.testcontainers.containers;\n\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.PostgreSQLTestImages;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.catchThrowable;\n\nclass PostgreSQLConnectionURLTest {\n\n    @Test\n    void shouldCorrectlyAppendQueryString() {\n        PostgreSQLContainer<?> postgres = new FixedJdbcUrlPostgreSQLContainer();\n        String connectionUrl = postgres.constructUrlForConnection(\"?stringtype=unspecified&stringtype=unspecified\");\n        String queryString = connectionUrl.substring(connectionUrl.indexOf('?'));\n\n        assertThat(queryString)\n            .as(\"Query String contains expected params\")\n            .contains(\"?stringtype=unspecified&stringtype=unspecified\");\n        assertThat(queryString.indexOf('?')).as(\"Query String starts with '?'\").isZero();\n        assertThat(queryString.substring(1)).as(\"Query String does not contain extra '?'\").doesNotContain(\"?\");\n    }\n\n    @Test\n    void shouldCorrectlyAppendQueryStringWhenNoBaseParams() {\n        PostgreSQLContainer<?> postgres = new NoParamsUrlPostgreSQLContainer();\n        String connectionUrl = postgres.constructUrlForConnection(\"?stringtype=unspecified&stringtype=unspecified\");\n        String queryString = connectionUrl.substring(connectionUrl.indexOf('?'));\n\n        assertThat(queryString)\n            .as(\"Query String contains expected params\")\n            .contains(\"?stringtype=unspecified&stringtype=unspecified\");\n        assertThat(queryString.indexOf('?')).as(\"Query String starts with '?'\").isZero();\n        assertThat(queryString.substring(1)).as(\"Query String does not contain extra '?'\").doesNotContain(\"?\");\n    }\n\n    @Test\n    void shouldReturnOriginalURLWhenEmptyQueryString() {\n        PostgreSQLContainer<?> postgres = new FixedJdbcUrlPostgreSQLContainer();\n        String connectionUrl = postgres.constructUrlForConnection(\"\");\n\n        assertThat(postgres.getJdbcUrl()).as(\"Query String remains unchanged\").isEqualTo(connectionUrl);\n    }\n\n    @Test\n    void shouldRejectInvalidQueryString() {\n        assertThat(\n            catchThrowable(() -> {\n                new NoParamsUrlPostgreSQLContainer().constructUrlForConnection(\"stringtype=unspecified\");\n            })\n        )\n            .as(\"Fails when invalid query string provided\")\n            .isInstanceOf(IllegalArgumentException.class);\n    }\n\n    static class FixedJdbcUrlPostgreSQLContainer extends PostgreSQLContainer<FixedJdbcUrlPostgreSQLContainer> {\n\n        public FixedJdbcUrlPostgreSQLContainer() {\n            super(PostgreSQLTestImages.POSTGRES_TEST_IMAGE);\n        }\n\n        @Override\n        public String getHost() {\n            return \"localhost\";\n        }\n\n        @Override\n        public Integer getMappedPort(int originalPort) {\n            return 34532;\n        }\n    }\n\n    static class NoParamsUrlPostgreSQLContainer extends PostgreSQLContainer<FixedJdbcUrlPostgreSQLContainer> {\n\n        public NoParamsUrlPostgreSQLContainer() {\n            super(PostgreSQLTestImages.POSTGRES_TEST_IMAGE);\n        }\n\n        @Override\n        public String getJdbcUrl() {\n            return \"jdbc:postgresql://host:port/database\";\n        }\n    }\n}\n"
  },
  {
    "path": "modules/postgresql/src/test/java/org/testcontainers/containers/PostgreSQLR2DBCDatabaseContainerTest.java",
    "content": "package org.testcontainers.containers;\n\nimport io.r2dbc.spi.ConnectionFactoryOptions;\nimport org.testcontainers.PostgreSQLTestImages;\nimport org.testcontainers.r2dbc.AbstractR2DBCDatabaseContainerTest;\n\npublic class PostgreSQLR2DBCDatabaseContainerTest extends AbstractR2DBCDatabaseContainerTest<PostgreSQLContainer<?>> {\n\n    @Override\n    protected PostgreSQLContainer<?> createContainer() {\n        return new PostgreSQLContainer<>(PostgreSQLTestImages.POSTGRES_TEST_IMAGE);\n    }\n\n    @Override\n    protected ConnectionFactoryOptions getOptions(PostgreSQLContainer<?> container) {\n        // spotless:off\n        // get_options {\n        ConnectionFactoryOptions options = PostgreSQLR2DBCDatabaseContainer.getOptions(\n            container\n        );\n        // }\n        // spotless:on\n\n        return options;\n    }\n\n    protected String createR2DBCUrl() {\n        return \"r2dbc:tc:postgresql:///db?TC_IMAGE_TAG=10-alpine\";\n    }\n}\n"
  },
  {
    "path": "modules/postgresql/src/test/java/org/testcontainers/containers/TimescaleDBContainerTest.java",
    "content": "package org.testcontainers.containers;\n\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.db.AbstractContainerDatabaseTest;\n\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass TimescaleDBContainerTest extends AbstractContainerDatabaseTest {\n\n    @Test\n    void testSimple() throws SQLException {\n        try (JdbcDatabaseContainer<?> postgres = new TimescaleDBContainerProvider().newInstance()) {\n            postgres.start();\n\n            ResultSet resultSet = performQuery(postgres, \"SELECT 1\");\n            int resultSetInt = resultSet.getInt(1);\n            assertThat(resultSetInt).as(\"A basic SELECT query succeeds\").isEqualTo(1);\n        }\n    }\n\n    @Test\n    void testCommandOverride() throws SQLException {\n        try (\n            GenericContainer<?> postgres = new TimescaleDBContainerProvider()\n                .newInstance()\n                .withCommand(\"postgres -c max_connections=42\")\n        ) {\n            postgres.start();\n\n            ResultSet resultSet = performQuery(\n                (JdbcDatabaseContainer<?>) postgres,\n                \"SELECT current_setting('max_connections')\"\n            );\n            String result = resultSet.getString(1);\n            assertThat(result).as(\"max_connections should be overridden\").isEqualTo(\"42\");\n        }\n    }\n\n    @Test\n    void testUnsetCommand() throws SQLException {\n        try (\n            GenericContainer<?> postgres = new TimescaleDBContainerProvider()\n                .newInstance()\n                .withCommand(\"postgres -c max_connections=42\")\n                .withCommand()\n        ) {\n            postgres.start();\n\n            ResultSet resultSet = performQuery(\n                (JdbcDatabaseContainer<?>) postgres,\n                \"SELECT current_setting('max_connections')\"\n            );\n            String result = resultSet.getString(1);\n            assertThat(result).as(\"max_connections should not be overridden\").isNotEqualTo(\"42\");\n        }\n    }\n\n    @Test\n    void testExplicitInitScript() throws SQLException {\n        try (\n            JdbcDatabaseContainer<?> postgres = new TimescaleDBContainerProvider()\n                .newInstance()\n                .withInitScript(\"somepath/init_timescaledb.sql\")\n        ) {\n            postgres.start();\n\n            ResultSet resultSet = performQuery(postgres, \"SELECT foo FROM bar\");\n\n            String firstColumnValue = resultSet.getString(1);\n            assertThat(firstColumnValue).as(\"Value from init script should equal real value\").isEqualTo(\"hello world\");\n        }\n    }\n}\n"
  },
  {
    "path": "modules/postgresql/src/test/java/org/testcontainers/jdbc/DatabaseDriverShutdownTest.java",
    "content": "package org.testcontainers.jdbc;\n\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.JdbcDatabaseContainer;\n\nimport java.sql.Connection;\nimport java.sql.DriverManager;\nimport java.sql.SQLException;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * This test belongs in the jdbc module, as it is focused on testing the behaviour of {@link org.testcontainers.containers.JdbcDatabaseContainer}.\n * However, the need to use the {@link org.testcontainers.containers.PostgreSQLContainerProvider} (due to the jdbc:tc:postgresql) URL forces it to live here in\n * the mysql module, to avoid circular dependencies.\n * TODO: Move to the jdbc module and either (a) implement a barebones {@link org.testcontainers.containers.JdbcDatabaseContainerProvider} for testing, or (b) refactor into a unit test.\n */\nclass DatabaseDriverShutdownTest {\n\n    @BeforeAll\n    public static void testCleanup() {\n        ContainerDatabaseDriver.killContainers();\n    }\n\n    @Test\n    void shouldStopContainerWhenAllConnectionsClosed() throws SQLException {\n        final String jdbcUrl = \"jdbc:tc:postgresql:9.6.8://hostname/databasename\";\n\n        getConnectionAndClose(jdbcUrl);\n\n        JdbcDatabaseContainer<?> container = ContainerDatabaseDriver.getContainer(jdbcUrl);\n        assertThat(container).as(\"Database container instance is null as expected\").isNull();\n    }\n\n    @Test\n    void shouldNotStopDaemonContainerWhenAllConnectionsClosed() throws SQLException {\n        final String jdbcUrl = \"jdbc:tc:postgresql:9.6.8://hostname/databasename?TC_DAEMON=true\";\n\n        getConnectionAndClose(jdbcUrl);\n\n        JdbcDatabaseContainer<?> container = ContainerDatabaseDriver.getContainer(jdbcUrl);\n        assertThat(container).as(\"Database container instance is not null as expected\").isNotNull();\n        assertThat(container.isRunning()).as(\"Database container is running as expected\").isTrue();\n    }\n\n    private void getConnectionAndClose(String jdbcUrl) throws SQLException {\n        try (Connection connection = DriverManager.getConnection(jdbcUrl)) {\n            assertThat(connection).as(\"Obtained connection as expected\").isNotNull();\n        }\n    }\n}\n"
  },
  {
    "path": "modules/postgresql/src/test/java/org/testcontainers/jdbc/DatabaseDriverTmpfsTest.java",
    "content": "package org.testcontainers.jdbc;\n\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.Container;\nimport org.testcontainers.containers.JdbcDatabaseContainer;\n\nimport java.sql.Connection;\nimport java.sql.DriverManager;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * This test belongs in the jdbc module, as it is focused on testing the behaviour of {@link org.testcontainers.containers.JdbcDatabaseContainer}.\n * However, the need to use the {@link org.testcontainers.containers.PostgreSQLContainerProvider} (due to the jdbc:tc:postgresql) URL forces it to live here in\n * the mysql module, to avoid circular dependencies.\n * TODO: Move to the jdbc module and either (a) implement a barebones {@link org.testcontainers.containers.JdbcDatabaseContainerProvider} for testing, or (b) refactor into a unit test.\n */\nclass DatabaseDriverTmpfsTest {\n\n    @Test\n    void testDatabaseHasTmpFsViaConnectionString() throws Exception {\n        final String jdbcUrl = \"jdbc:tc:postgresql:9.6.8://hostname/databasename?TC_TMPFS=/testtmpfs:rw\";\n        try (Connection ignored = DriverManager.getConnection(jdbcUrl)) {\n            JdbcDatabaseContainer<?> container = ContainerDatabaseDriver.getContainer(jdbcUrl);\n            // check file doesn't exist\n            String path = \"/testtmpfs/test.file\";\n            Container.ExecResult execResult = container.execInContainer(\"ls\", path);\n            assertThat(execResult.getExitCode())\n                .as(\"tmpfs inside container doesn't have file that doesn't exist\")\n                .isNotZero();\n            // touch && check file does exist\n            container.execInContainer(\"touch\", path);\n            execResult = container.execInContainer(\"ls\", path);\n            assertThat(execResult.getExitCode()).as(\"tmpfs inside container has file that does exist\").isZero();\n        }\n    }\n}\n"
  },
  {
    "path": "modules/postgresql/src/test/java/org/testcontainers/jdbc/pgvector/PgVectorJDBCDriverTest.java",
    "content": "package org.testcontainers.jdbc.pgvector;\n\nimport org.testcontainers.jdbc.AbstractJDBCDriverTest;\n\nimport java.util.Arrays;\nimport java.util.EnumSet;\n\nclass PgVectorJDBCDriverTest extends AbstractJDBCDriverTest {\n\n    public static Iterable<Object[]> data() {\n        return Arrays.asList(\n            new Object[][] {\n                {\n                    \"jdbc:tc:pgvector://hostname/databasename?user=someuser&password=somepwd\",\n                    EnumSet.of(Options.JDBCParams),\n                },\n                {\n                    \"jdbc:tc:pgvector:pg14://hostname/databasename?user=someuser&password=somepwd\",\n                    EnumSet.of(Options.JDBCParams),\n                },\n            }\n        );\n    }\n}\n"
  },
  {
    "path": "modules/postgresql/src/test/java/org/testcontainers/jdbc/postgis/PostgisJDBCDriverTest.java",
    "content": "package org.testcontainers.jdbc.postgis;\n\nimport org.testcontainers.jdbc.AbstractJDBCDriverTest;\n\nimport java.util.Arrays;\nimport java.util.EnumSet;\n\nclass PostgisJDBCDriverTest extends AbstractJDBCDriverTest {\n\n    public static Iterable<Object[]> data() {\n        return Arrays.asList(\n            new Object[][] {\n                {\n                    \"jdbc:tc:postgis://hostname/databasename?user=someuser&password=somepwd\",\n                    EnumSet.of(Options.JDBCParams),\n                },\n                {\n                    \"jdbc:tc:postgis:9.6-2.5://hostname/databasename?user=someuser&password=somepwd\",\n                    EnumSet.of(Options.JDBCParams),\n                },\n            }\n        );\n    }\n}\n"
  },
  {
    "path": "modules/postgresql/src/test/java/org/testcontainers/jdbc/postgresql/PostgreSQLJDBCDriverTest.java",
    "content": "package org.testcontainers.jdbc.postgresql;\n\nimport org.testcontainers.jdbc.AbstractJDBCDriverTest;\n\nimport java.util.Arrays;\nimport java.util.EnumSet;\n\npublic class PostgreSQLJDBCDriverTest extends AbstractJDBCDriverTest {\n\n    public static Iterable<Object[]> data() {\n        return Arrays.asList(\n            new Object[][] {\n                {\n                    \"jdbc:tc:postgresql:9.6.8://hostname/databasename?user=someuser&password=somepwd\",\n                    EnumSet.of(Options.JDBCParams),\n                },\n            }\n        );\n    }\n}\n"
  },
  {
    "path": "modules/postgresql/src/test/java/org/testcontainers/jdbc/timescaledb/TimescaleDBJDBCDriverTest.java",
    "content": "package org.testcontainers.jdbc.timescaledb;\n\nimport org.testcontainers.jdbc.AbstractJDBCDriverTest;\n\nimport java.util.Arrays;\nimport java.util.EnumSet;\n\npublic class TimescaleDBJDBCDriverTest extends AbstractJDBCDriverTest {\n\n    public static Iterable<Object[]> data() {\n        return Arrays.asList(\n            new Object[][] {\n                {\n                    \"jdbc:tc:timescaledb://hostname/databasename?user=someuser&password=somepwd\",\n                    EnumSet.of(Options.JDBCParams),\n                },\n                {\n                    \"jdbc:tc:timescaledb:2.1.0-pg13://hostname/databasename?user=someuser&password=somepwd\",\n                    EnumSet.of(Options.JDBCParams),\n                },\n            }\n        );\n    }\n}\n"
  },
  {
    "path": "modules/postgresql/src/test/java/org/testcontainers/postgresql/CompatibleImageTest.java",
    "content": "package org.testcontainers.postgresql;\n\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.db.AbstractContainerDatabaseTest;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass CompatibleImageTest extends AbstractContainerDatabaseTest {\n\n    @Test\n    void pgvector() throws SQLException {\n        try (\n            // pgvectorContainer {\n            PostgreSQLContainer pgvector = new PostgreSQLContainer(\"pgvector/pgvector:pg16\")\n            // }\n        ) {\n            pgvector.start();\n\n            ResultSet resultSet = performQuery(pgvector, \"SELECT 1\");\n            int resultSetInt = resultSet.getInt(1);\n            assertThat(resultSetInt).as(\"A basic SELECT query succeeds\").isEqualTo(1);\n        }\n    }\n\n    @Test\n    void postgis() throws SQLException {\n        try (\n            // postgisContainer {\n            PostgreSQLContainer postgis = new PostgreSQLContainer(\n                DockerImageName.parse(\"postgis/postgis:16-3.4-alpine\").asCompatibleSubstituteFor(\"postgres\")\n            )\n            // }\n        ) {\n            postgis.start();\n\n            ResultSet resultSet = performQuery(postgis, \"SELECT 1\");\n            int resultSetInt = resultSet.getInt(1);\n            assertThat(resultSetInt).as(\"A basic SELECT query succeeds\").isEqualTo(1);\n        }\n    }\n\n    @Test\n    void timescaledb() throws SQLException {\n        try (\n            // timescaledbContainer {\n            PostgreSQLContainer timescaledb = new PostgreSQLContainer(\n                DockerImageName.parse(\"timescale/timescaledb:2.14.2-pg16\").asCompatibleSubstituteFor(\"postgres\")\n            )\n            // }\n        ) {\n            timescaledb.start();\n\n            ResultSet resultSet = performQuery(timescaledb, \"SELECT 1\");\n            int resultSetInt = resultSet.getInt(1);\n            assertThat(resultSetInt).as(\"A basic SELECT query succeeds\").isEqualTo(1);\n        }\n    }\n}\n"
  },
  {
    "path": "modules/postgresql/src/test/java/org/testcontainers/postgresql/PostgreSQLContainerTest.java",
    "content": "package org.testcontainers.postgresql;\n\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.PostgreSQLTestImages;\nimport org.testcontainers.db.AbstractContainerDatabaseTest;\n\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.util.logging.Level;\nimport java.util.logging.LogManager;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatNoException;\n\nclass PostgreSQLContainerTest extends AbstractContainerDatabaseTest {\n    static {\n        // Postgres JDBC driver uses JUL; disable it to avoid annoying, irrelevant, stderr logs during connection testing\n        LogManager.getLogManager().getLogger(\"\").setLevel(Level.OFF);\n    }\n\n    @Test\n    void testSimple() throws SQLException {\n        try ( // container {\n            PostgreSQLContainer postgres = new PostgreSQLContainer(\"postgres:9.6.12\")\n            // }\n        ) {\n            postgres.start();\n\n            ResultSet resultSet = performQuery(postgres, \"SELECT 1\");\n            int resultSetInt = resultSet.getInt(1);\n            assertThat(resultSetInt).as(\"A basic SELECT query succeeds\").isEqualTo(1);\n            assertHasCorrectExposedAndLivenessCheckPorts(postgres);\n        }\n    }\n\n    @Test\n    void testCommandOverride() throws SQLException {\n        try (\n            PostgreSQLContainer postgres = new PostgreSQLContainer(PostgreSQLTestImages.POSTGRES_TEST_IMAGE)\n                .withCommand(\"postgres -c max_connections=42\")\n        ) {\n            postgres.start();\n\n            ResultSet resultSet = performQuery(postgres, \"SELECT current_setting('max_connections')\");\n            String result = resultSet.getString(1);\n            assertThat(result).as(\"max_connections should be overridden\").isEqualTo(\"42\");\n        }\n    }\n\n    @Test\n    void testUnsetCommand() throws SQLException {\n        try (\n            PostgreSQLContainer postgres = new PostgreSQLContainer(PostgreSQLTestImages.POSTGRES_TEST_IMAGE)\n                .withCommand(\"postgres -c max_connections=42\")\n                .withCommand()\n        ) {\n            postgres.start();\n\n            ResultSet resultSet = performQuery(postgres, \"SELECT current_setting('max_connections')\");\n            String result = resultSet.getString(1);\n            assertThat(result).as(\"max_connections should not be overridden\").isNotEqualTo(\"42\");\n        }\n    }\n\n    @Test\n    void testMissingInitScript() {\n        try (\n            PostgreSQLContainer postgres = new PostgreSQLContainer(PostgreSQLTestImages.POSTGRES_TEST_IMAGE)\n                .withInitScript(null)\n        ) {\n            assertThatNoException().isThrownBy(postgres::start);\n        }\n    }\n\n    @Test\n    void testExplicitInitScript() throws SQLException {\n        try (\n            PostgreSQLContainer postgres = new PostgreSQLContainer(PostgreSQLTestImages.POSTGRES_TEST_IMAGE)\n                .withInitScript(\"somepath/init_postgresql.sql\")\n        ) {\n            postgres.start();\n\n            ResultSet resultSet = performQuery(postgres, \"SELECT foo FROM bar\");\n\n            String firstColumnValue = resultSet.getString(1);\n            assertThat(firstColumnValue).as(\"Value from init script should equal real value\").isEqualTo(\"hello world\");\n        }\n    }\n\n    @Test\n    void testExplicitInitScripts() throws SQLException {\n        try (\n            PostgreSQLContainer postgres = new PostgreSQLContainer(PostgreSQLTestImages.POSTGRES_TEST_IMAGE)\n                .withInitScripts(\"somepath/init_postgresql.sql\", \"somepath/init_postgresql_2.sql\")\n        ) {\n            postgres.start();\n\n            ResultSet resultSet = performQuery(\n                postgres,\n                \"SELECT foo AS value FROM bar UNION SELECT bar AS value FROM foo\"\n            );\n\n            String columnValue1 = resultSet.getString(1);\n            resultSet.next();\n            String columnValue2 = resultSet.getString(1);\n            assertThat(columnValue1).as(\"Value from init script 1 should equal real value\").isEqualTo(\"hello world\");\n            assertThat(columnValue2).as(\"Value from init script 2 should equal real value\").isEqualTo(\"hello world 2\");\n        }\n    }\n\n    @Test\n    void testWithAdditionalUrlParamInJdbcUrl() {\n        try (\n            PostgreSQLContainer postgres = new PostgreSQLContainer(PostgreSQLTestImages.POSTGRES_TEST_IMAGE)\n                .withUrlParam(\"charSet\", \"UNICODE\")\n        ) {\n            postgres.start();\n            String jdbcUrl = postgres.getJdbcUrl();\n            assertThat(jdbcUrl).contains(\"?\");\n            assertThat(jdbcUrl).contains(\"&\");\n            assertThat(jdbcUrl).contains(\"charSet=UNICODE\");\n        }\n    }\n\n    private void assertHasCorrectExposedAndLivenessCheckPorts(PostgreSQLContainer postgres) {\n        assertThat(postgres.getExposedPorts()).containsExactly(PostgreSQLContainer.POSTGRESQL_PORT);\n        assertThat(postgres.getLivenessCheckPortNumbers())\n            .containsExactly(postgres.getMappedPort(PostgreSQLContainer.POSTGRESQL_PORT));\n    }\n}\n"
  },
  {
    "path": "modules/postgresql/src/test/java/org/testcontainers/postgresql/PostgreSQLR2DBCDatabaseContainerTest.java",
    "content": "package org.testcontainers.postgresql;\n\nimport io.r2dbc.spi.ConnectionFactoryOptions;\nimport org.testcontainers.PostgreSQLTestImages;\nimport org.testcontainers.r2dbc.AbstractR2DBCDatabaseContainerTest;\n\npublic class PostgreSQLR2DBCDatabaseContainerTest extends AbstractR2DBCDatabaseContainerTest<PostgreSQLContainer> {\n\n    @Override\n    protected PostgreSQLContainer createContainer() {\n        return new PostgreSQLContainer(PostgreSQLTestImages.POSTGRES_TEST_IMAGE);\n    }\n\n    @Override\n    protected ConnectionFactoryOptions getOptions(PostgreSQLContainer container) {\n        // spotless:off\n        // get_options {\n        ConnectionFactoryOptions options = PostgreSQLR2DBCDatabaseContainer.getOptions(\n            container\n        );\n        // }\n        // spotless:on\n\n        return options;\n    }\n\n    protected String createR2DBCUrl() {\n        return \"r2dbc:tc:postgresql:///db?TC_IMAGE_TAG=10-alpine\";\n    }\n}\n"
  },
  {
    "path": "modules/postgresql/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/postgresql/src/test/resources/somepath/init_postgresql.sql",
    "content": "CREATE TABLE bar (\n  foo VARCHAR(255)\n);\n\nINSERT INTO bar (foo) VALUES ('hello world');"
  },
  {
    "path": "modules/postgresql/src/test/resources/somepath/init_postgresql_2.sql",
    "content": "CREATE TABLE foo (\n  bar VARCHAR(255)\n);\n\nINSERT INTO foo (bar) VALUES ('hello world 2');\n"
  },
  {
    "path": "modules/postgresql/src/test/resources/somepath/init_timescaledb.sql",
    "content": "-- Extend the database with TimescaleDB\nCREATE EXTENSION IF NOT EXISTS timescaledb CASCADE;\n\nCREATE TABLE bar\n(\n    foo  VARCHAR(255),\n    time TIMESTAMPTZ NOT NULL\n);\n\nSELECT create_hypertable('bar', 'time');\n\nINSERT INTO bar (time, foo)\nVALUES (CURRENT_TIMESTAMP, 'hello world');\n"
  },
  {
    "path": "modules/presto/build.gradle",
    "content": "description = \"Testcontainers :: JDBC :: Presto\"\n\ndependencies {\n    api project(':testcontainers-jdbc')\n\n    testImplementation project(':testcontainers-jdbc-test')\n    testRuntimeOnly 'io.prestosql:presto-jdbc:350'\n    compileOnly 'org.jetbrains:annotations:26.0.2-1'\n}\n"
  },
  {
    "path": "modules/presto/src/main/java/org/testcontainers/containers/PrestoContainer.java",
    "content": "package org.testcontainers.containers;\n\nimport com.google.common.base.Strings;\nimport org.jetbrains.annotations.NotNull;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.sql.Connection;\nimport java.sql.SQLException;\nimport java.time.Duration;\nimport java.time.temporal.ChronoUnit;\nimport java.util.Set;\n\n/**\n * @deprecated Use {@code TrinoContainer} instead.\n */\n@Deprecated\npublic class PrestoContainer<SELF extends PrestoContainer<SELF>> extends JdbcDatabaseContainer<SELF> {\n\n    public static final String NAME = \"presto\";\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"ghcr.io/trinodb/presto\");\n\n    public static final String IMAGE = \"ghcr.io/trinodb/presto\";\n\n    public static final String DEFAULT_TAG = \"344\";\n\n    public static final Integer PRESTO_PORT = 8080;\n\n    private String username = \"test\";\n\n    private String catalog = null;\n\n    /**\n     * @deprecated use {@link #PrestoContainer(DockerImageName)} instead\n     */\n    @Deprecated\n    public PrestoContainer() {\n        this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG));\n    }\n\n    public PrestoContainer(final String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    public PrestoContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n\n        waitingFor(\n            Wait\n                .forLogMessage(\".*======== SERVER STARTED ========.*\", 1)\n                .withStartupTimeout(Duration.of(60, ChronoUnit.SECONDS))\n        );\n\n        addExposedPort(PRESTO_PORT);\n    }\n\n    /**\n     * @return the ports on which to check if the container is ready\n     * @deprecated use {@link #getLivenessCheckPortNumbers()} instead\n     */\n    @NotNull\n    @Override\n    @Deprecated\n    protected Set<Integer> getLivenessCheckPorts() {\n        return super.getLivenessCheckPorts();\n    }\n\n    @Override\n    public String getDriverClassName() {\n        return \"io.prestosql.jdbc.PrestoDriver\";\n    }\n\n    @Override\n    public String getJdbcUrl() {\n        return String.format(\n            \"jdbc:presto://%s:%s/%s\",\n            getHost(),\n            getMappedPort(PRESTO_PORT),\n            Strings.nullToEmpty(catalog)\n        );\n    }\n\n    @Override\n    public String getUsername() {\n        return username;\n    }\n\n    @Override\n    public String getPassword() {\n        return \"\";\n    }\n\n    @Override\n    public String getDatabaseName() {\n        return catalog;\n    }\n\n    @Override\n    public String getTestQueryString() {\n        return \"SELECT count(*) FROM tpch.tiny.nation\";\n    }\n\n    @Override\n    public SELF withUsername(final String username) {\n        this.username = username;\n        return self();\n    }\n\n    /**\n     * @deprecated This operation is not supported.\n     */\n    @Override\n    @Deprecated\n    public SELF withPassword(final String password) {\n        // ignored, Presto does not support password authentication without TLS\n        // TODO: make JDBCDriverTest not pass a password unconditionally and remove this method\n        return self();\n    }\n\n    @Override\n    public SELF withDatabaseName(String dbName) {\n        this.catalog = dbName;\n        return self();\n    }\n\n    public Connection createConnection() throws SQLException, NoDriverFoundException {\n        return createConnection(\"\");\n    }\n}\n"
  },
  {
    "path": "modules/presto/src/main/java/org/testcontainers/containers/PrestoContainerProvider.java",
    "content": "package org.testcontainers.containers;\n\nimport org.testcontainers.jdbc.ConnectionUrl;\nimport org.testcontainers.utility.DockerImageName;\n\n/**\n * Factory for Presto containers.\n */\npublic class PrestoContainerProvider extends JdbcDatabaseContainerProvider {\n\n    public static final String USER_PARAM = \"user\";\n\n    public static final String PASSWORD_PARAM = \"password\";\n\n    @Override\n    public boolean supports(String databaseType) {\n        return databaseType.equals(PrestoContainer.NAME);\n    }\n\n    @Override\n    public JdbcDatabaseContainer newInstance() {\n        return newInstance(PrestoContainer.DEFAULT_TAG);\n    }\n\n    @Override\n    public JdbcDatabaseContainer newInstance(String tag) {\n        return new PrestoContainer(DockerImageName.parse(PrestoContainer.IMAGE).withTag(tag));\n    }\n\n    @Override\n    public JdbcDatabaseContainer newInstance(ConnectionUrl connectionUrl) {\n        return newInstanceFromConnectionUrl(connectionUrl, USER_PARAM, PASSWORD_PARAM);\n    }\n}\n"
  },
  {
    "path": "modules/presto/src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider",
    "content": "org.testcontainers.containers.PrestoContainerProvider\n"
  },
  {
    "path": "modules/presto/src/test/java/org/testcontainers/PrestoTestImages.java",
    "content": "package org.testcontainers;\n\nimport org.testcontainers.utility.DockerImageName;\n\npublic interface PrestoTestImages {\n    DockerImageName PRESTO_TEST_IMAGE = DockerImageName.parse(\"ghcr.io/trinodb/presto:344\");\n\n    DockerImageName PRESTO_PREVIOUS_VERSION_TEST_IMAGE = DockerImageName.parse(\"ghcr.io/trinodb/presto:343\");\n}\n"
  },
  {
    "path": "modules/presto/src/test/java/org/testcontainers/containers/PrestoContainerTest.java",
    "content": "package org.testcontainers.containers;\n\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.PrestoTestImages;\n\nimport java.sql.Connection;\nimport java.sql.DriverManager;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass PrestoContainerTest {\n\n    @Test\n    void testSimple() throws Exception {\n        try (PrestoContainer<?> prestoSql = new PrestoContainer<>(PrestoTestImages.PRESTO_TEST_IMAGE)) {\n            prestoSql.start();\n            try (\n                Connection connection = prestoSql.createConnection();\n                Statement statement = connection.createStatement();\n                ResultSet resultSet = statement.executeQuery(\"SELECT DISTINCT node_version FROM system.runtime.nodes\")\n            ) {\n                assertThat(resultSet.next()).as(\"has result\").isTrue();\n                assertThat(resultSet.getString(\"node_version\"))\n                    .as(\"Presto version\")\n                    .isEqualTo(PrestoContainer.DEFAULT_TAG);\n                assertHasCorrectExposedAndLivenessCheckPorts(prestoSql);\n            }\n        }\n    }\n\n    @Test\n    void testSpecificVersion() throws Exception {\n        try (\n            PrestoContainer<?> prestoSql = new PrestoContainer<>(PrestoTestImages.PRESTO_PREVIOUS_VERSION_TEST_IMAGE)\n        ) {\n            prestoSql.start();\n            try (\n                Connection connection = prestoSql.createConnection();\n                Statement statement = connection.createStatement();\n                ResultSet resultSet = statement.executeQuery(\"SELECT DISTINCT node_version FROM system.runtime.nodes\")\n            ) {\n                assertThat(resultSet.next()).as(\"has result\").isTrue();\n                assertThat(resultSet.getString(\"node_version\"))\n                    .as(\"Presto version\")\n                    .isEqualTo(PrestoTestImages.PRESTO_PREVIOUS_VERSION_TEST_IMAGE.getVersionPart());\n            }\n        }\n    }\n\n    @Test\n    void testQueryMemoryAndTpch() throws SQLException {\n        try (PrestoContainer<?> prestoSql = new PrestoContainer<>(PrestoTestImages.PRESTO_TEST_IMAGE)) {\n            prestoSql.start();\n            try (\n                Connection connection = prestoSql.createConnection();\n                Statement statement = connection.createStatement()\n            ) {\n                // Prepare data\n                statement.execute(\n                    \"CREATE TABLE memory.default.table_with_array AS SELECT 1 id, ARRAY[1, 42, 2, 42, 4, 42] my_array\"\n                );\n\n                // Query Presto using newly created table and a builtin connector\n                try (\n                    ResultSet resultSet = statement.executeQuery(\n                        \"\" +\n                        \"SELECT nationkey, element \" +\n                        \"FROM tpch.tiny.nation \" +\n                        \"JOIN memory.default.table_with_array twa ON nationkey = twa.id \" +\n                        \"LEFT JOIN UNNEST(my_array) a(element) ON true \" +\n                        \"ORDER BY element OFFSET 1 FETCH NEXT 3 ROWS WITH TIES \"\n                    )\n                ) {\n                    List<Integer> actualElements = new ArrayList<>();\n                    while (resultSet.next()) {\n                        actualElements.add(resultSet.getInt(\"element\"));\n                    }\n                    assertThat(actualElements).isEqualTo(Arrays.asList(2, 4, 42, 42, 42));\n                }\n            }\n        }\n    }\n\n    @Test\n    void testInitScript() throws Exception {\n        try (PrestoContainer<?> prestoSql = new PrestoContainer<>(PrestoTestImages.PRESTO_TEST_IMAGE)) {\n            prestoSql.withInitScript(\"initial.sql\");\n            prestoSql.start();\n            try (\n                Connection connection = prestoSql.createConnection();\n                Statement statement = connection.createStatement();\n                ResultSet resultSet = statement.executeQuery(\"SELECT a FROM memory.default.test_table\")\n            ) {\n                assertThat(resultSet.next()).as(\"has result\").isTrue();\n                assertThat(resultSet.getObject(\"a\")).as(\"Value\").isEqualTo(12345678909324L);\n                assertThat(resultSet.next()).as(\"only has one result\").isFalse();\n            }\n        }\n    }\n\n    @Test\n    void testTcJdbcUri() throws Exception {\n        try (\n            Connection connection = DriverManager.getConnection(\n                String.format(\"jdbc:tc:presto:%s://hostname/\", PrestoContainer.DEFAULT_TAG)\n            )\n        ) {\n            // Verify metadata with tc: JDBC connection URI\n            assertThat(Integer.parseInt(PrestoContainer.DEFAULT_TAG))\n                .isEqualTo(connection.getMetaData().getDatabaseMajorVersion());\n\n            // Verify transactions with tc: JDBC connection URI\n            assertThat(connection.getAutoCommit()).as(\"Is autocommit\").isTrue();\n            connection.setAutoCommit(false);\n            assertThat(connection.getAutoCommit()).as(\"Is autocommit\").isFalse();\n            assertThat(connection.getTransactionIsolation())\n                .as(\"Transaction isolation\")\n                .isEqualTo(Connection.TRANSACTION_READ_UNCOMMITTED);\n\n            try (Statement statement = connection.createStatement()) {\n                assertThat(statement.executeUpdate(\"CREATE TABLE memory.default.test_tc(a bigint)\"))\n                    .as(\"Update result\")\n                    .isEqualTo(0);\n                try (\n                    ResultSet resultSet = statement.executeQuery(\n                        \"SELECT sum(cast(node_version AS bigint)) AS v FROM system.runtime.nodes\"\n                    )\n                ) {\n                    assertThat(resultSet.next()).isTrue();\n                    assertThat(resultSet.getString(\"v\")).isEqualTo(PrestoContainer.DEFAULT_TAG);\n                    assertThat(resultSet.next()).isFalse();\n                }\n                connection.commit();\n            } finally {\n                connection.rollback();\n            }\n            connection.setAutoCommit(true);\n            assertThat(connection.getAutoCommit()).as(\"Is autocommit\").isTrue();\n            assertThat(connection.getTransactionIsolation())\n                .as(\"Transaction isolation should be retained\")\n                .isEqualTo(Connection.TRANSACTION_READ_UNCOMMITTED);\n        }\n    }\n\n    private void assertHasCorrectExposedAndLivenessCheckPorts(PrestoContainer<?> prestoSql) {\n        assertThat(prestoSql.getExposedPorts()).containsExactly(PrestoContainer.PRESTO_PORT);\n        assertThat(prestoSql.getLivenessCheckPortNumbers())\n            .containsExactly(prestoSql.getMappedPort(PrestoContainer.PRESTO_PORT));\n    }\n}\n"
  },
  {
    "path": "modules/presto/src/test/java/org/testcontainers/jdbc/presto/PrestoJDBCDriverTest.java",
    "content": "package org.testcontainers.jdbc.presto;\n\nimport org.testcontainers.jdbc.AbstractJDBCDriverTest;\n\nimport java.util.Arrays;\nimport java.util.EnumSet;\n\nclass PrestoJDBCDriverTest extends AbstractJDBCDriverTest {\n\n    public static Iterable<Object[]> data() {\n        return Arrays.asList(\n            new Object[][] { //\n                { \"jdbc:tc:presto:344://hostname/\", EnumSet.of(Options.PmdKnownBroken) },\n            }\n        );\n    }\n}\n"
  },
  {
    "path": "modules/presto/src/test/resources/initial.sql",
    "content": "CREATE TABLE memory.default.test_table(a bigint);\nINSERT INTO memory.default.test_table(a) VALUES (12345678909324);\n"
  },
  {
    "path": "modules/presto/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/pulsar/build.gradle",
    "content": "description = \"Testcontainers :: Pulsar\"\n\ndependencies {\n    api project(':testcontainers')\n\n    testImplementation platform(\"org.apache.pulsar:pulsar-bom:4.1.1\")\n    testImplementation 'org.apache.pulsar:pulsar-client'\n    testImplementation 'org.apache.pulsar:pulsar-client-admin'\n}\n"
  },
  {
    "path": "modules/pulsar/src/main/java/org/testcontainers/containers/PulsarContainer.java",
    "content": "package org.testcontainers.containers;\n\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.containers.wait.strategy.WaitAllStrategy;\nimport org.testcontainers.utility.DockerImageName;\n\n/**\n * Testcontainers implementation for Apache Pulsar.\n * <p>\n * Supported images: {@code apachepulsar/pulsar}, {@code apachepulsar/pulsar-all}\n * <p>\n * Exposed ports:\n * <ul>\n *     <li>Pulsar: 6650</li>\n *     <li>HTTP: 8080</li>\n * </ul>\n *\n * @deprecated use {@link org.testcontainers.pulsar.PulsarContainer} instead.\n */\n@Deprecated\npublic class PulsarContainer extends GenericContainer<PulsarContainer> {\n\n    public static final int BROKER_PORT = 6650;\n\n    public static final int BROKER_HTTP_PORT = 8080;\n\n    private static final String ADMIN_CLUSTERS_ENDPOINT = \"/admin/v2/clusters\";\n\n    /**\n     * See <a href=\"https://github.com/apache/pulsar/blob/master/pulsar-common/src/main/java/org/apache/pulsar/common/naming/SystemTopicNames.java\">SystemTopicNames</a>.\n     */\n    private static final String TRANSACTION_TOPIC_ENDPOINT =\n        \"/admin/v2/persistent/pulsar/system/transaction_coordinator_assign/partitions\";\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"apachepulsar/pulsar\");\n\n    @Deprecated\n    private static final String DEFAULT_TAG = \"3.0.0\";\n\n    private final WaitAllStrategy waitAllStrategy = new WaitAllStrategy();\n\n    private boolean functionsWorkerEnabled = false;\n\n    private boolean transactionsEnabled = false;\n\n    /**\n     * @deprecated use {@link #PulsarContainer(DockerImageName)} instead\n     */\n    @Deprecated\n    public PulsarContainer() {\n        this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG));\n    }\n\n    /**\n     * @deprecated use {@link #PulsarContainer(DockerImageName)} instead\n     */\n    @Deprecated\n    public PulsarContainer(String pulsarVersion) {\n        this(DEFAULT_IMAGE_NAME.withTag(pulsarVersion));\n    }\n\n    public PulsarContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME, DockerImageName.parse(\"apachepulsar/pulsar-all\"));\n        withExposedPorts(BROKER_PORT, BROKER_HTTP_PORT);\n        setWaitStrategy(waitAllStrategy);\n    }\n\n    @Override\n    protected void configure() {\n        super.configure();\n        setupCommandAndEnv();\n    }\n\n    public PulsarContainer withFunctionsWorker() {\n        functionsWorkerEnabled = true;\n        return this;\n    }\n\n    public PulsarContainer withTransactions() {\n        transactionsEnabled = true;\n        return this;\n    }\n\n    public String getPulsarBrokerUrl() {\n        return String.format(\"pulsar://%s:%s\", getHost(), getMappedPort(BROKER_PORT));\n    }\n\n    public String getHttpServiceUrl() {\n        return String.format(\"http://%s:%s\", getHost(), getMappedPort(BROKER_HTTP_PORT));\n    }\n\n    protected void setupCommandAndEnv() {\n        String standaloneBaseCommand =\n            \"/pulsar/bin/apply-config-from-env.py /pulsar/conf/standalone.conf \" + \"&& bin/pulsar standalone\";\n\n        if (!functionsWorkerEnabled) {\n            standaloneBaseCommand += \" --no-functions-worker -nss\";\n        }\n\n        withCommand(\"/bin/bash\", \"-c\", standaloneBaseCommand);\n\n        final String clusterName = getEnvMap().getOrDefault(\"PULSAR_PREFIX_clusterName\", \"standalone\");\n        final String response = String.format(\"[\\\"%s\\\"]\", clusterName);\n        waitAllStrategy.withStrategy(\n            Wait.forHttp(ADMIN_CLUSTERS_ENDPOINT).forPort(BROKER_HTTP_PORT).forResponsePredicate(response::equals)\n        );\n\n        if (transactionsEnabled) {\n            withEnv(\"PULSAR_PREFIX_transactionCoordinatorEnabled\", \"true\");\n            waitAllStrategy.withStrategy(\n                Wait.forHttp(TRANSACTION_TOPIC_ENDPOINT).forStatusCode(200).forPort(BROKER_HTTP_PORT)\n            );\n        }\n        if (functionsWorkerEnabled) {\n            waitAllStrategy.withStrategy(Wait.forLogMessage(\".*Function worker service started.*\", 1));\n        }\n    }\n}\n"
  },
  {
    "path": "modules/pulsar/src/main/java/org/testcontainers/pulsar/PulsarContainer.java",
    "content": "package org.testcontainers.pulsar;\n\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.containers.wait.strategy.WaitAllStrategy;\nimport org.testcontainers.utility.DockerImageName;\n\n/**\n * Testcontainers implementation for Apache Pulsar.\n * <p>\n * Supported images: {@code apachepulsar/pulsar}, {@code apachepulsar/pulsar-all}\n * <p>\n * Exposed ports:\n * <ul>\n *     <li>Pulsar: 6650</li>\n *     <li>HTTP: 8080</li>\n * </ul>\n */\npublic class PulsarContainer extends GenericContainer<PulsarContainer> {\n\n    public static final int BROKER_PORT = 6650;\n\n    public static final int BROKER_HTTP_PORT = 8080;\n\n    private static final String ADMIN_CLUSTERS_ENDPOINT = \"/admin/v2/clusters\";\n\n    /**\n     * See <a href=\"https://github.com/apache/pulsar/blob/master/pulsar-common/src/main/java/org/apache/pulsar/common/naming/SystemTopicNames.java\">SystemTopicNames</a>.\n     */\n    private static final String TRANSACTION_TOPIC_ENDPOINT =\n        \"/admin/v2/persistent/pulsar/system/transaction_coordinator_assign/partitions\";\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"apachepulsar/pulsar\");\n\n    private final WaitAllStrategy waitAllStrategy = new WaitAllStrategy();\n\n    private boolean functionsWorkerEnabled = false;\n\n    private boolean transactionsEnabled = false;\n\n    @Deprecated\n    public PulsarContainer(String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    public PulsarContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME, DockerImageName.parse(\"apachepulsar/pulsar-all\"));\n        withExposedPorts(BROKER_PORT, BROKER_HTTP_PORT);\n        setWaitStrategy(waitAllStrategy);\n    }\n\n    @Override\n    protected void configure() {\n        super.configure();\n        setupCommandAndEnv();\n    }\n\n    public PulsarContainer withFunctionsWorker() {\n        functionsWorkerEnabled = true;\n        return this;\n    }\n\n    public PulsarContainer withTransactions() {\n        transactionsEnabled = true;\n        return this;\n    }\n\n    public String getPulsarBrokerUrl() {\n        return String.format(\"pulsar://%s:%s\", getHost(), getMappedPort(BROKER_PORT));\n    }\n\n    public String getHttpServiceUrl() {\n        return String.format(\"http://%s:%s\", getHost(), getMappedPort(BROKER_HTTP_PORT));\n    }\n\n    protected void setupCommandAndEnv() {\n        String standaloneBaseCommand =\n            \"/pulsar/bin/apply-config-from-env.py /pulsar/conf/standalone.conf \" + \"&& bin/pulsar standalone\";\n\n        if (!functionsWorkerEnabled) {\n            standaloneBaseCommand += \" --no-functions-worker -nss\";\n        }\n\n        withCommand(\"/bin/bash\", \"-c\", standaloneBaseCommand);\n\n        final String clusterName = getEnvMap().getOrDefault(\"PULSAR_PREFIX_clusterName\", \"standalone\");\n        final String response = String.format(\"[\\\"%s\\\"]\", clusterName);\n        waitAllStrategy.withStrategy(\n            Wait.forHttp(ADMIN_CLUSTERS_ENDPOINT).forPort(BROKER_HTTP_PORT).forResponsePredicate(response::equals)\n        );\n\n        if (transactionsEnabled) {\n            withEnv(\"PULSAR_PREFIX_transactionCoordinatorEnabled\", \"true\");\n            waitAllStrategy.withStrategy(\n                Wait.forHttp(TRANSACTION_TOPIC_ENDPOINT).forStatusCode(200).forPort(BROKER_HTTP_PORT)\n            );\n        }\n        if (functionsWorkerEnabled) {\n            waitAllStrategy.withStrategy(Wait.forLogMessage(\".*Function worker service started.*\", 1));\n        }\n    }\n}\n"
  },
  {
    "path": "modules/pulsar/src/test/java/org/testcontainers/pulsar/AbstractPulsar.java",
    "content": "package org.testcontainers.pulsar;\n\nimport org.apache.pulsar.client.admin.ListTopicsOptions;\nimport org.apache.pulsar.client.admin.PulsarAdmin;\nimport org.apache.pulsar.client.admin.PulsarAdminException;\nimport org.apache.pulsar.client.api.Consumer;\nimport org.apache.pulsar.client.api.Message;\nimport org.apache.pulsar.client.api.Producer;\nimport org.apache.pulsar.client.api.PulsarClient;\nimport org.apache.pulsar.client.api.Schema;\nimport org.apache.pulsar.client.api.SubscriptionInitialPosition;\nimport org.apache.pulsar.client.api.transaction.Transaction;\n\nimport java.util.List;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.TimeUnit;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\npublic class AbstractPulsar {\n\n    public static final String TEST_TOPIC = \"test_topic\";\n\n    protected void testPulsarFunctionality(String pulsarBrokerUrl) throws Exception {\n        try (\n            PulsarClient client = PulsarClient.builder().serviceUrl(pulsarBrokerUrl).build();\n            Consumer<byte[]> consumer = client\n                .newConsumer()\n                .topic(TEST_TOPIC)\n                .subscriptionName(\"test-subs\")\n                .subscribe();\n            Producer<byte[]> producer = client.newProducer().topic(TEST_TOPIC).create()\n        ) {\n            producer.send(\"test containers\".getBytes());\n            CompletableFuture<Message<byte[]>> future = consumer.receiveAsync();\n            Message<byte[]> message = future.get(5, TimeUnit.SECONDS);\n\n            assertThat(new String(message.getData())).isEqualTo(\"test containers\");\n        }\n    }\n\n    protected void testTransactionFunctionality(String pulsarBrokerUrl) throws Exception {\n        try (\n            PulsarClient client = PulsarClient.builder().serviceUrl(pulsarBrokerUrl).enableTransaction(true).build();\n            Consumer<String> consumer = client\n                .newConsumer(Schema.STRING)\n                .topic(\"transaction-topic\")\n                .subscriptionInitialPosition(SubscriptionInitialPosition.Earliest)\n                .subscriptionName(\"test-transaction-sub\")\n                .subscribe();\n            Producer<String> producer = client\n                .newProducer(Schema.STRING)\n                .sendTimeout(0, TimeUnit.SECONDS)\n                .topic(\"transaction-topic\")\n                .create()\n        ) {\n            final Transaction transaction = client.newTransaction().build().get();\n            producer.newMessage(transaction).value(\"first\").send();\n            transaction.commit();\n            Message<String> message = consumer.receive();\n            assertThat(message.getValue()).isEqualTo(\"first\");\n        }\n    }\n\n    protected void assertTransactionsTopicCreated(PulsarAdmin pulsarAdmin) throws PulsarAdminException {\n        final List<String> topics = pulsarAdmin\n            .topics()\n            .getPartitionedTopicList(\"pulsar/system\", ListTopicsOptions.builder().includeSystemTopic(true).build());\n        assertThat(topics).contains(\"persistent://pulsar/system/transaction_coordinator_assign\");\n    }\n}\n"
  },
  {
    "path": "modules/pulsar/src/test/java/org/testcontainers/pulsar/CompatibleApachePulsarImageTest.java",
    "content": "package org.testcontainers.pulsar;\n\nimport org.apache.pulsar.client.admin.PulsarAdmin;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.testcontainers.utility.DockerImageName;\n\nclass CompatibleApachePulsarImageTest extends AbstractPulsar {\n\n    public static String[] params() {\n        return new String[] { \"apachepulsar/pulsar:3.0.0\", \"apachepulsar/pulsar-all:3.0.0\" };\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"params\")\n    void testUsage(String imageName) throws Exception {\n        try (PulsarContainer pulsar = new PulsarContainer(DockerImageName.parse(imageName));) {\n            pulsar.start();\n            final String pulsarBrokerUrl = pulsar.getPulsarBrokerUrl();\n\n            testPulsarFunctionality(pulsarBrokerUrl);\n        }\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"params\")\n    void testTransactions(String imageName) throws Exception {\n        try (PulsarContainer pulsar = new PulsarContainer(DockerImageName.parse(imageName)).withTransactions();) {\n            pulsar.start();\n\n            try (PulsarAdmin pulsarAdmin = PulsarAdmin.builder().serviceHttpUrl(pulsar.getHttpServiceUrl()).build()) {\n                assertTransactionsTopicCreated(pulsarAdmin);\n            }\n            testTransactionFunctionality(pulsar.getPulsarBrokerUrl());\n        }\n    }\n}\n"
  },
  {
    "path": "modules/pulsar/src/test/java/org/testcontainers/pulsar/PulsarContainerTest.java",
    "content": "package org.testcontainers.pulsar;\n\nimport org.apache.pulsar.client.admin.PulsarAdmin;\nimport org.apache.pulsar.client.admin.PulsarAdminException;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.time.Duration;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\n\nclass PulsarContainerTest extends AbstractPulsar {\n\n    private static final DockerImageName PULSAR_IMAGE = DockerImageName.parse(\"apachepulsar/pulsar:3.0.0\");\n\n    @Test\n    void testUsage() throws Exception {\n        try (\n            // do not use PULSAR_IMAGE to make the doc looks easier\n            // constructorWithVersion {\n            PulsarContainer pulsar = new PulsarContainer(\"apachepulsar/pulsar:3.0.0\");\n            // }\n        ) {\n            pulsar.start();\n            // coordinates {\n            final String pulsarBrokerUrl = pulsar.getPulsarBrokerUrl();\n            final String httpServiceUrl = pulsar.getHttpServiceUrl();\n            // }\n            testPulsarFunctionality(pulsarBrokerUrl);\n        }\n    }\n\n    @Test\n    void envVarsUsage() throws Exception {\n        try (\n            // constructorWithEnv {\n            PulsarContainer pulsar = new PulsarContainer(PULSAR_IMAGE)\n                .withEnv(\"PULSAR_PREFIX_brokerDeduplicationEnabled\", \"true\");\n            // }\n        ) {\n            pulsar.start();\n            testPulsarFunctionality(pulsar.getPulsarBrokerUrl());\n        }\n    }\n\n    @Test\n    void customClusterName() throws Exception {\n        try (\n            PulsarContainer pulsar = new PulsarContainer(PULSAR_IMAGE)\n                .withEnv(\"PULSAR_PREFIX_clusterName\", \"tc-cluster\");\n        ) {\n            pulsar.start();\n            testPulsarFunctionality(pulsar.getPulsarBrokerUrl());\n        }\n    }\n\n    @Test\n    void shouldNotEnableFunctionsWorkerByDefault() throws Exception {\n        try (PulsarContainer pulsar = new PulsarContainer(PULSAR_IMAGE)) {\n            pulsar.start();\n\n            try (PulsarAdmin pulsarAdmin = PulsarAdmin.builder().serviceHttpUrl(pulsar.getHttpServiceUrl()).build()) {\n                assertThatThrownBy(() -> pulsarAdmin.functions().getFunctions(\"public\", \"default\"))\n                    .isInstanceOf(PulsarAdminException.class);\n            }\n        }\n    }\n\n    @Test\n    void shouldWaitForFunctionsWorkerStarted() throws Exception {\n        try (\n            // constructorWithFunctionsWorker {\n            PulsarContainer pulsar = new PulsarContainer(DockerImageName.parse(\"apachepulsar/pulsar:3.0.0\"))\n                .withFunctionsWorker();\n            // }\n        ) {\n            pulsar.start();\n\n            try (PulsarAdmin pulsarAdmin = PulsarAdmin.builder().serviceHttpUrl(pulsar.getHttpServiceUrl()).build()) {\n                assertThat(pulsarAdmin.functions().getFunctions(\"public\", \"default\")).hasSize(0);\n            }\n        }\n    }\n\n    @Test\n    void testTransactions() throws Exception {\n        try (\n            // constructorWithTransactions {\n            PulsarContainer pulsar = new PulsarContainer(PULSAR_IMAGE).withTransactions();\n            // }\n        ) {\n            pulsar.start();\n\n            try (PulsarAdmin pulsarAdmin = PulsarAdmin.builder().serviceHttpUrl(pulsar.getHttpServiceUrl()).build()) {\n                assertTransactionsTopicCreated(pulsarAdmin);\n            }\n            testTransactionFunctionality(pulsar.getPulsarBrokerUrl());\n        }\n    }\n\n    @Test\n    void testTransactionsAndFunctionsWorker() throws Exception {\n        try (PulsarContainer pulsar = new PulsarContainer(PULSAR_IMAGE).withTransactions().withFunctionsWorker()) {\n            pulsar.start();\n\n            try (PulsarAdmin pulsarAdmin = PulsarAdmin.builder().serviceHttpUrl(pulsar.getHttpServiceUrl()).build();) {\n                assertTransactionsTopicCreated(pulsarAdmin);\n                assertThat(pulsarAdmin.functions().getFunctions(\"public\", \"default\")).hasSize(0);\n            }\n            testTransactionFunctionality(pulsar.getPulsarBrokerUrl());\n        }\n    }\n\n    @Test\n    void testClusterFullyInitialized() throws Exception {\n        try (PulsarContainer pulsar = new PulsarContainer(PULSAR_IMAGE)) {\n            pulsar.start();\n\n            try (PulsarAdmin pulsarAdmin = PulsarAdmin.builder().serviceHttpUrl(pulsar.getHttpServiceUrl()).build()) {\n                assertThat(pulsarAdmin.clusters().getClusters()).hasSize(1).contains(\"standalone\");\n            }\n        }\n    }\n\n    @Test\n    void testStartupTimeoutIsHonored() {\n        try (PulsarContainer pulsar = new PulsarContainer(PULSAR_IMAGE).withStartupTimeout(Duration.ZERO)) {\n            assertThatThrownBy(pulsar::start)\n                .hasRootCauseMessage(\"Precondition failed: timeout must be greater than zero\");\n        }\n    }\n}\n"
  },
  {
    "path": "modules/pulsar/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/qdrant/build.gradle",
    "content": "description = \"Testcontainers :: Qdrant\"\n\ndependencies {\n    api project(':testcontainers')\n\n    testImplementation 'io.qdrant:client:1.16.1'\n    testImplementation platform('io.grpc:grpc-bom:1.75.0')\n    testImplementation 'io.grpc:grpc-stub'\n    testImplementation 'io.grpc:grpc-protobuf'\n    testImplementation 'io.grpc:grpc-netty-shaded'\n}\n"
  },
  {
    "path": "modules/qdrant/src/main/java/org/testcontainers/qdrant/QdrantContainer.java",
    "content": "package org.testcontainers.qdrant;\n\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.images.builder.Transferable;\nimport org.testcontainers.utility.DockerImageName;\n\n/**\n * Testcontainers implementation for Qdrant.\n * <p>\n * Supported image: {@code qdrant/qdrant}\n * <p>\n * Exposed ports:\n * <ul>\n *     <li>HTTP: 6333</li>\n *     <li>GRPC: 6334</li>\n * </ul>\n */\npublic class QdrantContainer extends GenericContainer<QdrantContainer> {\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"qdrant/qdrant\");\n\n    private static final int QDRANT_REST_PORT = 6333;\n\n    private static final int QDRANT_GRPC_PORT = 6334;\n\n    private static final String CONFIG_FILE_PATH = \"/qdrant/config/config.yaml\";\n\n    private static final String API_KEY_ENV = \"QDRANT__SERVICE__API_KEY\";\n\n    public QdrantContainer(String image) {\n        this(DockerImageName.parse(image));\n    }\n\n    public QdrantContainer(DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n        withExposedPorts(QDRANT_REST_PORT, QDRANT_GRPC_PORT);\n        waitingFor(Wait.forHttp(\"/readyz\").forPort(QDRANT_REST_PORT));\n    }\n\n    public QdrantContainer withApiKey(String apiKey) {\n        return withEnv(API_KEY_ENV, apiKey);\n    }\n\n    public QdrantContainer withConfigFile(Transferable configFile) {\n        return withCopyToContainer(configFile, CONFIG_FILE_PATH);\n    }\n\n    public int getGrpcPort() {\n        return getMappedPort(QDRANT_GRPC_PORT);\n    }\n\n    public String getGrpcHostAddress() {\n        return getHost() + \":\" + getGrpcPort();\n    }\n}\n"
  },
  {
    "path": "modules/qdrant/src/test/java/org/testcontainers/qdrant/QdrantContainerTest.java",
    "content": "package org.testcontainers.qdrant;\n\nimport io.qdrant.client.QdrantClient;\nimport io.qdrant.client.QdrantGrpcClient;\nimport io.qdrant.client.grpc.QdrantOuterClass;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.images.builder.Transferable;\n\nimport java.util.UUID;\nimport java.util.concurrent.ExecutionException;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\n\nclass QdrantContainerTest {\n\n    @Test\n    void shouldReturnVersion() throws ExecutionException, InterruptedException {\n        try (\n            // qdrantContainer {\n            QdrantContainer qdrant = new QdrantContainer(\"qdrant/qdrant:v1.7.4\")\n            // }\n        ) {\n            qdrant.start();\n\n            QdrantClient client = new QdrantClient(\n                QdrantGrpcClient.newBuilder(qdrant.getHost(), qdrant.getGrpcPort(), false).build()\n            );\n            QdrantOuterClass.HealthCheckReply healthCheckReply = client.healthCheckAsync().get();\n            assertThat(healthCheckReply.getVersion()).isEqualTo(\"1.7.4\");\n\n            client.close();\n        }\n    }\n\n    @Test\n    void shouldSetApiKey() throws ExecutionException, InterruptedException {\n        String apiKey = UUID.randomUUID().toString();\n        try (QdrantContainer qdrant = new QdrantContainer(\"qdrant/qdrant:v1.7.4\").withApiKey(apiKey)) {\n            qdrant.start();\n\n            final QdrantClient unauthClient = new QdrantClient(\n                QdrantGrpcClient.newBuilder(qdrant.getHost(), qdrant.getGrpcPort(), false).build()\n            );\n\n            assertThatThrownBy(() -> unauthClient.healthCheckAsync().get()).isInstanceOf(ExecutionException.class);\n\n            unauthClient.close();\n\n            final QdrantClient client = new QdrantClient(\n                QdrantGrpcClient.newBuilder(qdrant.getHost(), qdrant.getGrpcPort(), false).withApiKey(apiKey).build()\n            );\n\n            QdrantOuterClass.HealthCheckReply healthCheckReply = client.healthCheckAsync().get();\n            assertThat(healthCheckReply.getVersion()).isEqualTo(\"1.7.4\");\n\n            client.close();\n        }\n    }\n\n    @Test\n    void shouldSetApiKeyUsingConfigFile() throws ExecutionException, InterruptedException {\n        String apiKey = UUID.randomUUID().toString();\n        String configFile = \"service:\\n    api_key: \" + apiKey;\n        try (\n            QdrantContainer qdrant = new QdrantContainer(\"qdrant/qdrant:v1.7.4\")\n                .withConfigFile(Transferable.of(configFile))\n        ) {\n            qdrant.start();\n\n            final QdrantClient unauthClient = new QdrantClient(\n                QdrantGrpcClient.newBuilder(qdrant.getHost(), qdrant.getGrpcPort(), false).build()\n            );\n\n            assertThatThrownBy(() -> unauthClient.healthCheckAsync().get()).isInstanceOf(ExecutionException.class);\n\n            unauthClient.close();\n\n            final QdrantClient client = new QdrantClient(\n                QdrantGrpcClient.newBuilder(qdrant.getHost(), qdrant.getGrpcPort(), false).withApiKey(apiKey).build()\n            );\n\n            QdrantOuterClass.HealthCheckReply healthCheckReply = client.healthCheckAsync().get();\n            assertThat(healthCheckReply.getVersion()).isEqualTo(\"1.7.4\");\n\n            client.close();\n        }\n    }\n}\n"
  },
  {
    "path": "modules/qdrant/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/questdb/build.gradle",
    "content": "description = \"Testcontainers :: QuestDB\"\n\ndependencies {\n    api project(':testcontainers')\n    api project(':testcontainers-jdbc')\n\n    testRuntimeOnly 'org.postgresql:postgresql:42.7.8'\n\n    testImplementation project(':testcontainers-jdbc-test')\n    testImplementation 'org.questdb:questdb:9.2.2'\n    testImplementation 'org.awaitility:awaitility:4.3.0'\n    testImplementation 'org.apache.httpcomponents:httpclient:4.5.14'\n}\n"
  },
  {
    "path": "modules/questdb/src/main/java/org/testcontainers/containers/LegacyQuestDBProvider.java",
    "content": "package org.testcontainers.containers;\n\n@Deprecated\npublic class LegacyQuestDBProvider extends JdbcDatabaseContainerProvider {\n\n    @Override\n    public boolean supports(String databaseType) {\n        return databaseType.equals(QuestDBContainer.LEGACY_DATABASE_PROVIDER);\n    }\n\n    @Override\n    public JdbcDatabaseContainer newInstance(String tag) {\n        return new QuestDBContainer(QuestDBContainer.DEFAULT_IMAGE_NAME.withTag(tag));\n    }\n}\n"
  },
  {
    "path": "modules/questdb/src/main/java/org/testcontainers/containers/QuestDBContainer.java",
    "content": "package org.testcontainers.containers;\n\nimport lombok.NonNull;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.utility.DockerImageName;\n\n/**\n * Testcontainers implementation for QuestDB.\n * <p>\n * Supported image: {@code questdb/questdb}\n * <p>\n * Exposed ports:\n * <ul>\n *     <li>Postgres: 8812</li>\n *     <li>HTTP: 9000</li>\n *     <li>ILP: 9009</li>\n * </ul>\n */\npublic class QuestDBContainer extends JdbcDatabaseContainer<QuestDBContainer> {\n\n    @Deprecated\n    static final String LEGACY_DATABASE_PROVIDER = \"postgresql\";\n\n    static final String DATABASE_PROVIDER = \"questdb\";\n\n    private static final String DEFAULT_DATABASE_NAME = \"qdb\";\n\n    private static final int DEFAULT_COMMIT_LAG_MS = 1000;\n\n    private static final String DEFAULT_USERNAME = \"admin\";\n\n    private static final String DEFAULT_PASSWORD = \"quest\";\n\n    private static final Integer POSTGRES_PORT = 8812;\n\n    private static final Integer REST_PORT = 9000;\n\n    private static final Integer ILP_PORT = 9009;\n\n    static final String TEST_QUERY = \"SELECT 1\";\n\n    static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"questdb/questdb\");\n\n    public QuestDBContainer(@NonNull String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    public QuestDBContainer(DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n        withExposedPorts(POSTGRES_PORT, REST_PORT, ILP_PORT);\n        addEnv(\"QDB_CAIRO_COMMIT_LAG\", String.valueOf(DEFAULT_COMMIT_LAG_MS));\n        waitingFor(Wait.forLogMessage(\"(?i).*A server-main enjoy.*\", 1));\n    }\n\n    @Override\n    public String getDriverClassName() {\n        return \"org.postgresql.Driver\";\n    }\n\n    @Override\n    public String getJdbcUrl() {\n        return String.format(\"jdbc:postgresql://%s:%d/%s\", getHost(), getMappedPort(8812), getDefaultDatabaseName());\n    }\n\n    @Override\n    public String getUsername() {\n        return DEFAULT_USERNAME;\n    }\n\n    @Override\n    public String getPassword() {\n        return DEFAULT_PASSWORD;\n    }\n\n    @Override\n    public String getTestQueryString() {\n        return TEST_QUERY;\n    }\n\n    @Override\n    protected void waitUntilContainerStarted() {\n        getWaitStrategy().waitUntilReady(this);\n    }\n\n    public String getDefaultDatabaseName() {\n        return DEFAULT_DATABASE_NAME;\n    }\n\n    public String getIlpUrl() {\n        return getHost() + \":\" + getMappedPort(ILP_PORT);\n    }\n\n    public String getHttpUrl() {\n        return \"http://\" + getHost() + \":\" + getMappedPort(REST_PORT);\n    }\n}\n"
  },
  {
    "path": "modules/questdb/src/main/java/org/testcontainers/containers/QuestDBProvider.java",
    "content": "package org.testcontainers.containers;\n\npublic class QuestDBProvider extends JdbcDatabaseContainerProvider {\n\n    @Override\n    public boolean supports(String databaseType) {\n        return databaseType.equals(QuestDBContainer.DATABASE_PROVIDER);\n    }\n\n    @Override\n    public JdbcDatabaseContainer newInstance(String tag) {\n        return new QuestDBContainer(QuestDBContainer.DEFAULT_IMAGE_NAME.withTag(tag));\n    }\n}\n"
  },
  {
    "path": "modules/questdb/src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider",
    "content": "org.testcontainers.containers.LegacyQuestDBProvider\norg.testcontainers.containers.QuestDBProvider\n"
  },
  {
    "path": "modules/questdb/src/test/java/org/testcontainers/QuestDBTestImages.java",
    "content": "package org.testcontainers;\n\nimport org.testcontainers.utility.DockerImageName;\n\npublic interface QuestDBTestImages {\n    DockerImageName QUESTDB_IMAGE = DockerImageName.parse(\"questdb/questdb:9.2.2\");\n}\n"
  },
  {
    "path": "modules/questdb/src/test/java/org/testcontainers/jdbc/questdb/QuestDBJDBCDriverTest.java",
    "content": "package org.testcontainers.jdbc.questdb;\n\nimport org.testcontainers.jdbc.AbstractJDBCDriverTest;\n\nimport java.util.Arrays;\nimport java.util.EnumSet;\n\nclass QuestDBJDBCDriverTest extends AbstractJDBCDriverTest {\n\n    public static Iterable<Object[]> data() {\n        return Arrays.asList(\n            new Object[][] {\n                { \"jdbc:tc:postgresql://hostname/databasename\", EnumSet.of(Options.PmdKnownBroken) },\n                { \"jdbc:tc:questdb://hostname/databasename\", EnumSet.of(Options.PmdKnownBroken) },\n            }\n        );\n    }\n}\n"
  },
  {
    "path": "modules/questdb/src/test/java/org/testcontainers/junit/questdb/SimpleQuestDBTest.java",
    "content": "package org.testcontainers.junit.questdb;\n\nimport io.questdb.client.Sender;\nimport org.apache.http.client.methods.CloseableHttpResponse;\nimport org.apache.http.client.methods.HttpGet;\nimport org.apache.http.impl.client.CloseableHttpClient;\nimport org.apache.http.impl.client.HttpClientBuilder;\nimport org.apache.http.util.EntityUtils;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.QuestDBTestImages;\nimport org.testcontainers.containers.QuestDBContainer;\nimport org.testcontainers.db.AbstractContainerDatabaseTest;\n\nimport java.io.IOException;\nimport java.net.URLEncoder;\nimport java.nio.charset.StandardCharsets;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.awaitility.Awaitility.await;\n\nclass SimpleQuestDBTest extends AbstractContainerDatabaseTest {\n\n    private static final String TABLE_NAME = \"mytable\";\n\n    @Test\n    void testSimple() throws SQLException {\n        try ( // container {\n            QuestDBContainer questDB = new QuestDBContainer(\"questdb/questdb:9.2.2\")\n            // }\n        ) {\n            questDB.start();\n\n            ResultSet resultSet = performQuery(questDB, questDB.getTestQueryString());\n\n            int resultSetInt = resultSet.getInt(1);\n            assertThat(resultSetInt).as(\"A basic SELECT query succeeds\").isEqualTo(1);\n        }\n    }\n\n    @Test\n    void testRest() throws IOException {\n        try (QuestDBContainer questdb = new QuestDBContainer(QuestDBTestImages.QUESTDB_IMAGE)) {\n            questdb.start();\n            populateByInfluxLineProtocol(questdb, 1_000);\n            try (CloseableHttpClient client = HttpClientBuilder.create().build()) {\n                String encodedSql = URLEncoder.encode(\"select * from \" + TABLE_NAME, \"UTF-8\");\n                HttpGet httpGet = new HttpGet(questdb.getHttpUrl() + \"/exec?query=\" + encodedSql);\n                await()\n                    .untilAsserted(() -> {\n                        try (CloseableHttpResponse response = client.execute(httpGet)) {\n                            assertThat(response.getStatusLine().getStatusCode()).isEqualTo(200);\n                            String json = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);\n                            assertThat(json.contains(\"\\\"count\\\":1000\")).isTrue();\n                        }\n                    });\n            }\n        }\n    }\n\n    private static void populateByInfluxLineProtocol(QuestDBContainer questdb, int rowCount) {\n        try (Sender sender = Sender.builder(Sender.Transport.TCP).address(questdb.getIlpUrl()).build()) {\n            for (int i = 0; i < rowCount; i++) {\n                sender\n                    .table(TABLE_NAME)\n                    .symbol(\"sym\", \"sym1\" + i)\n                    .stringColumn(\"str\", \"str1\" + i)\n                    .longColumn(\"long\", i)\n                    .atNow();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "modules/questdb/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/r2dbc/build.gradle",
    "content": "plugins {\n    id \"java-test-fixtures\"\n}\n\ndescription = \"Testcontainers :: R2DBC\"\n\ndependencies {\n    api project(':testcontainers')\n    api 'io.r2dbc:r2dbc-spi:0.9.0.RELEASE'\n\n    testImplementation 'io.r2dbc:r2dbc-postgresql:0.8.13.RELEASE'\n    testImplementation project(':testcontainers-postgresql')\n\n    testFixturesImplementation 'io.projectreactor:reactor-core:3.8.1'\n    testFixturesImplementation 'org.assertj:assertj-core:3.27.6'\n    testFixturesImplementation 'org.junit.jupiter:junit-jupiter:5.14.1'\n}\n"
  },
  {
    "path": "modules/r2dbc/src/main/java/org/testcontainers/r2dbc/CancellableSubscription.java",
    "content": "package org.testcontainers.r2dbc;\n\nimport org.reactivestreams.Subscription;\n\nimport java.util.concurrent.atomic.AtomicBoolean;\n\nclass CancellableSubscription implements Subscription {\n\n    private final AtomicBoolean cancelled = new AtomicBoolean();\n\n    @Override\n    public void request(long n) {}\n\n    @Override\n    public void cancel() {\n        cancelled.set(true);\n    }\n\n    public boolean isCancelled() {\n        return cancelled.get();\n    }\n}\n"
  },
  {
    "path": "modules/r2dbc/src/main/java/org/testcontainers/r2dbc/ConnectionPublisher.java",
    "content": "package org.testcontainers.r2dbc;\n\nimport io.r2dbc.spi.Connection;\nimport io.r2dbc.spi.ConnectionFactory;\nimport org.reactivestreams.Publisher;\nimport org.reactivestreams.Subscriber;\nimport org.reactivestreams.Subscription;\n\nimport java.util.concurrent.CompletableFuture;\nimport java.util.function.Supplier;\n\n/**\n * Design notes:\n * - ConnectionPublisher is Mono-like (0..1), the request amount is ignored\n * - given the testing nature, the performance requirements are less strict\n * - \"synchronized\" is used to avoid races\n * - Reactive Streams spec violations are not checked (e.g. non-positive request)\n */\nclass ConnectionPublisher implements Publisher<Connection> {\n\n    private final Supplier<CompletableFuture<ConnectionFactory>> futureSupplier;\n\n    ConnectionPublisher(Supplier<CompletableFuture<ConnectionFactory>> futureSupplier) {\n        this.futureSupplier = futureSupplier;\n    }\n\n    @Override\n    public void subscribe(Subscriber<? super Connection> actual) {\n        actual.onSubscribe(new StateMachineSubscription(actual));\n    }\n\n    private class StateMachineSubscription implements Subscription {\n\n        private final Subscriber<? super Connection> actual;\n\n        Subscription subscriptionState;\n\n        StateMachineSubscription(Subscriber<? super Connection> actual) {\n            this.actual = actual;\n            subscriptionState = new WaitRequestSubscriptionState();\n        }\n\n        @Override\n        public synchronized void request(long n) {\n            subscriptionState.request(n);\n        }\n\n        @Override\n        public synchronized void cancel() {\n            subscriptionState.cancel();\n        }\n\n        synchronized void transitionTo(SubscriptionState newState) {\n            subscriptionState = newState;\n            newState.enter();\n        }\n\n        abstract class SubscriptionState implements Subscription {\n\n            void enter() {}\n        }\n\n        class WaitRequestSubscriptionState extends SubscriptionState {\n\n            @Override\n            public void request(long n) {\n                transitionTo(new WaitFutureCompletionSubscriptionState());\n            }\n\n            @Override\n            public void cancel() {}\n        }\n\n        class WaitFutureCompletionSubscriptionState extends SubscriptionState {\n\n            private CompletableFuture<ConnectionFactory> future;\n\n            @Override\n            void enter() {\n                this.future = futureSupplier.get();\n\n                future.whenComplete((connectionFactory, e) -> {\n                    if (e != null) {\n                        actual.onSubscribe(EmptySubscription.INSTANCE);\n                        actual.onError(e);\n                        return;\n                    }\n\n                    Publisher<? extends Connection> publisher = connectionFactory.create();\n                    transitionTo(new ProxySubscriptionState(publisher));\n                });\n            }\n\n            @Override\n            public void request(long n) {}\n\n            @Override\n            public void cancel() {\n                future.cancel(true);\n            }\n        }\n\n        class ProxySubscriptionState extends SubscriptionState implements Subscriber<Connection> {\n\n            private final Publisher<? extends Connection> publisher;\n\n            private Subscription s;\n\n            private boolean cancelled = false;\n\n            ProxySubscriptionState(Publisher<? extends Connection> publisher) {\n                this.publisher = publisher;\n            }\n\n            @Override\n            void enter() {\n                publisher.subscribe(this);\n            }\n\n            @Override\n            public void request(long n) {\n                // Ignore\n            }\n\n            @Override\n            public synchronized void cancel() {\n                cancelled = true;\n                if (s != null) {\n                    s.cancel();\n                }\n            }\n\n            @Override\n            public synchronized void onSubscribe(Subscription s) {\n                this.s = s;\n                if (!cancelled) {\n                    s.request(1);\n                } else {\n                    s.cancel();\n                }\n            }\n\n            @Override\n            public void onNext(Connection connection) {\n                actual.onNext(connection);\n            }\n\n            @Override\n            public void onError(Throwable t) {\n                actual.onError(t);\n            }\n\n            @Override\n            public void onComplete() {\n                actual.onComplete();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "modules/r2dbc/src/main/java/org/testcontainers/r2dbc/EmptySubscription.java",
    "content": "package org.testcontainers.r2dbc;\n\nimport org.reactivestreams.Subscription;\n\nenum EmptySubscription implements Subscription {\n    INSTANCE;\n\n    @Override\n    public void request(long n) {}\n\n    @Override\n    public void cancel() {}\n}\n"
  },
  {
    "path": "modules/r2dbc/src/main/java/org/testcontainers/r2dbc/Hidden.java",
    "content": "package org.testcontainers.r2dbc;\n\nimport io.r2dbc.spi.ConnectionFactory;\nimport io.r2dbc.spi.ConnectionFactoryOptions;\nimport io.r2dbc.spi.ConnectionFactoryProvider;\n\n/**\n * Hide inner classes that must be public due to the way {@link java.util.ServiceLoader} works\n */\nclass Hidden {\n\n    public static final class TestcontainersR2DBCConnectionFactoryProvider implements ConnectionFactoryProvider {\n\n        public static final String DRIVER = \"tc\";\n\n        @Override\n        public ConnectionFactory create(ConnectionFactoryOptions options) {\n            options = sanitize(options);\n            options = removeProxying(options);\n\n            return new TestcontainersR2DBCConnectionFactory(options);\n        }\n\n        private ConnectionFactoryOptions sanitize(ConnectionFactoryOptions options) {\n            ConnectionFactoryOptions.Builder builder = options.mutate();\n\n            Object reusable = options.getValue(R2DBCDatabaseContainerProvider.REUSABLE_OPTION);\n            if (reusable instanceof String) {\n                builder.option(R2DBCDatabaseContainerProvider.REUSABLE_OPTION, Boolean.valueOf((String) reusable));\n            }\n            return builder.build();\n        }\n\n        private ConnectionFactoryOptions removeProxying(ConnectionFactoryOptions options) {\n            // To delegate to the next factory provider, inspect the PROTOCOL and convert it to the next DRIVER and PROTOCOL values.\n            //\n            // example:\n            //   | Property | Input           | Output       |\n            //   |----------|-----------------|--------------|\n            //   | DRIVER   | tc              | postgres     |\n            //   | PROTOCOL | postgres        | <empty>      |\n\n            String protocol = (String) options.getRequiredValue(ConnectionFactoryOptions.PROTOCOL);\n            if (protocol.trim().length() == 0) {\n                throw new IllegalArgumentException(\"Invalid protocol: \" + protocol);\n            }\n            String[] protocols = protocol.split(\":\", 2);\n            String driverDelegate = protocols[0];\n\n            // when protocol does NOT contain COLON, the length becomes 1\n            String protocolDelegate = protocols.length == 2 ? protocols[1] : \"\";\n\n            return ConnectionFactoryOptions\n                .builder()\n                .from(options)\n                .option(ConnectionFactoryOptions.DRIVER, driverDelegate)\n                .option(ConnectionFactoryOptions.PROTOCOL, protocolDelegate)\n                .build();\n        }\n\n        @Override\n        public boolean supports(ConnectionFactoryOptions options) {\n            return DRIVER.equals(options.getValue(ConnectionFactoryOptions.DRIVER));\n        }\n\n        @Override\n        public String getDriver() {\n            return DRIVER;\n        }\n    }\n}\n"
  },
  {
    "path": "modules/r2dbc/src/main/java/org/testcontainers/r2dbc/R2DBCDatabaseContainer.java",
    "content": "package org.testcontainers.r2dbc;\n\nimport io.r2dbc.spi.ConnectionFactoryOptions;\nimport org.testcontainers.lifecycle.Startable;\n\npublic interface R2DBCDatabaseContainer extends Startable {\n    ConnectionFactoryOptions configure(ConnectionFactoryOptions options);\n}\n"
  },
  {
    "path": "modules/r2dbc/src/main/java/org/testcontainers/r2dbc/R2DBCDatabaseContainerProvider.java",
    "content": "package org.testcontainers.r2dbc;\n\nimport io.r2dbc.spi.ConnectionFactories;\nimport io.r2dbc.spi.ConnectionFactory;\nimport io.r2dbc.spi.ConnectionFactoryMetadata;\nimport io.r2dbc.spi.ConnectionFactoryOptions;\nimport io.r2dbc.spi.Option;\nimport org.testcontainers.DockerClientFactory;\n\nimport javax.annotation.Nullable;\n\npublic interface R2DBCDatabaseContainerProvider {\n    Option<Boolean> REUSABLE_OPTION = Option.valueOf(\"TC_REUSABLE\");\n\n    Option<String> IMAGE_TAG_OPTION = Option.valueOf(\"TC_IMAGE_TAG\");\n\n    boolean supports(ConnectionFactoryOptions options);\n\n    R2DBCDatabaseContainer createContainer(ConnectionFactoryOptions options);\n\n    @Nullable\n    default ConnectionFactoryMetadata getMetadata(ConnectionFactoryOptions options) {\n        ConnectionFactoryOptions.Builder builder = options.mutate();\n        if (!options.hasOption(ConnectionFactoryOptions.HOST)) {\n            builder.option(ConnectionFactoryOptions.HOST, DockerClientFactory.instance().dockerHostIpAddress());\n        }\n        if (!options.hasOption(ConnectionFactoryOptions.PORT)) {\n            builder.option(ConnectionFactoryOptions.PORT, 65535);\n        }\n\n        ConnectionFactory connectionFactory = ConnectionFactories.find(builder.build());\n        return connectionFactory != null ? connectionFactory.getMetadata() : null;\n    }\n}\n"
  },
  {
    "path": "modules/r2dbc/src/main/java/org/testcontainers/r2dbc/TestcontainersR2DBCConnectionFactory.java",
    "content": "package org.testcontainers.r2dbc;\n\nimport io.r2dbc.spi.Closeable;\nimport io.r2dbc.spi.Connection;\nimport io.r2dbc.spi.ConnectionFactories;\nimport io.r2dbc.spi.ConnectionFactory;\nimport io.r2dbc.spi.ConnectionFactoryMetadata;\nimport io.r2dbc.spi.ConnectionFactoryOptions;\nimport org.reactivestreams.Publisher;\nimport org.testcontainers.lifecycle.Startable;\n\nimport java.util.ServiceLoader;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.Executor;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.atomic.AtomicLong;\nimport java.util.stream.StreamSupport;\n\nclass TestcontainersR2DBCConnectionFactory implements ConnectionFactory, Closeable {\n\n    private static final AtomicLong THREAD_COUNT = new AtomicLong();\n\n    private static final Executor EXECUTOR = Executors.newCachedThreadPool(r -> {\n        Thread thread = new Thread(r);\n        thread.setName(\"testcontainers-r2dbc-\" + THREAD_COUNT.getAndIncrement());\n        thread.setDaemon(true);\n        return thread;\n    });\n\n    private final ConnectionFactoryOptions options;\n\n    private final R2DBCDatabaseContainerProvider containerProvider;\n\n    private CompletableFuture<R2DBCDatabaseContainer> future;\n\n    TestcontainersR2DBCConnectionFactory(ConnectionFactoryOptions options) {\n        this.options = options;\n\n        containerProvider =\n            StreamSupport\n                .stream(ServiceLoader.load(R2DBCDatabaseContainerProvider.class).spliterator(), false)\n                .filter(it -> it.supports(options))\n                .findAny()\n                .orElseThrow(() -> new IllegalArgumentException(\"Missing provider for \" + options));\n    }\n\n    @Override\n    public Publisher<? extends Connection> create() {\n        return new ConnectionPublisher(() -> {\n            if (future == null) {\n                synchronized (this) {\n                    if (future == null) {\n                        future =\n                            CompletableFuture.supplyAsync(\n                                () -> {\n                                    R2DBCDatabaseContainer container = containerProvider.createContainer(options);\n                                    container.start();\n                                    return container;\n                                },\n                                EXECUTOR\n                            );\n                    }\n                }\n            }\n            return future.thenApply(it -> {\n                return ConnectionFactories.find(it.configure(options));\n            });\n        });\n    }\n\n    @Override\n    public ConnectionFactoryMetadata getMetadata() {\n        return containerProvider.getMetadata(options);\n    }\n\n    @Override\n    public Publisher<Void> close() {\n        return s -> {\n            CompletableFuture<R2DBCDatabaseContainer> futureRef;\n            synchronized (this) {\n                futureRef = this.future;\n                this.future = null;\n            }\n\n            CancellableSubscription subscription = new CancellableSubscription();\n            s.onSubscribe(subscription);\n\n            if (futureRef == null) {\n                if (!subscription.isCancelled()) {\n                    s.onComplete();\n                }\n            } else {\n                futureRef.thenAcceptAsync(Startable::stop, EXECUTOR);\n\n                EXECUTOR.execute(() -> {\n                    futureRef.cancel(true);\n                    if (!subscription.isCancelled()) {\n                        s.onComplete();\n                    }\n                });\n            }\n        };\n    }\n}\n"
  },
  {
    "path": "modules/r2dbc/src/main/resources/META-INF/services/io.r2dbc.spi.ConnectionFactoryProvider",
    "content": "org.testcontainers.r2dbc.Hidden$TestcontainersR2DBCConnectionFactoryProvider\n"
  },
  {
    "path": "modules/r2dbc/src/test/java/org/testcontainers/r2dbc/TestcontainersR2DBCConnectionFactoryTest.java",
    "content": "package org.testcontainers.r2dbc;\n\nimport io.r2dbc.postgresql.api.PostgresqlException;\nimport io.r2dbc.spi.Closeable;\nimport io.r2dbc.spi.Connection;\nimport io.r2dbc.spi.ConnectionFactories;\nimport io.r2dbc.spi.ConnectionFactory;\nimport io.r2dbc.spi.Result;\nimport org.junit.jupiter.api.Test;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\n\nimport java.util.UUID;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\n\nclass TestcontainersR2DBCConnectionFactoryTest {\n\n    @Test\n    void failsOnUnknownProvider() {\n        String nonExistingProvider = UUID.randomUUID().toString();\n        assertThatThrownBy(() -> {\n                ConnectionFactories.get(String.format(\"r2dbc:tc:%s:///db\", nonExistingProvider));\n            })\n            .hasMessageContaining(\"Missing provider\")\n            .hasMessageContaining(nonExistingProvider);\n    }\n\n    @Test\n    void reusesUntilConnectionFactoryIsClosed() {\n        String url = \"r2dbc:tc:postgresql:///db?TC_IMAGE_TAG=10-alpine\";\n        ConnectionFactory connectionFactory = ConnectionFactories.get(url);\n\n        Integer updated = Flux\n            .usingWhen(\n                connectionFactory.create(),\n                connection -> {\n                    return Mono\n                        .from(connection.createStatement(\"CREATE TABLE test(id integer PRIMARY KEY)\").execute())\n                        .thenMany(connection.createStatement(\"INSERT INTO test(id) VALUES(123)\").execute())\n                        .flatMap(Result::getRowsUpdated);\n                },\n                Connection::close\n            )\n            .blockFirst();\n\n        assertThat(updated).isEqualTo(1);\n\n        Flux<Long> select = Flux.usingWhen(\n            Flux.defer(connectionFactory::create),\n            connection -> {\n                return Flux\n                    .from(connection.createStatement(\"SELECT COUNT(*) FROM test\").execute())\n                    .flatMap(it -> it.map((row, meta) -> (Long) row.get(0)));\n            },\n            Connection::close\n        );\n\n        Long rows = select.blockFirst();\n\n        assertThat(rows).isEqualTo(1);\n\n        close(connectionFactory);\n\n        assertThatThrownBy(select::blockFirst)\n            .isInstanceOf(PostgresqlException.class)\n            // relation \"X\" does not exists\n            // https://github.com/postgres/postgres/blob/REL_10_0/src/backend/utils/errcodes.txt#L349\n            .returns(\"42P01\", e -> ((PostgresqlException) e).getErrorDetails().getCode());\n    }\n\n    private static void close(ConnectionFactory connectionFactory) {\n        Mono.from(((Closeable) connectionFactory).close()).block();\n    }\n}\n"
  },
  {
    "path": "modules/r2dbc/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/r2dbc/src/testFixtures/java/org/testcontainers/r2dbc/AbstractR2DBCDatabaseContainerTest.java",
    "content": "package org.testcontainers.r2dbc;\n\nimport io.r2dbc.spi.Closeable;\nimport io.r2dbc.spi.Connection;\nimport io.r2dbc.spi.ConnectionFactories;\nimport io.r2dbc.spi.ConnectionFactory;\nimport io.r2dbc.spi.ConnectionFactoryMetadata;\nimport io.r2dbc.spi.ConnectionFactoryOptions;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.GenericContainer;\nimport reactor.core.publisher.Flux;\nimport reactor.core.publisher.Mono;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\npublic abstract class AbstractR2DBCDatabaseContainerTest<T extends GenericContainer<?>> {\n\n    protected abstract ConnectionFactoryOptions getOptions(T container);\n\n    protected abstract String createR2DBCUrl();\n\n    protected String query() {\n        return \"SELECT %d\";\n    }\n\n    protected String createTestQuery(int result) {\n        return String.format(query(), result);\n    }\n\n    @Test\n    void testGetOptions() {\n        try (T container = createContainer()) {\n            container.start();\n\n            ConnectionFactory connectionFactory = ConnectionFactories.get(getOptions(container));\n            runTestQuery(connectionFactory);\n        }\n    }\n\n    @Test\n    void testUrlSupport() {\n        ConnectionFactory connectionFactory = ConnectionFactories.get(createR2DBCUrl());\n        runTestQuery(connectionFactory);\n    }\n\n    @Test\n    void testGetMetadata() {\n        ConnectionFactory connectionFactory = ConnectionFactories.get(createR2DBCUrl());\n        ConnectionFactoryMetadata metadata = connectionFactory.getMetadata();\n        assertThat(metadata).isNotNull();\n    }\n\n    protected abstract T createContainer();\n\n    protected void runTestQuery(ConnectionFactory connectionFactory) {\n        try {\n            int expected = 42;\n            Number result = Flux\n                .usingWhen(\n                    connectionFactory.create(),\n                    connection -> connection.createStatement(createTestQuery(expected)).execute(),\n                    Connection::close\n                )\n                .flatMap(it -> it.map((row, meta) -> (Number) row.get(0)))\n                .blockFirst();\n\n            assertThat(result).isNotNull().returns(expected, Number::intValue);\n        } finally {\n            if (connectionFactory instanceof Closeable) {\n                Mono.from(((Closeable) connectionFactory).close()).block();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "modules/rabbitmq/build.gradle",
    "content": "description = \"Testcontainers :: RabbitMQ\"\n\ndependencies {\n    api project(\":testcontainers\")\n\n    testImplementation 'com.rabbitmq:amqp-client:5.28.0'\n    compileOnly 'org.jetbrains:annotations:26.0.2-1'\n}\n"
  },
  {
    "path": "modules/rabbitmq/src/main/java/org/testcontainers/containers/RabbitMQContainer.java",
    "content": "package org.testcontainers.containers;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.github.dockerjava.api.command.InspectContainerResponse;\nimport org.jetbrains.annotations.NotNull;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.MountableFile;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\n/**\n * Testcontainers implementation for RabbitMQ.\n * <p>\n * Supported image: {@code rabbitmq}\n * <p>\n * Exposed ports:\n * <ul>\n *     <li>5671 (AMQPS)</li>\n *     <li>5672 (AMQP)</li>\n *     <li>15671 (HTTPS)</li>\n *     <li>15672 (HTTP)</li>\n * </ul>\n *\n * @deprecated use {@link org.testcontainers.rabbitmq.RabbitMQContainer} instead.\n */\n@Deprecated\npublic class RabbitMQContainer extends GenericContainer<RabbitMQContainer> {\n\n    /**\n     * The image defaults to the official RabbitMQ image: <a href=\"https://hub.docker.com/_/rabbitmq/\">RabbitMQ</a>.\n     */\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"rabbitmq\");\n\n    private static final String DEFAULT_TAG = \"3.7.25-management-alpine\";\n\n    private static final int DEFAULT_AMQP_PORT = 5672;\n\n    private static final int DEFAULT_AMQPS_PORT = 5671;\n\n    private static final int DEFAULT_HTTPS_PORT = 15671;\n\n    private static final int DEFAULT_HTTP_PORT = 15672;\n\n    private String adminPassword = \"guest\";\n\n    private String adminUsername = \"guest\";\n\n    private final List<List<String>> values = new ArrayList<>();\n\n    /**\n     * Creates a RabbitMQ container using the official RabbitMQ docker image.\n     * @deprecated use {@link #RabbitMQContainer(DockerImageName)} instead\n     */\n    @Deprecated\n    public RabbitMQContainer() {\n        this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG));\n    }\n\n    /**\n     * Creates a RabbitMQ container using a specific docker image.\n     *\n     * @param dockerImageName The docker image to use.\n     */\n    public RabbitMQContainer(String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    public RabbitMQContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n\n        addExposedPorts(DEFAULT_AMQP_PORT, DEFAULT_AMQPS_PORT, DEFAULT_HTTP_PORT, DEFAULT_HTTPS_PORT);\n\n        waitingFor(Wait.forLogMessage(\".*Server startup complete.*\", 1));\n    }\n\n    @Override\n    protected void configure() {\n        if (this.adminUsername != null) {\n            addEnv(\"RABBITMQ_DEFAULT_USER\", this.adminUsername);\n        }\n        if (this.adminPassword != null) {\n            addEnv(\"RABBITMQ_DEFAULT_PASS\", this.adminPassword);\n        }\n    }\n\n    @Override\n    protected void containerIsStarted(InspectContainerResponse containerInfo) {\n        values.forEach(command -> {\n            try {\n                ExecResult execResult = execInContainer(command.toArray(new String[0]));\n                if (execResult.getExitCode() != 0) {\n                    logger().error(\"Could not execute command {}: {}\", command, execResult.getStderr());\n                }\n            } catch (IOException | InterruptedException e) {\n                logger().error(\"Could not execute command {}: {}\", command, e.getMessage());\n            }\n        });\n    }\n\n    /**\n     * @return The admin password for the <code>admin</code> account\n     */\n    public String getAdminPassword() {\n        return this.adminPassword;\n    }\n\n    /**\n     * @return The admin user for the <code>admin</code> account\n     */\n    public String getAdminUsername() {\n        return this.adminUsername;\n    }\n\n    public Integer getAmqpPort() {\n        return getMappedPort(DEFAULT_AMQP_PORT);\n    }\n\n    public Integer getAmqpsPort() {\n        return getMappedPort(DEFAULT_AMQPS_PORT);\n    }\n\n    public Integer getHttpsPort() {\n        return getMappedPort(DEFAULT_HTTPS_PORT);\n    }\n\n    public Integer getHttpPort() {\n        return getMappedPort(DEFAULT_HTTP_PORT);\n    }\n\n    /**\n     * @return AMQP URL for use with AMQP clients.\n     */\n    public String getAmqpUrl() {\n        return \"amqp://\" + getHost() + \":\" + getAmqpPort();\n    }\n\n    /**\n     * @return AMQPS URL for use with AMQPS clients.\n     */\n    public String getAmqpsUrl() {\n        return \"amqps://\" + getHost() + \":\" + getAmqpsPort();\n    }\n\n    /**\n     * @return URL of the HTTP management endpoint.\n     */\n    public String getHttpUrl() {\n        return \"http://\" + getHost() + \":\" + getHttpPort();\n    }\n\n    /**\n     * @return URL of the HTTPS management endpoint.\n     */\n    public String getHttpsUrl() {\n        return \"https://\" + getHost() + \":\" + getHttpsPort();\n    }\n\n    /**\n     * Sets the user for the admin (default is <pre>guest</pre>)\n     *\n     * @param adminUsername The admin user.\n     * @return This container.\n     */\n    public RabbitMQContainer withAdminUser(final String adminUsername) {\n        this.adminUsername = adminUsername;\n        return this;\n    }\n\n    /**\n     * Sets the password for the admin (default is <pre>guest</pre>)\n     *\n     * @param adminPassword The admin password.\n     * @return This container.\n     */\n    public RabbitMQContainer withAdminPassword(final String adminPassword) {\n        this.adminPassword = adminPassword;\n        return this;\n    }\n\n    public enum SslVerification {\n        VERIFY_NONE(\"verify_none\"),\n        VERIFY_PEER(\"verify_peer\");\n\n        SslVerification(String value) {\n            this.value = value;\n        }\n\n        private final String value;\n    }\n\n    public RabbitMQContainer withSSL(\n        final MountableFile keyFile,\n        final MountableFile certFile,\n        final MountableFile caFile,\n        final SslVerification verify,\n        boolean failIfNoCert,\n        int verificationDepth\n    ) {\n        return withSSL(keyFile, certFile, caFile, verify, failIfNoCert)\n            .withEnv(\"RABBITMQ_SSL_DEPTH\", String.valueOf(verificationDepth));\n    }\n\n    public RabbitMQContainer withSSL(\n        final MountableFile keyFile,\n        final MountableFile certFile,\n        final MountableFile caFile,\n        final SslVerification verify,\n        boolean failIfNoCert\n    ) {\n        return withSSL(keyFile, certFile, caFile, verify)\n            .withEnv(\"RABBITMQ_SSL_FAIL_IF_NO_PEER_CERT\", String.valueOf(failIfNoCert));\n    }\n\n    public RabbitMQContainer withSSL(\n        final MountableFile keyFile,\n        final MountableFile certFile,\n        final MountableFile caFile,\n        final SslVerification verify\n    ) {\n        return withEnv(\"RABBITMQ_SSL_CACERTFILE\", \"/etc/rabbitmq/ca_cert.pem\")\n            .withEnv(\"RABBITMQ_SSL_CERTFILE\", \"/etc/rabbitmq/rabbitmq_cert.pem\")\n            .withEnv(\"RABBITMQ_SSL_KEYFILE\", \"/etc/rabbitmq/rabbitmq_key.pem\")\n            .withEnv(\"RABBITMQ_SSL_VERIFY\", verify.value)\n            .withCopyFileToContainer(certFile, \"/etc/rabbitmq/rabbitmq_cert.pem\")\n            .withCopyFileToContainer(caFile, \"/etc/rabbitmq/ca_cert.pem\")\n            .withCopyFileToContainer(keyFile, \"/etc/rabbitmq/rabbitmq_key.pem\");\n    }\n\n    /**\n     * @deprecated use {@link #execInContainer(String...)} instead\n     */\n    @Deprecated\n    public RabbitMQContainer withPluginsEnabled(String... pluginNames) {\n        List<String> command = new ArrayList<>(Arrays.asList(\"rabbitmq-plugins\", \"enable\"));\n        command.addAll(Arrays.asList(pluginNames));\n        values.add(command);\n        return self();\n    }\n\n    /**\n     * @deprecated use {@link #execInContainer(String...)} instead\n     */\n    @Deprecated\n    public RabbitMQContainer withBinding(String source, String destination) {\n        values.add(\n            Arrays.asList(\"rabbitmqadmin\", \"declare\", \"binding\", \"source=\" + source, \"destination=\" + destination)\n        );\n        return self();\n    }\n\n    /**\n     * @deprecated use {@link #execInContainer(String...)} instead\n     */\n    @Deprecated\n    public RabbitMQContainer withBinding(String vhost, String source, String destination) {\n        values.add(\n            Arrays.asList(\n                \"rabbitmqadmin\",\n                \"--vhost=\" + vhost,\n                \"declare\",\n                \"binding\",\n                \"source=\" + source,\n                \"destination=\" + destination\n            )\n        );\n        return self();\n    }\n\n    /**\n     * @deprecated use {@link #execInContainer(String...)} instead\n     */\n    @Deprecated\n    public RabbitMQContainer withBinding(\n        String source,\n        String destination,\n        Map<String, Object> arguments,\n        String routingKey,\n        String destinationType\n    ) {\n        values.add(\n            Arrays.asList(\n                \"rabbitmqadmin\",\n                \"declare\",\n                \"binding\",\n                \"source=\" + source,\n                \"destination=\" + destination,\n                \"routing_key=\" + routingKey,\n                \"destination_type=\" + destinationType,\n                \"arguments=\" + toJson(arguments)\n            )\n        );\n        return self();\n    }\n\n    /**\n     * @deprecated use {@link #execInContainer(String...)} instead\n     */\n    @Deprecated\n    public RabbitMQContainer withBinding(\n        String vhost,\n        String source,\n        String destination,\n        Map<String, Object> arguments,\n        String routingKey,\n        String destinationType\n    ) {\n        values.add(\n            Arrays.asList(\n                \"rabbitmqadmin\",\n                \"--vhost=\" + vhost,\n                \"declare\",\n                \"binding\",\n                \"source=\" + source,\n                \"destination=\" + destination,\n                \"routing_key=\" + routingKey,\n                \"destination_type=\" + destinationType,\n                \"arguments=\" + toJson(arguments)\n            )\n        );\n        return self();\n    }\n\n    /**\n     * @deprecated use {@link #execInContainer(String...)} instead\n     */\n    @Deprecated\n    public RabbitMQContainer withParameter(String component, String name, String value) {\n        values.add(\n            Arrays.asList(\n                \"rabbitmqadmin\",\n                \"declare\",\n                \"parameter\",\n                \"component=\" + component,\n                \"name=\" + name,\n                \"value=\" + value\n            )\n        );\n        return self();\n    }\n\n    /**\n     * @deprecated use {@link #execInContainer(String...)} instead\n     */\n    @Deprecated\n    public RabbitMQContainer withPermission(String vhost, String user, String configure, String write, String read) {\n        values.add(\n            Arrays.asList(\n                \"rabbitmqadmin\",\n                \"declare\",\n                \"permission\",\n                \"vhost=\" + vhost,\n                \"user=\" + user,\n                \"configure=\" + configure,\n                \"write=\" + write,\n                \"read=\" + read\n            )\n        );\n        return self();\n    }\n\n    /**\n     * @deprecated use {@link #execInContainer(String...)} instead\n     */\n    @Deprecated\n    public RabbitMQContainer withUser(String name, String password) {\n        values.add(Arrays.asList(\"rabbitmqadmin\", \"declare\", \"user\", \"name=\" + name, \"password=\" + password, \"tags=\"));\n        return self();\n    }\n\n    /**\n     * @deprecated use {@link #execInContainer(String...)} instead\n     */\n    @Deprecated\n    public RabbitMQContainer withUser(String name, String password, Set<String> tags) {\n        values.add(\n            Arrays.asList(\n                \"rabbitmqadmin\",\n                \"declare\",\n                \"user\",\n                \"name=\" + name,\n                \"password=\" + password,\n                \"tags=\" + String.join(\",\", tags)\n            )\n        );\n        return self();\n    }\n\n    /**\n     * @deprecated use {@link #execInContainer(String...)} instead\n     */\n    @Deprecated\n    public RabbitMQContainer withPolicy(String name, String pattern, Map<String, Object> definition) {\n        values.add(\n            Arrays.asList(\n                \"rabbitmqadmin\",\n                \"declare\",\n                \"policy\",\n                \"name=\" + name,\n                \"pattern=\" + pattern,\n                \"definition=\" + toJson(definition)\n            )\n        );\n        return self();\n    }\n\n    /**\n     * @deprecated use {@link #execInContainer(String...)} instead\n     */\n    @Deprecated\n    public RabbitMQContainer withPolicy(String vhost, String name, String pattern, Map<String, Object> definition) {\n        values.add(\n            Arrays.asList(\n                \"rabbitmqadmin\",\n                \"declare\",\n                \"policy\",\n                \"--vhost=\" + vhost,\n                \"name=\" + name,\n                \"pattern=\" + pattern,\n                \"definition=\" + toJson(definition)\n            )\n        );\n        return self();\n    }\n\n    /**\n     * @deprecated use {@link #execInContainer(String...)} instead\n     */\n    @Deprecated\n    public RabbitMQContainer withPolicy(\n        String name,\n        String pattern,\n        Map<String, Object> definition,\n        int priority,\n        String applyTo\n    ) {\n        values.add(\n            Arrays.asList(\n                \"rabbitmqadmin\",\n                \"declare\",\n                \"policy\",\n                \"name=\" + name,\n                \"pattern=\" + pattern,\n                \"priority=\" + priority,\n                \"apply-to=\" + applyTo,\n                \"definition=\" + toJson(definition)\n            )\n        );\n        return self();\n    }\n\n    /**\n     * @deprecated use {@link #execInContainer(String...)} instead\n     */\n    @Deprecated\n    public RabbitMQContainer withOperatorPolicy(String name, String pattern, Map<String, Object> definition) {\n        values.add(\n            new ArrayList<>(\n                Arrays.asList(\n                    \"rabbitmqadmin\",\n                    \"declare\",\n                    \"operator_policy\",\n                    \"name=\" + name,\n                    \"pattern=\" + pattern,\n                    \"definition=\" + toJson(definition)\n                )\n            )\n        );\n        return self();\n    }\n\n    /**\n     * @deprecated use {@link #execInContainer(String...)} instead\n     */\n    @Deprecated\n    public RabbitMQContainer withOperatorPolicy(\n        String name,\n        String pattern,\n        Map<String, Object> definition,\n        int priority,\n        String applyTo\n    ) {\n        values.add(\n            Arrays.asList(\n                \"rabbitmqadmin\",\n                \"declare\",\n                \"operator_policy\",\n                \"name=\" + name,\n                \"pattern=\" + pattern,\n                \"priority=\" + priority,\n                \"apply-to=\" + applyTo,\n                \"definition=\" + toJson(definition)\n            )\n        );\n        return self();\n    }\n\n    /**\n     * @deprecated use {@link #execInContainer(String...)} instead\n     */\n    @Deprecated\n    public RabbitMQContainer withVhost(String name) {\n        values.add(Arrays.asList(\"rabbitmqadmin\", \"declare\", \"vhost\", \"name=\" + name));\n        return self();\n    }\n\n    /**\n     * @deprecated use {@link #execInContainer(String...)} instead\n     */\n    @Deprecated\n    public RabbitMQContainer withVhost(String name, boolean tracing) {\n        values.add(Arrays.asList(\"rabbitmqadmin\", \"declare\", \"vhost\", \"name=\" + name, \"tracing=\" + tracing));\n        return self();\n    }\n\n    /**\n     * @deprecated use {@link #execInContainer(String...)} instead\n     */\n    @Deprecated\n    public RabbitMQContainer withVhostLimit(String vhost, String name, int value) {\n        values.add(\n            Arrays.asList(\"rabbitmqadmin\", \"declare\", \"vhost_limit\", \"vhost=\" + vhost, \"name=\" + name, \"value=\" + value)\n        );\n        return self();\n    }\n\n    /**\n     * @deprecated use {@link #execInContainer(String...)} instead\n     */\n    @Deprecated\n    public RabbitMQContainer withQueue(String name) {\n        values.add(Arrays.asList(\"rabbitmqadmin\", \"declare\", \"queue\", \"name=\" + name));\n        return self();\n    }\n\n    /**\n     * @deprecated use {@link #execInContainer(String...)} instead\n     */\n    @Deprecated\n    public RabbitMQContainer withQueue(String vhost, String name) {\n        values.add(Arrays.asList(\"rabbitmqadmin\", \"--vhost=\" + vhost, \"declare\", \"queue\", \"name=\" + name));\n        return self();\n    }\n\n    /**\n     * @deprecated use {@link #execInContainer(String...)} instead\n     */\n    @Deprecated\n    public RabbitMQContainer withQueue(\n        String name,\n        boolean autoDelete,\n        boolean durable,\n        Map<String, Object> arguments\n    ) {\n        values.add(\n            Arrays.asList(\n                \"rabbitmqadmin\",\n                \"declare\",\n                \"queue\",\n                \"name=\" + name,\n                \"auto_delete=\" + autoDelete,\n                \"durable=\" + durable,\n                \"arguments=\" + toJson(arguments)\n            )\n        );\n        return self();\n    }\n\n    /**\n     * @deprecated use {@link #execInContainer(String...)} instead\n     */\n    @Deprecated\n    public RabbitMQContainer withQueue(\n        String vhost,\n        String name,\n        boolean autoDelete,\n        boolean durable,\n        Map<String, Object> arguments\n    ) {\n        values.add(\n            Arrays.asList(\n                \"rabbitmqadmin\",\n                \"--vhost=\" + vhost,\n                \"declare\",\n                \"queue\",\n                \"name=\" + name,\n                \"auto_delete=\" + autoDelete,\n                \"durable=\" + durable,\n                \"arguments=\" + toJson(arguments)\n            )\n        );\n        return self();\n    }\n\n    /**\n     * @deprecated use {@link #execInContainer(String...)} instead\n     */\n    @Deprecated\n    public RabbitMQContainer withExchange(String name, String type) {\n        values.add(Arrays.asList(\"rabbitmqadmin\", \"declare\", \"exchange\", \"name=\" + name, \"type=\" + type));\n        return self();\n    }\n\n    /**\n     * @deprecated use {@link #execInContainer(String...)} instead\n     */\n    @Deprecated\n    public RabbitMQContainer withExchange(String vhost, String name, String type) {\n        values.add(\n            Arrays.asList(\"rabbitmqadmin\", \"--vhost=\" + vhost, \"declare\", \"exchange\", \"name=\" + name, \"type=\" + type)\n        );\n        return self();\n    }\n\n    /**\n     * @deprecated use {@link #execInContainer(String...)} instead\n     */\n    @Deprecated\n    public RabbitMQContainer withExchange(\n        String name,\n        String type,\n        boolean autoDelete,\n        boolean internal,\n        boolean durable,\n        Map<String, Object> arguments\n    ) {\n        values.add(\n            Arrays.asList(\n                \"rabbitmqadmin\",\n                \"declare\",\n                \"exchange\",\n                \"name=\" + name,\n                \"type=\" + type,\n                \"auto_delete=\" + autoDelete,\n                \"internal=\" + internal,\n                \"durable=\" + durable,\n                \"arguments=\" + toJson(arguments)\n            )\n        );\n        return self();\n    }\n\n    /**\n     * @deprecated use {@link #execInContainer(String...)} instead\n     */\n    @Deprecated\n    public RabbitMQContainer withExchange(\n        String vhost,\n        String name,\n        String type,\n        boolean autoDelete,\n        boolean internal,\n        boolean durable,\n        Map<String, Object> arguments\n    ) {\n        values.add(\n            Arrays.asList(\n                \"rabbitmqadmin\",\n                \"--vhost=\" + vhost,\n                \"declare\",\n                \"exchange\",\n                \"name=\" + name,\n                \"type=\" + type,\n                \"auto_delete=\" + autoDelete,\n                \"internal=\" + internal,\n                \"durable=\" + durable,\n                \"arguments=\" + toJson(arguments)\n            )\n        );\n        return self();\n    }\n\n    /**\n     * Overwrites the default RabbitMQ configuration file with the supplied one.\n     *\n     * @param rabbitMQConf The rabbitmq.conf file to use (in sysctl format, don't forget empty line in the end of file)\n     * @return This container.\n     */\n    public RabbitMQContainer withRabbitMQConfig(MountableFile rabbitMQConf) {\n        return withRabbitMQConfigSysctl(rabbitMQConf);\n    }\n\n    /**\n     * Overwrites the default RabbitMQ configuration file with the supplied one.\n     *\n     * This function doesn't work with RabbitMQ &lt; 3.7.\n     *\n     * This function and the Sysctl format is recommended for RabbitMQ &gt;= 3.7\n     *\n     * @param rabbitMQConf The rabbitmq.config file to use (in sysctl format, don't forget empty line in the end of file)\n     * @return This container.\n     */\n    public RabbitMQContainer withRabbitMQConfigSysctl(MountableFile rabbitMQConf) {\n        withEnv(\"RABBITMQ_CONFIG_FILE\", \"/etc/rabbitmq/rabbitmq-custom.conf\");\n        return withCopyFileToContainer(rabbitMQConf, \"/etc/rabbitmq/rabbitmq-custom.conf\");\n    }\n\n    /**\n     * Overwrites the default RabbitMQ configuration file with the supplied one.\n     *\n     * @param rabbitMQConf The rabbitmq.config file to use (in erlang format)\n     * @return This container.\n     */\n    public RabbitMQContainer withRabbitMQConfigErlang(MountableFile rabbitMQConf) {\n        withEnv(\"RABBITMQ_CONFIG_FILE\", \"/etc/rabbitmq/rabbitmq-custom.config\");\n        return withCopyFileToContainer(rabbitMQConf, \"/etc/rabbitmq/rabbitmq-custom.config\");\n    }\n\n    @NotNull\n    private String toJson(Map<String, Object> arguments) {\n        try {\n            return new ObjectMapper().writeValueAsString(arguments);\n        } catch (JsonProcessingException e) {\n            throw new RuntimeException(\"Failed to convert arguments into json: \" + e.getMessage(), e);\n        }\n    }\n}\n"
  },
  {
    "path": "modules/rabbitmq/src/main/java/org/testcontainers/rabbitmq/RabbitMQContainer.java",
    "content": "package org.testcontainers.rabbitmq;\n\nimport com.github.dockerjava.api.command.InspectContainerResponse;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.MountableFile;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Testcontainers implementation for RabbitMQ.\n * <p>\n * Supported image: {@code rabbitmq}\n * <p>\n * Exposed ports:\n * <ul>\n *     <li>5671 (AMQPS)</li>\n *     <li>5672 (AMQP)</li>\n *     <li>15671 (HTTPS)</li>\n *     <li>15672 (HTTP)</li>\n * </ul>\n */\npublic class RabbitMQContainer extends GenericContainer<RabbitMQContainer> {\n\n    /**\n     * The image defaults to the official RabbitMQ image: <a href=\"https://hub.docker.com/_/rabbitmq/\">RabbitMQ</a>.\n     */\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"rabbitmq\");\n\n    private static final int DEFAULT_AMQP_PORT = 5672;\n\n    private static final int DEFAULT_AMQPS_PORT = 5671;\n\n    private static final int DEFAULT_HTTPS_PORT = 15671;\n\n    private static final int DEFAULT_HTTP_PORT = 15672;\n\n    private String adminPassword = \"guest\";\n\n    private String adminUsername = \"guest\";\n\n    private final List<List<String>> values = new ArrayList<>();\n\n    /**\n     * Creates a RabbitMQ container using a specific docker image.\n     *\n     * @param dockerImageName The docker image to use.\n     */\n    public RabbitMQContainer(String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    public RabbitMQContainer(DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n\n        addExposedPorts(DEFAULT_AMQP_PORT, DEFAULT_AMQPS_PORT, DEFAULT_HTTP_PORT, DEFAULT_HTTPS_PORT);\n\n        waitingFor(Wait.forLogMessage(\".*Server startup complete.*\", 1));\n    }\n\n    @Override\n    protected void configure() {\n        if (this.adminUsername != null) {\n            addEnv(\"RABBITMQ_DEFAULT_USER\", this.adminUsername);\n        }\n        if (this.adminPassword != null) {\n            addEnv(\"RABBITMQ_DEFAULT_PASS\", this.adminPassword);\n        }\n    }\n\n    @Override\n    protected void containerIsStarted(InspectContainerResponse containerInfo) {\n        values.forEach(command -> {\n            try {\n                ExecResult execResult = execInContainer(command.toArray(new String[0]));\n                if (execResult.getExitCode() != 0) {\n                    logger().error(\"Could not execute command {}: {}\", command, execResult.getStderr());\n                }\n            } catch (IOException | InterruptedException e) {\n                logger().error(\"Could not execute command {}: {}\", command, e.getMessage());\n            }\n        });\n    }\n\n    /**\n     * @return The admin password for the <code>admin</code> account\n     */\n    public String getAdminPassword() {\n        return this.adminPassword;\n    }\n\n    /**\n     * @return The admin user for the <code>admin</code> account\n     */\n    public String getAdminUsername() {\n        return this.adminUsername;\n    }\n\n    public Integer getAmqpPort() {\n        return getMappedPort(DEFAULT_AMQP_PORT);\n    }\n\n    public Integer getAmqpsPort() {\n        return getMappedPort(DEFAULT_AMQPS_PORT);\n    }\n\n    public Integer getHttpsPort() {\n        return getMappedPort(DEFAULT_HTTPS_PORT);\n    }\n\n    public Integer getHttpPort() {\n        return getMappedPort(DEFAULT_HTTP_PORT);\n    }\n\n    /**\n     * @return AMQP URL for use with AMQP clients.\n     */\n    public String getAmqpUrl() {\n        return \"amqp://\" + getHost() + \":\" + getAmqpPort();\n    }\n\n    /**\n     * @return AMQPS URL for use with AMQPS clients.\n     */\n    public String getAmqpsUrl() {\n        return \"amqps://\" + getHost() + \":\" + getAmqpsPort();\n    }\n\n    /**\n     * @return URL of the HTTP management endpoint.\n     */\n    public String getHttpUrl() {\n        return \"http://\" + getHost() + \":\" + getHttpPort();\n    }\n\n    /**\n     * @return URL of the HTTPS management endpoint.\n     */\n    public String getHttpsUrl() {\n        return \"https://\" + getHost() + \":\" + getHttpsPort();\n    }\n\n    /**\n     * Sets the user for the admin (default is <pre>guest</pre>)\n     *\n     * @param adminUsername The admin user.\n     * @return This container.\n     */\n    public RabbitMQContainer withAdminUser(final String adminUsername) {\n        this.adminUsername = adminUsername;\n        return this;\n    }\n\n    /**\n     * Sets the password for the admin (default is <pre>guest</pre>)\n     *\n     * @param adminPassword The admin password.\n     * @return This container.\n     */\n    public RabbitMQContainer withAdminPassword(final String adminPassword) {\n        this.adminPassword = adminPassword;\n        return this;\n    }\n\n    /**\n     * Overwrites the default RabbitMQ configuration file with the supplied one.\n     *\n     * @param rabbitMQConf The rabbitmq.conf file to use (in sysctl format, don't forget empty line in the end of file)\n     * @return This container.\n     */\n    public RabbitMQContainer withRabbitMQConfig(MountableFile rabbitMQConf) {\n        return withRabbitMQConfigSysctl(rabbitMQConf);\n    }\n\n    /**\n     * Overwrites the default RabbitMQ configuration file with the supplied one.\n     *\n     * This function doesn't work with RabbitMQ &lt; 3.7.\n     *\n     * This function and the Sysctl format is recommended for RabbitMQ &gt;= 3.7\n     *\n     * @param rabbitMQConf The rabbitmq.config file to use (in sysctl format, don't forget empty line in the end of file)\n     * @return This container.\n     */\n    public RabbitMQContainer withRabbitMQConfigSysctl(MountableFile rabbitMQConf) {\n        withEnv(\"RABBITMQ_CONFIG_FILE\", \"/etc/rabbitmq/rabbitmq-custom.conf\");\n        return withCopyFileToContainer(rabbitMQConf, \"/etc/rabbitmq/rabbitmq-custom.conf\");\n    }\n\n    /**\n     * Overwrites the default RabbitMQ configuration file with the supplied one.\n     *\n     * @param rabbitMQConf The rabbitmq.config file to use (in erlang format)\n     * @return This container.\n     */\n    public RabbitMQContainer withRabbitMQConfigErlang(MountableFile rabbitMQConf) {\n        withEnv(\"RABBITMQ_CONFIG_FILE\", \"/etc/rabbitmq/rabbitmq-custom.config\");\n        return withCopyFileToContainer(rabbitMQConf, \"/etc/rabbitmq/rabbitmq-custom.config\");\n    }\n}\n"
  },
  {
    "path": "modules/rabbitmq/src/test/java/org/testcontainers/rabbitmq/RabbitMQContainerTest.java",
    "content": "package org.testcontainers.rabbitmq;\n\nimport com.rabbitmq.client.Channel;\nimport com.rabbitmq.client.Connection;\nimport com.rabbitmq.client.ConnectionFactory;\nimport com.rabbitmq.client.DeliverCallback;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.utility.MountableFile;\n\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.util.concurrent.TimeoutException;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass RabbitMQContainerTest {\n\n    public static final int DEFAULT_AMQPS_PORT = 5671;\n\n    public static final int DEFAULT_AMQP_PORT = 5672;\n\n    public static final int DEFAULT_HTTPS_PORT = 15671;\n\n    public static final int DEFAULT_HTTP_PORT = 15672;\n\n    @Test\n    void shouldCreateRabbitMQContainer() {\n        try (RabbitMQContainer container = new RabbitMQContainer(RabbitMQTestImages.RABBITMQ_IMAGE)) {\n            container.start();\n\n            assertThat(container.getAdminPassword()).isEqualTo(\"guest\");\n            assertThat(container.getAdminUsername()).isEqualTo(\"guest\");\n\n            assertThat(container.getAmqpsUrl())\n                .isEqualTo(String.format(\"amqps://%s:%d\", container.getHost(), container.getAmqpsPort()));\n            assertThat(container.getAmqpUrl())\n                .isEqualTo(String.format(\"amqp://%s:%d\", container.getHost(), container.getAmqpPort()));\n            assertThat(container.getHttpsUrl())\n                .isEqualTo(String.format(\"https://%s:%d\", container.getHost(), container.getHttpsPort()));\n            assertThat(container.getHttpUrl())\n                .isEqualTo(String.format(\"http://%s:%d\", container.getHost(), container.getHttpPort()));\n\n            assertThat(container.getLivenessCheckPortNumbers())\n                .containsExactlyInAnyOrder(\n                    container.getMappedPort(DEFAULT_AMQP_PORT),\n                    container.getMappedPort(DEFAULT_AMQPS_PORT),\n                    container.getMappedPort(DEFAULT_HTTP_PORT),\n                    container.getMappedPort(DEFAULT_HTTPS_PORT)\n                );\n\n            assertFunctionality(container);\n        }\n    }\n\n    @Test\n    void shouldCreateRabbitMQContainerWithCustomCredentials() {\n        try (\n            RabbitMQContainer container = new RabbitMQContainer(RabbitMQTestImages.RABBITMQ_IMAGE)\n                .withAdminUser(\"admin\")\n                .withAdminPassword(\"admin\")\n        ) {\n            container.start();\n\n            assertThat(container.getAdminPassword()).isEqualTo(\"admin\");\n            assertThat(container.getAdminUsername()).isEqualTo(\"admin\");\n\n            assertFunctionality(container);\n        }\n    }\n\n    @Test\n    void shouldMountConfigurationFile() {\n        try (RabbitMQContainer container = new RabbitMQContainer(RabbitMQTestImages.RABBITMQ_IMAGE)) {\n            container.withRabbitMQConfig(MountableFile.forClasspathResource(\"/rabbitmq-custom.conf\"));\n            container.start();\n\n            assertThat(container.getLogs()).contains(\"debug\"); // config file changes log level to `debug`\n        }\n    }\n\n    @Test\n    void shouldMountConfigurationFileErlang() {\n        try (RabbitMQContainer container = new RabbitMQContainer(RabbitMQTestImages.RABBITMQ_IMAGE)) {\n            container.withRabbitMQConfigErlang(MountableFile.forClasspathResource(\"/rabbitmq-custom.config\"));\n            container.start();\n\n            assertThat(container.getLogs()).contains(\"debug\"); // config file changes log level to `debug`\n        }\n    }\n\n    @Test\n    void shouldMountConfigurationFileSysctl() {\n        try (RabbitMQContainer container = new RabbitMQContainer(RabbitMQTestImages.RABBITMQ_IMAGE)) {\n            container.withRabbitMQConfigSysctl(MountableFile.forClasspathResource(\"/rabbitmq-custom.conf\"));\n            container.start();\n\n            assertThat(container.getLogs()).contains(\"debug\"); // config file changes log level to `debug`\n        }\n    }\n\n    private void assertFunctionality(RabbitMQContainer container) {\n        String queueName = \"test-queue\";\n        String text = \"Hello World!\";\n\n        ConnectionFactory factory = new ConnectionFactory();\n        factory.setHost(container.getHost());\n        factory.setPort(container.getAmqpPort());\n        factory.setUsername(container.getAdminUsername());\n        factory.setPassword(container.getAdminPassword());\n        try (Connection connection = factory.newConnection(); Channel channel = connection.createChannel()) {\n            channel.queueDeclare(queueName, false, false, false, null);\n            channel.basicPublish(\"\", queueName, null, text.getBytes());\n\n            DeliverCallback deliverCallback = (consumerTag, delivery) -> {\n                String message = new String(delivery.getBody(), StandardCharsets.UTF_8);\n                assertThat(message).isEqualTo(text);\n            };\n            channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {});\n        } catch (IOException | TimeoutException e) {\n            throw new RuntimeException(e);\n        }\n    }\n}\n"
  },
  {
    "path": "modules/rabbitmq/src/test/java/org/testcontainers/rabbitmq/RabbitMQTestImages.java",
    "content": "package org.testcontainers.rabbitmq;\n\nimport org.testcontainers.utility.DockerImageName;\n\npublic interface RabbitMQTestImages {\n    DockerImageName RABBITMQ_IMAGE = DockerImageName.parse(\"rabbitmq:3.7.25-management-alpine\");\n}\n"
  },
  {
    "path": "modules/rabbitmq/src/test/resources/certs/ca_certificate.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDRzCCAi+gAwIBAgIJAJJIMzvZuRzlMA0GCSqGSIb3DQEBCwUAMDExIDAeBgNV\nBAMMF1RMU0dlblNlbGZTaWduZWR0Um9vdENBMQ0wCwYDVQQHDAQkJCQkMCAXDTE5\nMDUwMjA3MjI0OVoYDzIxMTkwNDA4MDcyMjQ5WjAxMSAwHgYDVQQDDBdUTFNHZW5T\nZWxmU2lnbmVkdFJvb3RDQTENMAsGA1UEBwwEJCQkJDCCASIwDQYJKoZIhvcNAQEB\nBQADggEPADCCAQoCggEBAKko8FmfzrLHyZckvdR1oiSZf80m0t66TMqtLat1Oxjh\nCjsxvswwJ/m2I5dM48hwZ+0b2ufkvaudLPq/8jDGyONVfjMGlbe1YlmQMDC7YWdI\nXM1nCWAZIKaOHwIkfswuVBAdBVYV4Polu6wjVt5edEpl/IWEpPicXjLOY1Fw3q67\n5tP2Mmo6TJg5YqgB4fH4SmajtP3j+H4puQ8ZPIs26mInEgfCyrMWey/oQX8qqMph\npKMEJYE7DHawriFraOooJadJYojbY5H27nmJe8yXURb3wSQSaKnFZL25cmVm2kue\n/lw+n+a2wLdHdU4cmghCURalhcXUNZe7UbdRZ9e9r2cCAwEAAaNgMF4wCwYDVR0P\nBAQDAgEGMB0GA1UdDgQWBBSZiNur/XHsqSfdWnB1NPi/ql5+tzAfBgNVHSMEGDAW\ngBSZiNur/XHsqSfdWnB1NPi/ql5+tzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3\nDQEBCwUAA4IBAQAar/db/T7izD4pyh2titl7Dkgp2iTditfgqRlU0yVGiiB6rLmY\nsYE2QAuFhgqyRLPcjVV8F39iRJHQ17SGT8e2iAaUTnbQj0AiskKjonF9+quKuVbr\nTpYHk+guS0Jn2rU6HK8WQeYZOh3WdLTu4ArXkxywgwVssQQ9JmpTd9YEYePWfs7i\nWZB6AQyL9CD3z1j4i1G4ft6pB1Ps5XjznqMZ2//7AUpoRTrettWqorPWwudQ9yna\nB4S6KtvpnxUQSeHJW6Q4NvTrOsvHEOCa6OtwYbWmLf+qbpPb8oHt9UF3ze2PJopB\nQzsQop1+gPudG0DX0SgyuQT+SsFjYlDazZdZ\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "modules/rabbitmq/src/test/resources/certs/ca_key.pem",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCpKPBZn86yx8mX\nJL3UdaIkmX/NJtLeukzKrS2rdTsY4Qo7Mb7MMCf5tiOXTOPIcGftG9rn5L2rnSz6\nv/IwxsjjVX4zBpW3tWJZkDAwu2FnSFzNZwlgGSCmjh8CJH7MLlQQHQVWFeD6Jbus\nI1beXnRKZfyFhKT4nF4yzmNRcN6uu+bT9jJqOkyYOWKoAeHx+Epmo7T94/h+KbkP\nGTyLNupiJxIHwsqzFnsv6EF/KqjKYaSjBCWBOwx2sK4ha2jqKCWnSWKI22OR9u55\niXvMl1EW98EkEmipxWS9uXJlZtpLnv5cPp/mtsC3R3VOHJoIQlEWpYXF1DWXu1G3\nUWfXva9nAgMBAAECggEAVHUVM5o/aDGp6+WzRa2Jy/47ueEFbaDUkGjQoYeBfxV0\nt0NKAMaWXu3abUM9gyjkKpU6wYcKT/HEsFk/gazuRdq8jJtgCv3r4c3E0b/sjNWr\nR/6Gxs0k6SOSRc6U5DrJS9ZBgM6hqiNGxVZLm/DK3Q54eu1UNLBVs8Yp/lJ9S/3C\noM6lhex6SoCTiZyFb3ZvVTmBAZe/ROFXZAtmVac1KORBFN8FpkXQcB3EPTkvZmNJ\nsSp8dVPBLABm6gAbdJRLVqqndkHDPsopFGHLGzo4LtR7AlEklfxBdC/XlYmudZeY\npeWba+ZdLH3sCybE55Xcm7nTfRRSQSLDv++r73GCCQKBgQDe/7ltr8N+gWpb1fDC\nnEyBU4WHoTRp09sD+MZe3B06PX3WNRrf+3wHj9oB9UkhZ1baCq84mRzBiUKpAYrw\nGrC4iL4kqbWeIZzcJqLZ7zk1zzHzW0+zzNJSjzE8dq3W26+SnuLqKDFpkxtAyIMQ\nRp9AZJIW8q9ay6ZCsZk/NNBoKwKBgQDCMYh53dXKiYtr6tAXXManWWpQ24af6WKl\n3faXCw5eXG7ic5bNZD7ql+DWJzRylkI4yeLDvWAsHprtyIBYmkRki3lBAnpLeHY8\n7XWjZeOVeIpiSbG+1Fo7LrOuWi3wYGOZ61QUTjqJhvZJpzZBMEgo7cx+WQhlrioI\n9BbXqPabtQKBgAZJv7jQE/slOxKL3dYfAilDaaiBazDwwGRER5O1MT3LLhk0NiXK\nuZyc+dDEUeOXPmO3mWlHKABtFmwdlwVeO014zaPLBUwINpwemsj6beqOhSIPmRfA\n1s1tLD5AOnasiy7fPBbOO1Z2x3X0MX3r/+GY9GWhQkCVLYMD7wZRPu8xAoGBAJMr\nPNW2s+ZJtPq1Orzp17dOAU+D/xPDqLoxbEbt3xbOEE7X8Mp5lWDudzt0/L92dntZ\nLNzQ8UiebSWVlQcQ6pIUTXFiMlJt2ZW1FDkf54kIkD+KwATyI+vEKfIRb81DD1i/\nyrmUy7IcMRyCd5CRya4TAa4jRUTh6ANfEMyhxTsdAoGBAIFub7nikxPQ8J4ItWq8\nEKOptyziJ38KnL2nZnFyVDu6uZDtSk9Tf1dHqWy8D4yP9K3OLshipEp7rnNG25dr\ne7fkB58K77eSoaEpCZmwzPZstwkXbeBl+DHerDyC2UBXwIUWWFq+FuCdRQqCoYmU\nOGiaT92l+UInN7VchZjJS7Oj\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "modules/rabbitmq/src/test/resources/certs/client_certificate.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDKDCCAhCgAwIBAgIBAjANBgkqhkiG9w0BAQsFADAxMSAwHgYDVQQDDBdUTFNH\nZW5TZWxmU2lnbmVkdFJvb3RDQTENMAsGA1UEBwwEJCQkJDAgFw0xOTA1MDIwNzIy\nNDlaGA8yMTE5MDQwODA3MjI0OVowIzEQMA4GA1UEAwwHQzY1U1RUMjEPMA0GA1UE\nCgwGY2xpZW50MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtcAse1RO\nmRngQ4NNFFgyPgbq2eZc3c/ERKdg2xKi2XESMIbOreVkoBRq1IXoxkJHgQZJvPP8\nJbH7hmY+DFZcreszlEk5FI7bvxFNQM3CKsy2LeEcN9BTchSEsWgmSbwiZJ31kEB8\nu/i+/GpgTTxtwPWi/zTep+7fKM4dyzYX9WcMu8wif3jKIzqXVUqHpWopFUK9zLQf\nJRnyfiD2uYbfcZYopsIG+wdL2Ebgfr8fpc+YsWn0aA0zBiEFAKYh9bewPqDIZKyg\nqd4FKzeUaqGHynttNxKDJ9z0oD84PKFEOuryf7V8ODU8+kI1+y/+3C5Hlq6n92y6\neSPn81W44+WZeQIDAQABo1cwVTAJBgNVHRMEAjAAMAsGA1UdDwQEAwIFoDATBgNV\nHSUEDDAKBggrBgEFBQcDAjAmBgNVHREEHzAdggdDNjVTVFQyggdDNjVTVFQyggls\nb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggEBAH3oqsmr+hIfMEpZUolPNuAl43TF\nKUu14SeFyS+P8fEIQVGkHdpEpHzC0G+fvREAy4YSw0WE8upOq6tkbbjen222SP0J\nC2vj0B9cCPLZUQ4kW6Z7JJD/1qryML0XB9uMPHqDeV/yBETJZW80TV0f7p8GWKXe\n+spu1W13rJ0jvQQd9anNEx3VhcKTwBIcTAhTHothxFLLhX1zb/oVZcl1riBopBFm\nWtrZRTCHs31o5GBnIs540uH0f2iCk7wUf8M/evxv5F1hms+NV7kfOz7fyCEXc9La\nP9pDjuxTNOUeOZH815BR9Lj71txY9AdjXqEXj7JdecACLsjHc+LMEP36cls=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "modules/rabbitmq/src/test/resources/certs/client_key.pem",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAtcAse1ROmRngQ4NNFFgyPgbq2eZc3c/ERKdg2xKi2XESMIbO\nreVkoBRq1IXoxkJHgQZJvPP8JbH7hmY+DFZcreszlEk5FI7bvxFNQM3CKsy2LeEc\nN9BTchSEsWgmSbwiZJ31kEB8u/i+/GpgTTxtwPWi/zTep+7fKM4dyzYX9WcMu8wi\nf3jKIzqXVUqHpWopFUK9zLQfJRnyfiD2uYbfcZYopsIG+wdL2Ebgfr8fpc+YsWn0\naA0zBiEFAKYh9bewPqDIZKygqd4FKzeUaqGHynttNxKDJ9z0oD84PKFEOuryf7V8\nODU8+kI1+y/+3C5Hlq6n92y6eSPn81W44+WZeQIDAQABAoIBAQCooWMkEnbSakW/\nniV4CNSk5DombiwfyVOq9zlQSZw670QXLhy5D6srM4ZjJNNyj7BUMAdef2mld9uN\nOXO8cqyO2TkEDmQdhOayAlWRGNdcao9lRgWua2Xg5NSw3ZcYtquae0yJyKtypDpf\nbDtprfWPINlYvC8R1PnMnGDcWJYmIyQyQuB2OJVCJ4ayhS4miBk91fR0T0yMhmlZ\nRKZWbECvQirwA1podKvVSpwhAEUlLkVWRqVVYkiadWoXc8u6Dg2NrI9KNCgk2/3W\nm8sVC9VgjkXw5L8cToTBmTrjRS831O0JR1iDuZIzbwliJhGtVcHtiTUvybu3boYL\nR4F9Z4fhAoGBAN0h+7yBIr7TvCXyURfemWrcWBYWmMwlr6GdcE0WACnUUiFL7aQs\n0Mo5DWnwUG74hk1E8TG6oRU9oJs+69295cXe1m6wUwZySh4qlqfFEwnB2PzZwGrS\nFnRrCnJGiyLfY5v5hFlzWl/APtuxXGMa3S+ehbtFBhvLfN6vmSPpBTzNAoGBANJo\niM6AK+ldkbi9SCES8zMWPLNWrEh7SiA+bItyoKOY275TUUxVLz0MP2S1B2yGo9Og\nfe6fWGeXfMgcL7+1q2cwMuieIaG5Qcs4u+3qJNVmerf5PlMJUkN7D3Prw1C73SLq\ncQVaRXs4LBpmMC5ajOODrZt/GdrRrn2D13kQKI9dAoGAaqTW+MP2c709Qbeo8DAE\nIQr+2DgxnFKYbwK0hBiWH5Yrva8WflS2pK/7Dho9UCc+7cjP4UG2Kb481GH18kyA\noXqkQ2F5yOQZZo73dRWP5ua7tMV3DI0hEygEM7RdqYW+Thx5fYIqFX9rURwqCAmO\nnkZ/DB9voLv0Dpj06+KXCgkCgYAmigck680fPYhHckQX6sSpAtWzc5iy3gJBza1M\nDX7m+ESno9MsTB4O7INgCtiFRFQVmzv1zTIAJ3svnBoS30+54tYwTWaTnL80Xfvu\nJAkDHXY05G5J/1cWDSBTd0ebLg3fK1nwRQyc+Tj6zOTeWK+drKzL4of10JpJWzDI\nd/E18QKBgQC9IIZ5oc30NL5Ie5ypcHG4xrim6OR0akeDDeZfV6BrhOnpdi8l4aIC\nJswilj/PQF657s5trujxN3YxUeCunCtuOqcrE0O5AAL4CTQGy3LIq4IGYWGV/19I\nZTyM0Bf5iPEpVYLTflf9wW2UElPw4Uz2+ky0mKBhtRBYMAYAR0NvfA==\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "modules/rabbitmq/src/test/resources/certs/server_certificate.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDajCCAlKgAwIBAgIBATANBgkqhkiG9w0BAQsFADAxMSAwHgYDVQQDDBdUTFNH\nZW5TZWxmU2lnbmVkdFJvb3RDQTENMAsGA1UEBwwEJCQkJDAgFw0xOTA1MDIwNzIy\nNDlaGA8yMTE5MDQwODA3MjI0OVowIzEQMA4GA1UEAwwHQzY1U1RUMjEPMA0GA1UE\nCgwGc2VydmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqR0QXKtb\nKVeEuCmZGcZAlAlTBC8E/G3UuX6qKwTR1xEOvUWeBH1n0WeXXGd/p/y6P4lRBeWN\nBZ9KcvIlNDeDMy05NfxnO1vnJk9E8/0xwMiY1LJdMHzIzhmrrqXo0u3DT8MmoNR6\n7CTcnG21gi1GrjW8a747yFF0xfukEc6FkyVqLsjtCkHPwrc/sBHVS3aivNWGkJzA\neBXBdWJAg3ZC6T9U+Y8cndWQrpYMJvek1IewlyDSspHZDFmM1OwVwypnMt4fGgaX\n5IlUMnNgKmisOSuI529rxLF+mvYIQLRl5bP+1/c9JD5MZ5krA3SrjdwRFS3sQXC3\nnuHqJofFXNkbXQIDAQABo4GYMIGVMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgWgMBMG\nA1UdJQQMMAoGCCsGAQUFBwMBMCYGA1UdEQQfMB2CB0M2NVNUVDKCB0M2NVNUVDKC\nCWxvY2FsaG9zdDAdBgNVHQ4EFgQURq22sa46tA0SGHhEm9jxGP9aDrswHwYDVR0j\nBBgwFoAUmYjbq/1x7Kkn3VpwdTT4v6pefrcwDQYJKoZIhvcNAQELBQADggEBAKUP\n7RgmJyMVoHxg46F1fjWVhlF4BbQuEtB8mC+4G4e68lDU/TPAbmB3aj91oQDgBiTd\nR2O7U6tyitxxrU2r7rFAHGhFHeyCQ3yZMwydO2V3Nm2Ywzdyk8er4yghjg9FS8tH\negDGDDod3l1yrAbHHuXmzDjnAFwHwRkm5cYUz00/IuZ3sQZ70XofL3KXNj1tAtfK\nPSpdSAxSTO99ofjVKjlyywQSZKNbXfqD5DGz8e0rmqPfZ+3zi75E5nEuJ3UI2wXg\nLuI4j6FIzNQyei/FdSynktcIm+hefQEyex4cho4C8RYB2S5S8RWrnP9jOzsaQFHn\nbHXf7dKwRfA6/u8JmtQ=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "modules/rabbitmq/src/test/resources/certs/server_key.pem",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAqR0QXKtbKVeEuCmZGcZAlAlTBC8E/G3UuX6qKwTR1xEOvUWe\nBH1n0WeXXGd/p/y6P4lRBeWNBZ9KcvIlNDeDMy05NfxnO1vnJk9E8/0xwMiY1LJd\nMHzIzhmrrqXo0u3DT8MmoNR67CTcnG21gi1GrjW8a747yFF0xfukEc6FkyVqLsjt\nCkHPwrc/sBHVS3aivNWGkJzAeBXBdWJAg3ZC6T9U+Y8cndWQrpYMJvek1IewlyDS\nspHZDFmM1OwVwypnMt4fGgaX5IlUMnNgKmisOSuI529rxLF+mvYIQLRl5bP+1/c9\nJD5MZ5krA3SrjdwRFS3sQXC3nuHqJofFXNkbXQIDAQABAoIBAA0dxvYZCEIFmrKZ\n71jzanDQ5FJvvyhA8H3OmC4r+oZ+uTDu5FmezF2OdkvhbyI9VMi2wsT9T9m+yAxw\nQXhyUce3WzeXsv4Em8H55fQykBhOtqPQja/EDeMGVK2ACrXJYRufnDBfKoWEOmQb\nkjddgZzjaBDHOWXJA5CTet8ysGOAJBTxyzU69k5Vj9B5abG9CofNzGOFF+Uleff5\nip3sz7JpDXCex3oEs98veco6+8i/MZNo3BnwB5J+P+2MFFKONfPwuNyKAWBza2/X\n66Lk3xXBjLJJ+Ww16jkqueTXEq6GCFXavNfdL9aonth5V5YYR/cj+2u2LM1oj9cJ\nbp0xbvUCgYEA2Svq1DyR9cfTwrbc/0J2JfrjavClzDYU2oeO2fSU85WEEjJguaja\n17Vdo/UsJtiUiSq4UhI1n0haaIpTBCeF2tHGXVEYZ7ZBi1zzdWbWlDxFmi+rcE57\nytx5w+iLE366tQEMa/Jn3bly54pG5JZAr9TXkpg9sMbzWZri2ocyU/cCgYEAx1l/\n9X9C/OruDp/MhhmVwKfw/X2+RhZRuv0pPcpJu7/gIoLgaxNj41XSeLqLYMlisaRk\nGFU17GFXtfRGE1a3z+jj8UPTP2sHk3w8m0yI+pgWgsvG0TJ0B+XsRfpVxFiIoaEs\n3AsBaGR+hrRY1dpaJ9Cu3J9mEeToTpbCzPzVDksCgYEAzwSvWNvYY4u2UFHSvz2S\ntMfBzCpUUiNno50/ToN5De4ENPhy/eh5nNDVz7qh+PHSPiNMC2gyV4E4NZlOY5Jt\nZdc8ma35brvtJTVZGxwKBsqhqsYwTeFy3kFnjZn6IX5X6r1yIuCzpEfowdEtnS+h\nwDtLuAGKJR6x0UP1Zk0ka6cCgYBGE6I1rJzhx7wTi/0bjtbjuKWwlolSnfnxH5ll\nzTyKMXMa7qLxQQm2Gq84HWtthJ2bEMzW+O1RwQ5SOiKAHdXT0mx+nXcfLgKlx+CO\nPyNP5DLVm8iyNWgwdpTOLKgFs5GkL8JTP9Mo3VrVA4TO+EkFAgjWKXp6A9vd9IVa\nBe7nbQKBgAVtFKuf9nbCMfN+W1gN0vlW2lwxCTa4w0KHgIlGIIvnYVuixSgu9fGt\nuylQcQirEjqrdzdVF9L2BQ37ZcLaGh1LoCmx8XVCX/HhbwW2RP798P3Z1P7htm16\nha5OfuPjHvoZklbYJo6EORJZQehS2VP63pjdnmUeMHPFzrPUevI5\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "modules/rabbitmq/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/rabbitmq/src/test/resources/rabbitmq-custom.conf",
    "content": "log.console.level = debug\n"
  },
  {
    "path": "modules/rabbitmq/src/test/resources/rabbitmq-custom.config",
    "content": "[\n{rabbit,\n    [\n        {log,\n            [{console, [{level, debug}]}]\n        }\n    ]\n}\n].\n"
  },
  {
    "path": "modules/redpanda/build.gradle",
    "content": "description = \"Testcontainers :: Redpanda\"\n\ndependencies {\n    api project(':testcontainers')\n    shaded 'org.freemarker:freemarker:2.3.34'\n\n    testImplementation 'org.apache.kafka:kafka-clients:4.1.1'\n    testImplementation 'io.rest-assured:rest-assured:5.5.6'\n    testImplementation 'org.awaitility:awaitility:4.3.0'\n}\n"
  },
  {
    "path": "modules/redpanda/src/main/java/org/testcontainers/redpanda/RedpandaContainer.java",
    "content": "package org.testcontainers.redpanda;\n\nimport com.github.dockerjava.api.command.InspectContainerResponse;\nimport freemarker.template.Configuration;\nimport freemarker.template.Template;\nimport lombok.AllArgsConstructor;\nimport lombok.Cleanup;\nimport lombok.Data;\nimport lombok.SneakyThrows;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.images.builder.Transferable;\nimport org.testcontainers.utility.ComparableVersion;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.MountableFile;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.OutputStreamWriter;\nimport java.io.Writer;\nimport java.nio.charset.StandardCharsets;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.function.Supplier;\nimport java.util.stream.Collectors;\n\n/**\n * Testcontainers implementation for Redpanda.\n * <p>\n * Supported images: {@code redpandadata/redpanda}, {@code docker.redpanda.com/redpandadata/redpanda}\n * <p>\n * Exposed ports:\n * <ul>\n *     <li>Broker: 9092</li>\n *     <li>Schema Registry: 8081</li>\n *     <li>Proxy: 8082</li>\n * </ul>\n */\npublic class RedpandaContainer extends GenericContainer<RedpandaContainer> {\n\n    private static final String REDPANDA_FULL_IMAGE_NAME = \"docker.redpanda.com/redpandadata/redpanda\";\n\n    private static final String IMAGE_NAME = \"redpandadata/redpanda\";\n\n    private static final DockerImageName REDPANDA_IMAGE = DockerImageName.parse(REDPANDA_FULL_IMAGE_NAME);\n\n    private static final DockerImageName IMAGE = DockerImageName.parse(IMAGE_NAME);\n\n    private static final int REDPANDA_PORT = 9092;\n\n    private static final int REDPANDA_ADMIN_PORT = 9644;\n\n    private static final int SCHEMA_REGISTRY_PORT = 8081;\n\n    private static final int REST_PROXY_PORT = 8082;\n\n    private boolean enableAuthorization;\n\n    private String authenticationMethod = \"none\";\n\n    private String schemaRegistryAuthenticationMethod = \"none\";\n\n    private final List<String> superusers = new ArrayList<>();\n\n    @Deprecated\n    private final Set<Supplier<Listener>> listenersValueSupplier = new HashSet<>();\n\n    private final Map<String, Supplier<String>> listeners = new HashMap<>();\n\n    public RedpandaContainer(String image) {\n        this(DockerImageName.parse(image));\n    }\n\n    public RedpandaContainer(DockerImageName imageName) {\n        super(imageName);\n        imageName.assertCompatibleWith(REDPANDA_IMAGE, IMAGE);\n\n        boolean isLessThanBaseVersion = new ComparableVersion(imageName.getVersionPart()).isLessThan(\"v22.2.1\");\n        boolean isPublicCompatibleImage =\n            REDPANDA_FULL_IMAGE_NAME.equals(imageName.getUnversionedPart()) ||\n            IMAGE_NAME.equals(imageName.getUnversionedPart());\n        if (isPublicCompatibleImage && isLessThanBaseVersion) {\n            throw new IllegalArgumentException(\"Redpanda version must be >= v22.2.1\");\n        }\n\n        withExposedPorts(REDPANDA_PORT, REDPANDA_ADMIN_PORT, SCHEMA_REGISTRY_PORT, REST_PROXY_PORT);\n        withCreateContainerCmdModifier(cmd -> {\n            cmd.withEntrypoint(\"/entrypoint-tc.sh\");\n            cmd.withUser(\"root:root\");\n        });\n        waitingFor(Wait.forLogMessage(\".*Successfully started Redpanda!.*\", 1));\n        withCopyFileToContainer(\n            MountableFile.forClasspathResource(\"testcontainers/entrypoint-tc.sh\", 0700),\n            \"/entrypoint-tc.sh\"\n        );\n        withCommand(\"redpanda\", \"start\", \"--mode=dev-container\", \"--smp=1\", \"--memory=1G\");\n    }\n\n    @Override\n    protected void configure() {\n        this.listenersValueSupplier.stream()\n            .map(Supplier::get)\n            .map(Listener::getAddress)\n            .forEach(this::withNetworkAliases);\n        this.listeners.keySet().stream().map(listener -> listener.split(\":\")[0]).forEach(this::withNetworkAliases);\n    }\n\n    @SneakyThrows\n    @Override\n    protected void containerIsStarting(InspectContainerResponse containerInfo) {\n        super.containerIsStarting(containerInfo);\n\n        Configuration cfg = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);\n        cfg.setClassLoaderForTemplateLoading(getClass().getClassLoader(), \"testcontainers\");\n        cfg.setDefaultEncoding(\"UTF-8\");\n\n        copyFileToContainer(getBootstrapFile(cfg), \"/etc/redpanda/.bootstrap.yaml\");\n        copyFileToContainer(getRedpandaFile(cfg), \"/etc/redpanda/redpanda.yaml\");\n    }\n\n    /**\n     * Returns the bootstrap servers address.\n     * @return the bootstrap servers address\n     */\n    public String getBootstrapServers() {\n        return String.format(\"PLAINTEXT://%s:%s\", getHost(), getMappedPort(REDPANDA_PORT));\n    }\n\n    /**\n     * Returns the schema registry address.\n     * @return the schema registry address\n     */\n    public String getSchemaRegistryAddress() {\n        return String.format(\"http://%s:%s\", getHost(), getMappedPort(SCHEMA_REGISTRY_PORT));\n    }\n\n    /**\n     * Returns the admin address.\n     * @return the admin address\n     */\n    public String getAdminAddress() {\n        return String.format(\"http://%s:%s\", getHost(), getMappedPort(REDPANDA_ADMIN_PORT));\n    }\n\n    /**\n     * Returns the rest proxy address.\n     * @return the rest proxy address\n     */\n    public String getRestProxyAddress() {\n        return String.format(\"http://%s:%s\", getHost(), getMappedPort(REST_PROXY_PORT));\n    }\n\n    /**\n     * Enables authorization.\n     * @return this {@link RedpandaContainer} instance\n     */\n    public RedpandaContainer enableAuthorization() {\n        this.enableAuthorization = true;\n        return this;\n    }\n\n    /**\n     * Enables SASL.\n     * @return this {@link RedpandaContainer} instance\n     */\n    public RedpandaContainer enableSasl() {\n        this.authenticationMethod = \"sasl\";\n        return this;\n    }\n\n    /**\n     * Enables Http Basic Auth for Schema Registry.\n     * @return this {@link RedpandaContainer} instance\n     */\n    public RedpandaContainer enableSchemaRegistryHttpBasicAuth() {\n        this.schemaRegistryAuthenticationMethod = \"http_basic\";\n        return this;\n    }\n\n    /**\n     * Register username as a superuser.\n     * @param username username to register as a superuser\n     * @return this {@link RedpandaContainer} instance\n     */\n    public RedpandaContainer withSuperuser(String username) {\n        this.superusers.add(username);\n        return this;\n    }\n\n    /**\n     * Add a {@link Supplier} that will provide a listener with format {@code host:port}.\n     * Host will be added as a network alias.\n     * <p>\n     * The listener will be added to the default listeners.\n     * <p>\n     * Default listeners:\n     * <ul>\n     *     <li>0.0.0.0:9092</li>\n     *     <li>0.0.0.0:9093</li>\n     * </ul>\n     * <p>\n     * Default advertised listeners:\n     * <ul>\n     *      <li>{@code container.getHost():container.getMappedPort(9092)}</li>\n     *      <li>127.0.0.1:9093</li>\n     * </ul>\n     * @param listenerSupplier a supplier that will provide a listener\n     * @return this {@link RedpandaContainer} instance\n     * @deprecated use {@link #withListener(String, Supplier)} instead\n     */\n    @Deprecated\n    public RedpandaContainer withListener(Supplier<String> listenerSupplier) {\n        String[] parts = listenerSupplier.get().split(\":\");\n        this.listenersValueSupplier.add(() -> new Listener(parts[0], Integer.parseInt(parts[1])));\n        return this;\n    }\n\n    /**\n     * Add a listener in the format {@code host:port}.\n     * Host will be included as a network alias.\n     * <p>\n     * Use it to register additional connections to the Kafka broker within the same container network.\n     * <p>\n     * The listener will be added to the list of default listeners.\n     * <p>\n     * Default listeners:\n     * <ul>\n     *     <li>0.0.0.0:9092</li>\n     *     <li>0.0.0.0:9093</li>\n     * </ul>\n     * <p>\n     * The listener will be added to the list of default advertised listeners.\n     * <p>\n     * Default advertised listeners:\n     * <ul>\n     *      <li>{@code container.getConfig().getHostName():9092}</li>\n     *      <li>{@code container.getHost():container.getMappedPort(9093)}</li>\n     * </ul>\n     * @param listener a listener with format {@code host:port}\n     * @return this {@link RedpandaContainer} instance\n     */\n    public RedpandaContainer withListener(String listener) {\n        this.listeners.put(listener, () -> listener);\n        return this;\n    }\n\n    /**\n     * Add a listener in the format {@code host:port} and a {@link Supplier} for the advertised listener.\n     * Host from listener will be included as a network alias.\n     * <p>\n     * Use it to register additional connections to the Kafka broker from outside the container network\n     * <p>\n     * The listener will be added to the list of default listeners.\n     * <p>\n     * Default listeners:\n     * <ul>\n     *     <li>0.0.0.0:9092</li>\n     *     <li>0.0.0.0:9093</li>\n     * </ul>\n     * <p>\n     * The {@link Supplier} will be added to the list of default advertised listeners.\n     * <p>\n     * Default advertised listeners:\n     * <ul>\n     *      <li>{@code container.getConfig().getHostName():9092}</li>\n     *      <li>{@code container.getHost():container.getMappedPort(9093)}</li>\n     * </ul>\n     * @param listener a supplier that will provide a listener\n     * @param advertisedListener a supplier that will provide a listener\n     * @return this {@link RedpandaContainer} instance\n     */\n    public RedpandaContainer withListener(String listener, Supplier<String> advertisedListener) {\n        this.listeners.put(listener, advertisedListener);\n        return this;\n    }\n\n    private Transferable getBootstrapFile(Configuration cfg) {\n        Map<String, Object> kafkaApi = new HashMap<>();\n        kafkaApi.put(\"enableAuthorization\", this.enableAuthorization);\n        kafkaApi.put(\"superusers\", this.superusers);\n\n        Map<String, Object> root = new HashMap<>();\n        root.put(\"kafkaApi\", kafkaApi);\n\n        String file = resolveTemplate(cfg, \"bootstrap.yaml.ftl\", root);\n\n        return Transferable.of(file, 0700);\n    }\n\n    private Transferable getRedpandaFile(Configuration cfg) {\n        Map<String, Object> kafkaApi = new HashMap<>();\n        kafkaApi.put(\"authenticationMethod\", this.authenticationMethod);\n        kafkaApi.put(\"enableAuthorization\", this.enableAuthorization);\n        kafkaApi.put(\"advertisedHost\", getHost());\n        kafkaApi.put(\"advertisedPort\", getMappedPort(9092));\n\n        List<Map<String, Object>> listeners =\n            this.listenersValueSupplier.stream()\n                .map(Supplier::get)\n                .map(listener -> {\n                    Map<String, Object> listenerMap = new HashMap<>();\n                    listenerMap.put(\"address\", listener.getAddress());\n                    listenerMap.put(\"port\", listener.getPort());\n                    listenerMap.put(\"authentication_method\", this.authenticationMethod);\n                    return listenerMap;\n                })\n                .collect(Collectors.toList());\n        kafkaApi.put(\"listeners\", listeners);\n\n        List<Map<String, Object>> kafkaListeners =\n            this.listeners.keySet()\n                .stream()\n                .map(listener -> {\n                    Map<String, Object> listenerMap = new HashMap<>();\n                    listenerMap.put(\"name\", listener.split(\":\")[0]);\n                    listenerMap.put(\"address\", listener.split(\":\")[0]);\n                    listenerMap.put(\"port\", listener.split(\":\")[1]);\n                    listenerMap.put(\"authentication_method\", this.authenticationMethod);\n                    return listenerMap;\n                })\n                .collect(Collectors.toList());\n\n        List<Map<String, Object>> kafkaAdvertisedListeners =\n            this.listeners.entrySet()\n                .stream()\n                .map(entry -> {\n                    String advertisedListener = entry.getValue().get();\n                    Map<String, Object> listenerMap = new HashMap<>();\n                    listenerMap.put(\"name\", entry.getKey().split(\":\")[0]);\n                    listenerMap.put(\"address\", advertisedListener.split(\":\")[0]);\n                    listenerMap.put(\"port\", advertisedListener.split(\":\")[1]);\n                    return listenerMap;\n                })\n                .collect(Collectors.toList());\n\n        Map<String, Object> kafka = new HashMap<>();\n        kafka.put(\"listeners\", kafkaListeners);\n        kafka.put(\"advertisedListeners\", kafkaAdvertisedListeners);\n\n        Map<String, Object> schemaRegistry = new HashMap<>();\n        schemaRegistry.put(\"authenticationMethod\", this.schemaRegistryAuthenticationMethod);\n\n        Map<String, Object> root = new HashMap<>();\n        root.put(\"kafkaApi\", kafkaApi);\n        root.put(\"kafka\", kafka);\n        root.put(\"schemaRegistry\", schemaRegistry);\n\n        String file = resolveTemplate(cfg, \"redpanda.yaml.ftl\", root);\n\n        return Transferable.of(file, 0700);\n    }\n\n    @SneakyThrows\n    private String resolveTemplate(Configuration cfg, String template, Map<String, Object> data) {\n        Template temp = cfg.getTemplate(template);\n\n        @Cleanup\n        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();\n        @Cleanup\n        Writer out = new OutputStreamWriter(byteArrayOutputStream, StandardCharsets.UTF_8);\n        temp.process(data, out);\n\n        return new String(byteArrayOutputStream.toByteArray(), StandardCharsets.UTF_8);\n    }\n\n    @Data\n    @AllArgsConstructor\n    private static class Listener {\n\n        private String address;\n\n        private int port;\n    }\n}\n"
  },
  {
    "path": "modules/redpanda/src/main/resources/testcontainers/bootstrap.yaml.ftl",
    "content": "# Injected by testcontainers\n# This file contains cluster properties which will only be considered when\n# starting the cluster for the first time. Afterwards, you can configure cluster\n# properties via the Redpanda Admin API.\nsuperusers:\n<#if kafkaApi.superusers?has_content >\n    <#list kafkaApi.superusers as superuser>\n  - ${superuser}\n    </#list>\n<#else>\n  []\n</#if>\n\n<#if kafkaApi.enableAuthorization >\nkafka_enable_authorization: true\n</#if>\n\nauto_create_topics_enabled: true\n"
  },
  {
    "path": "modules/redpanda/src/main/resources/testcontainers/entrypoint-tc.sh",
    "content": "#!/usr/bin/env bash\n\n# Wait for testcontainer's injected redpanda config with the port only known after docker start\nuntil grep -q \"# Injected by testcontainers\" \"/etc/redpanda/redpanda.yaml\"\ndo\n  sleep 0.1\ndone\nexec /entrypoint.sh $@"
  },
  {
    "path": "modules/redpanda/src/main/resources/testcontainers/redpanda.yaml.ftl",
    "content": "# Injected by testcontainers\n<#setting boolean_format=\"c\">\n<#setting number_format=\"c\">\nredpanda:\n  admin:\n    address: 0.0.0.0\n    port: 9644\n\n  kafka_api:\n    - address: 0.0.0.0\n      name: external\n      port: 9092\n      authentication_method: ${ kafkaApi.authenticationMethod }\n\n    # This listener is required for the schema registry client. The schema\n    # registry client connects via an advertised listener like a normal Kafka\n    # client would do. It can't use the other listener because the mapped\n    # port is not accessible from within the Redpanda container.\n    - address: 0.0.0.0\n      name: internal\n      port: 9093\n      authentication_method: <#if kafkaApi.enableAuthorization >sasl<#else>none</#if>\n\n<#list kafkaApi.listeners as listener>\n    - address: 0.0.0.0\n      name: ${listener.address}\n      port: ${listener.port}\n      authentication_method: ${listener.authentication_method}\n</#list>\n<#list kafka.listeners as listener>\n    - address: ${listener.address}\n      name: ${listener.name}\n      port: ${listener.port}\n      authentication_method: ${listener.authentication_method}\n</#list>\n\n  advertised_kafka_api:\n    - address: ${ kafkaApi.advertisedHost }\n      name: external\n      port: ${ kafkaApi.advertisedPort }\n    - address: 127.0.0.1\n      name: internal\n      port: 9093\n<#list kafkaApi.listeners as listener>\n    - address: ${listener.address}\n      name: ${listener.address}\n      port: ${listener.port}\n</#list>\n<#list kafka.advertisedListeners as listener>\n    - address: ${listener.address}\n      name: ${listener.name}\n      port: ${listener.port}\n</#list>\n\nschema_registry:\n  schema_registry_api:\n  - address: \"0.0.0.0\"\n    name: main\n    port: 8081\n    authentication_method: ${ schemaRegistry.authenticationMethod }\n\nschema_registry_client:\n  brokers:\n    - address: localhost\n      port: 9093\n\npandaproxy:\n  pandaproxy_api:\n    - address: 0.0.0.0\n      port: 8082\n      name: proxy-internal\n  advertised_pandaproxy_api:\n    - address: 127.0.0.1\n      port: 8082\n      name: proxy-internal\n\npandaproxy_client:\n  brokers:\n    - address: localhost\n      port: 9093\n\nrpk:\n  kafka_api:\n    brokers:\n    - localhost:9093\n"
  },
  {
    "path": "modules/redpanda/src/test/java/org/testcontainers/redpanda/AbstractRedpanda.java",
    "content": "package org.testcontainers.redpanda;\n\nimport com.google.common.collect.ImmutableMap;\nimport org.apache.kafka.clients.admin.AdminClient;\nimport org.apache.kafka.clients.admin.AdminClientConfig;\nimport org.apache.kafka.clients.admin.NewTopic;\nimport org.apache.kafka.clients.consumer.ConsumerConfig;\nimport org.apache.kafka.clients.consumer.ConsumerRecord;\nimport org.apache.kafka.clients.consumer.ConsumerRecords;\nimport org.apache.kafka.clients.consumer.KafkaConsumer;\nimport org.apache.kafka.clients.producer.KafkaProducer;\nimport org.apache.kafka.clients.producer.ProducerConfig;\nimport org.apache.kafka.clients.producer.ProducerRecord;\nimport org.apache.kafka.common.serialization.StringDeserializer;\nimport org.apache.kafka.common.serialization.StringSerializer;\nimport org.awaitility.Awaitility;\n\nimport java.time.Duration;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.UUID;\nimport java.util.concurrent.TimeUnit;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.tuple;\n\npublic class AbstractRedpanda {\n\n    protected void testKafkaFunctionality(String bootstrapServers) throws Exception {\n        testKafkaFunctionality(bootstrapServers, 1, 1);\n    }\n\n    protected void testKafkaFunctionality(String bootstrapServers, int partitions, int rf) throws Exception {\n        try (\n            AdminClient adminClient = AdminClient.create(\n                ImmutableMap.of(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers)\n            );\n            KafkaProducer<String, String> producer = new KafkaProducer<>(\n                ImmutableMap.of(\n                    ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,\n                    bootstrapServers,\n                    ProducerConfig.CLIENT_ID_CONFIG,\n                    UUID.randomUUID().toString()\n                ),\n                new StringSerializer(),\n                new StringSerializer()\n            );\n            KafkaConsumer<String, String> consumer = new KafkaConsumer<>(\n                ImmutableMap.of(\n                    ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,\n                    bootstrapServers,\n                    ConsumerConfig.GROUP_ID_CONFIG,\n                    \"tc-\" + UUID.randomUUID(),\n                    ConsumerConfig.AUTO_OFFSET_RESET_CONFIG,\n                    \"earliest\"\n                ),\n                new StringDeserializer(),\n                new StringDeserializer()\n            );\n        ) {\n            String topicName = \"messages-\" + UUID.randomUUID();\n\n            Collection<NewTopic> topics = Collections.singletonList(new NewTopic(topicName, partitions, (short) rf));\n            adminClient.createTopics(topics).all().get(30, TimeUnit.SECONDS);\n\n            consumer.subscribe(Collections.singletonList(topicName));\n\n            producer.send(new ProducerRecord<>(topicName, \"testcontainers\", \"rulezzz\")).get();\n\n            Awaitility\n                .await()\n                .atMost(Duration.ofSeconds(10))\n                .untilAsserted(() -> {\n                    ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));\n\n                    assertThat(records)\n                        .hasSize(1)\n                        .extracting(ConsumerRecord::topic, ConsumerRecord::key, ConsumerRecord::value)\n                        .containsExactly(tuple(topicName, \"testcontainers\", \"rulezzz\"));\n                });\n\n            consumer.unsubscribe();\n        }\n    }\n}\n"
  },
  {
    "path": "modules/redpanda/src/test/java/org/testcontainers/redpanda/CompatibleImageTest.java",
    "content": "package org.testcontainers.redpanda;\n\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nclass CompatibleImageTest extends AbstractRedpanda {\n\n    public static String[] image() {\n        return new String[] { \"docker.redpanda.com/redpandadata/redpanda:v22.2.1\", \"redpandadata/redpanda:v22.2.1\" };\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"image\")\n    void shouldProduceAndConsumeMessage(String image) throws Exception {\n        try (RedpandaContainer container = new RedpandaContainer(image)) {\n            container.start();\n            testKafkaFunctionality(container.getBootstrapServers());\n        }\n    }\n}\n"
  },
  {
    "path": "modules/redpanda/src/test/java/org/testcontainers/redpanda/RedpandaContainerTest.java",
    "content": "package org.testcontainers.redpanda;\n\nimport com.google.common.collect.ImmutableMap;\nimport io.restassured.RestAssured;\nimport io.restassured.common.mapper.TypeRef;\nimport io.restassured.response.Response;\nimport lombok.SneakyThrows;\nimport org.apache.kafka.clients.admin.AdminClient;\nimport org.apache.kafka.clients.admin.AdminClientConfig;\nimport org.apache.kafka.clients.admin.NewTopic;\nimport org.apache.kafka.common.config.SaslConfigs;\nimport org.apache.kafka.common.errors.SaslAuthenticationException;\nimport org.apache.kafka.common.errors.TopicAuthorizationException;\nimport org.awaitility.Awaitility;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.Network;\nimport org.testcontainers.containers.SocatContainer;\nimport org.testcontainers.images.builder.Transferable;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\nimport java.util.concurrent.TimeUnit;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\n\nclass RedpandaContainerTest extends AbstractRedpanda {\n\n    private static final String REDPANDA_IMAGE = \"docker.redpanda.com/redpandadata/redpanda:v22.2.1\";\n\n    private static final DockerImageName REDPANDA_DOCKER_IMAGE = DockerImageName.parse(REDPANDA_IMAGE);\n\n    @Test\n    void testUsage() throws Exception {\n        try (RedpandaContainer container = new RedpandaContainer(REDPANDA_DOCKER_IMAGE)) {\n            container.start();\n            testKafkaFunctionality(container.getBootstrapServers());\n        }\n    }\n\n    @Test\n    void testUsageWithStringImage() throws Exception {\n        try (\n            // constructorWithVersion {\n            RedpandaContainer container = new RedpandaContainer(\"docker.redpanda.com/redpandadata/redpanda:v23.1.2\")\n            // }\n        ) {\n            container.start();\n            testKafkaFunctionality(\n                // getBootstrapServers {\n                container.getBootstrapServers()\n                // }\n            );\n        }\n    }\n\n    @Test\n    void testNotCompatibleVersion() {\n        assertThatThrownBy(() -> new RedpandaContainer(\"docker.redpanda.com/redpandadata/redpanda:v21.11.19\"))\n            .isInstanceOf(IllegalArgumentException.class)\n            .hasMessageContaining(\"Redpanda version must be >= v22.2.1\");\n    }\n\n    @Test\n    void redpandadataRedpandaImageVersion2221ShouldNotBeCompatible() {\n        assertThatThrownBy(() -> new RedpandaContainer(\"redpandadata/redpanda:v21.11.19\"))\n            .isInstanceOf(IllegalArgumentException.class)\n            .hasMessageContaining(\"Redpanda version must be >= v22.2.1\");\n    }\n\n    @Test\n    void testSchemaRegistry() {\n        try (RedpandaContainer container = new RedpandaContainer(REDPANDA_DOCKER_IMAGE)) {\n            container.start();\n\n            String subjectsEndpoint = String.format(\n                \"%s/subjects\",\n                // getSchemaRegistryAddress {\n                container.getSchemaRegistryAddress()\n                // }\n            );\n\n            String subjectName = String.format(\"test-%s-value\", UUID.randomUUID());\n\n            Response createSubject = RestAssured\n                .given()\n                .contentType(\"application/vnd.schemaregistry.v1+json\")\n                .pathParam(\"subject\", subjectName)\n                .body(\"{\\\"schema\\\": \\\"{\\\\\\\"type\\\\\\\": \\\\\\\"string\\\\\\\"}\\\"}\")\n                .when()\n                .post(subjectsEndpoint + \"/{subject}/versions\")\n                .thenReturn();\n            assertThat(createSubject.getStatusCode()).isEqualTo(200);\n\n            Response allSubjects = RestAssured.given().when().get(subjectsEndpoint).thenReturn();\n            assertThat(allSubjects.getStatusCode()).isEqualTo(200);\n            assertThat(allSubjects.jsonPath().getList(\"$\")).contains(subjectName);\n        }\n    }\n\n    @Test\n    void testUsageWithListener() throws Exception {\n        try (\n            Network network = Network.newNetwork();\n            RedpandaContainer redpanda = new RedpandaContainer(\"docker.redpanda.com/redpandadata/redpanda:v23.1.7\")\n                .withListener(() -> \"redpanda:19092\")\n                .withNetwork(network);\n            GenericContainer<?> kcat = new GenericContainer<>(\"confluentinc/cp-kcat:7.9.0\")\n                .withCreateContainerCmdModifier(cmd -> {\n                    cmd.withEntrypoint(\"sh\");\n                })\n                .withCopyToContainer(Transferable.of(\"Message produced by kcat\"), \"/data/msgs.txt\")\n                .withNetwork(network)\n                .withCommand(\"-c\", \"tail -f /dev/null\")\n        ) {\n            redpanda.start();\n            kcat.start();\n\n            kcat.execInContainer(\"kcat\", \"-b\", \"redpanda:19092\", \"-t\", \"msgs\", \"-P\", \"-l\", \"/data/msgs.txt\");\n            String stdout = kcat\n                .execInContainer(\"kcat\", \"-b\", \"redpanda:19092\", \"-C\", \"-t\", \"msgs\", \"-c\", \"1\")\n                .getStdout();\n\n            assertThat(stdout).contains(\"Message produced by kcat\");\n        }\n    }\n\n    @Test\n    void testUsageWithListenerInTheSameNetwork() throws Exception {\n        try (\n            Network network = Network.newNetwork();\n            // registerListener {\n            RedpandaContainer kafka = new RedpandaContainer(\"docker.redpanda.com/redpandadata/redpanda:v23.1.7\")\n                .withListener(\"kafka:19092\")\n                .withNetwork(network);\n            // }\n            // createKCatContainer {\n            GenericContainer<?> kcat = new GenericContainer<>(\"confluentinc/cp-kcat:7.9.0\")\n                .withCreateContainerCmdModifier(cmd -> {\n                    cmd.withEntrypoint(\"sh\");\n                })\n                .withCopyToContainer(Transferable.of(\"Message produced by kcat\"), \"/data/msgs.txt\")\n                .withNetwork(network)\n                .withCommand(\"-c\", \"tail -f /dev/null\")\n            // }\n        ) {\n            kafka.start();\n            kcat.start();\n\n            // produceConsumeMessage {\n            kcat.execInContainer(\"kcat\", \"-b\", \"kafka:19092\", \"-t\", \"msgs\", \"-P\", \"-l\", \"/data/msgs.txt\");\n            String stdout = kcat\n                .execInContainer(\"kcat\", \"-b\", \"kafka:19092\", \"-C\", \"-t\", \"msgs\", \"-c\", \"1\")\n                .getStdout();\n            // }\n\n            assertThat(stdout).contains(\"Message produced by kcat\");\n        }\n    }\n\n    @Test\n    void testUsageWithListenerFromProxy() throws Exception {\n        try (\n            Network network = Network.newNetwork();\n            // createProxy {\n            SocatContainer socat = new SocatContainer().withNetwork(network).withTarget(2000, \"kafka\", 19092);\n            // }\n            // registerListenerAndAdvertisedListener {\n            RedpandaContainer kafka = new RedpandaContainer(\"docker.redpanda.com/redpandadata/redpanda:v23.1.7\")\n                .withListener(\"kafka:19092\", () -> socat.getHost() + \":\" + socat.getMappedPort(2000))\n                .withNetwork(network)\n            // }\n        ) {\n            socat.start();\n            kafka.start();\n            // produceConsumeMessageFromProxy {\n            String bootstrapServers = String.format(\"%s:%s\", socat.getHost(), socat.getMappedPort(2000));\n            testKafkaFunctionality(bootstrapServers);\n            // }\n        }\n    }\n\n    @Test\n    void testUsageWithListenerAndSasl() throws Exception {\n        final String username = \"panda\";\n        final String password = \"pandapass\";\n        final String algorithm = \"SCRAM-SHA-256\";\n\n        try (\n            Network network = Network.newNetwork();\n            RedpandaContainer redpanda = new RedpandaContainer(\"docker.redpanda.com/redpandadata/redpanda:v23.1.7\")\n                .enableAuthorization()\n                .enableSasl()\n                .withSuperuser(\"panda\")\n                .withListener(\"my-panda:29092\")\n                .withNetwork(network);\n            GenericContainer<?> kcat = new GenericContainer<>(\"confluentinc/cp-kcat:7.9.0\")\n                .withCreateContainerCmdModifier(cmd -> {\n                    cmd.withEntrypoint(\"sh\");\n                })\n                .withCopyToContainer(Transferable.of(\"Message produced by kcat\"), \"/data/msgs.txt\")\n                .withNetwork(network)\n                .withCommand(\"-c\", \"tail -f /dev/null\")\n        ) {\n            redpanda.start();\n\n            String adminUrl = String.format(\"%s/v1/security/users\", redpanda.getAdminAddress());\n            Map<String, String> params = new HashMap<>();\n            params.put(\"username\", username);\n            params.put(\"password\", password);\n            params.put(\"algorithm\", algorithm);\n\n            RestAssured.given().contentType(\"application/json\").body(params).post(adminUrl).then().statusCode(200);\n\n            kcat.start();\n\n            kcat.execInContainer(\n                \"kcat\",\n                \"-b\",\n                \"my-panda:29092\",\n                \"-X\",\n                \"security.protocol=SASL_PLAINTEXT\",\n                \"-X\",\n                \"sasl.mechanisms=\" + algorithm,\n                \"-X\",\n                \"sasl.username=\" + username,\n                \"-X\",\n                \"sasl.password=\" + password,\n                \"-t\",\n                \"msgs\",\n                \"-P\",\n                \"-l\",\n                \"/data/msgs.txt\"\n            );\n\n            String stdout = kcat\n                .execInContainer(\n                    \"kcat\",\n                    \"-b\",\n                    \"my-panda:29092\",\n                    \"-X\",\n                    \"security.protocol=SASL_PLAINTEXT\",\n                    \"-X\",\n                    \"sasl.mechanisms=\" + algorithm,\n                    \"-X\",\n                    \"sasl.username=\" + username,\n                    \"-X\",\n                    \"sasl.password=\" + password,\n                    \"-C\",\n                    \"-t\",\n                    \"msgs\",\n                    \"-c\",\n                    \"1\"\n                )\n                .getStdout();\n\n            assertThat(stdout).contains(\"Message produced by kcat\");\n        }\n    }\n\n    @SneakyThrows\n    @Test\n    void enableSaslWithSuccessfulTopicCreation() {\n        try (\n            // security {\n            RedpandaContainer redpanda = new RedpandaContainer(\"docker.redpanda.com/redpandadata/redpanda:v23.1.7\")\n                .enableAuthorization()\n                .enableSasl()\n                .withSuperuser(\"superuser-1\")\n            // }\n        ) {\n            redpanda.start();\n\n            createSuperUser(redpanda);\n\n            AdminClient adminClient = getAdminClient(redpanda);\n            String topicName = \"messages-\" + UUID.randomUUID();\n            Collection<NewTopic> topics = Collections.singletonList(new NewTopic(topicName, 1, (short) 1));\n            adminClient.createTopics(topics).all().get(30, TimeUnit.SECONDS);\n\n            assertThat(adminClient.listTopics().names().get()).contains(topicName);\n        }\n    }\n\n    @Test\n    void enableSaslWithUnsuccessfulTopicCreation() {\n        try (\n            RedpandaContainer redpanda = new RedpandaContainer(\"docker.redpanda.com/redpandadata/redpanda:v23.1.7\")\n                .enableAuthorization()\n                .enableSasl()\n        ) {\n            redpanda.start();\n\n            createSuperUser(redpanda);\n\n            AdminClient adminClient = getAdminClient(redpanda);\n            String topicName = \"messages-\" + UUID.randomUUID();\n            Collection<NewTopic> topics = Collections.singletonList(new NewTopic(topicName, 1, (short) 1));\n\n            Awaitility\n                .await()\n                .untilAsserted(() -> {\n                    assertThatThrownBy(() -> adminClient.createTopics(topics).all().get(30, TimeUnit.SECONDS))\n                        .hasCauseInstanceOf(TopicAuthorizationException.class);\n                });\n        }\n    }\n\n    @Test\n    void enableSaslAndWithAuthenticationError() {\n        try (\n            RedpandaContainer redpanda = new RedpandaContainer(\"docker.redpanda.com/redpandadata/redpanda:v23.1.7\")\n                .enableAuthorization()\n                .enableSasl()\n        ) {\n            redpanda.start();\n\n            AdminClient adminClient = getAdminClient(redpanda);\n            String topicName = \"messages-\" + UUID.randomUUID();\n            Collection<NewTopic> topics = Collections.singletonList(new NewTopic(topicName, 1, (short) 1));\n\n            Awaitility\n                .await()\n                .untilAsserted(() -> {\n                    assertThatThrownBy(() -> adminClient.createTopics(topics).all().get(30, TimeUnit.SECONDS))\n                        .hasCauseInstanceOf(SaslAuthenticationException.class);\n                });\n        }\n    }\n\n    @Test\n    void schemaRegistryWithHttpBasic() {\n        try (\n            RedpandaContainer redpanda = new RedpandaContainer(\"docker.redpanda.com/redpandadata/redpanda:v23.1.7\")\n                .enableSchemaRegistryHttpBasicAuth()\n                .withSuperuser(\"superuser-1\")\n        ) {\n            redpanda.start();\n\n            createSuperUser(redpanda);\n\n            String subjectsEndpoint = String.format(\"%s/subjects\", redpanda.getSchemaRegistryAddress());\n\n            RestAssured.when().get(subjectsEndpoint).then().statusCode(401);\n\n            RestAssured\n                .given()\n                .auth()\n                .preemptive()\n                .basic(\"superuser-1\", \"test\")\n                .get(subjectsEndpoint)\n                .then()\n                .statusCode(200);\n        }\n    }\n\n    @SneakyThrows\n    @Test\n    void testRestProxy() {\n        try (RedpandaContainer redpanda = new RedpandaContainer(\"docker.redpanda.com/redpandadata/redpanda:v23.1.7\")) {\n            redpanda.start();\n\n            redpanda.execInContainer(\"rpk\", \"topic\", \"create\", \"test_topic\", \"-p\", \"3\");\n\n            String applicationKafkaJson = \"application/vnd.kafka.json.v2+json\";\n\n            String restProxy = redpanda.getRestProxyAddress();\n            RestAssured\n                .given()\n                .contentType(applicationKafkaJson)\n                .body(\n                    \"{\\\"records\\\":[{\\\"value\\\":\\\"jsmith\\\",\\\"partition\\\":0},{\\\"value\\\":\\\"htanaka\\\",\\\"partition\\\":1},{\\\"value\\\":\\\"awalther\\\",\\\"partition\\\":2}]}\"\n                )\n                .post(String.format(\"%s/topics/test_topic\", restProxy))\n                .then()\n                .statusCode(200);\n\n            RestAssured\n                .given()\n                .contentType(\"application/vnd.kafka.v2+json\")\n                .body(\"{\\\"name\\\": \\\"test_consumer\\\", \\\"format\\\": \\\"json\\\", \\\"auto.offset.reset\\\": \\\"earliest\\\"}\")\n                .post(String.format(\"%s/consumers/test_group\", restProxy))\n                .then()\n                .statusCode(200);\n            RestAssured\n                .given()\n                .contentType(\"application/vnd.kafka.v2+json\")\n                .body(\"{\\\"topics\\\":[\\\"test_topic\\\"]}\")\n                .post(String.format(\"%s/consumers/test_group/instances/test_consumer/subscription\", restProxy))\n                .then()\n                .statusCode(204);\n\n            List<Map<String, String>> response = RestAssured\n                .given()\n                .accept(applicationKafkaJson)\n                .get(String.format(\"%s/consumers/test_group/instances/test_consumer/records\", restProxy))\n                .getBody()\n                .as(new TypeRef<List<Map<String, String>>>() {});\n            assertThat(response).hasSize(3).extracting(\"value\").containsExactly(\"jsmith\", \"htanaka\", \"awalther\");\n        }\n    }\n\n    private AdminClient getAdminClient(RedpandaContainer redpanda) {\n        String bootstrapServer = String.format(\"%s:%s\", redpanda.getHost(), redpanda.getMappedPort(9092));\n        // createAdminClient {\n        AdminClient adminClient = AdminClient.create(\n            ImmutableMap.of(\n                AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG,\n                bootstrapServer,\n                AdminClientConfig.SECURITY_PROTOCOL_CONFIG,\n                \"SASL_PLAINTEXT\",\n                SaslConfigs.SASL_MECHANISM,\n                \"SCRAM-SHA-256\",\n                SaslConfigs.SASL_JAAS_CONFIG,\n                \"org.apache.kafka.common.security.scram.ScramLoginModule required username=\\\"superuser-1\\\" password=\\\"test\\\";\"\n            )\n        );\n        // }\n        return adminClient;\n    }\n\n    private void createSuperUser(RedpandaContainer redpanda) {\n        String adminUrl = String.format(\"%s/v1/security/users\", redpanda.getAdminAddress());\n        RestAssured\n            .given()\n            .contentType(\"application/json\")\n            .body(\"{\\\"username\\\": \\\"superuser-1\\\", \\\"password\\\": \\\"test\\\", \\\"algorithm\\\": \\\"SCRAM-SHA-256\\\"}\")\n            .post(adminUrl)\n            .then()\n            .statusCode(200);\n    }\n}\n"
  },
  {
    "path": "modules/redpanda/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/scylladb/build.gradle",
    "content": "description = \"Testcontainers :: ScyllaDB\"\n\ndependencies {\n    api project(\":testcontainers\")\n\n    testImplementation 'com.scylladb:java-driver-core:4.19.0.4'\n    testImplementation 'software.amazon.awssdk:dynamodb:2.40.4'\n}\n"
  },
  {
    "path": "modules/scylladb/src/main/java/org/testcontainers/scylladb/ScyllaDBContainer.java",
    "content": "package org.testcontainers.scylladb;\n\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.MountableFile;\n\nimport java.net.InetSocketAddress;\nimport java.util.Optional;\n\n/**\n * Testcontainers implementation for ScyllaDB.\n * <p>\n * Supported image: {@code scylladb/scylla}\n * <p>\n * Exposed ports:\n * <ul>\n *     <li>CQL Port: 9042</li>\n *     <li>Shard Aware Port: 19042</li>\n *     <li>Alternator Port: 8000</li>\n * </ul>\n */\npublic class ScyllaDBContainer extends GenericContainer<ScyllaDBContainer> {\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"scylladb/scylla\");\n\n    private static final Integer CQL_PORT = 9042;\n\n    private static final Integer SHARD_AWARE_PORT = 19042;\n\n    private static final Integer ALTERNATOR_PORT = 8000;\n\n    private static final String COMMAND = \"--developer-mode=1 --overprovisioned=1\";\n\n    private static final String CONTAINER_CONFIG_LOCATION = \"/etc/scylla\";\n\n    private boolean alternatorEnabled = false;\n\n    private String configLocation;\n\n    public ScyllaDBContainer(String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    public ScyllaDBContainer(DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n\n        withExposedPorts(CQL_PORT, SHARD_AWARE_PORT);\n\n        withCommand(COMMAND);\n        waitingFor(Wait.forLogMessage(\".*initialization completed..*\", 1));\n    }\n\n    @Override\n    protected void configure() {\n        if (this.alternatorEnabled) {\n            addExposedPort(8000);\n            String newCommand =\n                COMMAND + \" --alternator-port=\" + ALTERNATOR_PORT + \" --alternator-write-isolation=always\";\n            withCommand(newCommand);\n        }\n\n        // Map (effectively replace) directory in Docker with the content of resourceLocation if resource location is\n        // not null.\n        Optional\n            .ofNullable(configLocation)\n            .map(MountableFile::forClasspathResource)\n            .ifPresent(mountableFile -> withCopyFileToContainer(mountableFile, CONTAINER_CONFIG_LOCATION));\n    }\n\n    public ScyllaDBContainer withConfigurationOverride(String configLocation) {\n        this.configLocation = configLocation;\n        return this;\n    }\n\n    public ScyllaDBContainer withSsl(MountableFile certificate, MountableFile keyfile, MountableFile truststore) {\n        withCopyFileToContainer(certificate, \"/etc/scylla/scylla.cer.pem\");\n        withCopyFileToContainer(keyfile, \"/etc/scylla/scylla.key.pem\");\n        withCopyFileToContainer(truststore, \"/etc/scylla/scylla.truststore\");\n        withEnv(\"SSL_CERTFILE\", \"/etc/scylla/scylla.cer.pem\");\n        return this;\n    }\n\n    public ScyllaDBContainer withAlternator() {\n        this.alternatorEnabled = true;\n        return this;\n    }\n\n    /**\n     * Retrieve an {@link InetSocketAddress} for connecting to the ScyllaDB container via the driver.\n     *\n     * @return A InetSocketAddress representation of this ScyllaDB container's host and port.\n     */\n    public InetSocketAddress getContactPoint() {\n        return new InetSocketAddress(getHost(), getMappedPort(CQL_PORT));\n    }\n\n    public InetSocketAddress getShardAwareContactPoint() {\n        return new InetSocketAddress(getHost(), getMappedPort(SHARD_AWARE_PORT));\n    }\n\n    public String getAlternatorEndpoint() {\n        if (!this.alternatorEnabled) {\n            throw new IllegalStateException(\"Alternator is not enabled\");\n        }\n        return \"http://\" + getHost() + \":\" + getMappedPort(ALTERNATOR_PORT);\n    }\n}\n"
  },
  {
    "path": "modules/scylladb/src/test/java/org/testcontainers/scylladb/ScyllaDBContainerTest.java",
    "content": "package org.testcontainers.scylladb;\n\nimport com.datastax.oss.driver.api.core.CqlSession;\nimport com.datastax.oss.driver.api.core.cql.ResultSet;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.Container;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.MountableFile;\nimport software.amazon.awssdk.auth.credentials.AwsBasicCredentials;\nimport software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;\nimport software.amazon.awssdk.regions.Region;\nimport software.amazon.awssdk.services.dynamodb.DynamoDbClient;\nimport software.amazon.awssdk.services.dynamodb.model.AttributeDefinition;\nimport software.amazon.awssdk.services.dynamodb.model.BillingMode;\nimport software.amazon.awssdk.services.dynamodb.model.CreateTableRequest;\nimport software.amazon.awssdk.services.dynamodb.model.KeySchemaElement;\nimport software.amazon.awssdk.services.dynamodb.model.KeyType;\nimport software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType;\n\nimport java.io.IOException;\nimport java.net.URI;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\nimport java.security.KeyManagementException;\nimport java.security.KeyStore;\nimport java.security.KeyStoreException;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.UnrecoverableKeyException;\nimport java.security.cert.CertificateException;\n\nimport javax.net.ssl.KeyManagerFactory;\nimport javax.net.ssl.SSLContext;\nimport javax.net.ssl.TrustManagerFactory;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.assertThatThrownBy;\n\nclass ScyllaDBContainerTest {\n\n    private static final DockerImageName SCYLLADB_IMAGE = DockerImageName.parse(\"scylladb/scylla:6.2\");\n\n    private static final String BASIC_QUERY = \"SELECT release_version FROM system.local\";\n\n    @Test\n    void testSimple() {\n        try ( // container {\n            ScyllaDBContainer scylladb = new ScyllaDBContainer(\"scylladb/scylla:6.2\")\n            // }\n        ) {\n            scylladb.start();\n            // session {\n            CqlSession session = CqlSession\n                .builder()\n                .addContactPoint(scylladb.getContactPoint())\n                .withLocalDatacenter(\"datacenter1\")\n                .build();\n            // }\n            ResultSet resultSet = session.execute(BASIC_QUERY);\n            assertThat(resultSet.wasApplied()).isTrue();\n            assertThat(resultSet.one().getString(0)).isNotNull();\n            assertThat(session.getMetadata().getNodes().values()).hasSize(1);\n        }\n    }\n\n    @Test\n    void testSimpleSsl()\n        throws NoSuchAlgorithmException, KeyStoreException, IOException, CertificateException, UnrecoverableKeyException, KeyManagementException {\n        try (\n            // customConfiguration {\n            ScyllaDBContainer scylladb = new ScyllaDBContainer(\"scylladb/scylla:6.2\")\n                .withConfigurationOverride(\"scylla-test-ssl\")\n                .withSsl(\n                    MountableFile.forClasspathResource(\"keys/scylla.cer.pem\"),\n                    MountableFile.forClasspathResource(\"keys/scylla.key.pem\"),\n                    MountableFile.forClasspathResource(\"keys/scylla.truststore\")\n                )\n            // }\n        ) {\n            // sslContext {\n            String testResourcesDir = getClass().getClassLoader().getResource(\"keys/\").getPath();\n\n            KeyStore keyStore = KeyStore.getInstance(\"PKCS12\");\n            keyStore.load(\n                Files.newInputStream(Paths.get(testResourcesDir + \"scylla.keystore\")),\n                \"scylla\".toCharArray()\n            );\n\n            KeyStore trustStore = KeyStore.getInstance(\"PKCS12\");\n            trustStore.load(\n                Files.newInputStream(Paths.get(testResourcesDir + \"scylla.truststore\")),\n                \"scylla\".toCharArray()\n            );\n\n            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(\n                KeyManagerFactory.getDefaultAlgorithm()\n            );\n            keyManagerFactory.init(keyStore, \"scylla\".toCharArray());\n\n            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(\n                TrustManagerFactory.getDefaultAlgorithm()\n            );\n            trustManagerFactory.init(trustStore);\n\n            SSLContext sslContext = SSLContext.getInstance(\"TLS\");\n            sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);\n            // }\n\n            scylladb.start();\n\n            CqlSession session = CqlSession\n                .builder()\n                .addContactPoint(scylladb.getContactPoint())\n                .withLocalDatacenter(\"datacenter1\")\n                .withSslContext(sslContext)\n                .build();\n            ResultSet resultSet = session.execute(BASIC_QUERY);\n            assertThat(resultSet.wasApplied()).isTrue();\n            assertThat(resultSet.one().getString(0)).isNotNull();\n            assertThat(session.getMetadata().getNodes().values()).hasSize(1);\n        }\n    }\n\n    @Test\n    void testSimpleSslCqlsh() throws IllegalStateException, InterruptedException, IOException {\n        try (\n            ScyllaDBContainer scylladb = new ScyllaDBContainer(SCYLLADB_IMAGE)\n                .withConfigurationOverride(\"scylla-test-ssl\")\n                .withSsl(\n                    MountableFile.forClasspathResource(\"keys/scylla.cer.pem\"),\n                    MountableFile.forClasspathResource(\"keys/scylla.key.pem\"),\n                    MountableFile.forClasspathResource(\"keys/scylla.truststore\")\n                )\n        ) {\n            scylladb.start();\n\n            Container.ExecResult execResult = scylladb.execInContainer(\n                \"cqlsh\",\n                \"--ssl\",\n                \"-e\",\n                \"select * from system_schema.keyspaces;\"\n            );\n            assertThat(execResult.getStdout()).contains(\"keyspace_name\");\n        }\n    }\n\n    @Test\n    void testShardAwareness() {\n        try (ScyllaDBContainer scylladb = new ScyllaDBContainer(SCYLLADB_IMAGE)) {\n            scylladb.start();\n            // shardAwarenessSession {\n            CqlSession session = CqlSession\n                .builder()\n                .addContactPoint(scylladb.getShardAwareContactPoint())\n                .withLocalDatacenter(\"datacenter1\")\n                .build();\n            // }\n            ResultSet resultSet = session.execute(\"SELECT driver_name FROM system.clients\");\n            assertThat(resultSet.one().getString(0)).isNotNull();\n            assertThat(session.getMetadata().getNodes().values()).hasSize(1);\n        }\n    }\n\n    @Test\n    void testAlternator() {\n        try ( // alternator {\n            ScyllaDBContainer scylladb = new ScyllaDBContainer(SCYLLADB_IMAGE).withAlternator()\n            // }\n        ) {\n            scylladb.start();\n\n            // dynamodDbClient {\n            DynamoDbClient client = DynamoDbClient\n                .builder()\n                .endpointOverride(URI.create(scylladb.getAlternatorEndpoint()))\n                .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(\"test\", \"test\")))\n                .region(Region.US_EAST_1)\n                .build();\n            // }\n            client.createTable(\n                CreateTableRequest\n                    .builder()\n                    .tableName(\"demo_table\")\n                    .keySchema(KeySchemaElement.builder().attributeName(\"id\").keyType(KeyType.HASH).build())\n                    .attributeDefinitions(\n                        AttributeDefinition.builder().attributeName(\"id\").attributeType(ScalarAttributeType.S).build()\n                    )\n                    .billingMode(BillingMode.PAY_PER_REQUEST)\n                    .build()\n            );\n            assertThat(client.listTables().tableNames()).containsExactly((\"demo_table\"));\n        }\n    }\n\n    @Test\n    void throwExceptionWhenAlternatorDisabled() {\n        try (ScyllaDBContainer scylladb = new ScyllaDBContainer(SCYLLADB_IMAGE)) {\n            scylladb.start();\n            assertThatThrownBy(scylladb::getAlternatorEndpoint)\n                .isInstanceOf(IllegalStateException.class)\n                .hasMessageContaining(\"Alternator is not enabled\");\n        }\n    }\n}\n"
  },
  {
    "path": "modules/scylladb/src/test/resources/keys/node0.cer",
    "content": ""
  },
  {
    "path": "modules/scylladb/src/test/resources/keys/node0.p12",
    "content": ""
  },
  {
    "path": "modules/scylladb/src/test/resources/keys/scylla.cer.pem",
    "content": "Bag Attributes\n    friendlyName: node0\n    localKeyID: 54 69 6D 65 20 31 37 33 35 39 34 30 37 38 39 31 39 34 \nsubject=C=None, L=None, O=None, OU=None, CN=None\nissuer=C=None, L=None, O=None, OU=None, CN=None\n-----BEGIN CERTIFICATE-----\nMIIEOzCCAqOgAwIBAgIIY4iVNsJSWiEwDQYJKoZIhvcNAQEMBQAwSzENMAsGA1UE\nBhMETm9uZTENMAsGA1UEBxMETm9uZTENMAsGA1UEChMETm9uZTENMAsGA1UECxME\nTm9uZTENMAsGA1UEAxMETm9uZTAgFw0yNTAxMDMyMTM5MzFaGA8yMTI0MTIxMDIx\nMzkzMVowSzENMAsGA1UEBhMETm9uZTENMAsGA1UEBxMETm9uZTENMAsGA1UEChME\nTm9uZTENMAsGA1UECxMETm9uZTENMAsGA1UEAxMETm9uZTCCAaIwDQYJKoZIhvcN\nAQEBBQADggGPADCCAYoCggGBAJuC18n+jlDcmR8CWxSK3fR2t1Am8P7IK5FY3ky8\nvEJSCMh+GoiqXVq67zhpOJnlgvEEZIDJGzBmJ/nIZvQwIAMxs792fHIEpEI2GTpf\noaMf/9AAuPXuscg+5i4us1eVyVbrq3sREJ2NXHIPylcjtbwLjuepvmXTLp1d7oOJ\nAd0X0W3UN/uwrlV3NPBuVLjJiCvJijWrCv1lFTuIcclqs478ozllp8UfcwJ57OH2\nHq1ee9Ex9y7HouDPfFzmMRp1/jEcb0xbefpdW3Am6P9AXQuw2JMempwt5KbrAE+Z\nV1JnZCjSYSkspwid2bt5To/o60ypZUUswElasgAV/k8AxxDOkJGZusEqqVH7EFvk\nh3FiY/jb9cM1t5eLcpjx0wA+GOuErW3dgH5/WYugY2iiYjP1IQTb8Pk+gfAvq+2p\nSX3wISDCAh53j+aceUvNf+lItXsz66V9e+VH1xcOZcyO4gAMUVNYQFv/2wZ9knK4\no30Aiqir1g2Hd5F/rWYNum+UbQIDAQABoyEwHzAdBgNVHQ4EFgQUqAWcYa3l/OHI\nJACasy+bZUwHP9kwDQYJKoZIhvcNAQEMBQADggGBAJQo55VJd8aEv6uiC5bKdACo\nM1GMvxWXUFzTdh2XKTOMF5GWwGJ3WRuW9o9wMZwXjvRihPfnx+DnfCCgZBOTGLXB\n3ObsogR9rij4uquUIkGJsshggY2gO82NVD7dRwGClncwTI+/RU7qGUym4SEdg6GP\nyfad3eTvqscQU1mNTxkaH0IDzPm0SWF8lcgGnrdHWlN+Nb8MJSHL5NFc9DA9pZck\n5/4MG1X8Hsk/UT04ln+8VrhYFkxkDv4fSKlr65slrst5721J0j+VLEwnuEl1onpW\nWHTTTIcOTDR5asrN9ZACCUsBxST8yfoJQ5G4HMO+UI1/1d928Ug6kHNWw2WR5FGG\npJVu9vpTdA01MNkSeCuZhaPe2XgZcNPyHXcVxslNvFFZ0FVt6pSIhtmZ+4a8dRsm\neU4NQ+PJ24En/8dErxaPqmi31wRZBg5Y9YlugJV4GQszCKHr0OYNK+Lpdq9dboUj\n6lxX7+gshUgKMzunUl/rTvddG7e/WuZbi9IvmJ4MYw==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "modules/scylladb/src/test/resources/keys/scylla.key.pem",
    "content": "Bag Attributes\n    friendlyName: node0\n    localKeyID: 54 69 6D 65 20 31 37 33 35 39 34 30 37 38 39 31 39 34 \nKey Attributes: <No Attributes>\n-----BEGIN PRIVATE KEY-----\nMIIG/AIBADANBgkqhkiG9w0BAQEFAASCBuYwggbiAgEAAoIBgQCbgtfJ/o5Q3Jkf\nAlsUit30drdQJvD+yCuRWN5MvLxCUgjIfhqIql1auu84aTiZ5YLxBGSAyRswZif5\nyGb0MCADMbO/dnxyBKRCNhk6X6GjH//QALj17rHIPuYuLrNXlclW66t7ERCdjVxy\nD8pXI7W8C47nqb5l0y6dXe6DiQHdF9Ft1Df7sK5VdzTwblS4yYgryYo1qwr9ZRU7\niHHJarOO/KM5ZafFH3MCeezh9h6tXnvRMfcux6Lgz3xc5jEadf4xHG9MW3n6XVtw\nJuj/QF0LsNiTHpqcLeSm6wBPmVdSZ2Qo0mEpLKcIndm7eU6P6OtMqWVFLMBJWrIA\nFf5PAMcQzpCRmbrBKqlR+xBb5IdxYmP42/XDNbeXi3KY8dMAPhjrhK1t3YB+f1mL\noGNoomIz9SEE2/D5PoHwL6vtqUl98CEgwgIed4/mnHlLzX/pSLV7M+ulfXvlR9cX\nDmXMjuIADFFTWEBb/9sGfZJyuKN9AIqoq9YNh3eRf61mDbpvlG0CAwEAAQKCAYAT\nSMt3qhB96I04cjNXPc0+ZoZe8yVJgwscEBgpDfKOitu5+SFTN0UyXiISLcIuG278\ncl4ANnAftVtZt0dFGr6thrlSkd/mx7qS12CTg45oyywO4DgPj1UOjvY+Xd4xi0qX\nc8wlC72yu/ft0RV3bt83fXtwMPWCbQjHzQEp4JCRmUWISBvVI1jLEmhHNHdfHua6\n/1gbRaWsPJ/AbTAnGQtBPQUEth1y7W52rSX582pkd2YFUBvl+i2xkSlL3+PQ8zar\n5giPYZrGh5pCu/bflAsBGZyRx9keSsRK/bzqE0xeRAwTOir2V6g7LbSKLC04xKNc\n06/rHf1gslHNNOC3SjHvPyPfTJFHG9Tm+J5OoGo/Rr/W+GNgFMsFJ1fIq1VedpTt\nov4CBnBgew8uHTwCoiL6T7f/ttd206A6nhEZ9tWFf8v0o6+y6Z7g0VniU9IuLRLr\nhXuKkxbBDZQRO8equlAKtbkqv6YFbGImmF/1YwP1/Ct1TR1BDM3m1UB6eez7BWEC\ngcEAx2RL8dJCVbKoRMjsKqNNh0R3vIz0+S8PTi3yjFjhggUCWOzlwMVFv/y0ztGf\npj6Y41eaIdTwQu76uZra748Uj1Vwj5zAKXhb/THWoAidONFRj+qJ3ylDobrO5Fme\nRiCFlIfjNc6wYQiGqSMXTF02O67to44G+4zsrz+syIZO3ANOR+uB+LUNqvFKL5Kk\nBUDtU+r9poIoXkgYylzRb/6H0J+D0fcPGg+LHeRvp3DL6uueDN7eGXxdy7hF/q3L\nDqHlAoHBAMepUZUe5m6h6wIYWoaXPwvSeuBSHWUiGEqoNCrA/1tBI49AOjfn6ccy\nvu51ng/hEI/XpQ+QXvM/MNk3wyKe3HMjaPKiRbro9EFtva3pz3SrLoRHHzSGkzW3\niTavg8RKo76Pz7MNEVqfkFn0pYr85EMIe4hmmrdR6nwd1oJY1CEMf4wllhWG+v1y\n901xLisuRZFE/X4ASvyDyY0Nh+9Cfd+80QS9fpZwuCR+mHQvIpp89F/Ohqyhk9CU\nHLncQD2f6QKBwBJZUX/UeJRIV6HU157o3kaXb2ljk1unEAKCyfJOb5o2ecvTKSV/\nQfbz+3OY6Nc0pX8uXZnFbcLLGTmhXYp0IVE7bJtasnhegiCfyH97q3RCFv5md/+Y\nXYfxl/59nMoZThGoG6mk9qhHT5UbDJbTcR028Nl/RXc6tcE+29isO2+VwktuCczo\nZHSZtdkA5qUxH2X8lxEOo0Zh3h4pQoDK7JavR0M4OCSOz5+VmQzQnYNl4WqPy+KO\nhlcsAwz301rqXQKBwHkY2+9q924gbM4vgTBiqY19EqPdihCd1kfprwJDXl21q2Cm\nHulrkqILyDwPQFf3NLlZnLZM5Rn5uKH2rTbhTWnUD0IiY9KSmhrY+ZNy3S2w6Zy3\nGlkcSkrpT6LIX039y0S4Ksw5X84sOzwkIweijLuPeIVpXetUFrlCy6jxQW/uCaox\n3c6euLpiMVZaEBuGjBEo2+rBOLnhIKyZiVn3ZSr/dXK/j/ik0zrnQYYuVHmI0hsN\nwycPNPzr6GReDuSRiQKBwChoS1Vvv49agWjyViIohGm6GHsY1Y1FNIqddHN5KgfA\nLGZRm8JhlTBPX89KgWUpemDjRHw84vqF46Md9+eeuovr697/fEVQ1W4FWJs9JLej\n2zmRlZqQgFnR6hdeeg1l7V8bPLR1zfl0R7+UkguP1xuI55fZc9H5icMCrOOCo1ug\nvdBrhNl4Swzn+wTVY62J/GX86Rfeybvn+BJQW4RCuKFqcxqctPuR5i+wMOxKWZP3\nfMq1U6czbhYvEjp3Y42Exw==\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "modules/scylladb/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/scylladb/src/test/resources/scylla-test-ssl/scylla.yaml",
    "content": "# Scylla storage config YAML\n\n#######################################\n# This file is split to two sections:\n# 1. Supported parameters\n# 2. Unsupported parameters: reserved for future use or backwards\n#    compatibility.\n# Scylla will only read and use the first segment\n#######################################\n\n### Supported Parameters\n\n# The name of the cluster. This is mainly used to prevent machines in\n# one logical cluster from joining another.\n# It is recommended to change the default value when creating a new cluster.\n# You can NOT modify this value for an existing cluster\n#cluster_name: 'Test Cluster'\n\n# This defines the number of tokens randomly assigned to this node on the ring\n# The more tokens, relative to other nodes, the larger the proportion of data\n# that this node will store. You probably want all nodes to have the same number\n# of tokens assuming they have equal hardware capability.\nnum_tokens: 256\n\n# Directory where Scylla should store all its files, which are commitlog,\n# data, hints, view_hints and saved_caches subdirectories. All of these\n# subs can be overridden by the respective options below.\n# If unset, the value defaults to /var/lib/scylla\n# workdir: /var/lib/scylla\n\n# Directory where Scylla should store data on disk.\n# data_file_directories:\n#    - /var/lib/scylla/data\n\n# commit log.  when running on magnetic HDD, this should be a\n# separate spindle than the data directories.\n# commitlog_directory: /var/lib/scylla/commitlog\n\n# schema commit log. A special commitlog instance\n# used for schema and system tables.\n# When running on magnetic HDD, this should be a\n# separate spindle than the data directories.\n# schema_commitlog_directory: /var/lib/scylla/commitlog/schema\n\n# commitlog_sync may be either \"periodic\" or \"batch.\"\n#\n# When in batch mode, Scylla won't ack writes until the commit log\n# has been fsynced to disk.  It will wait\n# commitlog_sync_batch_window_in_ms milliseconds between fsyncs.\n# This window should be kept short because the writer threads will\n# be unable to do extra work while waiting.  (You may need to increase\n# concurrent_writes for the same reason.)\n#\n# commitlog_sync: batch\n# commitlog_sync_batch_window_in_ms: 2\n#\n# the other option is \"periodic\" where writes may be acked immediately\n# and the CommitLog is simply synced every commitlog_sync_period_in_ms\n# milliseconds.\ncommitlog_sync: periodic\ncommitlog_sync_period_in_ms: 10000\n\n# The size of the individual commitlog file segments.  A commitlog\n# segment may be archived, deleted, or recycled once all the data\n# in it (potentially from each columnfamily in the system) has been\n# flushed to sstables.\n#\n# The default size is 32, which is almost always fine, but if you are\n# archiving commitlog segments (see commitlog_archiving.properties),\n# then you probably want a finer granularity of archiving; 8 or 16 MB\n# is reasonable.\ncommitlog_segment_size_in_mb: 32\n\n# The size of the individual schema commitlog file segments.\n#\n# The default size is 128, which is 4 times larger than the default\n# size of the data commitlog. It's because the segment size puts\n# a limit on the mutation size that can be written at once, and some\n# schema mutation writes are much larger than average.\nschema_commitlog_segment_size_in_mb: 128\n\n# any class that implements the SeedProvider interface and has a\n# constructor that takes a Map<String, String> of parameters will do.\nseed_provider:\n    # Addresses of hosts that are deemed contact points.\n    # Cassandra nodes use this list of hosts to find each other and learn\n    # the topology of the ring.  You must change this if you are running\n    # multiple nodes!\n    - class_name: org.apache.cassandra.locator.SimpleSeedProvider\n      parameters:\n          # seeds is actually a comma-delimited list of addresses.\n          # Ex: \"<ip1>,<ip2>,<ip3>\"\n          - seeds: \"172.17.0.3,127.0.0.1,172.17.0.2,172.17.0.4,172.17.0.5\"\n\n\n# Address to bind to and tell other Scylla nodes to connect to.\n# You _must_ change this if you want multiple nodes to be able to communicate!\n#\n# If you leave broadcast_address (below) empty, then setting listen_address\n# to 0.0.0.0 is wrong as other nodes will not know how to reach this node.\n# If you set broadcast_address, then you can set listen_address to 0.0.0.0.\nlisten_address: localhost\n\n# Address to broadcast to other Scylla nodes\n# Leaving this blank will set it to the same value as listen_address\n# broadcast_address: 1.2.3.4\n\n\n# When using multiple physical network interfaces, set this to true to listen on broadcast_address\n# in addition to the listen_address, allowing nodes to communicate in both interfaces.\n# Ignore this property if the network configuration automatically routes between the public and private networks such as EC2.\n#\n# listen_on_broadcast_address: false\n\n# port for the CQL native transport to listen for clients on\n# For security reasons, you should not expose this port to the internet. Firewall it if needed.\n# To disable the CQL native transport, remove this option and configure native_transport_port_ssl.\nnative_transport_port: 9042\n\n# Like native_transport_port, but clients are forwarded to specific shards, based on the\n# client-side port numbers.\nnative_shard_aware_transport_port: 19042\n\n# Enabling native transport encryption in client_encryption_options allows you to either use\n# encryption for the standard port or to use a dedicated, additional port along with the unencrypted\n# standard native_transport_port.\n# Enabling client encryption and keeping native_transport_port_ssl disabled will use encryption\n# for native_transport_port. Setting native_transport_port_ssl to a different value\n# from native_transport_port will use encryption for native_transport_port_ssl while\n# keeping native_transport_port unencrypted.\n#native_transport_port_ssl: 9142\n\n# Like native_transport_port_ssl, but clients are forwarded to specific shards, based on the\n# client-side port numbers.\n#native_shard_aware_transport_port_ssl: 19142\n\n# How long the coordinator should wait for read operations to complete\nread_request_timeout_in_ms: 5000\n\n# How long the coordinator should wait for writes to complete\nwrite_request_timeout_in_ms: 2000\n# how long a coordinator should continue to retry a CAS operation\n# that contends with other proposals for the same row\ncas_contention_timeout_in_ms: 1000\n\n# phi value that must be reached for a host to be marked down.\n# most users should never need to adjust this.\n# phi_convict_threshold: 8\n\n# IEndpointSnitch.  The snitch has two functions:\n# - it teaches Scylla enough about your network topology to route\n#   requests efficiently\n# - it allows Scylla to spread replicas around your cluster to avoid\n#   correlated failures. It does this by grouping machines into\n#   \"datacenters\" and \"racks.\"  Scylla will do its best not to have\n#   more than one replica on the same \"rack\" (which may not actually\n#   be a physical location)\n#\n# IF YOU CHANGE THE SNITCH AFTER DATA IS INSERTED INTO THE CLUSTER,\n# YOU MUST RUN A FULL REPAIR, SINCE THE SNITCH AFFECTS WHERE REPLICAS\n# ARE PLACED.\n#\n# Out of the box, Scylla provides\n#  - SimpleSnitch:\n#    Treats Strategy order as proximity. This can improve cache\n#    locality when disabling read repair.  Only appropriate for\n#    single-datacenter deployments.\n#  - GossipingPropertyFileSnitch\n#    This should be your go-to snitch for production use.  The rack\n#    and datacenter for the local node are defined in\n#    cassandra-rackdc.properties and propagated to other nodes via\n#    gossip.  If cassandra-topology.properties exists, it is used as a\n#    fallback, allowing migration from the PropertyFileSnitch.\n#  - PropertyFileSnitch:\n#    Proximity is determined by rack and data center, which are\n#    explicitly configured in cassandra-topology.properties.\n#  - Ec2Snitch:\n#    Appropriate for EC2 deployments in a single Region. Loads Region\n#    and Availability Zone information from the EC2 API. The Region is\n#    treated as the datacenter, and the Availability Zone as the rack.\n#    Only private IPs are used, so this will not work across multiple\n#    Regions.\n#  - Ec2MultiRegionSnitch:\n#    Uses public IPs as broadcast_address to allow cross-region\n#    connectivity.  (Thus, you should set seed addresses to the public\n#    IP as well.) You will need to open the storage_port or\n#    ssl_storage_port on the public IP firewall.  (For intra-Region\n#    traffic, Scylla will switch to the private IP after\n#    establishing a connection.)\n#  - RackInferringSnitch:\n#    Proximity is determined by rack and data center, which are\n#    assumed to correspond to the 3rd and 2nd octet of each node's IP\n#    address, respectively.  Unless this happens to match your\n#    deployment conventions, this is best used as an example of\n#    writing a custom Snitch class and is provided in that spirit.\n#\n# You can use a custom Snitch by setting this to the full class name\n# of the snitch, which will be assumed to be on your classpath.\nendpoint_snitch: SimpleSnitch\n\n# The address or interface to bind the native transport server to.\n#\n# Set rpc_address OR rpc_interface, not both. Interfaces must correspond\n# to a single address, IP aliasing is not supported.\n#\n# Leaving rpc_address blank has the same effect as on listen_address\n# (i.e. it will be based on the configured hostname of the node).\n#\n# Note that unlike listen_address, you can specify 0.0.0.0, but you must also\n# set broadcast_rpc_address to a value other than 0.0.0.0.\n#\n# For security reasons, you should not expose this port to the internet.  Firewall it if needed.\n#\n# If you choose to specify the interface by name and the interface has an ipv4 and an ipv6 address\n# you can specify which should be chosen using rpc_interface_prefer_ipv6. If false the first ipv4\n# address will be used. If true the first ipv6 address will be used. Defaults to false preferring\n# ipv4. If there is only one address it will be selected regardless of ipv4/ipv6.\nrpc_address: localhost\n# rpc_interface: eth1\n# rpc_interface_prefer_ipv6: false\n\n# port for REST API server\napi_port: 10000\n\n# IP for the REST API server\napi_address: 127.0.0.1\n\n# Log WARN on any batch size exceeding this value. 128 kiB per batch by default.\n# Caution should be taken on increasing the size of this threshold as it can lead to node instability.\nbatch_size_warn_threshold_in_kb: 128\n\n# Fail any multiple-partition batch exceeding this value. 1 MiB (8x warn threshold) by default.\nbatch_size_fail_threshold_in_kb: 1024\n\n  # Authentication backend, identifying users\n  # Out of the box, Scylla provides org.apache.cassandra.auth.{AllowAllAuthenticator,\n  # PasswordAuthenticator}.\n  #\n  # - AllowAllAuthenticator performs no checks - set it to disable authentication.\n  # - PasswordAuthenticator relies on username/password pairs to authenticate\n  #   users. It keeps usernames and hashed passwords in system_auth.credentials table.\n  #   Please increase system_auth keyspace replication factor if you use this authenticator.\n  # - com.scylladb.auth.TransitionalAuthenticator requires username/password pair\n  #   to authenticate in the same manner as PasswordAuthenticator, but improper credentials\n  #   result in being logged in as an anonymous user. Use for upgrading clusters' auth.\n  # authenticator: AllowAllAuthenticator\n\n  # Authorization backend, implementing IAuthorizer; used to limit access/provide permissions\n  # Out of the box, Scylla provides org.apache.cassandra.auth.{AllowAllAuthorizer,\n  # CassandraAuthorizer}.\n  #\n  # - AllowAllAuthorizer allows any action to any user - set it to disable authorization.\n  # - CassandraAuthorizer stores permissions in system_auth.permissions table. Please\n  #   increase system_auth keyspace replication factor if you use this authorizer.\n  # - com.scylladb.auth.TransitionalAuthorizer wraps around the CassandraAuthorizer, using it for\n  #   authorizing permission management. Otherwise, it allows all. Use for upgrading\n  #   clusters' auth.\n  # authorizer: AllowAllAuthorizer\n\n  # initial_token allows you to specify tokens manually.  While you can use # it with\n  # vnodes (num_tokens > 1, above) -- in which case you should provide a\n  # comma-separated list -- it's primarily used when adding nodes # to legacy clusters\n  # that do not have vnodes enabled.\n  # initial_token:\n\n  # RPC address to broadcast to drivers and other Scylla nodes. This cannot\n  # be set to 0.0.0.0. If left blank, this will be set to the value of\n  # rpc_address. If rpc_address is set to 0.0.0.0, broadcast_rpc_address must\n  # be set.\n  # broadcast_rpc_address: 1.2.3.4\n\n  # Uncomment to enable experimental features\n  # experimental_features:\n  #     - udf\n  #     - alternator-streams\n  #     - broadcast-tables\n  #     - keyspace-storage-options\n\n  # The directory where hints files are stored if hinted handoff is enabled.\n  # hints_directory: /var/lib/scylla/hints\n\n# The directory where hints files are stored for materialized-view updates\n# view_hints_directory: /var/lib/scylla/view_hints\n\n# See https://docs.scylladb.com/architecture/anti-entropy/hinted-handoff\n# May either be \"true\" or \"false\" to enable globally, or contain a list\n# of data centers to enable per-datacenter.\n# hinted_handoff_enabled: DC1,DC2\n# hinted_handoff_enabled: true\n\n# this defines the maximum amount of time a dead host will have hints\n# generated.  After it has been dead this long, new hints for it will not be\n# created until it has been seen alive and gone down again.\n# max_hint_window_in_ms: 10800000 # 3 hours\n\n\n# Validity period for permissions cache (fetching permissions can be an\n# expensive operation depending on the authorizer, CassandraAuthorizer is\n# one example). Defaults to 10000, set to 0 to disable.\n# Will be disabled automatically for AllowAllAuthorizer.\n# permissions_validity_in_ms: 10000\n\n# Refresh interval for permissions cache (if enabled).\n# After this interval, cache entries become eligible for refresh. Upon next\n# access, an async reload is scheduled and the old value returned until it\n# completes. If permissions_validity_in_ms is non-zero, then this also must have\n# a non-zero value. Defaults to 2000. It's recommended to set this value to\n# be at least 3 times smaller than the permissions_validity_in_ms.\n# permissions_update_interval_in_ms: 2000\n\n# The partitioner is responsible for distributing groups of rows (by\n# partition key) across nodes in the cluster.  You should leave this\n# alone for new clusters.  The partitioner can NOT be changed without\n# reloading all data, so when upgrading you should set this to the\n# same partitioner you were already using.\n#\n# Murmur3Partitioner is currently the only supported partitioner,\n#\npartitioner: org.apache.cassandra.dht.Murmur3Partitioner\n\n# Total space to use for commitlogs.\n#\n# If space gets above this value (it will round up to the next nearest\n# segment multiple), Scylla will flush every dirty CF in the oldest\n# segment and remove it.  So a small total commitlog space will tend\n# to cause more flush activity on less-active columnfamilies.\n#\n# A value of -1 (default) will automatically equate it to the total amount of memory\n# available for Scylla.\ncommitlog_total_space_in_mb: -1\n\n# TCP port, for commands and data\n# For security reasons, you should not expose this port to the internet.  Firewall it if needed.\n# storage_port: 7000\n\n# SSL port, for encrypted communication.  Unused unless enabled in\n# encryption_options\n# For security reasons, you should not expose this port to the internet.  Firewall it if needed.\n# ssl_storage_port: 7001\n\n# listen_interface: eth0\n# listen_interface_prefer_ipv6: false\n\n# Whether to start the native transport server.\n# Please note that the address on which the native transport is bound is the\n# same as the rpc_address. The port however is different and specified below.\n# start_native_transport: true\n\n# The maximum size of allowed frame. Frame (requests) larger than this will\n# be rejected as invalid. The default is 256MB.\n# native_transport_max_frame_size_in_mb: 256\n\n# enable or disable keepalive on rpc/native connections\n# rpc_keepalive: true\n\n# Set to true to have Scylla create a hard link to each sstable\n# flushed or streamed locally in a backups/ subdirectory of the\n# keyspace data.  Removing these links is the operator's\n# responsibility.\n# incremental_backups: false\n\n# Whether or not to take a snapshot before each compaction.  Be\n# careful using this option, since Scylla won't clean up the\n# snapshots for you.  Mostly useful if you're paranoid when there\n# is a data format change.\n# snapshot_before_compaction: false\n\n# Whether or not a snapshot is taken of the data before keyspace truncation\n# or dropping of column families. The STRONGLY advised default of true\n# should be used to provide data safety. If you set this flag to false, you will\n# lose data on truncation or drop.\n# auto_snapshot: true\n\n# When executing a scan, within or across a partition, we need to keep the\n# tombstones seen in memory so we can return them to the coordinator, which\n# will use them to make sure other replicas also know about the deleted rows.\n# With workloads that generate a lot of tombstones, this can cause performance\n# problems and even exhaust the server heap.\n# (http://www.datastax.com/dev/blog/cassandra-anti-patterns-queues-and-queue-like-datasets)\n# Adjust the thresholds here if you understand the dangers and want to\n# scan more tombstones anyway.  These thresholds may also be adjusted at runtime\n# using the StorageService mbean.\n# tombstone_warn_threshold: 1000\n# tombstone_failure_threshold: 100000\n\n# Granularity of the collation index of rows within a partition.\n# Increase if your rows are large, or if you have a very large\n# number of rows per partition.  The competing goals are these:\n#   1) a smaller granularity means more index entries are generated\n#      and looking up rows within the partition by collation column\n#      is faster\n#   2) but, Scylla will keep the collation index in memory for hot\n#      rows (as part of the key cache), so a larger granularity means\n#      you can cache more hot rows\n# column_index_size_in_kb: 64\n\n# Auto-scaling of the promoted index prevents running out of memory\n# when the promoted index grows too large (due to partitions with many rows\n# vs. too small column_index_size_in_kb).  When the serialized representation\n# of the promoted index grows by this threshold, the desired block size\n# for this partition (initialized to column_index_size_in_kb)\n# is doubled, to decrease the sampling resolution by half.\n#\n# To disable promoted index auto-scaling, set the threshold to 0.\n# column_index_auto_scale_threshold_in_kb: 10240\n\n# Log a warning when writing partitions larger than this value\n# compaction_large_partition_warning_threshold_mb: 1000\n\n# Log a warning when writing rows larger than this value\n# compaction_large_row_warning_threshold_mb: 10\n\n# Log a warning when writing cells larger than this value\n# compaction_large_cell_warning_threshold_mb: 1\n\n# Log a warning when row number is larger than this value\n# compaction_rows_count_warning_threshold: 100000\n\n# Log a warning when writing a collection containing more elements than this value\n# compaction_collection_elements_count_warning_threshold: 10000\n\n# How long the coordinator should wait for seq or index scans to complete\n# range_request_timeout_in_ms: 10000\n# How long the coordinator should wait for writes to complete\n# counter_write_request_timeout_in_ms: 5000\n# How long a coordinator should continue to retry a CAS operation\n# that contends with other proposals for the same row\n# cas_contention_timeout_in_ms: 1000\n# How long the coordinator should wait for truncates to complete\n# (This can be much longer, because unless auto_snapshot is disabled\n# we need to flush first so we can snapshot before removing the data.)\n# truncate_request_timeout_in_ms: 60000\n# The default timeout for other, miscellaneous operations\n# request_timeout_in_ms: 10000\n\n# Enable or disable inter-node encryption.\n# You must also generate keys and provide the appropriate key and trust store locations and passwords.\n#\n# The available internode options are : all, none, dc, rack\n# If set to dc scylla  will encrypt the traffic between the DCs\n# If set to rack scylla  will encrypt the traffic between the racks\n#\n# SSL/TLS algorithm and ciphers used can be controlled by\n# the priority_string parameter. Info on priority string\n# syntax and values is available at:\n#   https://gnutls.org/manual/html_node/Priority-Strings.html\n#\n# The require_client_auth parameter allows you to\n# restrict access to service based on certificate\n# validation. Client must provide a certificate\n# accepted by the used trust store to connect.\n#\n# server_encryption_options:\n#    internode_encryption: none\n#    certificate: conf/scylla.crt\n#    keyfile: conf/scylla.key\n#    truststore: <not set, use system trust>\n#    certficate_revocation_list: <not set>\n#    require_client_auth: False\n#    priority_string: <not set, use default>\n\n# enable or disable client/server encryption.\nclient_encryption_options:\n    enabled: true\n    certificate: /etc/scylla/scylla.cer.pem\n    keyfile: /etc/scylla/scylla.key.pem\n    truststore: /etc/scylla/scylla.truststore\n    truststore_password: scylla\n#    certficate_revocation_list: <not set>\n#    require_client_auth: False\n#    priority_string: <not set, use default>\n\n# internode_compression controls whether traffic between nodes is\n# compressed.\n# can be:  all  - all traffic is compressed\n#          dc   - traffic between different datacenters is compressed\n#          none - nothing is compressed.\n# internode_compression: none\n\n# Enables inter-node traffic compression metrics (`scylla_rpc_compression_...`)\n# and enables a new implementation of inter-node traffic compressors,\n# capable of using zstd (in addition to the default lz4)\n# and shared dictionaries.\n# (Those features must still be enabled by other settings).\n# Has minor CPU cost.\n#\n# internode_compression_enable_advanced: false\n\n# Enables training of shared compression dictionaries on inter-node traffic.\n# New dictionaries are distributed throughout the cluster via Raft,\n# and used to improve the effectiveness of inter-node traffic compression\n# when `internode_compression_enable_advanced` is enabled.\n#\n# WARNING: this may leak unencrypted data to disk. The trained dictionaries\n# contain randomly-selected pieces of data written to the cluster.\n# When the Raft log is unencrypted, those pieces of data will be\n# written to disk unencrypted. At the moment of writing, there is no\n# way to encrypt the Raft log.\n# This problem is tracked by https://github.com/scylladb/scylla-enterprise/issues/4717.\n#\n# Can be:  never       - Dictionaries aren't trained by this node.\n#          when_leader - New dictionaries are trained by this node only if\n#                        it's the current Raft leader.\n#          always      - Dictionaries are trained by this node unconditionally.\n#\n# For efficiency reasons, training shouldn't be enabled on more than one node.\n# To enable it on a single node, one can let the cluster pick the trainer\n# by setting `when_leader` on all nodes, or specify one manually by setting `always`\n# on one node and `never` on others.\n#\n# rpc_dict_training_when: never\n\n# A number in range [0.0, 1.0] specifying the share of CPU which can be spent\n# by this node on compressing inter-node traffic with zstd.\n#\n# Depending on the workload, enabling zstd might have a drastic negative\n# effect on performance, so it shouldn't be done lightly.\n#\n# internode_compression_zstd_max_cpu_fraction: 0.0\n\n# Enable or disable tcp_nodelay for inter-dc communication.\n# Disabling it will result in larger (but fewer) network packets being sent,\n# reducing overhead from the TCP protocol itself, at the cost of increasing\n# latency if you block for cross-datacenter responses.\n# inter_dc_tcp_nodelay: false\n\n# Relaxation of environment checks.\n#\n# Scylla places certain requirements on its environment.  If these requirements are\n# not met, performance and reliability can be degraded.\n#\n# These requirements include:\n#    - A filesystem with good support for asynchronous I/O (AIO). Currently,\n#      this means XFS.\n#\n# false: strict environment checks are in place; do not start if they are not met.\n# true: relaxed environment checks; performance and reliability may degraade.\n#\n# developer_mode: false\n\n\n# Idle-time background processing\n#\n# Scylla can perform certain jobs in the background while the system is otherwise idle,\n# freeing processor resources when there is other work to be done.\n#\n# defragment_memory_on_idle: true\n#\n# prometheus port\n# By default, Scylla opens prometheus API port on port 9180\n# setting the port to 0 will disable the prometheus API.\n# prometheus_port: 9180\n#\n# prometheus address\n# Leaving this blank will set it to the same value as listen_address.\n# This means that by default, Scylla listens to the prometheus API on the same\n# listening address (and therefore network interface) used to listen for\n# internal communication. If the monitoring node is not in this internal\n# network, you can override prometheus_address explicitly - e.g., setting\n# it to 0.0.0.0 to listen on all interfaces.\n# prometheus_address: 1.2.3.4\n\n# Distribution of data among cores (shards) within a node\n#\n# Scylla distributes data within a node among shards, using a round-robin\n# strategy:\n#  [shard0] [shard1] ... [shardN-1] [shard0] [shard1] ... [shardN-1] ...\n#\n# Scylla versions 1.6 and below used just one repetition of the pattern;\n# this interfered with data placement among nodes (vnodes).\n#\n# Scylla versions 1.7 and above use 4096 repetitions of the pattern; this\n# provides for better data distribution.\n#\n# the value below is log (base 2) of the number of repetitions.\n#\n# Set to 0 to avoid rewriting all data when upgrading from Scylla 1.6 and\n# below.\n#\n# Keep at 12 for new clusters.\nmurmur3_partitioner_ignore_msb_bits: 12\n\n# Use on a new, parallel algorithm for performing aggregate queries.\n# Set to `false` to fall-back to the old algorithm.\n# enable_parallelized_aggregation: true\n\n# Time for which task manager task started internally is kept in memory after it completes.\n# task_ttl_in_seconds: 0\n\n# Time for which task manager task started by user is kept in memory after it completes.\n# user_task_ttl_in_seconds: 3600\n\n# In materialized views, restrictions are allowed only on the view's primary key columns.\n# In old versions Scylla mistakenly allowed IS NOT NULL restrictions on columns which were not part\n# of the view's primary key. These invalid restrictions were ignored.\n# This option controls the behavior when someone tries to create a view with such invalid IS NOT NULL restrictions.\n#\n# Can be true, false, or warn.\n# * `true`: IS NOT NULL is allowed only on the view's primary key columns,\n#           trying to use it on other columns will cause an error, as it should.\n# * `false`: Scylla accepts IS NOT NULL restrictions on regular columns, but they're silently ignored.\n#            It's useful for backwards compatibility.\n# * `warn`: The same as false, but there's a warning about invalid view restrictions.\n#\n# To preserve backwards compatibility on old clusters, Scylla's default setting is `warn`.\n# New clusters have this option set to `true` by scylla.yaml (which overrides the default `warn`)\n# to make sure that trying to create an invalid view causes an error.\nstrict_is_not_null_in_views: true\n\n# The Unix Domain Socket the node uses for maintenance socket.\n# The possible options are:\n# * ignore: the node will not open the maintenance socket,\n# * workdir: the node will open the maintenance socket on the path <scylla's workdir>/cql.m,\n#            where <scylla's workdir> is a path defined by the workdir configuration option,\n# * <socket path>: the node will open the maintenance socket on the path <socket path>.\nmaintenance_socket: ignore\n\n# If set to true, configuration parameters defined with LiveUpdate option can be updated in runtime with CQL\n# by updating system.config virtual table. If we don't want any configuration parameter to be changed in runtime\n# via CQL, this option should be set to false. This parameter doesn't impose any limits on other mechanisms updating\n# configuration parameters in runtime, e.g. sending SIGHUP or using API. This option should be set to false\n# e.g. for cloud users, for whom scylla's configuration should be changed only by support engineers.\n# live_updatable_config_params_changeable_via_cql: true\n\n# ****************\n# *  GUARDRAILS  *\n# ****************\n\n# Guardrails to warn or fail when Replication Factor is smaller/greater than the threshold.\n# Please note that the value of 0 is always allowed,\n# which means that having no replication at all, i.e. RF = 0, is always valid.\n# A guardrail value smaller than 0, e.g. -1, means that the guardrail is disabled.\n# Commenting out a guardrail also means it is disabled.\n# minimum_replication_factor_fail_threshold: -1\n# minimum_replication_factor_warn_threshold:  3\n# maximum_replication_factor_warn_threshold: -1\n# maximum_replication_factor_fail_threshold: -1\n\n# Guardrails to warn about or disallow creating a keyspace with specific replication strategy.\n# Each of these 2 settings is a list storing replication strategies considered harmful.\n# The replication strategies to choose from are:\n# 1) SimpleStrategy,\n# 2) NetworkTopologyStrategy,\n# 3) LocalStrategy,\n# 4) EverywhereStrategy\n#\n# replication_strategy_warn_list:\n#  - SimpleStrategy\n# replication_strategy_fail_list:\n\n# Enable tablets for new keyspaces.\n# When enabled, newly created keyspaces will have tablets enabled by default.\n# That can be explicitly disabled in the CREATE KEYSPACE query\n# by using the `tablets = {'enabled': false}` replication option.\n#\n# Correspondingly, when disabled, newly created keyspaces will use vnodes\n# unless tablets are explicitly enabled in the CREATE KEYSPACE query\n# by using the `tablets = {'enabled': true}` replication option.\n#\n# Note that creating keyspaces with tablets enabled or disabled is irreversible.\n# The `tablets` option cannot be changed using `ALTER KEYSPACE`.\nenable_tablets: true\n"
  },
  {
    "path": "modules/selenium/build.gradle",
    "content": "description = \"Testcontainers :: Selenium\"\n\ndependencies {\n    api project(':testcontainers')\n\n    provided 'org.seleniumhq.selenium:selenium-remote-driver:4.10.0'\n    provided 'org.seleniumhq.selenium:selenium-chrome-driver:4.10.0'\n\n    testImplementation platform('org.seleniumhq.selenium:selenium-bom:4.13.0')\n    testImplementation 'org.seleniumhq.selenium:selenium-firefox-driver'\n    testImplementation 'org.seleniumhq.selenium:selenium-edge-driver'\n    testImplementation 'org.seleniumhq.selenium:selenium-support'\n\n    testImplementation 'org.mortbay.jetty:jetty:6.1.26'\n    testImplementation project(':testcontainers-nginx')\n\n    compileOnly 'org.jetbrains:annotations:26.0.2-1'\n}\n"
  },
  {
    "path": "modules/selenium/src/main/java/org/testcontainers/containers/BrowserWebDriverContainer.java",
    "content": "package org.testcontainers.containers;\n\nimport com.github.dockerjava.api.command.InspectContainerResponse;\nimport com.github.dockerjava.api.model.AccessMode;\nimport com.github.dockerjava.api.model.Bind;\nimport com.github.dockerjava.api.model.Volume;\nimport com.google.common.collect.ImmutableSet;\nimport org.apache.commons.io.FileUtils;\nimport org.apache.commons.lang3.SystemUtils;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\nimport org.openqa.selenium.Capabilities;\nimport org.openqa.selenium.chrome.ChromeOptions;\nimport org.openqa.selenium.remote.DesiredCapabilities;\nimport org.openqa.selenium.remote.RemoteWebDriver;\nimport org.rnorth.ducttape.timeouts.Timeouts;\nimport org.rnorth.ducttape.unreliables.Unreliables;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.testcontainers.containers.VncRecordingContainer.VncRecordingFormat;\nimport org.testcontainers.containers.traits.LinkableContainer;\nimport org.testcontainers.containers.wait.strategy.HostPortWaitStrategy;\nimport org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy;\nimport org.testcontainers.containers.wait.strategy.WaitAllStrategy;\nimport org.testcontainers.containers.wait.strategy.WaitStrategy;\nimport org.testcontainers.lifecycle.TestDescription;\nimport org.testcontainers.lifecycle.TestLifecycleAware;\nimport org.testcontainers.utility.ComparableVersion;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.nio.file.Files;\nimport java.time.Duration;\nimport java.time.temporal.ChronoUnit;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * A chrome/firefox/custom container based on SeleniumHQ's standalone container sets.\n * <p>\n * Supported images: {@code selenium/standalone-chrome}, {@code selenium/standalone-firefox},\n * {@code selenium/standalone-edge}, {@code selenium/standalone-chrome-debug}, {@code selenium/standalone-firefox-debug}\n * <p>\n * Exposed ports: 4444\n *\n * @deprecated use {@link org.testcontainers.selenium.BrowserWebDriverContainer} instead.\n */\n@Deprecated\npublic class BrowserWebDriverContainer<SELF extends BrowserWebDriverContainer<SELF>>\n    extends GenericContainer<SELF>\n    implements LinkableContainer, TestLifecycleAware {\n\n    private static final DockerImageName CHROME_IMAGE = DockerImageName.parse(\"selenium/standalone-chrome\");\n\n    private static final DockerImageName FIREFOX_IMAGE = DockerImageName.parse(\"selenium/standalone-firefox\");\n\n    private static final DockerImageName EDGE_IMAGE = DockerImageName.parse(\"selenium/standalone-edge\");\n\n    private static final DockerImageName CHROME_DEBUG_IMAGE = DockerImageName.parse(\"selenium/standalone-chrome-debug\");\n\n    private static final DockerImageName FIREFOX_DEBUG_IMAGE = DockerImageName.parse(\n        \"selenium/standalone-firefox-debug\"\n    );\n\n    private static final DockerImageName[] COMPATIBLE_IMAGES = new DockerImageName[] {\n        CHROME_IMAGE,\n        FIREFOX_IMAGE,\n        EDGE_IMAGE,\n        CHROME_DEBUG_IMAGE,\n        FIREFOX_DEBUG_IMAGE,\n    };\n\n    private static final String DEFAULT_PASSWORD = \"secret\";\n\n    private static final int SELENIUM_PORT = 4444;\n\n    private static final int VNC_PORT = 5900;\n\n    private static final String NO_PROXY_KEY = \"no_proxy\";\n\n    private static final String TC_TEMP_DIR_PREFIX = \"tc\";\n\n    @Nullable\n    private Capabilities capabilities;\n\n    private DockerImageName customImageName = null;\n\n    @Nullable\n    private RemoteWebDriver driver;\n\n    private VncRecordingMode recordingMode = VncRecordingMode.RECORD_FAILING;\n\n    private VncRecordingFormat recordingFormat;\n\n    private RecordingFileFactory recordingFileFactory;\n\n    private File vncRecordingDirectory;\n\n    private VncRecordingContainer vncRecordingContainer = null;\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(BrowserWebDriverContainer.class);\n\n    public BrowserWebDriverContainer() {\n        super();\n        this.waitStrategy = getDefaultWaitStrategy();\n\n        this.withRecordingFileFactory(new DefaultRecordingFileFactory());\n    }\n\n    /**\n     * Constructor taking a specific webdriver container name and tag\n     * @param dockerImageName Name of the selenium docker image\n     */\n    public BrowserWebDriverContainer(String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    /**\n     * Constructor taking a specific webdriver container name and tag\n     * @param dockerImageName Name of the selenium docker image\n     */\n    public BrowserWebDriverContainer(DockerImageName dockerImageName) {\n        super(dockerImageName);\n        // we assert compatibility with the chrome/firefox/edge image later, after capabilities are processed\n\n        this.waitStrategy = getDefaultWaitStrategy();\n\n        this.withRecordingFileFactory(new DefaultRecordingFileFactory());\n\n        this.customImageName = dockerImageName;\n        // We have to force SKIP mode for the recording by default because we don't know if the image has VNC or not\n        recordingMode = VncRecordingMode.SKIP;\n    }\n\n    public SELF withCapabilities(Capabilities capabilities) {\n        this.capabilities = capabilities;\n        return self();\n    }\n\n    /**\n     * @deprecated Use withCapabilities(Capabilities capabilities) instead:\n     * withCapabilities(new FirefoxOptions())\n     *\n     * @param capabilities DesiredCapabilities\n     * @return SELF\n     * */\n    @Deprecated\n    public SELF withDesiredCapabilities(DesiredCapabilities capabilities) {\n        this.capabilities = capabilities;\n        return self();\n    }\n\n    @NotNull\n    @Override\n    protected Set<Integer> getLivenessCheckPorts() {\n        Integer seleniumPort = getMappedPort(SELENIUM_PORT);\n        if (recordingMode == VncRecordingMode.SKIP) {\n            return ImmutableSet.of(seleniumPort);\n        } else {\n            return ImmutableSet.of(seleniumPort, getMappedPort(VNC_PORT));\n        }\n    }\n\n    @Override\n    protected void configure() {\n        String seleniumVersion = SeleniumUtils.determineClasspathSeleniumVersion();\n\n        if (recordingMode != VncRecordingMode.SKIP) {\n            if (vncRecordingDirectory == null) {\n                try {\n                    vncRecordingDirectory = Files.createTempDirectory(TC_TEMP_DIR_PREFIX).toFile();\n                } catch (IOException e) {\n                    // should never happen as per javadoc, since we use valid prefix\n                    logger().error(\"Exception while trying to create temp directory\", e);\n                    throw new ContainerLaunchException(\"Exception while trying to create temp directory\", e);\n                }\n            }\n\n            if (getNetwork() == null) {\n                withNetwork(Network.SHARED);\n            }\n\n            vncRecordingContainer =\n                new VncRecordingContainer(this)\n                    .withVncPassword(DEFAULT_PASSWORD)\n                    .withVncPort(VNC_PORT)\n                    .withVideoFormat(recordingFormat);\n        }\n\n        if (customImageName != null) {\n            customImageName.assertCompatibleWith(COMPATIBLE_IMAGES);\n            super.setDockerImageName(customImageName.asCanonicalNameString());\n        } else {\n            DockerImageName standardImageForCapabilities = getStandardImageForCapabilities(\n                capabilities,\n                seleniumVersion\n            );\n            super.setDockerImageName(standardImageForCapabilities.asCanonicalNameString());\n        }\n\n        String timeZone = System.getProperty(\"user.timezone\");\n\n        if (timeZone == null || timeZone.isEmpty()) {\n            timeZone = \"Etc/UTC\";\n        }\n\n        addExposedPorts(SELENIUM_PORT, VNC_PORT);\n        addEnv(\"TZ\", timeZone);\n\n        if (!getEnvMap().containsKey(NO_PROXY_KEY)) {\n            addEnv(NO_PROXY_KEY, \"localhost\");\n        }\n\n        setCommand(\"/opt/bin/entry_point.sh\");\n\n        if (getShmSize() == null) {\n            if (SystemUtils.IS_OS_WINDOWS) {\n                withSharedMemorySize(512 * FileUtils.ONE_MB);\n            } else {\n                this.getBinds().add(new Bind(\"/dev/shm\", new Volume(\"/dev/shm\"), AccessMode.rw));\n            }\n        }\n\n        /*\n         * Some unreliability of the selenium browser containers has been observed, so allow multiple attempts to start.\n         */\n        setStartupAttempts(3);\n    }\n\n    /**\n     * @param capabilities a {@link Capabilities} object for either Chrome or Firefox\n     * @param seleniumVersion the version of selenium in use\n     * @return an image name for the default standalone Docker image for the appropriate browser\n     *\n     * @deprecated note that this method is deprecated and may be removed in the future. The no-args\n     * {@link BrowserWebDriverContainer#BrowserWebDriverContainer()} combined with the\n     * {@link BrowserWebDriverContainer#withCapabilities(Capabilities)} method should be considered. A decision on\n     * removal of this deprecated method will be taken at a future date.\n     */\n    @Deprecated\n    public static String getDockerImageForCapabilities(Capabilities capabilities, String seleniumVersion) {\n        return getStandardImageForCapabilities(capabilities, seleniumVersion).asCanonicalNameString();\n    }\n\n    private static DockerImageName getStandardImageForCapabilities(Capabilities capabilities, String seleniumVersion) {\n        String browserName = capabilities == null ? BrowserType.CHROME : capabilities.getBrowserName();\n        boolean supportsVncWithoutDebugImage = new ComparableVersion(seleniumVersion).isGreaterThanOrEqualTo(\"4\");\n\n        switch (browserName) {\n            case BrowserType.CHROME:\n                return (supportsVncWithoutDebugImage ? CHROME_IMAGE : CHROME_DEBUG_IMAGE).withTag(seleniumVersion);\n            case BrowserType.FIREFOX:\n                return (supportsVncWithoutDebugImage ? FIREFOX_IMAGE : FIREFOX_DEBUG_IMAGE).withTag(seleniumVersion);\n            case BrowserType.EDGE:\n                if (supportsVncWithoutDebugImage) {\n                    return EDGE_IMAGE.withTag(seleniumVersion);\n                }\n                throw new UnsupportedOperationException(\n                    \"For browser 'MicrosoftEdge' selenium version must be 4 or higher;\" +\n                    \"docker images are available from there upwards;\" +\n                    \"provided version: '\" +\n                    seleniumVersion +\n                    \"'\"\n                );\n            default:\n                throw new UnsupportedOperationException(\n                    \"Browser name must be 'chrome', 'firefox' or 'MicrosoftEdge';\" +\n                    \"provided '\" +\n                    browserName +\n                    \"' is not supported\"\n                );\n        }\n    }\n\n    public URL getSeleniumAddress() {\n        try {\n            return new URL(\"http\", getHost(), getMappedPort(SELENIUM_PORT), \"/wd/hub\");\n        } catch (MalformedURLException e) {\n            e.printStackTrace(); // TODO\n            return null;\n        }\n    }\n\n    public String getVncAddress() {\n        return \"vnc://vnc:secret@\" + getHost() + \":\" + getMappedPort(VNC_PORT);\n    }\n\n    @Deprecated\n    public String getPassword() {\n        return DEFAULT_PASSWORD;\n    }\n\n    @Deprecated\n    public int getPort() {\n        return VNC_PORT;\n    }\n\n    @Override\n    protected void containerIsStarted(InspectContainerResponse containerInfo) {\n        if (vncRecordingContainer != null) {\n            LOGGER.debug(\"Starting VNC recording\");\n            vncRecordingContainer.start();\n        }\n    }\n\n    /**\n     * Obtain a RemoteWebDriver instance that is bound to an instance of the browser running inside a new container.\n     * <p>\n     * All containers and drivers will be automatically shut down after the test method finishes (if used as a @Rule) or the test\n     * class (if used as a @ClassRule)\n     *\n     * @return a new Remote Web Driver instance\n     * @deprecated use {@link #getSeleniumAddress()} instead\n     */\n    @Deprecated\n    public synchronized RemoteWebDriver getWebDriver() {\n        if (driver == null) {\n            if (capabilities == null) {\n                logger()\n                    .warn(\n                        \"No capabilities provided - this will cause an exception in future versions. Falling back to ChromeOptions\"\n                    );\n                capabilities = new ChromeOptions();\n            }\n\n            driver =\n                Unreliables.retryUntilSuccess(\n                    30,\n                    TimeUnit.SECONDS,\n                    () -> {\n                        return Timeouts.getWithTimeout(\n                            10,\n                            TimeUnit.SECONDS,\n                            () -> {\n                                return new RemoteWebDriver(getSeleniumAddress(), capabilities);\n                            }\n                        );\n                    }\n                );\n        }\n        return driver;\n    }\n\n    @Override\n    public void afterTest(TestDescription description, Optional<Throwable> throwable) {\n        retainRecordingIfNeeded(description.getFilesystemFriendlyName(), !throwable.isPresent());\n    }\n\n    @Override\n    public void stop() {\n        if (driver != null) {\n            try {\n                driver.quit();\n            } catch (Exception e) {\n                LOGGER.debug(\"Failed to quit the driver\", e);\n            }\n            driver = null;\n        }\n\n        if (vncRecordingContainer != null) {\n            try {\n                vncRecordingContainer.stop();\n            } catch (Exception e) {\n                LOGGER.debug(\"Failed to stop vncRecordingContainer\", e);\n            }\n            vncRecordingContainer = null;\n        }\n\n        super.stop();\n    }\n\n    private void retainRecordingIfNeeded(String prefix, boolean succeeded) {\n        final boolean shouldRecord;\n        switch (recordingMode) {\n            case RECORD_ALL:\n                shouldRecord = true;\n                break;\n            case RECORD_FAILING:\n                shouldRecord = !succeeded;\n                break;\n            default:\n                shouldRecord = false;\n                break;\n        }\n\n        if (shouldRecord) {\n            File recordingFile = recordingFileFactory.recordingFileForTest(\n                vncRecordingDirectory,\n                prefix,\n                succeeded,\n                vncRecordingContainer.getVideoFormat()\n            );\n            LOGGER.info(\"Screen recordings for test {} will be stored at: {}\", prefix, recordingFile);\n\n            vncRecordingContainer.saveRecordingToFile(recordingFile);\n        }\n    }\n\n    /**\n     * Remember any other containers this needs to link to. We have to pass these down to the container so that\n     * the other containers will be initialized before linking occurs.\n     *\n     * @param otherContainer the container rule to link to\n     * @param alias          the alias (hostname) that this other container should be referred to by\n     * @return this\n     *\n     * @deprecated Links are deprecated (see <a href=\"https://github.com/testcontainers/testcontainers-java/issues/465\">#465</a>). Please use {@link Network} features instead.\n     */\n    @Deprecated\n    public SELF withLinkToContainer(LinkableContainer otherContainer, String alias) {\n        addLink(otherContainer, alias);\n        return self();\n    }\n\n    public SELF withRecordingMode(VncRecordingMode recordingMode, File vncRecordingDirectory) {\n        return withRecordingMode(recordingMode, vncRecordingDirectory, null);\n    }\n\n    public SELF withRecordingMode(\n        VncRecordingMode recordingMode,\n        File vncRecordingDirectory,\n        VncRecordingFormat recordingFormat\n    ) {\n        this.recordingMode = recordingMode;\n        this.vncRecordingDirectory = vncRecordingDirectory;\n        this.recordingFormat = recordingFormat;\n        return self();\n    }\n\n    public SELF withRecordingFileFactory(RecordingFileFactory recordingFileFactory) {\n        this.recordingFileFactory = recordingFileFactory;\n        return self();\n    }\n\n    private WaitStrategy getDefaultWaitStrategy() {\n        final WaitStrategy logWaitStrategy = new LogMessageWaitStrategy()\n            .withRegEx(\n                \".*(RemoteWebDriver instances should connect to|Selenium Server is up and running|Started Selenium Standalone).*\\n\"\n            )\n            .withStartupTimeout(Duration.of(60, ChronoUnit.SECONDS));\n\n        return new WaitAllStrategy()\n            .withStrategy(logWaitStrategy)\n            .withStrategy(new HostPortWaitStrategy())\n            .withStartupTimeout(Duration.of(60, ChronoUnit.SECONDS));\n    }\n\n    public enum VncRecordingMode {\n        SKIP,\n        RECORD_ALL,\n        RECORD_FAILING,\n    }\n\n    private static class BrowserType {\n\n        private static final String CHROME = \"chrome\";\n\n        private static final String FIREFOX = \"firefox\";\n\n        private static final String EDGE = \"MicrosoftEdge\";\n    }\n}\n"
  },
  {
    "path": "modules/selenium/src/main/java/org/testcontainers/containers/DefaultRecordingFileFactory.java",
    "content": "package org.testcontainers.containers;\n\nimport java.io.File;\nimport java.text.SimpleDateFormat;\nimport java.util.Date;\n\npublic class DefaultRecordingFileFactory implements RecordingFileFactory {\n\n    private static final SimpleDateFormat filenameDateFormat = new SimpleDateFormat(\"YYYYMMdd-HHmmss\");\n\n    private static final String PASSED = \"PASSED\";\n\n    private static final String FAILED = \"FAILED\";\n\n    private static final String FILENAME_FORMAT = \"%s-%s-%s.%s\";\n\n    @Override\n    public File recordingFileForTest(File vncRecordingDirectory, String prefix, boolean succeeded) {\n        return recordingFileForTest(\n            vncRecordingDirectory,\n            prefix,\n            succeeded,\n            VncRecordingContainer.DEFAULT_RECORDING_FORMAT\n        );\n    }\n\n    @Override\n    public File recordingFileForTest(\n        File vncRecordingDirectory,\n        String prefix,\n        boolean succeeded,\n        VncRecordingContainer.VncRecordingFormat recordingFormat\n    ) {\n        final String resultMarker = succeeded ? PASSED : FAILED;\n        final String fileName = String.format(\n            FILENAME_FORMAT,\n            resultMarker,\n            prefix,\n            filenameDateFormat.format(new Date()),\n            recordingFormat.getFilenameExtension()\n        );\n        return new File(vncRecordingDirectory, fileName);\n    }\n}\n"
  },
  {
    "path": "modules/selenium/src/main/java/org/testcontainers/containers/RecordingFileFactory.java",
    "content": "package org.testcontainers.containers;\n\nimport org.testcontainers.containers.VncRecordingContainer.VncRecordingFormat;\n\nimport java.io.File;\n\npublic interface RecordingFileFactory {\n    default File recordingFileForTest(\n        File vncRecordingDirectory,\n        String prefix,\n        boolean succeeded,\n        VncRecordingFormat recordingFormat\n    ) {\n        return recordingFileForTest(vncRecordingDirectory, prefix, succeeded);\n    }\n\n    File recordingFileForTest(File vncRecordingDirectory, String prefix, boolean succeeded);\n}\n"
  },
  {
    "path": "modules/selenium/src/main/java/org/testcontainers/containers/SeleniumUtils.java",
    "content": "package org.testcontainers.containers;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.InputStream;\nimport java.net.URL;\nimport java.util.Enumeration;\nimport java.util.HashSet;\nimport java.util.Set;\nimport java.util.jar.Attributes;\nimport java.util.jar.Manifest;\n\n/**\n * Utility methods for Selenium.\n */\npublic final class SeleniumUtils {\n\n    public static final String DEFAULT_SELENIUM_VERSION = \"2.45.0\";\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(SeleniumUtils.class);\n\n    private SeleniumUtils() {}\n\n    /**\n     * Based on the JARs detected on the classpath, determine which version of selenium-api is available.\n     * @return the detected version of Selenium API, or DEFAULT_SELENIUM_VERSION if it could not be determined\n     */\n    public static String determineClasspathSeleniumVersion() {\n        Set<String> seleniumVersions = new HashSet<>();\n        try {\n            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();\n            Enumeration<URL> manifests = classLoader.getResources(\"META-INF/MANIFEST.MF\");\n\n            while (manifests.hasMoreElements()) {\n                URL manifestURL = manifests.nextElement();\n                try (InputStream is = manifestURL.openStream()) {\n                    Manifest manifest = new Manifest();\n                    manifest.read(is);\n\n                    String seleniumVersion = getSeleniumVersionFromManifest(manifest);\n                    if (seleniumVersion != null) {\n                        seleniumVersions.add(seleniumVersion);\n                        LOGGER.info(\"Selenium API version {} detected on classpath\", seleniumVersion);\n                    }\n                }\n            }\n        } catch (Exception e) {\n            LOGGER.debug(\"Failed to determine Selenium-Version from selenium-api JAR Manifest\", e);\n        }\n\n        if (seleniumVersions.size() == 0) {\n            LOGGER.warn(\n                \"Failed to determine Selenium version from classpath - will use default version of {}\",\n                DEFAULT_SELENIUM_VERSION\n            );\n            return DEFAULT_SELENIUM_VERSION;\n        }\n\n        String foundVersion = seleniumVersions.iterator().next();\n        if (seleniumVersions.size() > 1) {\n            LOGGER.warn(\n                \"Multiple versions of Selenium API found on classpath - will select {}, but this may not be reliable\",\n                foundVersion\n            );\n        }\n\n        return foundVersion;\n    }\n\n    /**\n     * Read Manifest to get Selenium Version.\n     * @param manifest manifest\n     * @return Selenium Version detected\n     */\n    public static String getSeleniumVersionFromManifest(Manifest manifest) {\n        String seleniumVersion = null;\n        Attributes buildInfo = manifest.getAttributes(\"Build-Info\");\n        if (buildInfo != null) {\n            seleniumVersion = buildInfo.getValue(\"Selenium-Version\");\n        }\n\n        // Compatibility Selenium > 3.X\n        if (seleniumVersion == null) {\n            Attributes seleniumInfo = manifest.getAttributes(\"Selenium\");\n            if (seleniumInfo != null) {\n                seleniumVersion = seleniumInfo.getValue(\"Selenium-Version\");\n            }\n        }\n        return seleniumVersion;\n    }\n}\n"
  },
  {
    "path": "modules/selenium/src/main/java/org/testcontainers/selenium/BrowserWebDriverContainer.java",
    "content": "package org.testcontainers.selenium;\n\nimport com.github.dockerjava.api.command.InspectContainerResponse;\nimport com.github.dockerjava.api.model.AccessMode;\nimport com.github.dockerjava.api.model.Bind;\nimport com.github.dockerjava.api.model.Volume;\nimport com.google.common.collect.ImmutableSet;\nimport org.apache.commons.io.FileUtils;\nimport org.apache.commons.lang3.SystemUtils;\nimport org.jetbrains.annotations.NotNull;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.testcontainers.containers.ContainerLaunchException;\nimport org.testcontainers.containers.DefaultRecordingFileFactory;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.Network;\nimport org.testcontainers.containers.RecordingFileFactory;\nimport org.testcontainers.containers.VncRecordingContainer;\nimport org.testcontainers.containers.VncRecordingContainer.VncRecordingFormat;\nimport org.testcontainers.containers.wait.strategy.HostPortWaitStrategy;\nimport org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy;\nimport org.testcontainers.containers.wait.strategy.WaitAllStrategy;\nimport org.testcontainers.containers.wait.strategy.WaitStrategy;\nimport org.testcontainers.lifecycle.TestDescription;\nimport org.testcontainers.lifecycle.TestLifecycleAware;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.nio.file.Files;\nimport java.time.Duration;\nimport java.util.Optional;\nimport java.util.Set;\n\n/**\n * A chrome/firefox/custom container based on SeleniumHQ's standalone container sets.\n * <p>\n * Supported images: {@code selenium/standalone-chrome}, {@code selenium/standalone-firefox},\n * {@code selenium/standalone-edge}, {@code selenium/standalone-chrome-debug}, {@code selenium/standalone-firefox-debug}\n * <p>\n * Exposed ports: 4444\n */\npublic class BrowserWebDriverContainer\n    extends GenericContainer<BrowserWebDriverContainer>\n    implements TestLifecycleAware {\n\n    private static final DockerImageName CHROME_IMAGE = DockerImageName.parse(\"selenium/standalone-chrome\");\n\n    private static final DockerImageName FIREFOX_IMAGE = DockerImageName.parse(\"selenium/standalone-firefox\");\n\n    private static final DockerImageName EDGE_IMAGE = DockerImageName.parse(\"selenium/standalone-edge\");\n\n    private static final DockerImageName CHROME_DEBUG_IMAGE = DockerImageName.parse(\"selenium/standalone-chrome-debug\");\n\n    private static final DockerImageName FIREFOX_DEBUG_IMAGE = DockerImageName.parse(\n        \"selenium/standalone-firefox-debug\"\n    );\n\n    private static final DockerImageName[] COMPATIBLE_IMAGES = new DockerImageName[] {\n        CHROME_IMAGE,\n        FIREFOX_IMAGE,\n        EDGE_IMAGE,\n        CHROME_DEBUG_IMAGE,\n        FIREFOX_DEBUG_IMAGE,\n    };\n\n    private static final String DEFAULT_PASSWORD = \"secret\";\n\n    private static final int SELENIUM_PORT = 4444;\n\n    private static final int VNC_PORT = 5900;\n\n    private static final String NO_PROXY_KEY = \"no_proxy\";\n\n    private static final String TC_TEMP_DIR_PREFIX = \"tc\";\n\n    private VncRecordingMode recordingMode = VncRecordingMode.RECORD_FAILING;\n\n    private VncRecordingFormat recordingFormat;\n\n    private RecordingFileFactory recordingFileFactory;\n\n    private File vncRecordingDirectory;\n\n    private VncRecordingContainer vncRecordingContainer = null;\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(BrowserWebDriverContainer.class);\n\n    /**\n     * Constructor taking a specific webdriver container name and tag\n     * @param dockerImageName Name of the selenium docker image\n     */\n    public BrowserWebDriverContainer(String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    /**\n     * Constructor taking a specific webdriver container name and tag\n     * @param dockerImageName Name of the selenium docker image\n     */\n    public BrowserWebDriverContainer(DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(COMPATIBLE_IMAGES);\n\n        waitingFor(getDefaultWaitStrategy());\n\n        withRecordingFileFactory(new DefaultRecordingFileFactory());\n        // We have to force SKIP mode for the recording by default because we don't know if the image has VNC or not\n        recordingMode = VncRecordingMode.SKIP;\n    }\n\n    @NotNull\n    @Override\n    protected Set<Integer> getLivenessCheckPorts() {\n        Integer seleniumPort = getMappedPort(SELENIUM_PORT);\n        if (recordingMode == VncRecordingMode.SKIP) {\n            return ImmutableSet.of(seleniumPort);\n        } else {\n            return ImmutableSet.of(seleniumPort, getMappedPort(VNC_PORT));\n        }\n    }\n\n    @Override\n    protected void configure() {\n        if (recordingMode != VncRecordingMode.SKIP) {\n            if (vncRecordingDirectory == null) {\n                try {\n                    vncRecordingDirectory = Files.createTempDirectory(TC_TEMP_DIR_PREFIX).toFile();\n                } catch (IOException e) {\n                    // should never happen as per javadoc, since we use valid prefix\n                    logger().error(\"Exception while trying to create temp directory\", e);\n                    throw new ContainerLaunchException(\"Exception while trying to create temp directory\", e);\n                }\n            }\n\n            if (getNetwork() == null) {\n                withNetwork(Network.SHARED);\n            }\n\n            vncRecordingContainer =\n                new VncRecordingContainer(this)\n                    .withVncPassword(DEFAULT_PASSWORD)\n                    .withVncPort(VNC_PORT)\n                    .withVideoFormat(recordingFormat);\n        }\n\n        String timeZone = System.getProperty(\"user.timezone\");\n\n        if (timeZone == null || timeZone.isEmpty()) {\n            timeZone = \"Etc/UTC\";\n        }\n\n        addExposedPorts(SELENIUM_PORT, VNC_PORT);\n        addEnv(\"TZ\", timeZone);\n\n        if (!getEnvMap().containsKey(NO_PROXY_KEY)) {\n            addEnv(NO_PROXY_KEY, \"localhost\");\n        }\n\n        setCommand(\"/opt/bin/entry_point.sh\");\n\n        if (getShmSize() == null) {\n            if (SystemUtils.IS_OS_WINDOWS) {\n                withSharedMemorySize(512 * FileUtils.ONE_MB);\n            } else {\n                this.getBinds().add(new Bind(\"/dev/shm\", new Volume(\"/dev/shm\"), AccessMode.rw));\n            }\n        }\n\n        /*\n         * Some unreliability of the selenium browser containers has been observed, so allow multiple attempts to start.\n         */\n        setStartupAttempts(3);\n    }\n\n    public URL getSeleniumAddress() {\n        try {\n            return new URL(\"http\", getHost(), getMappedPort(SELENIUM_PORT), \"/wd/hub\");\n        } catch (MalformedURLException e) {\n            e.printStackTrace(); // TODO\n            return null;\n        }\n    }\n\n    public String getVncAddress() {\n        return \"vnc://vnc:secret@\" + getHost() + \":\" + getMappedPort(VNC_PORT);\n    }\n\n    @Override\n    protected void containerIsStarted(InspectContainerResponse containerInfo) {\n        if (vncRecordingContainer != null) {\n            LOGGER.debug(\"Starting VNC recording\");\n            vncRecordingContainer.start();\n        }\n    }\n\n    @Override\n    public void afterTest(TestDescription description, Optional<Throwable> throwable) {\n        retainRecordingIfNeeded(description.getFilesystemFriendlyName(), !throwable.isPresent());\n    }\n\n    @Override\n    public void stop() {\n        if (vncRecordingContainer != null) {\n            try {\n                vncRecordingContainer.stop();\n            } catch (Exception e) {\n                LOGGER.debug(\"Failed to stop vncRecordingContainer\", e);\n            }\n            vncRecordingContainer = null;\n        }\n\n        super.stop();\n    }\n\n    private void retainRecordingIfNeeded(String prefix, boolean succeeded) {\n        final boolean shouldRecord;\n        switch (recordingMode) {\n            case RECORD_ALL:\n                shouldRecord = true;\n                break;\n            case RECORD_FAILING:\n                shouldRecord = !succeeded;\n                break;\n            default:\n                shouldRecord = false;\n                break;\n        }\n\n        if (shouldRecord) {\n            File recordingFile = recordingFileFactory.recordingFileForTest(\n                vncRecordingDirectory,\n                prefix,\n                succeeded,\n                vncRecordingContainer.getVideoFormat()\n            );\n            LOGGER.info(\"Screen recordings for test {} will be stored at: {}\", prefix, recordingFile);\n\n            vncRecordingContainer.saveRecordingToFile(recordingFile);\n        }\n    }\n\n    public BrowserWebDriverContainer withRecordingMode(VncRecordingMode recordingMode, File vncRecordingDirectory) {\n        return withRecordingMode(recordingMode, vncRecordingDirectory, null);\n    }\n\n    public BrowserWebDriverContainer withRecordingMode(\n        VncRecordingMode recordingMode,\n        File vncRecordingDirectory,\n        VncRecordingFormat recordingFormat\n    ) {\n        this.recordingMode = recordingMode;\n        this.vncRecordingDirectory = vncRecordingDirectory;\n        this.recordingFormat = recordingFormat;\n        return self();\n    }\n\n    public BrowserWebDriverContainer withRecordingFileFactory(RecordingFileFactory recordingFileFactory) {\n        this.recordingFileFactory = recordingFileFactory;\n        return self();\n    }\n\n    private WaitStrategy getDefaultWaitStrategy() {\n        final WaitStrategy logWaitStrategy = new LogMessageWaitStrategy()\n            .withRegEx(\n                \".*(RemoteWebDriver instances should connect to|Selenium Server is up and running|Started Selenium Standalone).*\\n\"\n            )\n            .withStartupTimeout(Duration.ofMinutes(1));\n\n        return new WaitAllStrategy()\n            .withStrategy(logWaitStrategy)\n            .withStrategy(new HostPortWaitStrategy())\n            .withStartupTimeout(Duration.ofMinutes(1));\n    }\n\n    public enum VncRecordingMode {\n        SKIP,\n        RECORD_ALL,\n        RECORD_FAILING,\n    }\n}\n"
  },
  {
    "path": "modules/selenium/src/test/java/org/testcontainers/containers/DefaultRecordingFileFactoryTest.java",
    "content": "package org.testcontainers.containers;\n\nimport lombok.Value;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.TestInfo;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport java.io.File;\nimport java.nio.file.Files;\nimport java.time.LocalDateTime;\nimport java.time.format.DateTimeFormatter;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.List;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n@ParameterizedClass\n@MethodSource(\"data\")\n@Value\nclass DefaultRecordingFileFactoryTest {\n\n    private static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern(\"YYYYMMdd-HHmmss\");\n\n    private final DefaultRecordingFileFactory factory = new DefaultRecordingFileFactory();\n\n    private final String methodName;\n\n    private final String prefix;\n\n    private final boolean success;\n\n    public static Collection<Object[]> data() {\n        Collection<Object[]> args = new ArrayList<>();\n        args.add(new Object[] { \"testMethod1\", \"FAILED\", Boolean.FALSE });\n        args.add(new Object[] { \"testMethod2\", \"PASSED\", Boolean.TRUE });\n        return args;\n    }\n\n    @Test\n    public void recordingFileThatShouldDescribeTheTestResultAtThePresentTime(TestInfo testInfo) throws Exception {\n        File vncRecordingDirectory = Files.createTempDirectory(\"recording\").toFile();\n        String className = testInfo.getTestClass().orElseThrow(IllegalStateException::new).getSimpleName();\n        String description = className + \"-\" + methodName;\n\n        File recordingFile = factory.recordingFileForTest(vncRecordingDirectory, description, success);\n\n        String expectedFilePrefix = String.format(\"%s-%s-%s\", prefix, getClass().getSimpleName(), methodName);\n\n        List<File> expectedPossibleFileNames = Arrays.asList(\n            new File(\n                vncRecordingDirectory,\n                String.format(\"%s-%s.flv\", expectedFilePrefix, LocalDateTime.now().format(DATETIME_FORMATTER))\n            ),\n            new File(\n                vncRecordingDirectory,\n                String.format(\n                    \"%s-%s.flv\",\n                    expectedFilePrefix,\n                    LocalDateTime.now().minusSeconds(1L).format(DATETIME_FORMATTER)\n                )\n            )\n        );\n\n        assertThat(expectedPossibleFileNames).contains(recordingFile);\n    }\n}\n"
  },
  {
    "path": "modules/selenium/src/test/java/org/testcontainers/junit/SeleniumStartTest.java",
    "content": "package org.testcontainers.junit;\n\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.params.Parameter;\nimport org.junit.jupiter.params.ParameterizedClass;\nimport org.junit.jupiter.params.provider.MethodSource;\nimport org.openqa.selenium.chrome.ChromeOptions;\nimport org.testcontainers.containers.BrowserWebDriverContainer;\nimport org.testcontainers.utility.DockerImageName;\n\n/**\n * Simple test to check that readiness detection works correctly across major versions of the containers.\n */\n@ParameterizedClass(name = \"tag: {0}\")\n@MethodSource(\"data\")\npublic class SeleniumStartTest {\n\n    public static String[] data() {\n        return new String[] { \"4.0.0\", \"3.4.0\", \"2.53.0\" };\n    }\n\n    @Parameter\n    public String tag;\n\n    @Test\n    void testAdditionalStartupString() {\n        final DockerImageName imageName = DockerImageName.parse(\"selenium/standalone-chrome\").withTag(tag);\n        try (\n            BrowserWebDriverContainer<?> chrome = new BrowserWebDriverContainer<>(imageName)\n                .withCapabilities(new ChromeOptions())\n        ) {\n            chrome.start();\n        }\n    }\n}\n"
  },
  {
    "path": "modules/selenium/src/test/java/org/testcontainers/junit/SeleniumUtilsTest.java",
    "content": "package org.testcontainers.junit;\n\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.SeleniumUtils;\n\nimport java.io.IOException;\nimport java.util.jar.Manifest;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Created by Julien LAMY\n */\nclass SeleniumUtilsTest {\n\n    @Test\n    void detectSeleniumVersionUnder3() throws IOException {\n        checkSeleniumVersionDetected(\"manifests/MANIFEST-2.45.0.MF\", \"2.45.0\");\n    }\n\n    @Test\n    void detectSeleniumVersionUpper3() throws IOException {\n        checkSeleniumVersionDetected(\"manifests/MANIFEST-3.5.2.MF\", \"3.5.2\");\n    }\n\n    /**\n     * Check if Selenium Version detected is the correct one.\n     * @param urlManifest : manifest file\n     * @throws IOException\n     */\n    private void checkSeleniumVersionDetected(String urlManifest, String expectedVersion) throws IOException {\n        Manifest manifest = new Manifest();\n        manifest.read(this.getClass().getClassLoader().getResourceAsStream(urlManifest));\n        String seleniumVersion = SeleniumUtils.getSeleniumVersionFromManifest(manifest);\n        assertThat(seleniumVersion)\n            .as(\"Check if Selenium Version detected is the correct one.\")\n            .isEqualTo(expectedVersion);\n    }\n}\n"
  },
  {
    "path": "modules/selenium/src/test/java/org/testcontainers/selenium/BaseWebDriverContainerTest.java",
    "content": "package org.testcontainers.selenium;\n\nimport org.openqa.selenium.By;\nimport org.openqa.selenium.Capabilities;\nimport org.openqa.selenium.WebElement;\nimport org.openqa.selenium.remote.RemoteWebDriver;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.Network;\nimport org.testcontainers.containers.wait.strategy.HttpWaitStrategy;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.time.Duration;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\npublic class BaseWebDriverContainerTest {\n\n    public static Network NETWORK = Network.newNetwork();\n\n    public static GenericContainer<?> HELLO_WORLD = new GenericContainer<>(\n        DockerImageName.parse(\"testcontainers/helloworld:1.1.0\")\n    )\n        .withNetwork(NETWORK)\n        .withNetworkAliases(\"helloworld\")\n        .withExposedPorts(8080, 8081)\n        .waitingFor(new HttpWaitStrategy());\n\n    static {\n        HELLO_WORLD.start();\n    }\n\n    protected static void doSimpleExplore(BrowserWebDriverContainer rule, Capabilities capabilities) {\n        RemoteWebDriver driver = new RemoteWebDriver(rule.getSeleniumAddress(), capabilities);\n        driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(30));\n        System.out.println(\"Selenium remote URL is: \" + rule.getSeleniumAddress());\n        System.out.println(\"VNC URL is: \" + rule.getVncAddress());\n\n        driver.get(\"http://helloworld:8080\");\n        WebElement title = driver.findElement(By.tagName(\"h1\"));\n\n        assertThat(title.getText().trim())\n            .as(\"the index page contains the title 'Hello world'\")\n            .isEqualTo(\"Hello world\");\n        driver.quit();\n    }\n\n    protected void assertBrowserNameIs(\n        BrowserWebDriverContainer container,\n        String expectedName,\n        Capabilities capabilities\n    ) {\n        RemoteWebDriver driver = new RemoteWebDriver(container.getSeleniumAddress(), capabilities);\n        driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(30));\n        String actual = driver.getCapabilities().getBrowserName();\n        assertThat(actual).as(String.format(\"actual browser name is %s\", actual)).isEqualTo(expectedName);\n        driver.quit();\n    }\n}\n"
  },
  {
    "path": "modules/selenium/src/test/java/org/testcontainers/selenium/BrowserWebDriverContainerTest.java",
    "content": "package org.testcontainers.selenium;\n\nimport com.github.dockerjava.api.command.InspectContainerResponse;\nimport org.apache.commons.io.FileUtils;\nimport org.apache.commons.lang3.SystemUtils;\nimport org.junit.jupiter.api.Test;\n\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assumptions.assumeThat;\n\nclass BrowserWebDriverContainerTest {\n\n    private static final String NO_PROXY_KEY = \"no_proxy\";\n\n    private static final String NO_PROXY_VALUE = \"localhost,.noproxy-domain.com\";\n\n    @Test\n    void honorPresetNoProxyEnvironment() {\n        try (\n            BrowserWebDriverContainer chromeWithNoProxySet = new BrowserWebDriverContainer(\n                \"selenium/standalone-chrome:4.13.0\"\n            )\n                .withEnv(NO_PROXY_KEY, NO_PROXY_VALUE)\n        ) {\n            chromeWithNoProxySet.start();\n\n            Object noProxy = chromeWithNoProxySet.getEnvMap().get(NO_PROXY_KEY);\n            assertThat(noProxy).as(\"no_proxy should be preserved by the container rule\").isEqualTo(NO_PROXY_VALUE);\n        }\n    }\n\n    @Test\n    void provideDefaultNoProxyEnvironmentIfNotSet() {\n        try (\n            BrowserWebDriverContainer chromeWithoutNoProxySet = new BrowserWebDriverContainer(\n                \"selenium/standalone-chrome:4.13.0\"\n            )\n        ) {\n            chromeWithoutNoProxySet.start();\n\n            Object noProxy = chromeWithoutNoProxySet.getEnvMap().get(NO_PROXY_KEY);\n            assertThat(noProxy).as(\"no_proxy should be set to default if not already present\").isEqualTo(\"localhost\");\n        }\n    }\n\n    @Test\n    void createContainerWithShmVolume() {\n        assumeThat(SystemUtils.IS_OS_WINDOWS).isTrue();\n        try (\n            BrowserWebDriverContainer webDriverContainer = new BrowserWebDriverContainer(\n                \"selenium/standalone-firefox:4.13.0\"\n            )\n        ) {\n            webDriverContainer.start();\n\n            final List<InspectContainerResponse.Mount> shmVolumes = shmVolumes(webDriverContainer);\n\n            assertThat(shmVolumes).as(\"Only one shm mount present\").hasSize(1);\n            assertThat(shmVolumes.get(0).getSource()).as(\"Shm mount source is correct\").isEqualTo(\"/dev/shm\");\n            assertThat(shmVolumes.get(0).getMode()).as(\"Shm mount mode is correct\").isEqualTo(\"rw\");\n        }\n    }\n\n    @Test\n    void createContainerWithoutShmVolume() {\n        try (\n            BrowserWebDriverContainer webDriverContainer = new BrowserWebDriverContainer(\n                \"selenium/standalone-firefox:4.13.0\"\n            )\n                .withSharedMemorySize(512 * FileUtils.ONE_MB)\n        ) {\n            webDriverContainer.start();\n\n            assertThat(webDriverContainer.getShmSize())\n                .as(\"Shared memory size is configured\")\n                .isEqualTo(512 * FileUtils.ONE_MB);\n\n            assertThat(shmVolumes(webDriverContainer)).as(\"No shm mounts present\").isEqualTo(Collections.emptyList());\n        }\n    }\n\n    private List<InspectContainerResponse.Mount> shmVolumes(final BrowserWebDriverContainer container) {\n        return container\n            .getContainerInfo()\n            .getMounts()\n            .stream()\n            // destination path is always /dev/shm\n            .filter(m -> m.getDestination().getPath().equals(\"/dev/shm\"))\n            .collect(Collectors.toList());\n    }\n}\n"
  },
  {
    "path": "modules/selenium/src/test/java/org/testcontainers/selenium/ChromeRecordingWebDriverContainerTest.java",
    "content": "package org.testcontainers.selenium;\n\nimport com.google.common.io.PatternFilenameFilter;\nimport org.junit.jupiter.api.Nested;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.io.TempDir;\nimport org.openqa.selenium.chrome.ChromeOptions;\nimport org.testcontainers.containers.DefaultRecordingFileFactory;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.VncRecordingContainer;\nimport org.testcontainers.containers.VncRecordingContainer.VncRecordingFormat;\nimport org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy;\nimport org.testcontainers.lifecycle.TestDescription;\nimport org.testcontainers.selenium.BrowserWebDriverContainer.VncRecordingMode;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.MountableFile;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.nio.file.Path;\nimport java.time.Duration;\nimport java.time.temporal.ChronoUnit;\nimport java.util.Optional;\nimport java.util.concurrent.TimeUnit;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass ChromeRecordingWebDriverContainerTest extends BaseWebDriverContainerTest {\n\n    /**\n     * Guaranty a minimum video length for FFmpeg re-encoding.\n     * @see VncRecordingFormat#reencodeRecording(VncRecordingContainer, String)\n     */\n    private static final int MINIMUM_VIDEO_DURATION_MILLISECONDS = 200;\n\n    @Nested\n    class ChromeThatRecordsAllTests {\n\n        @TempDir\n        public Path vncRecordingDirectory;\n\n        @Test\n        void recordingTestThatShouldBeRecordedAndRetainedInFlvFormatAsDefault() throws InterruptedException {\n            File target = vncRecordingDirectory.toFile();\n            try (\n                // recordAll {\n                // To do this, simply add extra parameters to the rule constructor, so video will default to FLV format:\n                BrowserWebDriverContainer chrome = new BrowserWebDriverContainer(\"selenium/standalone-chrome:4.13.0\")\n                    .withRecordingMode(VncRecordingMode.RECORD_ALL, target)\n                    // }\n                    .withRecordingFileFactory(new DefaultRecordingFileFactory())\n                    .withNetwork(NETWORK)\n            ) {\n                File[] files = runSimpleExploreInContainer(chrome, \"PASSED-.*\\\\.flv\");\n                assertThat(files).as(\"Recorded file found\").hasSize(1);\n            }\n        }\n\n        private File[] runSimpleExploreInContainer(BrowserWebDriverContainer container, String fileNamePattern)\n            throws InterruptedException {\n            container.start();\n\n            TimeUnit.MILLISECONDS.sleep(MINIMUM_VIDEO_DURATION_MILLISECONDS);\n            doSimpleExplore(container, new ChromeOptions());\n            container.afterTest(\n                new TestDescription() {\n                    @Override\n                    public String getTestId() {\n                        return getFilesystemFriendlyName();\n                    }\n\n                    @Override\n                    public String getFilesystemFriendlyName() {\n                        return \"ChromeThatRecordsAllTests-recordingTestThatShouldBeRecordedAndRetained\";\n                    }\n                },\n                Optional.empty()\n            );\n\n            return vncRecordingDirectory.toFile().listFiles(new PatternFilenameFilter(fileNamePattern));\n        }\n\n        @Test\n        void recordingTestShouldHaveFlvExtension() throws InterruptedException {\n            File target = vncRecordingDirectory.toFile();\n            try (\n                // recordFlv {\n                // Set (explicitly) FLV format for recorded video:\n                BrowserWebDriverContainer chrome = new BrowserWebDriverContainer(\"selenium/standalone-chrome:4.13.0\")\n                    .withRecordingMode(VncRecordingMode.RECORD_ALL, target, VncRecordingFormat.FLV)\n                    // }\n                    .withRecordingFileFactory(new DefaultRecordingFileFactory())\n                    .withNetwork(NETWORK)\n            ) {\n                File[] files = runSimpleExploreInContainer(chrome, \"PASSED-.*\\\\.flv\");\n                assertThat(files).as(\"Recorded file found\").hasSize(1);\n            }\n        }\n\n        @Test\n        void recordingTestShouldHaveMp4Extension() throws InterruptedException {\n            File target = vncRecordingDirectory.toFile();\n            try (\n                // recordMp4 {\n                // Set MP4 format for recorded video:\n                BrowserWebDriverContainer chrome = new BrowserWebDriverContainer(\"selenium/standalone-chrome:4.13.0\")\n                    .withRecordingMode(VncRecordingMode.RECORD_ALL, target, VncRecordingFormat.MP4)\n                    // }\n                    .withRecordingFileFactory(new DefaultRecordingFileFactory())\n                    .withNetwork(NETWORK)\n            ) {\n                File[] files = runSimpleExploreInContainer(chrome, \"PASSED-.*\\\\.mp4\");\n                assertThat(files).as(\"Recorded file found\").hasSize(1);\n            }\n        }\n\n        @Test\n        void recordingTestThatShouldHaveCorrectDuration() throws IOException, InterruptedException {\n            MountableFile mountableFile;\n            try (\n                BrowserWebDriverContainer chrome = new BrowserWebDriverContainer(\"selenium/standalone-chrome:4.13.0\")\n                    .withRecordingMode(VncRecordingMode.RECORD_ALL, vncRecordingDirectory.toFile())\n                    .withRecordingFileFactory(new DefaultRecordingFileFactory())\n                    .withNetwork(NETWORK)\n            ) {\n                File[] recordedFiles = runSimpleExploreInContainer(chrome, \"PASSED-.*\\\\.flv\");\n                mountableFile = MountableFile.forHostPath(recordedFiles[0].getCanonicalPath());\n            }\n\n            try (\n                GenericContainer<?> container = new GenericContainer<>(\n                    DockerImageName.parse(\"testcontainers/vnc-recorder:1.3.0\")\n                )\n            ) {\n                String recordFileContainerPath = \"/tmp/chromeTestRecord.flv\";\n                container\n                    .withCopyFileToContainer(mountableFile, recordFileContainerPath)\n                    .withCreateContainerCmdModifier(createContainerCmd -> createContainerCmd.withEntrypoint(\"ffmpeg\"))\n                    .withCommand(\"-i\", recordFileContainerPath, \"-f\", \"null\", \"-\")\n                    .waitingFor(\n                        new LogMessageWaitStrategy()\n                            .withRegEx(\".*Duration.*\")\n                            .withStartupTimeout(Duration.of(60, ChronoUnit.SECONDS))\n                    )\n                    .start();\n                String ffmpegOutput = container.getLogs();\n\n                assertThat(ffmpegOutput)\n                    .as(\"Duration starts with 00:\")\n                    .contains(\"Duration: 00:\")\n                    .doesNotContain(\"Duration: 00:00:00.00\");\n            }\n        }\n    }\n\n    @Nested\n    class ChromeThatRecordsFailingTests {\n\n        @TempDir\n        public Path vncRecordingDirectory;\n\n        @Test\n        void recordingTestThatShouldBeRecordedButNotPersisted() {\n            try (\n                // withRecordingFileFactory {\n                BrowserWebDriverContainer chrome = new BrowserWebDriverContainer(\"selenium/standalone-chrome:4.13.0\")\n                    // }\n                    // withRecordingFileFactory {\n                    .withRecordingFileFactory(new CustomRecordingFileFactory())\n                    // }\n                    .withNetwork(NETWORK)\n            ) {\n                chrome.start();\n\n                doSimpleExplore(chrome, new ChromeOptions());\n            }\n        }\n\n        @Test\n        void recordingTestThatShouldBeRecordedAndRetained() throws InterruptedException {\n            File target = vncRecordingDirectory.toFile();\n            try (\n                // recordFailing {\n                // or if you only want videos for test failures:\n                BrowserWebDriverContainer chrome = new BrowserWebDriverContainer(\"selenium/standalone-chrome:4.13.0\")\n                    .withRecordingMode(VncRecordingMode.RECORD_FAILING, target)\n                    // }\n                    .withRecordingFileFactory(new DefaultRecordingFileFactory())\n                    .withNetwork(NETWORK)\n            ) {\n                chrome.start();\n\n                TimeUnit.MILLISECONDS.sleep(MINIMUM_VIDEO_DURATION_MILLISECONDS);\n                doSimpleExplore(chrome, new ChromeOptions());\n                chrome.afterTest(\n                    new TestDescription() {\n                        @Override\n                        public String getTestId() {\n                            return getFilesystemFriendlyName();\n                        }\n\n                        @Override\n                        public String getFilesystemFriendlyName() {\n                            return \"ChromeThatRecordsFailingTests-recordingTestThatShouldBeRecordedAndRetained\";\n                        }\n                    },\n                    Optional.of(new RuntimeException(\"Force writing of video file.\"))\n                );\n\n                String[] files = vncRecordingDirectory.toFile().list(new PatternFilenameFilter(\"FAILED-.*\\\\.flv\"));\n                assertThat(files).as(\"recorded file count\").hasSize(1);\n            }\n        }\n\n        class CustomRecordingFileFactory extends DefaultRecordingFileFactory {}\n    }\n}\n"
  },
  {
    "path": "modules/selenium/src/test/java/org/testcontainers/selenium/ChromeWebDriverContainerTest.java",
    "content": "package org.testcontainers.selenium;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.openqa.selenium.chrome.ChromeOptions;\n\nclass ChromeWebDriverContainerTest extends BaseWebDriverContainerTest {\n\n    // junitRule {\n    public BrowserWebDriverContainer chrome = new BrowserWebDriverContainer(\"selenium/standalone-chrome:4.13.0\")\n        // }\n        .withNetwork(NETWORK);\n\n    @BeforeEach\n    public void checkBrowserIsIndeedChrome() {\n        chrome.start();\n        assertBrowserNameIs(chrome, \"chrome\", new ChromeOptions());\n    }\n\n    @Test\n    void simpleExploreTest() {\n        doSimpleExplore(chrome, new ChromeOptions());\n    }\n}\n"
  },
  {
    "path": "modules/selenium/src/test/java/org/testcontainers/selenium/ContainerWithoutCapabilitiesTest.java",
    "content": "package org.testcontainers.selenium;\n\nimport org.junit.jupiter.api.AutoClose;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.openqa.selenium.chrome.ChromeOptions;\n\nclass ContainerWithoutCapabilitiesTest extends BaseWebDriverContainerTest {\n\n    @AutoClose\n    public BrowserWebDriverContainer chrome = new BrowserWebDriverContainer(\"selenium/standalone-chrome:4.13.0\")\n        .withNetwork(NETWORK);\n\n    @BeforeEach\n    public void setUp() {\n        chrome.start();\n    }\n\n    @Test\n    void chromeIsStartedIfNoCapabilitiesProvided() {\n        assertBrowserNameIs(chrome, \"chrome\", new ChromeOptions());\n    }\n\n    @Test\n    void simpleExploreTestWhenNoCapabilitiesProvided() {\n        doSimpleExplore(chrome, new ChromeOptions());\n    }\n}\n"
  },
  {
    "path": "modules/selenium/src/test/java/org/testcontainers/selenium/CustomWaitTimeoutWebDriverContainerTest.java",
    "content": "package org.testcontainers.selenium;\n\nimport org.junit.jupiter.api.Test;\nimport org.openqa.selenium.chrome.ChromeOptions;\n\nimport java.time.Duration;\nimport java.time.temporal.ChronoUnit;\n\nclass CustomWaitTimeoutWebDriverContainerTest extends BaseWebDriverContainerTest {\n\n    public BrowserWebDriverContainer chromeWithCustomTimeout = new BrowserWebDriverContainer(\n        \"selenium/standalone-chrome:4.13.0\"\n    )\n        .withStartupTimeout(Duration.of(30, ChronoUnit.SECONDS))\n        .withNetwork(NETWORK);\n\n    @Test\n    void simpleExploreTest() {\n        chromeWithCustomTimeout.start();\n        doSimpleExplore(chromeWithCustomTimeout, new ChromeOptions());\n    }\n}\n"
  },
  {
    "path": "modules/selenium/src/test/java/org/testcontainers/selenium/EdgeWebDriverContainerTest.java",
    "content": "package org.testcontainers.selenium;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.openqa.selenium.edge.EdgeOptions;\n\nclass EdgeWebDriverContainerTest extends BaseWebDriverContainerTest {\n\n    // junitRule {\n    public BrowserWebDriverContainer edge = new BrowserWebDriverContainer(\"selenium/standalone-edge:4.13.0\")\n        // }\n        .withNetwork(NETWORK);\n\n    @BeforeEach\n    public void checkBrowserIsIndeedMSEdge() {\n        edge.start();\n        assertBrowserNameIs(edge, \"msedge\", new EdgeOptions());\n    }\n\n    @Test\n    void simpleExploreTest() {\n        doSimpleExplore(edge, new EdgeOptions());\n    }\n}\n"
  },
  {
    "path": "modules/selenium/src/test/java/org/testcontainers/selenium/FirefoxWebDriverContainerTest.java",
    "content": "package org.testcontainers.selenium;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.openqa.selenium.firefox.FirefoxOptions;\n\nclass FirefoxWebDriverContainerTest extends BaseWebDriverContainerTest {\n\n    // junitRule {\n    public BrowserWebDriverContainer firefox = new BrowserWebDriverContainer(\"selenium/standalone-firefox:4.13.0\")\n        // }\n        .withNetwork(NETWORK);\n\n    @BeforeEach\n    public void checkBrowserIsIndeedFirefox() {\n        firefox.start();\n        assertBrowserNameIs(firefox, \"firefox\", new FirefoxOptions());\n    }\n\n    @Test\n    void simpleExploreTest() {\n        doSimpleExplore(firefox, new FirefoxOptions());\n    }\n}\n"
  },
  {
    "path": "modules/selenium/src/test/java/org/testcontainers/selenium/LocalServerWebDriverContainerTest.java",
    "content": "package org.testcontainers.selenium;\n\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mortbay.jetty.Server;\nimport org.mortbay.jetty.bio.SocketConnector;\nimport org.mortbay.jetty.handler.ResourceHandler;\nimport org.openqa.selenium.By;\nimport org.openqa.selenium.chrome.ChromeOptions;\nimport org.openqa.selenium.remote.RemoteWebDriver;\nimport org.testcontainers.Testcontainers;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * Test that a browser running in a container can access a web server hosted on the host machine (i.e. the one running\n * the tests)\n */\nclass LocalServerWebDriverContainerTest {\n\n    public BrowserWebDriverContainer chrome = new BrowserWebDriverContainer(\"selenium/standalone-chrome:4.13.0\")\n        .withAccessToHost(true);\n\n    private int localPort;\n\n    @BeforeEach\n    public void setupLocalServer() throws Exception {\n        chrome.start();\n        // Set up a local Jetty HTTP server\n        Server server = new Server();\n        server.addConnector(new SocketConnector());\n        ResourceHandler resourceHandler = new ResourceHandler();\n        resourceHandler.setResourceBase(\"src/test/resources/server\");\n        server.addHandler(resourceHandler);\n        server.start();\n\n        // The server will have a random port assigned, so capture that\n        localPort = server.getConnectors()[0].getLocalPort();\n    }\n\n    @Test\n    void testConnection() {\n        // getWebDriver {\n        RemoteWebDriver driver = new RemoteWebDriver(chrome.getSeleniumAddress(), new ChromeOptions());\n        // }\n\n        // Construct a URL that the browser container can access\n        // getPage {\n        Testcontainers.exposeHostPorts(localPort);\n        driver.get(\"http://host.testcontainers.internal:\" + localPort);\n        // }\n\n        String headingText = driver.findElement(By.cssSelector(\"h1\")).getText().trim();\n\n        assertThat(headingText)\n            .as(\"The hardcoded success message was found on a page fetched from a local server\")\n            .isEqualTo(\"It worked\");\n    }\n}\n"
  },
  {
    "path": "modules/selenium/src/test/java/org/testcontainers/selenium/SpecificImageNameWebDriverContainerTest.java",
    "content": "package org.testcontainers.selenium;\n\nimport org.junit.jupiter.api.Test;\nimport org.openqa.selenium.firefox.FirefoxOptions;\nimport org.testcontainers.utility.DockerImageName;\n\nclass SpecificImageNameWebDriverContainerTest extends BaseWebDriverContainerTest {\n\n    private static final DockerImageName FIREFOX_IMAGE = DockerImageName.parse(\"selenium/standalone-firefox:4.10.0\");\n\n    public BrowserWebDriverContainer firefox = new BrowserWebDriverContainer(FIREFOX_IMAGE).withNetwork(NETWORK);\n\n    @Test\n    void simpleExploreTest() {\n        firefox.start();\n        doSimpleExplore(firefox, new FirefoxOptions());\n    }\n}\n"
  },
  {
    "path": "modules/selenium/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/selenium/src/test/resources/manifests/MANIFEST-2.45.0.MF",
    "content": "Manifest-Version: 1.0\r\nBuilt-By: linman\r\nBuild-Jdk: 1.7.0_65\r\nCreated-By: Apache Maven 3.1.1\r\nArchiver-Version: Plexus Archiver\r\n\r\nName: Build-Info\r\nSelenium-Revision: 5017cb8e7ca8e37638dc3091b2440b90a1d8686f\r\nSelenium-Version: 2.45.0\r\nSelenium-Build-Time: 2015-02-27 09:10:26\r\n\r\n"
  },
  {
    "path": "modules/selenium/src/test/resources/manifests/MANIFEST-3.5.2.MF",
    "content": "Manifest-Version: 1.0\r\n\r\nName: Build-Info\r\nBuild-Revision: 21ac65f960\r\nBuild-Time: 2017-08-21T20:29:34.71Z\r\nBuild-User: lamaille\r\nSelenium-Version: 3.5.2\r\n\r\n"
  },
  {
    "path": "modules/selenium/src/test/resources/server/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>It worked</title>\n</head>\n<body>\n    <h1>It worked</h1>\n</body>\n</html>"
  },
  {
    "path": "modules/solace/build.gradle",
    "content": "description = \"Testcontainers :: Solace\"\n\ndependencies {\n    api project(':testcontainers')\n\n    shaded 'org.awaitility:awaitility:4.3.0'\n\n    testImplementation 'com.solacesystems:sol-jcsmp:10.29.0'\n    testImplementation 'org.apache.qpid:qpid-jms-client:0.61.0'\n    testImplementation 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5'\n    testImplementation 'org.apache.httpcomponents:fluent-hc:4.5.14'\n}\n"
  },
  {
    "path": "modules/solace/src/main/java/org/testcontainers/solace/Service.java",
    "content": "package org.testcontainers.solace;\n\n/**\n * Services that are supported by Testcontainers implementation\n */\npublic enum Service {\n    /**\n     * Advanced Message Queuing Protocol\n     */\n    AMQP(\"amqp\", 5672, \"amqp\", false),\n    /**\n     * Message Queuing Telemetry Transport\n     */\n    MQTT(\"mqtt\", 1883, \"tcp\", false),\n    /**\n     * Representational State Transfer\n     */\n    REST(\"rest\", 9000, \"http\", false),\n    /**\n     * Solace Message Format\n     */\n    SMF(\"smf\", 55555, \"tcp\", true),\n    /**\n     * Solace Message Format with SSL\n     */\n    SMF_SSL(\"smf\", 55443, \"tcps\", true);\n\n    private final String name;\n\n    private final Integer port;\n\n    private final String protocol;\n\n    private final boolean supportSSL;\n\n    Service(String name, Integer port, String protocol, boolean supportSSL) {\n        this.name = name;\n        this.port = port;\n        this.protocol = protocol;\n        this.supportSSL = supportSSL;\n    }\n\n    /**\n     * @return Port assigned for the service\n     */\n    public Integer getPort() {\n        return this.port;\n    }\n\n    /**\n     * @return Protocol of the service\n     */\n    public String getProtocol() {\n        return this.protocol;\n    }\n\n    /**\n     * @return Name of the service\n     */\n    public String getName() {\n        return this.name;\n    }\n\n    /**\n     * @return Is SSL for this service supported ?\n     */\n    public boolean isSupportSSL() {\n        return this.supportSSL;\n    }\n}\n"
  },
  {
    "path": "modules/solace/src/main/java/org/testcontainers/solace/SolaceContainer.java",
    "content": "package org.testcontainers.solace;\n\nimport com.github.dockerjava.api.command.InspectContainerResponse;\nimport com.github.dockerjava.api.model.Ulimit;\nimport org.apache.commons.lang3.tuple.Pair;\nimport org.awaitility.Awaitility;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.images.builder.Transferable;\nimport org.testcontainers.utility.DockerImageName;\nimport org.testcontainers.utility.MountableFile;\n\nimport java.io.IOException;\nimport java.time.Duration;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Testcontainers implementation of Solace PubSub+.\n * <p>\n * Supported image: {@code solace/solace-pubsub-standard}\n * <p>\n * Exposed ports:\n * <ul>\n *     <li>Console: 8080</li>\n *     <li>AMQP: 5672</li>\n *     <li>MQTT: 1883</li>\n *     <li>HTTP: 9000</li>\n *     <li>SMF: 55555</li>\n *     <li>SMF SSL: 55443</li>\n * </ul>\n */\npublic class SolaceContainer extends GenericContainer<SolaceContainer> {\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"solace/solace-pubsub-standard\");\n\n    private static final String DEFAULT_VPN = \"default\";\n\n    private static final String DEFAULT_USERNAME = \"default\";\n\n    private static final String SOLACE_READY_MESSAGE = \".*Running pre-startup checks:.*\";\n\n    private static final String SOLACE_ACTIVE_MESSAGE = \"Primary Virtual Router is now active\";\n\n    private static final String TMP_SCRIPT_LOCATION = \"/tmp/script.cli\";\n\n    private static final Long SHM_SIZE = (long) Math.pow(1024, 3);\n\n    private String username = \"root\";\n\n    private String password = \"password\";\n\n    private String vpn = DEFAULT_VPN;\n\n    private final List<Pair<String, Service>> topicsConfiguration = new ArrayList<>();\n\n    private boolean withClientCert;\n\n    /**\n     * Create a new solace container with the specified image name.\n     *\n     * @param dockerImageName the image name that should be used.\n     */\n    public SolaceContainer(String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    /**\n     * Create a new solace container with the specified docker image.\n     *\n     * @param dockerImageName the image name that should be used.\n     */\n    public SolaceContainer(DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n        withCreateContainerCmdModifier(cmd -> {\n            cmd\n                .getHostConfig()\n                .withShmSize(SHM_SIZE)\n                .withUlimits(new Ulimit[] { new Ulimit(\"nofile\", 2448L, 1048576L) });\n        });\n        waitingFor(Wait.forLogMessage(SOLACE_READY_MESSAGE, 1).withStartupTimeout(Duration.ofSeconds(60)));\n        withExposedPorts(8080);\n        withEnv(\"username_admin_globalaccesslevel\", \"admin\");\n        withEnv(\"username_admin_password\", \"admin\");\n    }\n\n    @Override\n    protected void configure() {\n        withCopyToContainer(createConfigurationScript(), TMP_SCRIPT_LOCATION);\n    }\n\n    @Override\n    protected void containerIsStarted(InspectContainerResponse containerInfo) {\n        if (withClientCert) {\n            executeCommand(\"cp\", \"/tmp/solace.pem\", \"/usr/sw/jail/certs/solace.pem\");\n            executeCommand(\"cp\", \"/tmp/rootCA.crt\", \"/usr/sw/jail/certs/rootCA.crt\");\n        }\n        executeCommand(\"cp\", TMP_SCRIPT_LOCATION, \"/usr/sw/jail/cliscripts/script.cli\");\n        waitOnCommandResult(SOLACE_ACTIVE_MESSAGE, \"grep\", \"-R\", SOLACE_ACTIVE_MESSAGE, \"/usr/sw/jail/logs/system.log\");\n        executeCommand(\"/usr/sw/loads/currentload/bin/cli\", \"-A\", \"-es\", \"script.cli\");\n    }\n\n    private Transferable createConfigurationScript() {\n        StringBuilder scriptBuilder = new StringBuilder();\n        updateConfigScript(scriptBuilder, \"enable\");\n        updateConfigScript(scriptBuilder, \"configure\");\n\n        // Create VPN if not default\n        if (!vpn.equals(DEFAULT_VPN)) {\n            updateConfigScript(scriptBuilder, \"create message-vpn \" + vpn);\n            updateConfigScript(scriptBuilder, \"no shutdown\");\n            updateConfigScript(scriptBuilder, \"exit\");\n            updateConfigScript(scriptBuilder, \"client-profile default message-vpn \" + vpn);\n            updateConfigScript(scriptBuilder, \"message-spool\");\n            updateConfigScript(scriptBuilder, \"allow-guaranteed-message-send\");\n            updateConfigScript(scriptBuilder, \"allow-guaranteed-message-receive\");\n            updateConfigScript(scriptBuilder, \"allow-guaranteed-endpoint-create\");\n            updateConfigScript(scriptBuilder, \"allow-guaranteed-endpoint-create-durability all\");\n            updateConfigScript(scriptBuilder, \"exit\");\n            updateConfigScript(scriptBuilder, \"exit\");\n            updateConfigScript(scriptBuilder, \"message-spool message-vpn \" + vpn);\n            updateConfigScript(scriptBuilder, \"max-spool-usage 60000\");\n            updateConfigScript(scriptBuilder, \"exit\");\n        }\n\n        // Configure username and password\n        if (username.equals(DEFAULT_USERNAME)) {\n            throw new RuntimeException(\"Cannot override password for default client\");\n        }\n        updateConfigScript(scriptBuilder, \"create client-username \" + username + \" message-vpn \" + vpn);\n        updateConfigScript(scriptBuilder, \"password \" + password);\n        updateConfigScript(scriptBuilder, \"no shutdown\");\n        updateConfigScript(scriptBuilder, \"exit\");\n\n        if (withClientCert) {\n            // Client certificate authority configuration\n            updateConfigScript(scriptBuilder, \"authentication\");\n            updateConfigScript(scriptBuilder, \"create client-certificate-authority RootCA\");\n            updateConfigScript(scriptBuilder, \"certificate file rootCA.crt\");\n            updateConfigScript(scriptBuilder, \"show client-certificate-authority ca-name *\");\n            updateConfigScript(scriptBuilder, \"end\");\n\n            // Server certificates configuration\n            updateConfigScript(scriptBuilder, \"configure\");\n            updateConfigScript(scriptBuilder, \"ssl\");\n            updateConfigScript(scriptBuilder, \"server-certificate solace.pem\");\n            updateConfigScript(scriptBuilder, \"cipher-suite msg-backbone name AES128-SHA\");\n            updateConfigScript(scriptBuilder, \"exit\");\n\n            updateConfigScript(scriptBuilder, \"message-vpn \" + vpn);\n            // Enable client certificate authentication\n            updateConfigScript(scriptBuilder, \"authentication client-certificate\");\n            updateConfigScript(scriptBuilder, \"allow-api-provided-username\");\n            updateConfigScript(scriptBuilder, \"no shutdown\");\n            updateConfigScript(scriptBuilder, \"end\");\n        } else {\n            // Configure VPN Basic authentication\n            updateConfigScript(scriptBuilder, \"message-vpn \" + vpn);\n            updateConfigScript(scriptBuilder, \"authentication basic auth-type internal\");\n            updateConfigScript(scriptBuilder, \"no shutdown\");\n            updateConfigScript(scriptBuilder, \"end\");\n        }\n\n        if (!topicsConfiguration.isEmpty()) {\n            // Enable services\n            updateConfigScript(scriptBuilder, \"configure\");\n            // Configure default ACL\n            updateConfigScript(scriptBuilder, \"acl-profile default message-vpn \" + vpn);\n            // Configure default action to disallow\n            updateConfigScript(scriptBuilder, \"subscribe-topic default-action disallow\");\n            updateConfigScript(scriptBuilder, \"publish-topic default-action disallow\");\n            updateConfigScript(scriptBuilder, \"exit\");\n\n            updateConfigScript(scriptBuilder, \"message-vpn \" + vpn);\n            updateConfigScript(scriptBuilder, \"service\");\n            for (Pair<String, Service> topicConfig : topicsConfiguration) {\n                Service service = topicConfig.getValue();\n                String topicName = topicConfig.getKey();\n                updateConfigScript(scriptBuilder, service.getName());\n                if (service.isSupportSSL()) {\n                    if (withClientCert) {\n                        updateConfigScript(scriptBuilder, \"ssl\");\n                    } else {\n                        updateConfigScript(scriptBuilder, \"plain-text\");\n                    }\n                }\n                updateConfigScript(scriptBuilder, \"no shutdown\");\n                updateConfigScript(scriptBuilder, \"end\");\n                // Add publish/subscribe topic exceptions\n                updateConfigScript(scriptBuilder, \"configure\");\n                updateConfigScript(scriptBuilder, \"acl-profile default message-vpn \" + vpn);\n                updateConfigScript(\n                    scriptBuilder,\n                    String.format(\"publish-topic exceptions %s list %s\", service.getName(), topicName)\n                );\n                updateConfigScript(\n                    scriptBuilder,\n                    String.format(\"subscribe-topic exceptions %s list %s\", service.getName(), topicName)\n                );\n                updateConfigScript(scriptBuilder, \"end\");\n            }\n        }\n        return Transferable.of(scriptBuilder.toString());\n    }\n\n    private void executeCommand(String... command) {\n        try {\n            ExecResult execResult = execInContainer(command);\n            if (execResult.getExitCode() != 0) {\n                logCommandError(execResult.getStderr(), command);\n            }\n        } catch (IOException | InterruptedException e) {\n            logCommandError(e.getMessage(), command);\n        }\n    }\n\n    private void updateConfigScript(StringBuilder scriptBuilder, String command) {\n        scriptBuilder.append(command).append(\"\\n\");\n    }\n\n    private void waitOnCommandResult(String waitingFor, String... command) {\n        Awaitility\n            .await()\n            .pollInterval(Duration.ofMillis(500))\n            .timeout(Duration.ofSeconds(30))\n            .until(() -> {\n                try {\n                    return execInContainer(command).getStdout().contains(waitingFor);\n                } catch (IOException | InterruptedException e) {\n                    logCommandError(e.getMessage(), command);\n                    return true;\n                }\n            });\n    }\n\n    private void logCommandError(String error, String... command) {\n        logger().error(\"Could not execute command {}: {}\", command, error);\n    }\n\n    /**\n     * Sets the client credentials\n     *\n     * @param username Client username\n     * @param password Client password\n     * @return This container.\n     */\n    public SolaceContainer withCredentials(final String username, final String password) {\n        this.username = username;\n        this.password = password;\n        return this;\n    }\n\n    /**\n     * Adds the topic configuration\n     *\n     * @param topic Name of the topic\n     * @param service Service to be supported on provided topic\n     * @return This container.\n     */\n    public SolaceContainer withTopic(String topic, Service service) {\n        topicsConfiguration.add(Pair.of(topic, service));\n        addExposedPort(service.getPort());\n        return this;\n    }\n\n    /**\n     * Sets the VPN name\n     *\n     * @param vpn VPN name\n     * @return This container.\n     */\n    public SolaceContainer withVpn(String vpn) {\n        this.vpn = vpn;\n        return this;\n    }\n\n    /**\n     * Sets the solace server ceritificates\n     *\n     * @param certFile Server certificate\n     * @param caFile Certified Authority certificate\n     * @return This container.\n     */\n    public SolaceContainer withClientCert(final MountableFile certFile, final MountableFile caFile) {\n        this.withClientCert = true;\n        return withCopyFileToContainer(certFile, \"/tmp/solace.pem\").withCopyFileToContainer(caFile, \"/tmp/rootCA.crt\");\n    }\n\n    /**\n     * Configured VPN\n     *\n     * @return the configured VPN that should be used for connections\n     */\n    public String getVpn() {\n        return this.vpn;\n    }\n\n    /**\n     * Host address for provided service\n     *\n     * @param service - service for which host needs to be retrieved\n     * @return host address exposed from the container\n     */\n    public String getOrigin(Service service) {\n        return String.format(\"%s://%s:%s\", service.getProtocol(), getHost(), getMappedPort(service.getPort()));\n    }\n\n    /**\n     * Configured username\n     *\n     * @return the standard username that should be used for connections\n     */\n    public String getUsername() {\n        return this.username;\n    }\n\n    /**\n     * Configured password\n     *\n     * @return the standard password that should be used for connections\n     */\n    public String getPassword() {\n        return this.password;\n    }\n}\n"
  },
  {
    "path": "modules/solace/src/test/java/org/testcontainers/solace/SolaceContainerAMQPTest.java",
    "content": "package org.testcontainers.solace;\n\nimport org.apache.qpid.jms.JmsConnectionFactory;\nimport org.junit.jupiter.api.Test;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\n\nimport javax.jms.Connection;\nimport javax.jms.ConnectionFactory;\nimport javax.jms.JMSException;\nimport javax.jms.MessageConsumer;\nimport javax.jms.MessageProducer;\nimport javax.jms.Session;\nimport javax.jms.TextMessage;\nimport javax.jms.Topic;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.fail;\n\nclass SolaceContainerAMQPTest {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(SolaceContainerAMQPTest.class);\n\n    private static final String MESSAGE = \"HelloWorld\";\n\n    private static final String TOPIC_NAME = \"Topic/ActualTopic\";\n\n    @Test\n    void testSolaceContainer() throws JMSException {\n        try (\n            SolaceContainer solaceContainer = new SolaceContainer(\"solace/solace-pubsub-standard:10.25.0\")\n                .withTopic(TOPIC_NAME, Service.AMQP)\n                .withVpn(\"amqp-vpn\")\n        ) {\n            solaceContainer.start();\n            // solaceContainerUsage {\n            Session session = createSession(\n                solaceContainer.getUsername(),\n                solaceContainer.getPassword(),\n                solaceContainer.getOrigin(Service.AMQP)\n            );\n            // }\n            assertThat(session).isNotNull();\n            assertThat(consumeMessageFromSolace(session)).isEqualTo(MESSAGE);\n            session.close();\n        }\n    }\n\n    private static Session createSession(String username, String password, String host) {\n        try {\n            ConnectionFactory connectionFactory = new JmsConnectionFactory(username, password, host);\n            Connection connection = connectionFactory.createConnection();\n            Session session = connection.createSession();\n            connection.start();\n            return session;\n        } catch (Exception e) {\n            fail(\"Error connecting and setting up session! \" + e.getMessage());\n            return null;\n        }\n    }\n\n    private void publishMessageToSolace(Session session, Topic topic) throws JMSException {\n        MessageProducer messageProducer = session.createProducer(topic);\n        TextMessage message = session.createTextMessage(MESSAGE);\n        messageProducer.send(message);\n        messageProducer.close();\n    }\n\n    private String consumeMessageFromSolace(Session session) {\n        CountDownLatch latch = new CountDownLatch(1);\n        try {\n            String[] result = new String[1];\n            Topic topic = session.createTopic(TOPIC_NAME);\n            MessageConsumer messageConsumer = session.createConsumer(topic);\n            messageConsumer.setMessageListener(message -> {\n                try {\n                    if (message instanceof TextMessage) {\n                        result[0] = ((TextMessage) message).getText();\n                    }\n                    latch.countDown();\n                } catch (Exception e) {\n                    LOGGER.error(\"Exception received: \" + e.getMessage());\n                    latch.countDown();\n                }\n            });\n            publishMessageToSolace(session, topic);\n            assertThat(latch.await(10L, TimeUnit.SECONDS)).isTrue();\n            messageConsumer.close();\n            return result[0];\n        } catch (Exception e) {\n            throw new RuntimeException(\"Cannot receive message from solace\", e);\n        }\n    }\n}\n"
  },
  {
    "path": "modules/solace/src/test/java/org/testcontainers/solace/SolaceContainerMQTTTest.java",
    "content": "package org.testcontainers.solace;\n\nimport org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;\nimport org.eclipse.paho.client.mqttv3.MqttCallback;\nimport org.eclipse.paho.client.mqttv3.MqttClient;\nimport org.eclipse.paho.client.mqttv3.MqttConnectOptions;\nimport org.eclipse.paho.client.mqttv3.MqttException;\nimport org.eclipse.paho.client.mqttv3.MqttMessage;\nimport org.junit.jupiter.api.Test;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.fail;\n\nclass SolaceContainerMQTTTest {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(SolaceContainerMQTTTest.class);\n\n    private static final String MESSAGE = \"HelloWorld\";\n\n    private static final String TOPIC_NAME = \"Topic/ActualTopic\";\n\n    @Test\n    void testSolaceContainer() {\n        try (\n            SolaceContainer solaceContainer = new SolaceContainer(\"solace/solace-pubsub-standard:10.25.0\")\n                .withTopic(TOPIC_NAME, Service.MQTT)\n                .withVpn(\"mqtt-vpn\")\n        ) {\n            solaceContainer.start();\n            MqttClient client = createClient(\n                solaceContainer.getUsername(),\n                solaceContainer.getPassword(),\n                solaceContainer.getOrigin(Service.MQTT)\n            );\n            assertThat(client).isNotNull();\n            assertThat(consumeMessageFromSolace(client)).isEqualTo(MESSAGE);\n        }\n    }\n\n    private static MqttClient createClient(String username, String password, String host) {\n        try {\n            MqttClient mqttClient = new MqttClient(host, MESSAGE);\n            MqttConnectOptions connOpts = new MqttConnectOptions();\n            connOpts.setCleanSession(true);\n            connOpts.setUserName(username);\n            connOpts.setPassword(password.toCharArray());\n            mqttClient.connect(connOpts);\n            return mqttClient;\n        } catch (Exception e) {\n            fail(\"Error connecting and setting up session! \" + e.getMessage());\n            return null;\n        }\n    }\n\n    private void publishMessageToSolace(MqttClient mqttClient) throws MqttException {\n        MqttMessage message = new MqttMessage(MESSAGE.getBytes());\n        message.setQos(0);\n        mqttClient.publish(TOPIC_NAME, message);\n    }\n\n    private String consumeMessageFromSolace(MqttClient client) {\n        CountDownLatch latch = new CountDownLatch(1);\n        try {\n            String[] result = new String[1];\n            client.setCallback(\n                new MqttCallback() {\n                    @Override\n                    public void connectionLost(Throwable cause) {\n                        LOGGER.error(\"Exception received: \" + cause.getMessage());\n                        latch.countDown();\n                    }\n\n                    @Override\n                    public void messageArrived(String topic, MqttMessage message) {\n                        result[0] = new String(message.getPayload());\n                        latch.countDown();\n                    }\n\n                    @Override\n                    public void deliveryComplete(IMqttDeliveryToken token) {}\n                }\n            );\n            client.subscribe(TOPIC_NAME, 0);\n            publishMessageToSolace(client);\n            assertThat(latch.await(10L, TimeUnit.SECONDS)).isTrue();\n            return result[0];\n        } catch (Exception e) {\n            throw new RuntimeException(\"Cannot receive message from solace\", e);\n        }\n    }\n}\n"
  },
  {
    "path": "modules/solace/src/test/java/org/testcontainers/solace/SolaceContainerRESTTest.java",
    "content": "package org.testcontainers.solace;\n\nimport org.apache.http.HttpHeaders;\nimport org.apache.http.HttpResponse;\nimport org.apache.http.HttpStatus;\nimport org.apache.http.auth.AuthScope;\nimport org.apache.http.auth.UsernamePasswordCredentials;\nimport org.apache.http.client.CredentialsProvider;\nimport org.apache.http.client.HttpClient;\nimport org.apache.http.client.methods.HttpPost;\nimport org.apache.http.entity.StringEntity;\nimport org.apache.http.impl.client.BasicCredentialsProvider;\nimport org.apache.http.impl.client.HttpClientBuilder;\nimport org.apache.http.util.EntityUtils;\nimport org.junit.jupiter.api.Test;\n\nimport java.io.IOException;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.fail;\n\nclass SolaceContainerRESTTest {\n\n    private static final String MESSAGE = \"HelloWorld\";\n\n    private static final String TOPIC_NAME = \"Topic/ActualTopic\";\n\n    @Test\n    void testSolaceContainer() throws IOException {\n        try (\n            SolaceContainer solaceContainer = new SolaceContainer(\"solace/solace-pubsub-standard:10.25.0\")\n                .withTopic(TOPIC_NAME, Service.REST)\n                .withVpn(\"rest-vpn\")\n        ) {\n            solaceContainer.start();\n            testPublishMessageToSolace(solaceContainer, Service.REST);\n        }\n    }\n\n    private void testPublishMessageToSolace(SolaceContainer solaceContainer, Service service) throws IOException {\n        HttpClient client = createClient(solaceContainer);\n        HttpPost request = new HttpPost(solaceContainer.getOrigin(service) + \"/\" + TOPIC_NAME);\n        request.setEntity(new StringEntity(MESSAGE));\n        request.addHeader(HttpHeaders.CONTENT_TYPE, \"text/plain\");\n        HttpResponse response = client.execute(request);\n        if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {\n            fail(\"Cannot send message to solace - \" + EntityUtils.toString(response.getEntity()));\n        }\n        assertThat(EntityUtils.toString(response.getEntity())).isEmpty();\n    }\n\n    private HttpClient createClient(SolaceContainer solaceContainer) {\n        CredentialsProvider provider = new BasicCredentialsProvider();\n        provider.setCredentials(\n            AuthScope.ANY,\n            new UsernamePasswordCredentials(solaceContainer.getUsername(), solaceContainer.getPassword())\n        );\n        return HttpClientBuilder.create().setDefaultCredentialsProvider(provider).build();\n    }\n}\n"
  },
  {
    "path": "modules/solace/src/test/java/org/testcontainers/solace/SolaceContainerSMFTest.java",
    "content": "package org.testcontainers.solace;\n\nimport com.solacesystems.jcsmp.BytesXMLMessage;\nimport com.solacesystems.jcsmp.ConsumerFlowProperties;\nimport com.solacesystems.jcsmp.EndpointProperties;\nimport com.solacesystems.jcsmp.JCSMPException;\nimport com.solacesystems.jcsmp.JCSMPFactory;\nimport com.solacesystems.jcsmp.JCSMPProperties;\nimport com.solacesystems.jcsmp.JCSMPSession;\nimport com.solacesystems.jcsmp.JCSMPStreamingPublishCorrelatingEventHandler;\nimport com.solacesystems.jcsmp.Queue;\nimport com.solacesystems.jcsmp.TextMessage;\nimport com.solacesystems.jcsmp.Topic;\nimport com.solacesystems.jcsmp.XMLMessageConsumer;\nimport com.solacesystems.jcsmp.XMLMessageListener;\nimport com.solacesystems.jcsmp.XMLMessageProducer;\nimport org.junit.jupiter.api.Test;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.testcontainers.utility.MountableFile;\n\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.fail;\n\nclass SolaceContainerSMFTest {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(SolaceContainerSMFTest.class);\n\n    private static final String MESSAGE = \"HelloWorld\";\n\n    private static final Topic TOPIC = JCSMPFactory.onlyInstance().createTopic(\"Topic/ActualTopic\");\n\n    private static final Queue QUEUE = JCSMPFactory.onlyInstance().createQueue(\"Queue\");\n\n    @Test\n    void testSolaceContainerWithSimpleAuthentication() {\n        try (\n            // solaceContainerSetup {\n            SolaceContainer solaceContainer = new SolaceContainer(\"solace/solace-pubsub-standard:10.25.0\")\n                .withCredentials(\"user\", \"pass\")\n                .withTopic(TOPIC.getName(), Service.SMF)\n                .withVpn(\"test_vpn\")\n            // }\n        ) {\n            solaceContainer.start();\n            JCSMPSession session = createSessionWithBasicAuth(solaceContainer);\n            assertThat(session).isNotNull();\n            consumeMessageFromTopics(session);\n            session.closeSession();\n        }\n    }\n\n    @Test\n    void testSolaceContainerWithCreateFlow() {\n        try (\n            SolaceContainer solaceContainer = new SolaceContainer(\"solace/solace-pubsub-standard:10.25.0\")\n                .withCredentials(\"user\", \"pass\")\n                .withTopic(TOPIC.getName(), Service.SMF)\n                .withVpn(\"test_vpn\")\n        ) {\n            solaceContainer.start();\n            JCSMPSession session = createSessionWithBasicAuth(solaceContainer);\n            assertThat(session).isNotNull();\n            testCreateFlow(session);\n            session.closeSession();\n        }\n    }\n\n    private static void testCreateFlow(JCSMPSession session) {\n        try {\n            EndpointProperties endpointProperties = new EndpointProperties();\n            endpointProperties.setAccessType(EndpointProperties.ACCESSTYPE_NONEXCLUSIVE);\n            endpointProperties.setQuota(1000);\n            session.provision(QUEUE, endpointProperties, JCSMPSession.FLAG_IGNORE_ALREADY_EXISTS);\n            session.addSubscription(QUEUE, TOPIC, JCSMPSession.WAIT_FOR_CONFIRM);\n            ConsumerFlowProperties flowProperties = new ConsumerFlowProperties().setEndpoint(QUEUE);\n            TestConsumer listener = new TestConsumer();\n            session.createFlow(listener, flowProperties).start();\n            publishMessageToSolaceTopic(session);\n            listener.waitForMessage();\n        } catch (Exception e) {\n            throw new RuntimeException(\"Cannot process message using solace topic/queue: \" + e.getMessage(), e);\n        }\n    }\n\n    @Test\n    void testSolaceContainerWithCertificates() {\n        try (\n            // solaceContainerUsageSSL {\n            SolaceContainer solaceContainer = new SolaceContainer(\"solace/solace-pubsub-standard:10.25.0\")\n                .withClientCert(\n                    MountableFile.forClasspathResource(\"solace.pem\"),\n                    MountableFile.forClasspathResource(\"rootCA.crt\")\n                )\n                .withTopic(TOPIC.getName(), Service.SMF_SSL)\n            // }\n        ) {\n            solaceContainer.start();\n            JCSMPSession session = createSessionWithCertificates(solaceContainer);\n            assertThat(session).isNotNull();\n            consumeMessageFromTopics(session);\n            session.closeSession();\n        }\n    }\n\n    private String getResourceFileLocation(String name) {\n        return getClass().getClassLoader().getResource(name).getPath();\n    }\n\n    private static JCSMPSession createSessionWithBasicAuth(SolaceContainer solace) {\n        JCSMPProperties properties = new JCSMPProperties();\n        properties.setProperty(JCSMPProperties.HOST, solace.getOrigin(Service.SMF));\n        properties.setProperty(JCSMPProperties.VPN_NAME, solace.getVpn());\n        properties.setProperty(JCSMPProperties.USERNAME, solace.getUsername());\n        properties.setProperty(JCSMPProperties.PASSWORD, solace.getPassword());\n        return createSession(properties);\n    }\n\n    private JCSMPSession createSessionWithCertificates(SolaceContainer solace) {\n        JCSMPProperties properties = new JCSMPProperties();\n        properties.setProperty(JCSMPProperties.HOST, solace.getOrigin(Service.SMF_SSL));\n        properties.setProperty(JCSMPProperties.VPN_NAME, solace.getVpn());\n        properties.setProperty(JCSMPProperties.USERNAME, solace.getUsername());\n        // Just for testing purposes\n        properties.setProperty(JCSMPProperties.SSL_VALIDATE_CERTIFICATE_HOST, false);\n        properties.setProperty(JCSMPProperties.SSL_VALIDATE_CERTIFICATE, true);\n        properties.setProperty(JCSMPProperties.SSL_VALIDATE_CERTIFICATE_DATE, true);\n        properties.setProperty(\n            JCSMPProperties.AUTHENTICATION_SCHEME,\n            JCSMPProperties.AUTHENTICATION_SCHEME_CLIENT_CERTIFICATE\n        );\n        properties.setProperty(JCSMPProperties.SSL_TRUST_STORE, getResourceFileLocation(\"truststore\"));\n        properties.setProperty(JCSMPProperties.SSL_TRUST_STORE_PASSWORD, \"solace\");\n        properties.setProperty(JCSMPProperties.SSL_KEY_STORE, getResourceFileLocation(\"client.pfx\"));\n        properties.setProperty(JCSMPProperties.SSL_KEY_STORE_PASSWORD, \"solace\");\n        return createSession(properties);\n    }\n\n    private static JCSMPSession createSession(JCSMPProperties properties) {\n        try {\n            JCSMPSession session = JCSMPFactory.onlyInstance().createSession(properties);\n            session.connect();\n            return session;\n        } catch (Exception e) {\n            fail(\"Error connecting and setting up session! \" + e.getMessage());\n            return null;\n        }\n    }\n\n    private static void publishMessageToSolaceTopic(JCSMPSession session) throws JCSMPException {\n        XMLMessageProducer producer = session.getMessageProducer(\n            new JCSMPStreamingPublishCorrelatingEventHandler() {\n                @Override\n                public void responseReceivedEx(Object o) {\n                    LOGGER.info(\"Producer received response for msg: \" + o);\n                }\n\n                @Override\n                public void handleErrorEx(Object o, JCSMPException e, long l) {\n                    LOGGER.error(String.format(\"Producer received error for msg: %s - %s\", o, e));\n                }\n            }\n        );\n        TextMessage msg = producer.createTextMessage();\n        msg.setText(MESSAGE);\n        producer.send(msg, TOPIC);\n    }\n\n    private static void consumeMessageFromTopics(JCSMPSession session) {\n        try {\n            TestConsumer listener = new TestConsumer();\n            XMLMessageConsumer cons = session.getMessageConsumer(listener);\n            session.addSubscription(TOPIC);\n            cons.start();\n            publishMessageToSolaceTopic(session);\n            listener.waitForMessage();\n        } catch (Exception e) {\n            throw new RuntimeException(\"Cannot process message using solace: \" + e.getMessage(), e);\n        }\n    }\n\n    static class TestConsumer implements XMLMessageListener {\n\n        private final CountDownLatch latch = new CountDownLatch(1);\n\n        private String result;\n\n        @Override\n        public void onReceive(BytesXMLMessage msg) {\n            if (msg instanceof TextMessage) {\n                TextMessage textMessage = (TextMessage) msg;\n                String message = textMessage.getText();\n                result = message;\n                LOGGER.info(\"Message received: \" + message);\n            }\n            latch.countDown();\n        }\n\n        @Override\n        public void onException(JCSMPException e) {\n            LOGGER.error(\"Exception received: \" + e.getMessage());\n            latch.countDown();\n        }\n\n        private void waitForMessage() {\n            try {\n                assertThat(latch.await(10L, TimeUnit.SECONDS)).isTrue();\n                assertThat(result).isEqualTo(MESSAGE);\n            } catch (Exception e) {\n                throw new RuntimeException(\"Cannot receive message from solace: \" + e.getMessage(), e);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "modules/solace/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/solace/src/test/resources/rootCA.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDmjCCAoKgAwIBAgIIVP5+fnVAfbwwDQYJKoZIhvcNAQELBQAwRjELMAkGA1UE\nBhMCUEwxFzAVBgNVBAoTDnRlc3Rjb250YWluZXJzMR4wHAYDVQQDExV0ZXN0Y29u\ndGFpbmVycy1zb2xhY2UwIBcNMjQwMTI0MTYyNDAwWhgPMjIwMDAxMDEwMDAwMDBa\nMEYxCzAJBgNVBAYTAlBMMRcwFQYDVQQKEw50ZXN0Y29udGFpbmVyczEeMBwGA1UE\nAxMVdGVzdGNvbnRhaW5lcnMtc29sYWNlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A\nMIIBCgKCAQEAr26PCDd4C7BsSh2aMn/c7ZLLX44FnQE2JqakGGt9JGzkWDQaTAJk\n8HlBnRvJAq03RsOq0hazhbW6NGOt//SuMqS0WxT/p8wyIaIMZy8H/nCJm4Uw88/i\nAG9rE/mQJpwNucrB3FBVihDBZRystYyO781dgOK7+lnW6YKXJ3jRK/QJOkuJs3F1\njtgcZJHNwCoy7hRzWzPKEjb67aGDRVs6Iq6TL94AIEw4Qdb+x9g7Vtdgzj6M9stJ\nAKJe9TJRTAbVoKRw6ROOSbpYFePS3DI71bS87Ho2erXBRSovnfioh4u7bZH4XPwu\nMIZvfeSVoKiIk2anReD+tM+zI/iU5bmCwQIDAQABo4GJMIGGMA8GA1UdEwEB/wQF\nMAMBAf8wHQYDVR0OBBYEFNvFeVvpx8cEIIcjvDtQIOIPr9W8MAsGA1UdDwQEAwIC\njDAUBgNVHREEDTALgglsb2NhbGhvc3QwEQYJYIZIAYb4QgEBBAQDAgAHMB4GCWCG\nSAGG+EIBDQQRFg94Y2EgY2VydGlmaWNhdGUwDQYJKoZIhvcNAQELBQADggEBAGiI\n7qyjIx6ssKi04pKi0cuUZWI+JeY/+nkLEXN9dgbSchFcVJMpV8l9VVqINNFt9LMX\naTzQLnjUYop6osHA16zF9WViVJ8LUzwec70A4J5gA3tiebLNWCoAp7v5+dRPUc8r\n0oi4QTP4Siu6kVjZFNWuuezUZyaVz+rZuEe+rVWyNakIiAzIacaK3FBAtcCYcF2L\n2CjHCCsrRgmmvkRydGsr0pJEub9wxY4kVzQS07CwB0OMxllZm65ZtVdpPWh4f3GG\n/bB6kL9InDfHfSleKG60HpEPGqwWFm6VQxQVIOoumyP1WDp3ix+u0u5Y/Mo6hakm\nLplIUWZWT4rv/NaB/Rw=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "modules/solace/src/test/resources/solace.pem",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEA43Us+KsRZ218C8JAzB7LiF5dhx95F/spQzkiFgSNJ97215Go\nSIPzDWm1ac4wTGZR1YHiTk2PEFHk21TeJi5sODGHqR0G0Qr2IweSz52Zs5h0p+Xw\nO3x55In/Dx+jIMdOR9HfuU05WlYSTWXdwFyq9VmmXK+ViJ0pZE9X18E1Qtm27S6v\nFIckdEKswh17DCKMQ6kRMDkLwe7yOoBYPAFw0R46o3PXAuVHbkI/3ewn/aopOQ+4\nI0dqTxp7m7spdIanT+oSd5EN4G1a/oTYPO67qbcq/SIgWLHR/iT2dOE28ftAco64\n+N5ds5wz5a0JL7CwIdxcgSsndsBSR+hM5cC5kwIDAQABAoIBAERwMkrT9hWfrK5B\nEYwZS/ZJJm0MvDvJ931hiG8FiY9QmAb+rZq8EPqdLteaEZA7TS4nuXcEASLQ8UJJ\nQ9pLJ4a06HOq5y0o1ixuD+9mJSQToC4Qknrjli18lADx7Pxk25nifSVdJf+XXERr\nfRBvEYVnJxZGQoDrgNPMx8qEOMlW9Wub9+O5OttsMdxpVeWw/G36mT8JOVZvIw9W\nfrRXNuX9F17lJZIt3E10QrmKSdyogPdTMXBTTw5TCIMck1KH+lRSwP7GRnJMHKNE\nbcq+yoePYHdWfIW87I8bFprgC3IyCumA+ERp3oWNvTEziXkt1bogWx4pe+tFAhIp\nflpCDFkCgYEA8jhvgqEYuX9I7tY5gJHeC5BOg7d0XNpP4RN9cIEYkamgcwil7DAf\nhAn2+Lzvf1wcBtFi4wyygCsXsdP3CeV60vFYHWrgXozlZVs5NIDTb4CWNbylSapR\nmlfhfCQQAGXP8WdKQdoXsGhSgbX3kuAaW7d9BscPgPUSqCbSMvuRKt8CgYEA8GW+\nagfK7EDRbYbLRNcof4UIOnDoZ+vi62tbQ0wRI9dt0dXDgyeUuPMoeD5uTSu1jcjf\nT59UIKHwZO5f6A7ADZeJLU72Oo4dCYxG3H3ZM1VwW6kqVlWOtBkwOoibb5ApUjEP\njVCtvRNAn+htDO+4nhQDK2xwgJnkOaXCWFZgO80CgYBjPkRSHXdn6YMUeKmuyBVW\nX5YL2crPkJNSAQ5QXlSWug2HlG+HSmBfVUXfvGnUoQTKtlfx923bncxjjBmX8HJW\no5Qa2YN8ufXzhWD25iG7edARzG1ctXAh8QfuOUhlIVIF8vA18wnpuZS0mL4La87g\n7VlIwZ7Uk5VFWEKfqPtduQKBgQDgqjWKYj4DDZCsC41siKgQhQNrmpmYhZtM6Mgh\n3LUoCe1Yba6KpDMZpiXsOmxbMr46A8CvaPf2h2Fi8mQvO5nBGh3ZejIkByyb/705\n02Np1i9rem1Wwh7bsa6hBYo+eTwk1DT0nLHCQnvi9hT0QhUHpyxPKMj7ZtckCQXY\nCOFnAQKBgQCnPzxlGmmv9ktOgDN6pOzTd3VLtthQu+9u7AeumUjC4al69ROfqNW2\ndfzJcdlJqMDfuHDFle+fO0Ahfp3j/BYSZFf7jAzo/jMVh32siYiq6C6MawzknwsS\nnUBfPFBD84Fkv5YaD0TT48p7YUANFL18Roq1PjezPdmkn7T8Nuaz2g==\n-----END RSA PRIVATE KEY-----\n-----BEGIN CERTIFICATE-----\nMIIDqTCCApGgAwIBAgIIIFE70OcBFc8wDQYJKoZIhvcNAQELBQAwRjELMAkGA1UE\nBhMCUEwxFzAVBgNVBAoTDnRlc3Rjb250YWluZXJzMR4wHAYDVQQDExV0ZXN0Y29u\ndGFpbmVycy1zb2xhY2UwIBcNMjQwMTI0MTYyNDAwWhgPMjIwMDAxMDEwMDAwMDBa\nMDkxFzAVBgNVBAoTDnRlc3Rjb250YWluZXJzMR4wHAYDVQQDExV0ZXN0Y29udGFp\nbmVycy1zb2xhY2UwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDjdSz4\nqxFnbXwLwkDMHsuIXl2HH3kX+ylDOSIWBI0n3vbXkahIg/MNabVpzjBMZlHVgeJO\nTY8QUeTbVN4mLmw4MYepHQbRCvYjB5LPnZmzmHSn5fA7fHnkif8PH6Mgx05H0d+5\nTTlaVhJNZd3AXKr1WaZcr5WInSlkT1fXwTVC2bbtLq8UhyR0QqzCHXsMIoxDqREw\nOQvB7vI6gFg8AXDRHjqjc9cC5UduQj/d7Cf9qik5D7gjR2pPGnubuyl0hqdP6hJ3\nkQ3gbVr+hNg87ruptyr9IiBYsdH+JPZ04Tbx+0Byjrj43l2znDPlrQkvsLAh3FyB\nKyd2wFJH6EzlwLmTAgMBAAGjgaUwgaIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU\nT5AeIO1T10daj92c9R0qALbPu+cwCwYDVR0PBAQDAgPoMB0GA1UdJQQWMBQGCCsG\nAQUFBwMBBggrBgEFBQcDAjAUBgNVHREEDTALgglsb2NhbGhvc3QwEQYJYIZIAYb4\nQgEBBAQDAgZAMB4GCWCGSAGG+EIBDQQRFg94Y2EgY2VydGlmaWNhdGUwDQYJKoZI\nhvcNAQELBQADggEBADyiXuXEx/Cka/xDxUmluYprTMubhf2Tt840WtXODoqa6mK0\nL74M18fvOeRL6G1ufzyrOUg5kt+0vyS8LlNmPJ/F7RbFvJ7Ca+vFVChi6NLag5U7\nXp6bdI7jwLKiP1Sh+AyucZafW9k+e3PO4beAJqYHmYxsLtXmnYlWFL/6VoYACpTu\n6qFYenAecqQAs49Ujs5y9l0IRC8Hj9ZcPVf63BTceAdiPqwUhIctD4vB6v1pTwjb\nCaQxJLY5P3sQNkvMMSK29p03EJOMZ4oVt09RBBEm1YwwUA5pp/JaxJvRlMgnTNHs\nh6xQyX9XEWS9cNUa2VktBEq1eoIg/njpnbiGvDo=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "modules/solr/build.gradle",
    "content": "description = \"Testcontainers :: Solr\"\n\ndependencies {\n    api project(':testcontainers')\n    // TODO use JDK's HTTP client and/or Apache HttpClient5\n    shaded 'com.squareup.okhttp3:okhttp:5.3.2'\n\n    testImplementation 'org.apache.solr:solr-solrj:8.11.4'\n}\n"
  },
  {
    "path": "modules/solr/src/main/java/org/testcontainers/containers/SolrClientUtils.java",
    "content": "package org.testcontainers.containers;\n\nimport okhttp3.HttpUrl;\nimport okhttp3.MediaType;\nimport okhttp3.OkHttpClient;\nimport okhttp3.Request;\nimport okhttp3.RequestBody;\nimport okhttp3.Response;\nimport org.apache.commons.io.IOUtils;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.net.URISyntaxException;\nimport java.net.URL;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.zip.ZipEntry;\nimport java.util.zip.ZipOutputStream;\n\n/**\n * Utils class which can create collections and configurations.\n */\npublic class SolrClientUtils {\n\n    private static OkHttpClient httpClient = new OkHttpClient();\n\n    /**\n     * Creates a new configuration and uploads the solrconfig.xml and schema.xml\n     *\n     * @param hostname          the Hostname under which solr is reachable\n     * @param port              the Port on which solr is running\n     * @param configurationName the name of the configuration which should be created\n     * @param solrConfig        the url under which the solrconfig.xml can be found\n     * @param solrSchema        the url under which the schema.xml can be found or null if the default schema should be used\n     */\n    public static void uploadConfiguration(\n        String hostname,\n        int port,\n        String configurationName,\n        URL solrConfig,\n        URL solrSchema\n    ) throws URISyntaxException, IOException {\n        Map<String, String> parameters = new HashMap<>();\n        parameters.put(\"action\", \"UPLOAD\");\n        parameters.put(\"name\", configurationName);\n        HttpUrl url = generateSolrURL(hostname, port, Arrays.asList(\"admin\", \"configs\"), parameters);\n\n        byte[] configurationZipFile = generateConfigZipFile(solrConfig, solrSchema);\n        executePost(url, configurationZipFile);\n    }\n\n    /**\n     * Creates a new collection\n     *\n     * @param hostname          the Hostname under which solr is reachable\n     * @param port              The Port on which solr is running\n     * @param collectionName    the name of the collection which should be created\n     * @param configurationName the name of the configuration which should used to create the collection\n     *                          or null if the default configuration should be used\n     */\n    public static void createCollection(String hostname, int port, String collectionName, String configurationName)\n        throws URISyntaxException, IOException {\n        Map<String, String> parameters = new HashMap<>();\n        parameters.put(\"action\", \"CREATE\");\n        parameters.put(\"name\", collectionName);\n        parameters.put(\"numShards\", \"1\");\n        parameters.put(\"replicationFactor\", \"1\");\n        parameters.put(\"wt\", \"json\");\n        if (configurationName != null) {\n            parameters.put(\"collection.configName\", configurationName);\n        }\n        HttpUrl url = generateSolrURL(hostname, port, Arrays.asList(\"admin\", \"collections\"), parameters);\n        executePost(url, null);\n    }\n\n    private static void executePost(HttpUrl url, byte[] data) throws IOException {\n        RequestBody requestBody = data == null\n            ? RequestBody.create(MediaType.parse(\"text/plain\"), \"\")\n            : RequestBody.create(MediaType.parse(\"application/octet-stream\"), data);\n        Request request = new Request.Builder().url(url).post(requestBody).build();\n\n        Response response = httpClient.newCall(request).execute();\n        if (!response.isSuccessful()) {\n            String responseBody = \"\";\n            if (response.body() != null) {\n                responseBody = response.body().string();\n                response.close();\n            }\n            throw new SolrClientUtilsException(response.code(), \"Unable to upload binary\\n\" + responseBody);\n        }\n        if (response.body() != null) {\n            response.close();\n        }\n    }\n\n    private static HttpUrl generateSolrURL(\n        String hostname,\n        int port,\n        List<String> pathSegments,\n        Map<String, String> parameters\n    ) throws URISyntaxException {\n        HttpUrl.Builder builder = new HttpUrl.Builder();\n        builder.scheme(\"http\");\n        builder.host(hostname);\n        builder.port(port);\n        // Path\n        builder.addPathSegment(\"solr\");\n        if (pathSegments != null) {\n            pathSegments.forEach(builder::addPathSegment);\n        }\n        // Query Parameters\n        parameters.forEach(builder::addQueryParameter);\n        return builder.build();\n    }\n\n    private static byte[] generateConfigZipFile(URL solrConfiguration, URL solrSchema) throws IOException {\n        ByteArrayOutputStream bos = new ByteArrayOutputStream();\n        ZipOutputStream zipOutputStream = new ZipOutputStream(bos);\n        // SolrConfig\n        zipOutputStream.putNextEntry(new ZipEntry(\"solrconfig.xml\"));\n        IOUtils.copy(solrConfiguration.openStream(), zipOutputStream);\n        zipOutputStream.closeEntry();\n\n        // Solr Schema\n        if (solrSchema != null) {\n            zipOutputStream.putNextEntry(new ZipEntry(\"schema.xml\"));\n            IOUtils.copy(solrSchema.openStream(), zipOutputStream);\n            zipOutputStream.closeEntry();\n        }\n\n        zipOutputStream.close();\n        return bos.toByteArray();\n    }\n}\n"
  },
  {
    "path": "modules/solr/src/main/java/org/testcontainers/containers/SolrClientUtilsException.java",
    "content": "package org.testcontainers.containers;\n\npublic class SolrClientUtilsException extends RuntimeException {\n\n    public SolrClientUtilsException(int statusCode, String msg) {\n        super(\"Http Call Status: \" + statusCode + \"\\n\" + msg);\n    }\n}\n"
  },
  {
    "path": "modules/solr/src/main/java/org/testcontainers/containers/SolrContainer.java",
    "content": "package org.testcontainers.containers;\n\nimport com.github.dockerjava.api.command.InspectContainerResponse;\nimport lombok.SneakyThrows;\nimport org.apache.commons.lang3.StringUtils;\nimport org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy;\nimport org.testcontainers.utility.ComparableVersion;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.net.URL;\nimport java.time.Duration;\nimport java.time.temporal.ChronoUnit;\nimport java.util.HashSet;\nimport java.util.Set;\n\n/**\n * Testcontainers implementation for Solr.\n * <p>\n * Supported image: {@code solr}\n * <p>\n * Exposed ports:\n * <ul>\n *     <li>Solr: 8983</li>\n *     <li>Zookeeper: 9983</li>\n * </ul>\n *\n * @deprecated use {@link org.testcontainers.solr.SolrContainer} instead.\n */\npublic class SolrContainer extends GenericContainer<SolrContainer> {\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"solr\");\n\n    @Deprecated\n    public static final String DEFAULT_TAG = \"8.3.0\";\n\n    public static final Integer ZOOKEEPER_PORT = 9983;\n\n    public static final Integer SOLR_PORT = 8983;\n\n    private SolrContainerConfiguration configuration;\n\n    private final ComparableVersion imageVersion;\n\n    /**\n     * @deprecated use {@link #SolrContainer(DockerImageName)} instead\n     */\n    @Deprecated\n    public SolrContainer() {\n        this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG));\n    }\n\n    public SolrContainer(final String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    public SolrContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n\n        this.waitStrategy =\n            new LogMessageWaitStrategy()\n                .withRegEx(\".*o\\\\.e\\\\.j\\\\.s\\\\.Server Started.*\")\n                .withStartupTimeout(Duration.of(60, ChronoUnit.SECONDS));\n        this.configuration = new SolrContainerConfiguration();\n        this.imageVersion = new ComparableVersion(dockerImageName.getVersionPart());\n    }\n\n    public SolrContainer withZookeeper(boolean zookeeper) {\n        configuration.setZookeeper(zookeeper);\n        return self();\n    }\n\n    public SolrContainer withCollection(String collection) {\n        if (StringUtils.isEmpty(collection)) {\n            throw new IllegalArgumentException(\"Collection name must not be empty\");\n        }\n        configuration.setCollectionName(collection);\n        return self();\n    }\n\n    public SolrContainer withConfiguration(String name, URL solrConfig) {\n        if (StringUtils.isEmpty(name) || solrConfig == null) {\n            throw new IllegalArgumentException();\n        }\n        configuration.setConfigurationName(name);\n        configuration.setSolrConfiguration(solrConfig);\n        return self();\n    }\n\n    public SolrContainer withSchema(URL schema) {\n        configuration.setSolrSchema(schema);\n        return self();\n    }\n\n    public int getSolrPort() {\n        return getMappedPort(SOLR_PORT);\n    }\n\n    public int getZookeeperPort() {\n        return getMappedPort(ZOOKEEPER_PORT);\n    }\n\n    @Override\n    @SneakyThrows\n    protected void configure() {\n        if (configuration.getSolrSchema() != null && configuration.getSolrConfiguration() == null) {\n            throw new IllegalStateException(\"Solr needs to have a configuration if you want to use a schema\");\n        }\n        // Generate Command Builder\n        String command = \"solr start -f\";\n        // Add Default Ports\n        this.addExposedPort(SOLR_PORT);\n\n        // Configure Zookeeper\n        if (configuration.isZookeeper()) {\n            this.addExposedPort(ZOOKEEPER_PORT);\n            if (this.imageVersion.isGreaterThanOrEqualTo(\"9.7.0\")) {\n                command = \"-DzkRun --host localhost\";\n            } else {\n                command = \"-DzkRun -h localhost\";\n            }\n        }\n\n        // Apply generated Command\n        this.setCommand(command);\n    }\n\n    @Override\n    public Set<Integer> getLivenessCheckPortNumbers() {\n        return new HashSet<>(getSolrPort());\n    }\n\n    @Override\n    protected void waitUntilContainerStarted() {\n        getWaitStrategy().waitUntilReady(this);\n    }\n\n    @Override\n    @SneakyThrows\n    protected void containerIsStarted(InspectContainerResponse containerInfo) {\n        if (!configuration.isZookeeper()) {\n            ExecResult result = execInContainer(\"solr\", \"create\", \"-c\", configuration.getCollectionName());\n            if (result.getExitCode() != 0) {\n                throw new IllegalStateException(\n                    \"Unable to create solr core:\\nStdout: \" + result.getStdout() + \"\\nStderr:\" + result.getStderr()\n                );\n            }\n            return;\n        }\n\n        if (StringUtils.isNotEmpty(configuration.getConfigurationName())) {\n            SolrClientUtils.uploadConfiguration(\n                getHost(),\n                getSolrPort(),\n                configuration.getConfigurationName(),\n                configuration.getSolrConfiguration(),\n                configuration.getSolrSchema()\n            );\n        }\n\n        SolrClientUtils.createCollection(\n            getHost(),\n            getSolrPort(),\n            configuration.getCollectionName(),\n            configuration.getConfigurationName()\n        );\n    }\n}\n"
  },
  {
    "path": "modules/solr/src/main/java/org/testcontainers/containers/SolrContainerConfiguration.java",
    "content": "package org.testcontainers.containers;\n\nimport lombok.Data;\n\nimport java.net.URL;\n\n@Data\npublic class SolrContainerConfiguration {\n\n    private boolean zookeeper = true;\n\n    private String collectionName = \"dummy\";\n\n    private String configurationName;\n\n    private URL solrConfiguration;\n\n    private URL solrSchema;\n}\n"
  },
  {
    "path": "modules/solr/src/main/java/org/testcontainers/solr/SolrContainer.java",
    "content": "package org.testcontainers.solr;\n\nimport com.github.dockerjava.api.command.InspectContainerResponse;\nimport lombok.SneakyThrows;\nimport org.apache.commons.lang3.StringUtils;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.SolrClientUtils;\nimport org.testcontainers.containers.SolrContainerConfiguration;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.utility.ComparableVersion;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.net.URL;\nimport java.time.Duration;\nimport java.util.HashSet;\nimport java.util.Set;\n\n/**\n * Testcontainers implementation for Solr.\n * <p>\n * Supported image: {@code solr}\n * <p>\n * Exposed ports:\n * <ul>\n *     <li>Solr: 8983</li>\n *     <li>Zookeeper: 9983</li>\n * </ul>\n */\npublic class SolrContainer extends GenericContainer<SolrContainer> {\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"solr\");\n\n    public static final Integer ZOOKEEPER_PORT = 9983;\n\n    public static final Integer SOLR_PORT = 8983;\n\n    private SolrContainerConfiguration configuration;\n\n    private final ComparableVersion imageVersion;\n\n    public SolrContainer(final String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    public SolrContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n\n        waitingFor(\n            Wait.forLogMessage(\".*o\\\\.e\\\\.j\\\\.s\\\\.Server Started.*\", 1).withStartupTimeout(Duration.ofMinutes(1))\n        );\n        this.configuration = new SolrContainerConfiguration();\n        this.imageVersion = new ComparableVersion(dockerImageName.getVersionPart());\n    }\n\n    public SolrContainer withZookeeper(boolean zookeeper) {\n        configuration.setZookeeper(zookeeper);\n        return self();\n    }\n\n    public SolrContainer withCollection(String collection) {\n        if (StringUtils.isEmpty(collection)) {\n            throw new IllegalArgumentException(\"Collection name must not be empty\");\n        }\n        configuration.setCollectionName(collection);\n        return self();\n    }\n\n    public SolrContainer withConfiguration(String name, URL solrConfig) {\n        if (StringUtils.isEmpty(name) || solrConfig == null) {\n            throw new IllegalArgumentException();\n        }\n        configuration.setConfigurationName(name);\n        configuration.setSolrConfiguration(solrConfig);\n        return self();\n    }\n\n    public SolrContainer withSchema(URL schema) {\n        configuration.setSolrSchema(schema);\n        return self();\n    }\n\n    public int getSolrPort() {\n        return getMappedPort(SOLR_PORT);\n    }\n\n    public int getZookeeperPort() {\n        return getMappedPort(ZOOKEEPER_PORT);\n    }\n\n    @Override\n    @SneakyThrows\n    protected void configure() {\n        if (configuration.getSolrSchema() != null && configuration.getSolrConfiguration() == null) {\n            throw new IllegalStateException(\"Solr needs to have a configuration if you want to use a schema\");\n        }\n        // Generate Command Builder\n        String command = \"solr start -f\";\n        // Add Default Ports\n        addExposedPort(SOLR_PORT);\n\n        // Configure Zookeeper\n        if (configuration.isZookeeper()) {\n            addExposedPort(ZOOKEEPER_PORT);\n            if (this.imageVersion.isGreaterThanOrEqualTo(\"9.7.0\")) {\n                command = \"-DzkRun --host localhost\";\n            } else {\n                command = \"-DzkRun -h localhost\";\n            }\n        }\n\n        // Apply generated Command\n        setCommand(command);\n    }\n\n    @Override\n    public Set<Integer> getLivenessCheckPortNumbers() {\n        return new HashSet<>(getSolrPort());\n    }\n\n    @Override\n    protected void waitUntilContainerStarted() {\n        getWaitStrategy().waitUntilReady(this);\n    }\n\n    @Override\n    @SneakyThrows\n    protected void containerIsStarted(InspectContainerResponse containerInfo) {\n        if (!configuration.isZookeeper()) {\n            ExecResult result = execInContainer(\"solr\", \"create\", \"-c\", configuration.getCollectionName());\n            if (result.getExitCode() != 0) {\n                throw new IllegalStateException(\n                    \"Unable to create solr core:\\nStdout: \" + result.getStdout() + \"\\nStderr:\" + result.getStderr()\n                );\n            }\n            return;\n        }\n\n        if (StringUtils.isNotEmpty(configuration.getConfigurationName())) {\n            SolrClientUtils.uploadConfiguration(\n                getHost(),\n                getSolrPort(),\n                configuration.getConfigurationName(),\n                configuration.getSolrConfiguration(),\n                configuration.getSolrSchema()\n            );\n        }\n\n        SolrClientUtils.createCollection(\n            getHost(),\n            getSolrPort(),\n            configuration.getCollectionName(),\n            configuration.getConfigurationName()\n        );\n    }\n}\n"
  },
  {
    "path": "modules/solr/src/test/java/org/testcontainers/solr/SolrContainerTest.java",
    "content": "package org.testcontainers.solr;\n\nimport org.apache.solr.client.solrj.SolrClient;\nimport org.apache.solr.client.solrj.SolrServerException;\nimport org.apache.solr.client.solrj.impl.Http2SolrClient;\nimport org.apache.solr.client.solrj.response.SolrPingResponse;\nimport org.junit.jupiter.api.AfterEach;\nimport org.junit.jupiter.params.ParameterizedTest;\nimport org.junit.jupiter.params.provider.MethodSource;\n\nimport java.io.IOException;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass SolrContainerTest {\n\n    private SolrClient client = null;\n\n    public static String[] getVersionsToTest() {\n        return new String[] { \"solr:8.11.4\", \"solr:9.8.0\" };\n    }\n\n    @AfterEach\n    void stopRestClient() throws IOException {\n        if (client != null) {\n            client.close();\n            client = null;\n        }\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"getVersionsToTest\")\n    void solrCloudTest(String solrImage) throws IOException, SolrServerException {\n        try (SolrContainer container = new SolrContainer(solrImage)) {\n            container.start();\n            SolrPingResponse response = getClient(container).ping(\"dummy\");\n            assertThat(response.getStatus()).isZero();\n            assertThat(response.jsonStr()).contains(\"zkConnected\\\":true\");\n        }\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"getVersionsToTest\")\n    void solrStandaloneTest(String solrImage) throws IOException, SolrServerException {\n        try (SolrContainer container = new SolrContainer(solrImage).withZookeeper(false)) {\n            container.start();\n            SolrPingResponse response = getClient(container).ping(\"dummy\");\n            assertThat(response.getStatus()).isZero();\n            assertThat(response.jsonStr()).contains(\"zkConnected\\\":null\");\n        }\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"getVersionsToTest\")\n    void solrCloudPingTest(String solrImage) throws IOException, SolrServerException {\n        // solrContainerUsage {\n        // Create the solr container.\n        SolrContainer container = new SolrContainer(solrImage);\n\n        // Start the container. This step might take some time...\n        container.start();\n\n        // Do whatever you want with the client ...\n        SolrClient client = new Http2SolrClient.Builder(\n            \"http://\" + container.getHost() + \":\" + container.getSolrPort() + \"/solr\"\n        )\n            .build();\n        SolrPingResponse response = client.ping(\"dummy\");\n\n        // Stop the container.\n        container.stop();\n        // }\n    }\n\n    private SolrClient getClient(SolrContainer container) {\n        if (client == null) {\n            client =\n                new Http2SolrClient.Builder(\"http://\" + container.getHost() + \":\" + container.getSolrPort() + \"/solr\")\n                    .build();\n        }\n        return client;\n    }\n}\n"
  },
  {
    "path": "modules/solr/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/spock/build.gradle",
    "content": "plugins {\n    id 'groovy'\n}\n\ndescription = \"Testcontainers :: Spock-Extension\"\n\ndependencies {\n    api project(':testcontainers')\n    implementation 'org.spockframework:spock-core:2.3-groovy-4.0'\n\n    testImplementation project(':testcontainers-selenium')\n    testImplementation project(':testcontainers-mysql')\n    testImplementation project(':testcontainers-postgresql')\n\n    testImplementation 'com.zaxxer:HikariCP:7.0.2'\n    testImplementation 'org.apache.httpcomponents:httpclient:4.5.14'\n\n    testRuntimeOnly 'org.postgresql:postgresql:42.7.8'\n    testRuntimeOnly 'com.mysql:mysql-connector-j:9.5.0'\n    testRuntimeOnly platform('org.junit:junit-bom:5.14.1')\n    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'\n    testRuntimeOnly 'org.junit.platform:junit-platform-testkit'\n\n    testCompileOnly 'org.jetbrains:annotations:26.0.2-1'\n}\n\ntasks.withType(GroovyCompile) {\n    sourceCompatibility = '1.8'\n    targetCompatibility = '1.8'\n    options.encoding = 'UTF-8'\n}\n\nsourceJar {\n    /* allJava is default (see gradle/publishing.gradle:sourceJar)\n       allSource contains both .java and .groovy files */\n    from sourceSets.main.allSource\n}\n\njavadocJar {\n    dependsOn groovydoc\n    archiveClassifier = 'javadoc'\n    from groovydoc.destinationDir\n}\n"
  },
  {
    "path": "modules/spock/src/main/groovy/org/testcontainers/spock/DockerAvailableDetector.groovy",
    "content": "package org.testcontainers.spock\n\nimport org.testcontainers.DockerClientFactory\n\nclass DockerAvailableDetector {\n\n\tboolean isDockerAvailable() {\n\t\ttry {\n\t\t\tDockerClientFactory.instance().client();\n\t\t\treturn true;\n\t\t} catch (Throwable ex) {\n\t\t\treturn false;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "modules/spock/src/main/groovy/org/testcontainers/spock/SpockTestDescription.groovy",
    "content": "package org.testcontainers.spock\n\nimport groovy.transform.PackageScope\nimport org.spockframework.runtime.extension.IMethodInvocation\nimport org.testcontainers.lifecycle.TestDescription\n\n/**\n * Spock specific implementation of a Testcontainers TestDescription.\n *\n * Filesystem friendly name is based on Specification and Feature.\n */\n@PackageScope\nclass SpockTestDescription implements TestDescription {\n\n\tString specName\n\tString featureName\n\n\tstatic SpockTestDescription fromTestDescription(IMethodInvocation invocation) {\n\t\treturn new SpockTestDescription([\n\t\t\tspecName: invocation.spec.name,\n\t\t\tfeatureName: invocation.feature.name\n\t\t])\n\t}\n\n\t@Override\n\tString getTestId() {\n\t\treturn getFilesystemFriendlyName()\n\t}\n\n\t@Override\n\tString getFilesystemFriendlyName() {\n\t\treturn [specName, featureName].collect {\n\t\t\tURLEncoder.encode(it, 'UTF-8')\n\t\t}.join('-')\n\t}\n}\n"
  },
  {
    "path": "modules/spock/src/main/groovy/org/testcontainers/spock/Testcontainers.groovy",
    "content": "package org.testcontainers.spock\n\nimport org.spockframework.runtime.extension.ExtensionAnnotation\n\nimport java.lang.annotation.ElementType\nimport java.lang.annotation.Inherited\nimport java.lang.annotation.Retention\nimport java.lang.annotation.RetentionPolicy\nimport java.lang.annotation.Target\n\n/**\n * {@code @Testcontainers} is a Spock extension to activate automatic\n * startup and stop of containers used in a test case.\n *\n * <p>The Testcontainers extension finds all fields that extend\n * {@link org.testcontainers.containers.GenericContainer} or\n * {@link org.testcontainers.containers.DockerComposeContainer} and calls their\n * container lifecycle methods. Containers annotated with {@link spock.lang.Shared}\n * will be shared between test methods. They will be\n * started only once before any test method is executed and stopped after the\n * last test method has executed. Containers without {@link spock.lang.Shared}\n * annotation will be started and stopped for every test method.</p>\n *\n * <p>The annotation {@code @Testcontainers} can be used on a superclass in\n * the test hierarchy as well. All subclasses will automatically inherit\n * support for the extension.</p>\n *\n * <p>Example:</p>\n *\n * <pre>\n * &#64;Testcontainers\n * class MyTestcontainersTests extends Specification {\n *\n *     // will be started only once in setupSpec() and stopped after last test method\n *     &#64;Shared\n *     MySQLContainer MY_SQL_CONTAINER = new MySQLContainer()\n *\n *     // will be started before and stopped after each test method\n *     PostgreSQLContainer postgresqlContainer = new PostgreSQLContainer()\n *             .withDatabaseName('foo')\n *             .withUsername('foo')\n *             .withPassword('secret')\n *\n *     def 'test'() {\n *         expect:\n *         MY_SQL_CONTAINER.running\n *         postgresqlContainer.running\n *     }\n * }\n * </pre>\n */\n@Inherited\n@Retention(RetentionPolicy.RUNTIME)\n@Target([ElementType.TYPE, ElementType.METHOD])\n@ExtensionAnnotation(TestcontainersExtension)\n@interface Testcontainers {\n\n\t/**\n\t * Whether tests should be disabled (rather than failing) when Docker is not available. Defaults to\n\t * {@code false}.\n\t * @return if the tests should be disabled when Docker is not available\n\t */\n\tboolean disabledWithoutDocker() default false;\n}\n"
  },
  {
    "path": "modules/spock/src/main/groovy/org/testcontainers/spock/TestcontainersExtension.groovy",
    "content": "package org.testcontainers.spock\n\nimport org.spockframework.runtime.AbstractRunListener\nimport org.spockframework.runtime.extension.AbstractAnnotationDrivenExtension\nimport org.spockframework.runtime.model.ErrorInfo\nimport org.spockframework.runtime.model.SpecInfo\n\nclass TestcontainersExtension extends AbstractAnnotationDrivenExtension<Testcontainers> {\n\n\tprivate final DockerAvailableDetector dockerDetector\n\n\tTestcontainersExtension() {\n\t\tthis(new DockerAvailableDetector())\n\t}\n\n\tTestcontainersExtension(DockerAvailableDetector dockerDetector) {\n\t\tthis.dockerDetector = dockerDetector\n\t}\n\n\t@Override\n\tvoid visitSpecAnnotation(Testcontainers annotation, SpecInfo spec) {\n\t\tif (annotation.disabledWithoutDocker()) {\n\t\t\tif (!dockerDetector.isDockerAvailable()) {\n\t\t\t\tspec.skip(\"disabledWithoutDocker is true and Docker is not available\")\n\t\t\t}\n\t\t}\n\t\tdef listener = new ErrorListener()\n\t\tdef interceptor = new TestcontainersMethodInterceptor(spec, listener)\n\t\tspec.addSetupSpecInterceptor(interceptor)\n\t\tspec.addCleanupSpecInterceptor(interceptor)\n\t\tspec.addSetupInterceptor(interceptor)\n\t\tspec.addCleanupInterceptor(interceptor)\n\n\t\tspec.addListener(listener)\n\t}\n\n\tprivate class ErrorListener extends AbstractRunListener {\n\t\tList<ErrorInfo> errors = []\n\n\t\t@Override\n\t\tvoid error(ErrorInfo error) {\n\t\t\terrors.add(error)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "modules/spock/src/main/groovy/org/testcontainers/spock/TestcontainersMethodInterceptor.groovy",
    "content": "package org.testcontainers.spock\n\nimport org.spockframework.runtime.extension.AbstractMethodInterceptor\nimport org.spockframework.runtime.extension.IMethodInvocation\nimport org.spockframework.runtime.model.FieldInfo\nimport org.spockframework.runtime.model.SpecInfo\nimport org.testcontainers.containers.ComposeContainer\nimport org.testcontainers.containers.DockerComposeContainer\nimport org.testcontainers.containers.GenericContainer\nimport org.testcontainers.lifecycle.TestLifecycleAware\nimport org.testcontainers.spock.TestcontainersExtension.ErrorListener\n\nclass TestcontainersMethodInterceptor extends AbstractMethodInterceptor {\n\n\tprivate final SpecInfo spec\n\tprivate final ErrorListener errorListener\n\n\tTestcontainersMethodInterceptor(SpecInfo spec, ErrorListener errorListener) {\n\t\tthis.spec = spec\n\t\tthis.errorListener = errorListener\n\t}\n\n\t@Override\n\tvoid interceptSetupSpecMethod(IMethodInvocation invocation) throws Throwable {\n\t\tdef containers = findAllContainers(true)\n\t\tstartContainers(containers, invocation)\n\n\t\tdef dockerCompose = findAllDockerComposeContainers(true)\n\t\tstartDockerComposeContainers(dockerCompose, invocation)\n\n\t\tdef compose = findAllComposeContainers(true)\n\t\tstartComposeContainers(compose, invocation)\n\n\t\tinvocation.proceed()\n\t}\n\n\t@Override\n\tvoid interceptCleanupSpecMethod(IMethodInvocation invocation) throws Throwable {\n\t\tdef containers = findAllContainers(true)\n\t\tstopContainers(containers, invocation)\n\n\t\tdef dockerCompose = findAllDockerComposeContainers(true)\n\t\tstopDockerComposeContainers(dockerCompose, invocation)\n\n\t\tdef compose = findAllComposeContainers(true)\n\t\tstopComposeContainers(compose, invocation)\n\n\t\tinvocation.proceed()\n\t}\n\n\t@Override\n\tvoid interceptSetupMethod(IMethodInvocation invocation) throws Throwable {\n\t\tdef containers = findAllContainers(false)\n\t\tstartContainers(containers, invocation)\n\n\t\tdef dockerCompose = findAllDockerComposeContainers(false)\n\t\tstartDockerComposeContainers(dockerCompose, invocation)\n\n\t\tdef compose = findAllComposeContainers(false)\n\t\tstartComposeContainers(compose, invocation)\n\n\t\tinvocation.proceed()\n\t}\n\n\n\t@Override\n\tvoid interceptCleanupMethod(IMethodInvocation invocation) throws Throwable {\n\t\tdef containers = findAllContainers(false)\n\t\tstopContainers(containers, invocation)\n\n\t\tdef dockerCompose = findAllDockerComposeContainers(false)\n\t\tstopDockerComposeContainers(dockerCompose, invocation)\n\n\t\tdef compose = findAllComposeContainers(false)\n\t\tstopComposeContainers(compose, invocation)\n\n\t\tinvocation.proceed()\n\t}\n\n\tprivate List<FieldInfo> findAllContainers(boolean shared) {\n\t\tspec.allFields.findAll { FieldInfo f ->\n\t\t\tGenericContainer.isAssignableFrom(f.type) && f.shared == shared\n\t\t}\n\t}\n\n\tprivate List<FieldInfo> findAllDockerComposeContainers(boolean shared) {\n\t\tspec.allFields.findAll { FieldInfo f ->\n\t\t\tDockerComposeContainer.isAssignableFrom(f.type) && f.shared == shared\n\t\t}\n\t}\n\n\tprivate List<FieldInfo> findAllComposeContainers(boolean shared) {\n\t\tspec.allFields.findAll { FieldInfo f ->\n\t\t\tComposeContainer.isAssignableFrom(f.type) && f.shared == shared\n\t\t}\n\t}\n\n\tprivate static void startContainers(List<FieldInfo> containers, IMethodInvocation invocation) {\n\t\tcontainers.each { FieldInfo f ->\n\t\t\tGenericContainer container = readContainerFromField(f, invocation)\n\t\t\tif(!container.isRunning()){\n\t\t\t\tcontainer.start()\n\t\t\t}\n\n\t\t\tif (container instanceof TestLifecycleAware) {\n\t\t\t\tdef testDescription = SpockTestDescription.fromTestDescription(invocation)\n\t\t\t\t(container as TestLifecycleAware).beforeTest(testDescription)\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate void stopContainers(List<FieldInfo> containers, IMethodInvocation invocation) {\n\t\tcontainers.each { FieldInfo f ->\n\t\t\tGenericContainer container = readContainerFromField(f, invocation)\n\n\t\t\tif (container instanceof TestLifecycleAware) {\n\t\t\t\t// we assume first error is the one we want\n\t\t\t\tdef maybeException = Optional.ofNullable(errorListener.errors[0]?.exception)\n\t\t\t\tdef testDescription = SpockTestDescription.fromTestDescription(invocation)\n\t\t\t\t(container as TestLifecycleAware).afterTest(testDescription, maybeException)\n\t\t\t}\n\n\t\t\tcontainer.stop()\n\t\t}\n\t}\n\n\tprivate static void startDockerComposeContainers(List<FieldInfo> compose, IMethodInvocation invocation) {\n\t\tcompose.each { FieldInfo f ->\n\t\t\tDockerComposeContainer c = f.readValue(invocation.instance) as DockerComposeContainer\n\t\t\tc.start()\n\t\t}\n\t}\n\n\tprivate static void startComposeContainers(List<FieldInfo> compose, IMethodInvocation invocation) {\n\t\tcompose.each { FieldInfo f ->\n\t\t\tComposeContainer c = f.readValue(invocation.instance) as ComposeContainer\n\t\t\tc.start()\n\t\t}\n\t}\n\n\tprivate static void stopDockerComposeContainers(List<FieldInfo> compose, IMethodInvocation invocation) {\n\t\tcompose.each { FieldInfo f ->\n\t\t\tDockerComposeContainer c = f.readValue(invocation.instance) as DockerComposeContainer\n\t\t\tc.stop()\n\t\t}\n\t}\n\n\tprivate static void stopComposeContainers(List<FieldInfo> compose, IMethodInvocation invocation) {\n\t\tcompose.each { FieldInfo f ->\n\t\t\tComposeContainer c = f.readValue(invocation.instance) as ComposeContainer\n\t\t\tc.stop()\n\t\t}\n\t}\n\n\n\tprivate static GenericContainer readContainerFromField(FieldInfo f, IMethodInvocation invocation) {\n\t\tf.readValue(invocation.instance) as GenericContainer\n\t}\n}\n"
  },
  {
    "path": "modules/spock/src/test/groovy/org/testcontainers/spock/ComposeContainerIT.groovy",
    "content": "package org.testcontainers.spock\n\nimport org.apache.http.client.methods.HttpGet\nimport org.apache.http.impl.client.HttpClientBuilder\nimport org.testcontainers.containers.ComposeContainer\nimport org.testcontainers.containers.wait.strategy.Wait\nimport org.testcontainers.utility.DockerImageName\nimport spock.lang.Specification\n\n@Testcontainers\nclass ComposeContainerIT extends Specification {\n\n\tComposeContainer composeContainer = new ComposeContainer(\n\tDockerImageName.parse(\"docker:25.0.5\"),\n\tnew File(\"src/test/resources/docker-compose.yml\"))\n\t.withExposedService(\"whoami-1\", 80, Wait.forHttp(\"/\"))\n\n\tString host\n\n\tint port\n\n\tdef setup() {\n\t\thost = composeContainer.getServiceHost(\"whoami-1\", 80)\n\t\tport = composeContainer.getServicePort(\"whoami-1\", 80)\n\t}\n\n\tdef \"running compose defined container is accessible on configured port\"() {\n\t\tgiven: \"a http client\"\n\t\tdef client = HttpClientBuilder.create().build()\n\n\t\twhen: \"accessing web server\"\n\t\tdef response = client.execute(new HttpGet(\"http://$host:$port\"))\n\n\t\tthen: \"docker container is running and returns http status code 200\"\n\t\tresponse.statusLine.statusCode == 200\n\t}\n}\n"
  },
  {
    "path": "modules/spock/src/test/groovy/org/testcontainers/spock/DockerComposeContainerIT.groovy",
    "content": "package org.testcontainers.spock\n\nimport org.apache.http.client.methods.HttpGet\nimport org.apache.http.impl.client.HttpClientBuilder\nimport org.testcontainers.containers.DockerComposeContainer\nimport org.testcontainers.containers.wait.strategy.Wait\nimport org.testcontainers.utility.DockerImageName\nimport spock.lang.Specification\n\n@Testcontainers\nclass DockerComposeContainerIT extends Specification {\n\n\tDockerComposeContainer composeContainer = new DockerComposeContainer(\n\tDockerImageName.parse(\"docker/compose:debian-1.29.2\"),\n\tnew File(\"src/test/resources/docker-compose.yml\"))\n\t.withExposedService(\"whoami_1\", 80, Wait.forHttp(\"/\"))\n\n\tString host\n\n\tint port\n\n\tdef setup() {\n\t\thost = composeContainer.getServiceHost(\"whoami_1\", 80)\n\t\tport = composeContainer.getServicePort(\"whoami_1\", 80)\n\t}\n\n\tdef \"running compose defined container is accessible on configured port\"() {\n\t\tgiven: \"a http client\"\n\t\tdef client = HttpClientBuilder.create().build()\n\n\t\twhen: \"accessing web server\"\n\t\tdef response = client.execute(new HttpGet(\"http://$host:$port\"))\n\n\t\tthen: \"docker container is running and returns http status code 200\"\n\t\tresponse.statusLine.statusCode == 200\n\t}\n}\n"
  },
  {
    "path": "modules/spock/src/test/groovy/org/testcontainers/spock/MySqlContainerIT.groovy",
    "content": "package org.testcontainers.spock\n\nimport org.testcontainers.containers.MySQLContainer\nimport spock.lang.Shared\nimport spock.lang.Specification\n\n/**\n * This test verifies, that setup and cleanup of containers works correctly.\n * It's easily achieved using the <code>MySQLContainer</code>, since it will fail\n * if the same image is running.\n *\n * @see <a href=\"https://github.com/testcontainers/testcontainers-spock/issues/19\">Second container is started when stopping old container</a>\n */\n@Testcontainers\nclass MySqlContainerIT extends Specification {\n\n\t@Shared\n\tMySQLContainer mySQLContainer = new MySQLContainer(SpockTestImages.MYSQL_IMAGE)\n\n\tdef \"dummy test\"() {\n\t\texpect:\n\t\tmySQLContainer.isRunning()\n\t}\n}\n"
  },
  {
    "path": "modules/spock/src/test/groovy/org/testcontainers/spock/PostgresContainerIT.groovy",
    "content": "package org.testcontainers.spock\n\nimport com.zaxxer.hikari.HikariConfig\nimport com.zaxxer.hikari.HikariDataSource\nimport org.testcontainers.containers.PostgreSQLContainer\nimport spock.lang.Shared\nimport spock.lang.Specification\n\nimport java.sql.ResultSet\nimport java.sql.Statement\n\n// PostgresContainerIT {\n@Testcontainers\nclass PostgresContainerIT extends Specification {\n\n\t@Shared\n\tPostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer(SpockTestImages.POSTGRES_TEST_IMAGE)\n\t.withDatabaseName(\"foo\")\n\t.withUsername(\"foo\")\n\t.withPassword(\"secret\")\n\n\tdef \"waits until postgres accepts jdbc connections\"() {\n\n\t\tgiven: \"a jdbc connection\"\n\t\tHikariConfig hikariConfig = new HikariConfig()\n\t\thikariConfig.setJdbcUrl(postgreSQLContainer.jdbcUrl)\n\t\thikariConfig.setUsername(\"foo\")\n\t\thikariConfig.setPassword(\"secret\")\n\t\tHikariDataSource ds = new HikariDataSource(hikariConfig)\n\n\t\twhen: \"querying the database\"\n\t\tStatement statement = ds.getConnection().createStatement()\n\t\tstatement.execute(\"SELECT 1\")\n\t\tResultSet resultSet = statement.getResultSet()\n\t\tresultSet.next()\n\n\t\tthen: \"result is returned\"\n\t\tint resultSetInt = resultSet.getInt(1)\n\t\tresultSetInt == 1\n\n\t\tcleanup:\n\t\tds.close()\n\t}\n}\n// }\n"
  },
  {
    "path": "modules/spock/src/test/groovy/org/testcontainers/spock/SharedComposeContainerIT.groovy",
    "content": "package org.testcontainers.spock\n\nimport org.apache.http.client.methods.HttpGet\nimport org.apache.http.impl.client.HttpClientBuilder\nimport org.testcontainers.containers.ComposeContainer\nimport org.testcontainers.containers.wait.strategy.Wait\nimport org.testcontainers.utility.DockerImageName\nimport spock.lang.Shared\nimport spock.lang.Specification\n\n@Testcontainers\nclass SharedComposeContainerIT extends Specification {\n\n\t@Shared\n\tComposeContainer composeContainer = new ComposeContainer(\n\tDockerImageName.parse(\"docker:25.0.5\"),\n\tnew File(\"src/test/resources/docker-compose.yml\"))\n\t.withExposedService(\"whoami-1\", 80, Wait.forHttp(\"/\"))\n\n\tString host\n\n\tint port\n\n\tdef setup() {\n\t\thost = composeContainer.getServiceHost(\"whoami-1\", 80)\n\t\tport = composeContainer.getServicePort(\"whoami-1\", 80)\n\t}\n\n\tdef \"running compose defined container is accessible on configured port\"() {\n\t\tgiven: \"a http client\"\n\t\tdef client = HttpClientBuilder.create().build()\n\n\t\twhen: \"accessing web server\"\n\t\tdef response = client.execute(new HttpGet(\"http://$host:$port\"))\n\n\t\tthen: \"docker container is running and returns http status code 200\"\n\t\tresponse.statusLine.statusCode == 200\n\t}\n}\n"
  },
  {
    "path": "modules/spock/src/test/groovy/org/testcontainers/spock/SharedDockerComposeContainerIT.groovy",
    "content": "package org.testcontainers.spock\n\nimport org.apache.http.client.methods.HttpGet\nimport org.apache.http.impl.client.HttpClientBuilder\nimport org.testcontainers.containers.DockerComposeContainer\nimport org.testcontainers.containers.wait.strategy.Wait\nimport org.testcontainers.utility.DockerImageName\nimport spock.lang.Shared\nimport spock.lang.Specification\n\n@Testcontainers\nclass SharedDockerComposeContainerIT extends Specification {\n\n\t@Shared\n\tDockerComposeContainer composeContainer = new DockerComposeContainer(\n\tDockerImageName.parse(\"docker/compose:debian-1.29.2\"),\n\tnew File(\"src/test/resources/docker-compose.yml\"))\n\t.withExposedService(\"whoami_1\", 80, Wait.forHttp(\"/\"))\n\n\tString host\n\n\tint port\n\n\tdef setup() {\n\t\thost = composeContainer.getServiceHost(\"whoami_1\", 80)\n\t\tport = composeContainer.getServicePort(\"whoami_1\", 80)\n\t}\n\n\tdef \"running compose defined container is accessible on configured port\"() {\n\t\tgiven: \"a http client\"\n\t\tdef client = HttpClientBuilder.create().build()\n\n\t\twhen: \"accessing web server\"\n\t\tdef response = client.execute(new HttpGet(\"http://$host:$port\"))\n\n\t\tthen: \"docker container is running and returns http status code 200\"\n\t\tresponse.statusLine.statusCode == 200\n\t}\n}\n"
  },
  {
    "path": "modules/spock/src/test/groovy/org/testcontainers/spock/SpockTestImages.groovy",
    "content": "package org.testcontainers.spock\n\nimport org.testcontainers.utility.DockerImageName\n\ninterface SpockTestImages {\n\tDockerImageName MYSQL_IMAGE = DockerImageName.parse(\"mysql:8.0.36\")\n\tDockerImageName POSTGRES_TEST_IMAGE = DockerImageName.parse(\"postgres:9.6.12\")\n\tDockerImageName HTTPD_IMAGE = DockerImageName.parse(\"httpd:2.4-alpine\")\n\tDockerImageName TINY_IMAGE = DockerImageName.parse(\"alpine:3.17\")\n}\n"
  },
  {
    "path": "modules/spock/src/test/groovy/org/testcontainers/spock/TestHierarchyIT.groovy",
    "content": "package org.testcontainers.spock\n\nimport org.testcontainers.containers.PostgreSQLContainer\nimport spock.lang.Shared\n\n/**\n * This test verifies that integration tests can subclass each other.\n * Also verifies that the @Testcontainers annotation is inherited.\n */\nclass TestHierarchyIT extends MySqlContainerIT {\n\n\t@Shared\n\tPostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer(SpockTestImages.POSTGRES_TEST_IMAGE)\n\t.withDatabaseName(\"foo\")\n\t.withUsername(\"foo\")\n\t.withPassword(\"secret\")\n\n\tdef \"both containers are running\"() {\n\t\texpect:\n\t\tpostgreSQLContainer.isRunning()\n\t\tmySQLContainer.isRunning()\n\t}\n}\n"
  },
  {
    "path": "modules/spock/src/test/groovy/org/testcontainers/spock/TestLifecycleAwareContainerMock.java",
    "content": "package org.testcontainers.spock;\n\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.lifecycle.TestDescription;\nimport org.testcontainers.lifecycle.TestLifecycleAware;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Optional;\n\npublic class TestLifecycleAwareContainerMock\n    extends GenericContainer<TestLifecycleAwareContainerMock>\n    implements TestLifecycleAware {\n\n    static final String BEFORE_TEST = \"beforeTest\";\n\n    static final String AFTER_TEST = \"afterTest\";\n\n    final List<String> lifecycleMethodCalls = new ArrayList<>();\n\n    final List<String> lifecycleFilesystemFriendlyNames = new ArrayList<>();\n\n    Throwable capturedThrowable;\n\n    public TestLifecycleAwareContainerMock() {\n        super(SpockTestImages.TINY_IMAGE);\n    }\n\n    @Override\n    public void beforeTest(TestDescription description) {\n        lifecycleMethodCalls.add(BEFORE_TEST);\n        lifecycleFilesystemFriendlyNames.add(description.getFilesystemFriendlyName());\n    }\n\n    @Override\n    public void afterTest(TestDescription description, Optional<Throwable> throwable) {\n        lifecycleMethodCalls.add(AFTER_TEST);\n        throwable.ifPresent(capturedThrowable -> this.capturedThrowable = capturedThrowable);\n    }\n\n    @Override\n    public void start() {\n        // Do nothing\n    }\n\n    @Override\n    public void stop() {\n        // Do nothing\n    }\n}\n"
  },
  {
    "path": "modules/spock/src/test/groovy/org/testcontainers/spock/TestLifecycleAwareIT.groovy",
    "content": "package org.testcontainers.spock\n\nimport org.intellij.lang.annotations.Language\nimport spock.lang.Specification\nimport spock.lang.Unroll\nimport spock.util.EmbeddedSpecRunner\n\nclass TestLifecycleAwareIT extends Specification {\n\n\t@Unroll(\"When failing test is #fails, afterTest receives '#filesystemFriendlyNames' and throwable starting with message '#errorMessageStartsWith'\")\n\tdef \"lifecycle awareness\"() {\n\t\tgiven:\n\n\t\t@Language(\"groovy\")\n\t\t\t\tString myTest = \"\"\"\nimport org.testcontainers.spock.Testcontainers\nimport org.testcontainers.containers.GenericContainer\nimport spock.lang.Specification\n\n@Testcontainers\nclass TestLifecycleAwareIT extends Specification {\n\n    GenericContainer container = System.properties[\"org.testcontainers.container\"] as GenericContainer\n\n    def \"perform test\"() {\n        expect:\n        !System.properties[\"org.testcontainers.shouldFail\"]\n    }\n}\n\"\"\"\n\t\tand:\n\t\tdef container = new TestLifecycleAwareContainerMock()\n\t\tSystem.properties[\"org.testcontainers.container\"] = container\n\t\tSystem.properties[\"org.testcontainers.shouldFail\"] = fails\n\n\t\twhen: \"executing the test\"\n\t\tdef runner = new EmbeddedSpecRunner(throwFailure: false)\n\t\trunner.run(myTest)\n\n\t\tthen: \"mock container received lifecycle calls as expected\"\n\t\tcontainer.lifecycleMethodCalls == [\"beforeTest\", \"afterTest\"]\n\t\tcontainer.lifecycleFilesystemFriendlyNames.join(\",\") == filesystemFriendlyNames\n\t\tif (errorMessageStartsWith) {\n\t\t\tassert container.capturedThrowable.message.startsWith(errorMessageStartsWith)\n\t\t} else {\n\t\t\tassert container.capturedThrowable == null\n\t\t}\n\n\t\twhere:\n\t\tfails | filesystemFriendlyNames             | errorMessageStartsWith\n\t\tfalse | 'TestLifecycleAwareIT-perform+test' | null\n\t\ttrue  | 'TestLifecycleAwareIT-perform+test' | \"Condition not satisfied:\"\n\t}\n}\n"
  },
  {
    "path": "modules/spock/src/test/groovy/org/testcontainers/spock/TestcontainersExtensionTest.groovy",
    "content": "package org.testcontainers.spock\n\nimport org.spockframework.runtime.model.SpecInfo\nimport spock.lang.Specification\nimport spock.lang.Unroll\n\nclass TestcontainersExtensionTest extends Specification {\n\n\t@Unroll\n\tdef \"should handle disabledWithoutDocker=#disabledWithoutDocker and dockerAvailable=#dockerAvailable correctly\"() {\n\t\tgiven:\n\t\tdef dockerDetector = Mock(DockerAvailableDetector)\n\t\tdockerDetector.isDockerAvailable() >> dockerAvailable\n\t\tdef extension = new TestcontainersExtension(dockerDetector)\n\t\tdef specInfo = Mock(SpecInfo)\n\t\tdef annotation = disabledWithoutDocker ?\n\t\t\t\tTestDisabledWithoutDocker.getAnnotation(Testcontainers) :\n\t\t\t\tTestEnabledWithoutDocker.getAnnotation(Testcontainers)\n\n\t\twhen:\n\t\textension.visitSpecAnnotation(annotation, specInfo)\n\n\t\tthen:\n\t\tskipCalls * specInfo.skip(\"disabledWithoutDocker is true and Docker is not available\")\n\n\t\twhere:\n\t\tdisabledWithoutDocker | dockerAvailable | skipCalls\n\t\ttrue                  | true            | 0\n\t\ttrue                  | false           | 1\n\t\tfalse                 | true            | 0\n\t\tfalse                 | false           | 0\n\t}\n\n\t@Testcontainers(disabledWithoutDocker = true)\n\tstatic class TestDisabledWithoutDocker {}\n\n\t@Testcontainers\n\tstatic class TestEnabledWithoutDocker {}\n}\n"
  },
  {
    "path": "modules/spock/src/test/groovy/org/testcontainers/spock/TestcontainersRestartBetweenTestsIT.groovy",
    "content": "package org.testcontainers.spock\n\nimport org.testcontainers.containers.GenericContainer\nimport spock.lang.Shared\nimport spock.lang.Specification\nimport spock.lang.Stepwise\n\n@Stepwise\n@Testcontainers\nclass TestcontainersRestartBetweenTestsIT extends Specification {\n\n\tGenericContainer genericContainer = new GenericContainer(SpockTestImages.HTTPD_IMAGE)\n\t.withExposedPorts(80)\n\n\t@Shared\n\tString lastContainerId\n\n\tdef \"retrieving first id\"() {\n\t\twhen:\n\t\tlastContainerId = genericContainer.containerId\n\n\t\tthen:\n\t\ttrue\n\t}\n\n\tdef \"containers is recreated between tests\"() {\n\t\texpect:\n\t\tgenericContainer.containerId != lastContainerId\n\t}\n}\n"
  },
  {
    "path": "modules/spock/src/test/groovy/org/testcontainers/spock/TestcontainersSharedContainerIT.groovy",
    "content": "package org.testcontainers.spock\n\nimport org.apache.http.client.methods.CloseableHttpResponse\nimport org.apache.http.client.methods.HttpGet\nimport org.apache.http.impl.client.CloseableHttpClient\nimport org.apache.http.impl.client.HttpClientBuilder\nimport org.testcontainers.containers.GenericContainer\nimport spock.lang.Shared\nimport spock.lang.Specification\nimport spock.lang.Stepwise\n\n@Stepwise\n@Testcontainers\nclass TestcontainersSharedContainerIT extends Specification {\n\n\t@Shared\n\tGenericContainer genericContainer = new GenericContainer(SpockTestImages.HTTPD_IMAGE)\n\t.withExposedPorts(80)\n\n\t@Shared\n\tString lastContainerId\n\n\tdef \"starts accessible docker container\"() {\n\t\tgiven: \"a http client\"\n\t\tdef client = HttpClientBuilder.create().build()\n\t\tlastContainerId = genericContainer.containerId\n\n\t\twhen: \"accessing web server\"\n\t\tCloseableHttpResponse response = performHttpRequest(client)\n\n\t\tthen: \"docker container is running and returns http status code 200\"\n\t\tresponse.statusLine.statusCode == 200\n\t}\n\n\tdef \"containers keeps on running between features\"() {\n\t\texpect:\n\t\tgenericContainer.containerId == lastContainerId\n\t}\n\n\tprivate CloseableHttpResponse performHttpRequest(CloseableHttpClient client) {\n\t\tString ip = genericContainer.host\n\t\tString port = genericContainer.getMappedPort(80)\n\t\tdef response = client.execute(new HttpGet(\"http://$ip:$port\"))\n\t\tresponse\n\t}\n}\n"
  },
  {
    "path": "modules/spock/src/test/resources/docker-compose.yml",
    "content": "version: '2.1'\nservices:\n  whoami:\n    image: emilevauge/whoami\n"
  },
  {
    "path": "modules/spock/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/tidb/build.gradle",
    "content": "description = \"Testcontainers :: JDBC :: TiDB\"\n\ndependencies {\n    api project(':testcontainers-jdbc')\n\n    testImplementation project(':testcontainers-jdbc-test')\n    testRuntimeOnly 'com.mysql:mysql-connector-j:9.5.0'\n    compileOnly 'org.jetbrains:annotations:26.0.2-1'\n}\n"
  },
  {
    "path": "modules/tidb/sql/init_mysql.sql",
    "content": "CREATE TABLE bar (\n  foo VARCHAR(255)\n);\n\nINSERT INTO bar (foo) VALUES ('hello world');"
  },
  {
    "path": "modules/tidb/src/main/java/org/testcontainers/tidb/TiDBContainer.java",
    "content": "package org.testcontainers.tidb;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.testcontainers.containers.JdbcDatabaseContainer;\nimport org.testcontainers.containers.wait.strategy.HttpWaitStrategy;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.time.Duration;\nimport java.util.Set;\n\n/**\n * Testcontainers implementation for TiDB.\n * <p>\n * Supported image: {@code pingcap/tidb}\n * <p>\n * Exposed ports:\n * <ul>\n *     <li>Database: 4000</li>\n *     <li>HTTP: 10080</li>\n * </ul>\n */\npublic class TiDBContainer extends JdbcDatabaseContainer<TiDBContainer> {\n\n    static final String NAME = \"tidb\";\n\n    static final String DOCKER_IMAGE_NAME = \"pingcap/tidb\";\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(DOCKER_IMAGE_NAME);\n\n    private static final Integer TIDB_PORT = 4000;\n\n    private static final int REST_API_PORT = 10080;\n\n    private String databaseName = \"test\";\n\n    private String username = \"root\";\n\n    private String password = \"\";\n\n    public TiDBContainer(String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    public TiDBContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n\n        addExposedPorts(TIDB_PORT, REST_API_PORT);\n\n        waitingFor(\n            new HttpWaitStrategy()\n                .forPath(\"/status\")\n                .forPort(REST_API_PORT)\n                .forStatusCode(200)\n                .withStartupTimeout(Duration.ofMinutes(1))\n        );\n    }\n\n    /**\n     * @return the ports on which to check if the container is ready\n     * @deprecated use {@link #getLivenessCheckPortNumbers()} instead\n     */\n    @NotNull\n    @Override\n    @Deprecated\n    protected Set<Integer> getLivenessCheckPorts() {\n        return super.getLivenessCheckPorts();\n    }\n\n    @Override\n    public String getDriverClassName() {\n        try {\n            Class.forName(\"com.mysql.cj.jdbc.Driver\");\n            return \"com.mysql.cj.jdbc.Driver\";\n        } catch (ClassNotFoundException e) {\n            return \"com.mysql.jdbc.Driver\";\n        }\n    }\n\n    @Override\n    public String getJdbcUrl() {\n        String additionalUrlParams = constructUrlParameters(\"?\", \"&\");\n        return \"jdbc:mysql://\" + getHost() + \":\" + getMappedPort(TIDB_PORT) + \"/\" + databaseName + additionalUrlParams;\n    }\n\n    @Override\n    protected String constructUrlForConnection(String queryString) {\n        String url = super.constructUrlForConnection(queryString);\n\n        if (!url.contains(\"useSSL=\")) {\n            String separator = url.contains(\"?\") ? \"&\" : \"?\";\n            url = url + separator + \"useSSL=false\";\n        }\n\n        if (!url.contains(\"allowPublicKeyRetrieval=\")) {\n            url = url + \"&allowPublicKeyRetrieval=true\";\n        }\n\n        return url;\n    }\n\n    @Override\n    public String getDatabaseName() {\n        return databaseName;\n    }\n\n    @Override\n    public String getUsername() {\n        return username;\n    }\n\n    @Override\n    public String getPassword() {\n        return password;\n    }\n\n    @Override\n    public String getTestQueryString() {\n        return \"SELECT 1\";\n    }\n\n    @Override\n    public TiDBContainer withDatabaseName(final String databaseName) {\n        throw new UnsupportedOperationException(\"The TiDB docker image does not currently support this\");\n    }\n\n    @Override\n    public TiDBContainer withUsername(final String username) {\n        throw new UnsupportedOperationException(\"The TiDB docker image does not currently support this\");\n    }\n\n    @Override\n    public TiDBContainer withPassword(final String password) {\n        throw new UnsupportedOperationException(\"The TiDB docker image does not currently support this\");\n    }\n}\n"
  },
  {
    "path": "modules/tidb/src/main/java/org/testcontainers/tidb/TiDBContainerProvider.java",
    "content": "package org.testcontainers.tidb;\n\nimport org.testcontainers.containers.JdbcDatabaseContainer;\nimport org.testcontainers.containers.JdbcDatabaseContainerProvider;\nimport org.testcontainers.utility.DockerImageName;\n\n/**\n * Factory for TiDB containers.\n */\npublic class TiDBContainerProvider extends JdbcDatabaseContainerProvider {\n\n    private static final String DEFAULT_TAG = \"v6.1.0\";\n\n    @Override\n    public boolean supports(String databaseType) {\n        return databaseType.equals(TiDBContainer.NAME);\n    }\n\n    @Override\n    public JdbcDatabaseContainer newInstance() {\n        return newInstance(DEFAULT_TAG);\n    }\n\n    @Override\n    public JdbcDatabaseContainer newInstance(String tag) {\n        if (tag != null) {\n            return new TiDBContainer(DockerImageName.parse(TiDBContainer.DOCKER_IMAGE_NAME).withTag(tag));\n        } else {\n            return newInstance();\n        }\n    }\n}\n"
  },
  {
    "path": "modules/tidb/src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider",
    "content": "org.testcontainers.tidb.TiDBContainerProvider\n"
  },
  {
    "path": "modules/tidb/src/test/java/org/testcontainers/TiDBTestImages.java",
    "content": "package org.testcontainers;\n\nimport org.testcontainers.utility.DockerImageName;\n\npublic class TiDBTestImages {\n\n    public static final DockerImageName TIDB_IMAGE = DockerImageName.parse(\"pingcap/tidb:v6.1.0\");\n}\n"
  },
  {
    "path": "modules/tidb/src/test/java/org/testcontainers/jdbc/tidb/TiDBJDBCDriverTest.java",
    "content": "package org.testcontainers.jdbc.tidb;\n\nimport org.testcontainers.jdbc.AbstractJDBCDriverTest;\n\nimport java.util.Arrays;\nimport java.util.EnumSet;\n\npublic class TiDBJDBCDriverTest extends AbstractJDBCDriverTest {\n\n    public static Iterable<Object[]> data() {\n        return Arrays.asList(\n            new Object[][] { { \"jdbc:tc:tidb://hostname/databasename\", EnumSet.noneOf(Options.class) } }\n        );\n    }\n}\n"
  },
  {
    "path": "modules/tidb/src/test/java/org/testcontainers/tidb/TiDBContainerTest.java",
    "content": "package org.testcontainers.tidb;\n\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.TiDBTestImages;\nimport org.testcontainers.db.AbstractContainerDatabaseTest;\n\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass TiDBContainerTest extends AbstractContainerDatabaseTest {\n\n    @Test\n    void testSimple() throws SQLException {\n        try ( // container {\n            TiDBContainer tidb = new TiDBContainer(\"pingcap/tidb:v6.1.0\")\n            // }\n        ) {\n            tidb.start();\n\n            ResultSet resultSet = performQuery(tidb, \"SELECT 1\");\n\n            int resultSetInt = resultSet.getInt(1);\n            assertThat(resultSetInt).isEqualTo(1);\n            assertHasCorrectExposedAndLivenessCheckPorts(tidb);\n        }\n    }\n\n    @Test\n    void testExplicitInitScript() throws SQLException {\n        try (\n            TiDBContainer tidb = new TiDBContainer(TiDBTestImages.TIDB_IMAGE).withInitScript(\"somepath/init_tidb.sql\")\n        ) { // TiDB is expected to be compatible with MySQL\n            tidb.start();\n\n            ResultSet resultSet = performQuery(tidb, \"SELECT foo FROM bar\");\n\n            String firstColumnValue = resultSet.getString(1);\n            assertThat(firstColumnValue).isEqualTo(\"hello world\");\n        }\n    }\n\n    @Test\n    void testWithAdditionalUrlParamInJdbcUrl() {\n        TiDBContainer tidb = new TiDBContainer(TiDBTestImages.TIDB_IMAGE).withUrlParam(\"sslmode\", \"disable\");\n\n        try {\n            tidb.start();\n            String jdbcUrl = tidb.getJdbcUrl();\n            assertThat(jdbcUrl).contains(\"?\");\n            assertThat(jdbcUrl).contains(\"sslmode=disable\");\n        } finally {\n            tidb.stop();\n        }\n    }\n\n    private void assertHasCorrectExposedAndLivenessCheckPorts(TiDBContainer tidb) {\n        Integer tidbPort = 4000;\n        Integer restApiPort = 10080;\n\n        assertThat(tidb.getExposedPorts()).containsExactlyInAnyOrder(tidbPort, restApiPort);\n        assertThat(tidb.getLivenessCheckPortNumbers())\n            .containsExactlyInAnyOrder(tidb.getMappedPort(tidbPort), tidb.getMappedPort(restApiPort));\n    }\n}\n"
  },
  {
    "path": "modules/tidb/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/tidb/src/test/resources/somepath/init_tidb.sql",
    "content": "CREATE TABLE bar (\n  foo VARCHAR(255)\n);\n\nSELECT \"a /* string literal containing comment characters like -- here\";\nSELECT \"a 'quoting' \\\"scenario ` involving BEGIN keyword\\\" here\";\nSELECT * from `bar`;\n\n-- What about a line comment containing imbalanced string delimiters? \"\n\n/* or a block comment\n    containing imbalanced string delimiters?\n    ' \"\n    */\n\nINSERT INTO bar (foo) /* ; */ VALUES ('hello world');\n"
  },
  {
    "path": "modules/timeplus/build.gradle",
    "content": "description = \"Testcontainers :: JDBC :: Timeplus\"\n\ndependencies {\n    api project(':testcontainers')\n    api project(':testcontainers-jdbc')\n\n    testImplementation project(':testcontainers-jdbc-test')\n    testRuntimeOnly 'com.timeplus:timeplus-native-jdbc:2.0.10'\n}\n"
  },
  {
    "path": "modules/timeplus/src/main/java/org/testcontainers/timeplus/TimeplusContainer.java",
    "content": "package org.testcontainers.timeplus;\n\nimport org.testcontainers.containers.JdbcDatabaseContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.time.Duration;\nimport java.util.HashSet;\nimport java.util.Set;\n\n/**\n * Testcontainers implementation for Timeplus.\n * <p>\n * Supported image: {@code timeplus/timeplusd}\n * <p>\n * Exposed ports:\n * <ul>\n *     <li>Database: 8463</li>\n *     <li>HTTP: 3218</li>\n * </ul>\n */\npublic class TimeplusContainer extends JdbcDatabaseContainer<TimeplusContainer> {\n\n    static final String NAME = \"timeplus\";\n\n    static final String DOCKER_IMAGE_NAME = \"timeplus/timeplusd\";\n\n    private static final DockerImageName TIMEPLUS_IMAGE_NAME = DockerImageName.parse(DOCKER_IMAGE_NAME);\n\n    private static final Integer HTTP_PORT = 3218;\n\n    private static final Integer NATIVE_PORT = 8463;\n\n    private static final String DRIVER_CLASS_NAME = \"com.timeplus.jdbc.TimeplusDriver\";\n\n    private static final String JDBC_URL_PREFIX = \"jdbc:\" + NAME + \"://\";\n\n    private static final String TEST_QUERY = \"SELECT 1\";\n\n    private String databaseName = \"default\";\n\n    private String username = \"default\";\n\n    private String password = \"\";\n\n    public TimeplusContainer(String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    public TimeplusContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(TIMEPLUS_IMAGE_NAME);\n\n        addExposedPorts(HTTP_PORT, NATIVE_PORT);\n        waitingFor(Wait.forHttp(\"/timeplusd/v1/ping\").forStatusCode(200).withStartupTimeout(Duration.ofMinutes(1)));\n    }\n\n    @Override\n    protected void configure() {\n        withEnv(\"TIMEPLUS_DB\", this.databaseName);\n        withEnv(\"TIMEPLUS_USER\", this.username);\n        withEnv(\"TIMEPLUS_PASSWORD\", this.password);\n    }\n\n    @Override\n    public Set<Integer> getLivenessCheckPortNumbers() {\n        return new HashSet<>(getMappedPort(HTTP_PORT));\n    }\n\n    @Override\n    public String getDriverClassName() {\n        return DRIVER_CLASS_NAME;\n    }\n\n    @Override\n    public String getJdbcUrl() {\n        return (\n            JDBC_URL_PREFIX +\n            getHost() +\n            \":\" +\n            getMappedPort(NATIVE_PORT) +\n            \"/\" +\n            this.databaseName +\n            constructUrlParameters(\"?\", \"&\")\n        );\n    }\n\n    @Override\n    public String getUsername() {\n        return this.username;\n    }\n\n    @Override\n    public String getPassword() {\n        return this.password;\n    }\n\n    @Override\n    public String getDatabaseName() {\n        return this.databaseName;\n    }\n\n    @Override\n    public String getTestQueryString() {\n        return TEST_QUERY;\n    }\n\n    @Override\n    public TimeplusContainer withUsername(String username) {\n        this.username = username;\n        return this;\n    }\n\n    @Override\n    public TimeplusContainer withPassword(String password) {\n        this.password = password;\n        return this;\n    }\n\n    @Override\n    public TimeplusContainer withDatabaseName(String databaseName) {\n        this.databaseName = databaseName;\n        return this;\n    }\n}\n"
  },
  {
    "path": "modules/timeplus/src/main/java/org/testcontainers/timeplus/TimeplusContainerProvider.java",
    "content": "package org.testcontainers.timeplus;\n\nimport org.testcontainers.containers.JdbcDatabaseContainer;\nimport org.testcontainers.containers.JdbcDatabaseContainerProvider;\nimport org.testcontainers.utility.DockerImageName;\n\n/**\n * Factory for Timeplus containers.\n */\npublic class TimeplusContainerProvider extends JdbcDatabaseContainerProvider {\n\n    private static final String DEFAULT_TAG = \"2.3.21\";\n\n    @Override\n    public boolean supports(String databaseType) {\n        return databaseType.equals(TimeplusContainer.NAME);\n    }\n\n    @Override\n    public JdbcDatabaseContainer newInstance() {\n        return newInstance(DEFAULT_TAG);\n    }\n\n    @Override\n    public JdbcDatabaseContainer newInstance(String tag) {\n        if (tag != null) {\n            return new TimeplusContainer(DockerImageName.parse(TimeplusContainer.DOCKER_IMAGE_NAME).withTag(tag));\n        } else {\n            return newInstance();\n        }\n    }\n}\n"
  },
  {
    "path": "modules/timeplus/src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider",
    "content": "org.testcontainers.timeplus.TimeplusContainerProvider\n"
  },
  {
    "path": "modules/timeplus/src/test/java/org/testcontainers/TimeplusImages.java",
    "content": "package org.testcontainers;\n\nimport org.testcontainers.utility.DockerImageName;\n\npublic interface TimeplusImages {\n    DockerImageName TIMEPLUS_IMAGE = DockerImageName.parse(\"timeplus/timeplusd:2.3.21\");\n}\n"
  },
  {
    "path": "modules/timeplus/src/test/java/org/testcontainers/junit/timeplus/TimeplusJDBCDriverTest.java",
    "content": "package org.testcontainers.junit.timeplus;\n\nimport org.testcontainers.jdbc.AbstractJDBCDriverTest;\n\nimport java.util.Arrays;\nimport java.util.EnumSet;\n\nclass TimeplusJDBCDriverTest extends AbstractJDBCDriverTest {\n\n    public static Iterable<Object[]> data() {\n        return Arrays.asList(\n            new Object[][] { { \"jdbc:tc:timeplus:2.3.21://hostname\", EnumSet.noneOf(Options.class) } }\n        );\n    }\n}\n"
  },
  {
    "path": "modules/timeplus/src/test/java/org/testcontainers/timeplus/TimeplusContainerTest.java",
    "content": "package org.testcontainers.timeplus;\n\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.TimeplusImages;\nimport org.testcontainers.db.AbstractContainerDatabaseTest;\n\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass TimeplusContainerTest extends AbstractContainerDatabaseTest {\n\n    @Test\n    void testSimple() throws SQLException {\n        try ( // container {\n            TimeplusContainer timeplus = new TimeplusContainer(\"timeplus/timeplusd:2.3.21\")\n            // }\n        ) {\n            timeplus.start();\n\n            ResultSet resultSet = performQuery(timeplus, \"SELECT 1\");\n\n            int resultSetInt = resultSet.getInt(1);\n            assertThat(resultSetInt).isEqualTo(1);\n        }\n    }\n\n    @Test\n    void customCredentialsWithUrlParams() throws SQLException {\n        try (\n            TimeplusContainer timeplus = new TimeplusContainer(TimeplusImages.TIMEPLUS_IMAGE)\n                .withUsername(\"system\")\n                .withPassword(\"sys@t+\")\n                .withDatabaseName(\"system\")\n                .withUrlParam(\"interactive_delay\", \"5\")\n        ) {\n            timeplus.start();\n\n            ResultSet resultSet = performQuery(\n                timeplus,\n                \"SELECT to_int(value) FROM system.settings where name='interactive_delay'\"\n            );\n\n            int resultSetInt = resultSet.getInt(1);\n            assertThat(resultSetInt).isEqualTo(5);\n        }\n    }\n}\n"
  },
  {
    "path": "modules/timeplus/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/toxiproxy/build.gradle",
    "content": "description = \"Testcontainers :: Toxiproxy\"\n\ndependencies {\n    api project(':testcontainers')\n    api 'eu.rekawek.toxiproxy:toxiproxy-java:2.1.11'\n\n    testImplementation 'redis.clients:jedis:7.1.0'\n}\n"
  },
  {
    "path": "modules/toxiproxy/src/main/java/org/testcontainers/containers/ToxiproxyContainer.java",
    "content": "package org.testcontainers.containers;\n\nimport com.github.dockerjava.api.command.InspectContainerResponse;\nimport eu.rekawek.toxiproxy.Proxy;\nimport eu.rekawek.toxiproxy.ToxiproxyClient;\nimport eu.rekawek.toxiproxy.model.ToxicDirection;\nimport eu.rekawek.toxiproxy.model.ToxicList;\nimport lombok.AccessLevel;\nimport lombok.Getter;\nimport lombok.RequiredArgsConstructor;\nimport org.testcontainers.containers.wait.strategy.HttpWaitStrategy;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.IOException;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * Testcontainers implementation for Toxiproxy.\n * <p>\n * Supported images: {@code ghcr.io/shopify/toxiproxy}, {@code shopify/toxiproxy}\n * <p>\n * Exposed ports:\n * <ul>\n *     <li>HTTP: 8474</li>\n *     <li>Proxied Ports: 8666-8697</li>\n * </ul>\n *\n * @deprecated use {@link org.testcontainers.toxiproxy.ToxiproxyContainer} instead.\n */\n@Deprecated\npublic class ToxiproxyContainer extends GenericContainer<ToxiproxyContainer> {\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"shopify/toxiproxy\");\n\n    private static final String DEFAULT_TAG = \"2.1.0\";\n\n    private static final DockerImageName GHCR_IMAGE_NAME = DockerImageName.parse(\"ghcr.io/shopify/toxiproxy\");\n\n    private static final int TOXIPROXY_CONTROL_PORT = 8474;\n\n    private static final int FIRST_PROXIED_PORT = 8666;\n\n    private static final int LAST_PROXIED_PORT = 8666 + 31;\n\n    private ToxiproxyClient client;\n\n    private final Map<String, ContainerProxy> proxies = new HashMap<>();\n\n    private final AtomicInteger nextPort = new AtomicInteger(FIRST_PROXIED_PORT);\n\n    /**\n     * @deprecated use {@link #ToxiproxyContainer(DockerImageName)} instead\n     */\n    @Deprecated\n    public ToxiproxyContainer() {\n        this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG));\n    }\n\n    public ToxiproxyContainer(String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    public ToxiproxyContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME, GHCR_IMAGE_NAME);\n\n        addExposedPorts(TOXIPROXY_CONTROL_PORT);\n        setWaitStrategy(new HttpWaitStrategy().forPath(\"/version\").forPort(TOXIPROXY_CONTROL_PORT));\n\n        // allow up to 32 ports to be proxied (arbitrary value). Here we make the ports exposed; whether or not\n        //  Toxiproxy will listen is controlled at runtime using getProxy(...)\n        for (int i = FIRST_PROXIED_PORT; i <= LAST_PROXIED_PORT; i++) {\n            addExposedPort(i);\n        }\n    }\n\n    @Override\n    protected void containerIsStarted(InspectContainerResponse containerInfo) {\n        client = new ToxiproxyClient(getHost(), getMappedPort(TOXIPROXY_CONTROL_PORT));\n    }\n\n    /**\n     * @return Publicly exposed Toxiproxy HTTP API control port.\n     */\n    public int getControlPort() {\n        return getMappedPort(TOXIPROXY_CONTROL_PORT);\n    }\n\n    /**\n     * Obtain a {@link ContainerProxy} instance for target container that is managed by Testcontainers. The target\n     * container should be routable <b>from this {@link ToxiproxyContainer} instance</b> (e.g. on the same\n     * Docker {@link Network}).\n     *\n     * @param container target container\n     * @param port port number on the target service that should be proxied\n     * @return a {@link ContainerProxy} instance\n     * @deprecated {@link ToxiproxyContainer} will not build the client. Proxies should be provided manually.\n     */\n    @Deprecated\n    public ContainerProxy getProxy(GenericContainer<?> container, int port) {\n        return this.getProxy(container.getNetworkAliases().get(0), port);\n    }\n\n    /**\n     * Obtain a {@link ContainerProxy} instance for a specific hostname and port, which can be for any host\n     * that is routable <b>from this {@link ToxiproxyContainer} instance</b> (e.g. on the same\n     * Docker {@link Network} or on routable from the Docker host).\n     *\n     * <p><em>It is expected that {@link ToxiproxyContainer#getProxy(GenericContainer, int)} will be more\n     * useful in most scenarios, but this method is present to allow use of Toxiproxy in front of containers\n     * or external servers that are not managed by Testcontainers.</em></p>\n     *\n     * @param hostname hostname of target server to be proxied\n     * @param port port number on the target server that should be proxied\n     * @return a {@link ContainerProxy} instance\n     * @deprecated {@link ToxiproxyContainer} will not build the client. Proxies should be provided manually.\n     */\n    @Deprecated\n    public ContainerProxy getProxy(String hostname, int port) {\n        String upstream = hostname + \":\" + port;\n\n        return proxies.computeIfAbsent(\n            upstream,\n            __ -> {\n                try {\n                    final int toxiPort = nextPort.getAndIncrement();\n                    if (toxiPort > LAST_PROXIED_PORT) {\n                        throw new IllegalStateException(\"Maximum number of proxies exceeded\");\n                    }\n\n                    final Proxy proxy = client.createProxy(upstream, \"0.0.0.0:\" + toxiPort, upstream);\n                    final int mappedPort = getMappedPort(toxiPort);\n                    return new ContainerProxy(proxy, getHost(), mappedPort, toxiPort);\n                } catch (IOException e) {\n                    throw new RuntimeException(\"Proxy could not be created\", e);\n                }\n            }\n        );\n    }\n\n    @RequiredArgsConstructor(access = AccessLevel.PROTECTED)\n    @Deprecated\n    public static class ContainerProxy {\n\n        private static final String CUT_CONNECTION_DOWNSTREAM = \"CUT_CONNECTION_DOWNSTREAM\";\n\n        private static final String CUT_CONNECTION_UPSTREAM = \"CUT_CONNECTION_UPSTREAM\";\n\n        private final Proxy toxi;\n\n        /**\n         * The IP address that this proxy container may be reached on from the host machine.\n         */\n        @Getter\n        private final String containerIpAddress;\n\n        /**\n         * The mapped port of this proxy. This is a port of the host machine. It can be used to\n         * access the Toxiproxy container from the host machine.\n         */\n        @Getter\n        private final int proxyPort;\n\n        /**\n         * The original (exposed) port of this proxy. This is a port of the Toxiproxy Docker\n         * container. It can be used to access this container from a different Docker container\n         * on the same network.\n         */\n        @Getter\n        private final int originalProxyPort;\n\n        private boolean isCurrentlyCut;\n\n        public String getName() {\n            return toxi.getName();\n        }\n\n        public ToxicList toxics() {\n            return toxi.toxics();\n        }\n\n        /**\n         * Cuts the connection by setting bandwidth in both directions to zero.\n         * @param shouldCutConnection true if the connection should be cut, or false if it should be re-enabled\n         */\n        public void setConnectionCut(boolean shouldCutConnection) {\n            try {\n                if (shouldCutConnection) {\n                    toxics().bandwidth(CUT_CONNECTION_DOWNSTREAM, ToxicDirection.DOWNSTREAM, 0);\n                    toxics().bandwidth(CUT_CONNECTION_UPSTREAM, ToxicDirection.UPSTREAM, 0);\n                    isCurrentlyCut = true;\n                } else if (isCurrentlyCut) {\n                    toxics().get(CUT_CONNECTION_DOWNSTREAM).remove();\n                    toxics().get(CUT_CONNECTION_UPSTREAM).remove();\n                    isCurrentlyCut = false;\n                }\n            } catch (IOException e) {\n                throw new RuntimeException(\"Could not control proxy\", e);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "modules/toxiproxy/src/main/java/org/testcontainers/toxiproxy/ToxiproxyContainer.java",
    "content": "package org.testcontainers.toxiproxy;\n\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.wait.strategy.HttpWaitStrategy;\nimport org.testcontainers.utility.DockerImageName;\n\n/**\n * Testcontainers implementation for Toxiproxy.\n * <p>\n * Supported images: {@code ghcr.io/shopify/toxiproxy}, {@code shopify/toxiproxy}\n * <p>\n * Exposed ports:\n * <ul>\n *     <li>HTTP: 8474</li>\n *     <li>Proxied Ports: 8666-8697</li>\n * </ul>\n */\npublic class ToxiproxyContainer extends GenericContainer<ToxiproxyContainer> {\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"shopify/toxiproxy\");\n\n    private static final DockerImageName GHCR_IMAGE_NAME = DockerImageName.parse(\"ghcr.io/shopify/toxiproxy\");\n\n    private static final int TOXIPROXY_CONTROL_PORT = 8474;\n\n    private static final int FIRST_PROXIED_PORT = 8666;\n\n    private static final int LAST_PROXIED_PORT = 8666 + 31;\n\n    public ToxiproxyContainer(String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    public ToxiproxyContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME, GHCR_IMAGE_NAME);\n\n        addExposedPorts(TOXIPROXY_CONTROL_PORT);\n        setWaitStrategy(new HttpWaitStrategy().forPath(\"/version\").forPort(TOXIPROXY_CONTROL_PORT));\n\n        // allow up to 32 ports to be proxied (arbitrary value). Here we make the ports exposed; whether or not\n        //  Toxiproxy will listen is controlled at runtime using getProxy(...)\n        for (int i = FIRST_PROXIED_PORT; i <= LAST_PROXIED_PORT; i++) {\n            addExposedPort(i);\n        }\n    }\n\n    /**\n     * @return Publicly exposed Toxiproxy HTTP API control port.\n     */\n    public int getControlPort() {\n        return getMappedPort(TOXIPROXY_CONTROL_PORT);\n    }\n}\n"
  },
  {
    "path": "modules/toxiproxy/src/test/java/org/testcontainers/toxiproxy/ToxiproxyContainerTest.java",
    "content": "package org.testcontainers.toxiproxy;\n\nimport eu.rekawek.toxiproxy.Proxy;\nimport eu.rekawek.toxiproxy.ToxiproxyClient;\nimport eu.rekawek.toxiproxy.model.ToxicDirection;\nimport org.junit.jupiter.api.AutoClose;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.Network;\nimport redis.clients.jedis.Jedis;\nimport redis.clients.jedis.exceptions.JedisConnectionException;\n\nimport java.io.IOException;\nimport java.time.Duration;\nimport java.util.concurrent.TimeUnit;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.catchThrowable;\n\npublic class ToxiproxyContainerTest {\n\n    private static final Duration JEDIS_TIMEOUT = Duration.ofSeconds(10);\n\n    // spotless:off\n    // creatingProxy {\n    // Create a common docker network so that containers can communicate\n    @AutoClose\n    public Network network = Network.newNetwork();\n\n    // The target container - this could be anything\n    @AutoClose\n    public GenericContainer<?> redis = new GenericContainer<>(\"redis:6-alpine\")\n        .withExposedPorts(6379)\n        .withNetwork(network)\n        .withNetworkAliases(\"redis\");\n\n    // Toxiproxy container, which will be used as a TCP proxy\n    @AutoClose\n    public ToxiproxyContainer toxiproxy = new ToxiproxyContainer(\"ghcr.io/shopify/toxiproxy:2.5.0\")\n        .withNetwork(network);\n    // }\n    // spotless:on\n\n    @BeforeEach\n    public void setUp() {\n        redis.start();\n        toxiproxy.start();\n    }\n\n    @Test\n    public void testDirect() {\n        final Jedis jedis = createJedis(redis.getHost(), redis.getFirstMappedPort());\n        jedis.set(\"somekey\", \"somevalue\");\n\n        final String s = jedis.get(\"somekey\");\n        assertThat(s).as(\"direct access to the container works OK\").isEqualTo(\"somevalue\");\n    }\n\n    @Test\n    public void testLatencyViaProxy() throws IOException {\n        // obtainProxyObject {\n        final ToxiproxyClient toxiproxyClient = new ToxiproxyClient(toxiproxy.getHost(), toxiproxy.getControlPort());\n        final Proxy proxy = toxiproxyClient.createProxy(\"redis\", \"0.0.0.0:8666\", \"redis:6379\");\n        // }\n\n        // obtainProxiedHostAndPortForHostMachine {\n        final String ipAddressViaToxiproxy = toxiproxy.getHost();\n        final int portViaToxiproxy = toxiproxy.getMappedPort(8666);\n        // }\n\n        final Jedis jedis = createJedis(ipAddressViaToxiproxy, portViaToxiproxy);\n        jedis.set(\"somekey\", \"somevalue\");\n\n        checkCallWithLatency(jedis, \"without interference\", 0, 250);\n\n        // spotless:off\n        // addingLatency {\n        proxy.toxics()\n            .latency(\"latency\", ToxicDirection.DOWNSTREAM, 1_100)\n            .setJitter(100);\n        // from now on the connection latency should be from 1000-1200 ms.\n        // }\n        // spotless:on\n\n        checkCallWithLatency(jedis, \"with interference\", 1_000, 1_500);\n    }\n\n    @Test\n    public void testConnectionCut() throws IOException {\n        final ToxiproxyClient toxiproxyClient = new ToxiproxyClient(toxiproxy.getHost(), toxiproxy.getControlPort());\n        final Proxy proxy = toxiproxyClient.createProxy(\"redis\", \"0.0.0.0:8666\", \"redis:6379\");\n        final Jedis jedis = createJedis(toxiproxy.getHost(), toxiproxy.getMappedPort(8666));\n        jedis.set(\"somekey\", \"somevalue\");\n\n        assertThat(jedis.get(\"somekey\"))\n            .as(\"access to the container works OK before cutting the connection\")\n            .isEqualTo(\"somevalue\");\n\n        // disableProxy {\n        proxy.toxics().bandwidth(\"CUT_CONNECTION_DOWNSTREAM\", ToxicDirection.DOWNSTREAM, 0);\n        proxy.toxics().bandwidth(\"CUT_CONNECTION_UPSTREAM\", ToxicDirection.UPSTREAM, 0);\n\n        // for example, expect failure when the connection is cut\n        assertThat(\n            catchThrowable(() -> {\n                jedis.get(\"somekey\");\n            })\n        )\n            .as(\"calls fail when the connection is cut\")\n            .isInstanceOf(JedisConnectionException.class);\n\n        proxy.toxics().get(\"CUT_CONNECTION_DOWNSTREAM\").remove();\n        proxy.toxics().get(\"CUT_CONNECTION_UPSTREAM\").remove();\n\n        jedis.close();\n        // and with the connection re-established, expect success\n        assertThat(jedis.get(\"somekey\"))\n            .as(\"access to the container works OK after re-establishing the connection\")\n            .isEqualTo(\"somevalue\");\n        // }\n    }\n\n    @Test\n    public void testMultipleProxiesCanBeCreated() throws IOException {\n        try (\n            GenericContainer<?> secondRedis = new GenericContainer<>(\"redis:6-alpine\")\n                .withExposedPorts(6379)\n                .withNetwork(network)\n                .withNetworkAliases(\"redis2\")\n        ) {\n            secondRedis.start();\n\n            final ToxiproxyClient toxiproxyClient = new ToxiproxyClient(\n                toxiproxy.getHost(),\n                toxiproxy.getControlPort()\n            );\n            final Proxy firstProxy = toxiproxyClient.createProxy(\"redis1\", \"0.0.0.0:8666\", \"redis:6379\");\n            toxiproxyClient.createProxy(\"redis2\", \"0.0.0.0:8667\", \"redis2:6379\");\n\n            final Jedis firstJedis = createJedis(toxiproxy.getHost(), toxiproxy.getMappedPort(8666));\n            final Jedis secondJedis = createJedis(toxiproxy.getHost(), toxiproxy.getMappedPort(8667));\n\n            firstJedis.set(\"somekey\", \"somevalue\");\n            secondJedis.set(\"somekey\", \"somevalue\");\n\n            firstProxy.toxics().bandwidth(\"CUT_CONNECTION_DOWNSTREAM\", ToxicDirection.DOWNSTREAM, 0);\n            firstProxy.toxics().bandwidth(\"CUT_CONNECTION_UPSTREAM\", ToxicDirection.UPSTREAM, 0);\n\n            assertThat(\n                catchThrowable(() -> {\n                    firstJedis.get(\"somekey\");\n                })\n            )\n                .as(\"calls fail when the connection is cut, for only the relevant proxy\")\n                .isInstanceOf(JedisConnectionException.class);\n\n            assertThat(secondJedis.get(\"somekey\")).as(\"access via a different proxy is OK\").isEqualTo(\"somevalue\");\n        }\n    }\n\n    private void checkCallWithLatency(\n        Jedis jedis,\n        final String description,\n        int expectedMinLatency,\n        long expectedMaxLatency\n    ) {\n        final long start = System.nanoTime();\n        String s = jedis.get(\"somekey\");\n        final long end = System.nanoTime();\n        final long duration = TimeUnit.NANOSECONDS.toMillis(end - start);\n\n        assertThat(s).as(String.format(\"access to the container %s works OK\", description)).isEqualTo(\"somevalue\");\n        assertThat(duration >= expectedMinLatency)\n            .as(String.format(\"%s there is at least %dms latency\", description, expectedMinLatency))\n            .isTrue();\n        assertThat(duration < expectedMaxLatency)\n            .as(String.format(\"%s there is no more than %dms latency\", description, expectedMaxLatency))\n            .isTrue();\n    }\n\n    private static Jedis createJedis(String host, int port) {\n        return new Jedis(host, port, Math.toIntExact(JEDIS_TIMEOUT.toMillis()));\n    }\n}\n"
  },
  {
    "path": "modules/toxiproxy/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/trino/build.gradle",
    "content": "description = \"Testcontainers :: JDBC :: Trino\"\n\ndependencies {\n    api project(':testcontainers-jdbc')\n\n    testImplementation project(':testcontainers-jdbc-test')\n    testRuntimeOnly 'io.trino:trino-jdbc:478'\n    compileOnly 'org.jetbrains:annotations:26.0.2-1'\n}\n"
  },
  {
    "path": "modules/trino/src/main/java/org/testcontainers/containers/TrinoContainer.java",
    "content": "package org.testcontainers.containers;\n\nimport com.google.common.base.Strings;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.VisibleForTesting;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.sql.Connection;\nimport java.sql.SQLException;\nimport java.util.Set;\n\n/**\n * Testcontainers implementation for TrinoDB.\n * <p>\n * Supported image: {@code trinodb/trino}\n * <p>\n * Exposed ports: 8080\n *\n * @deprecated use {@link org.testcontainers.trino.TrinoContainer} instead.\n */\n@Deprecated\npublic class TrinoContainer extends JdbcDatabaseContainer<TrinoContainer> {\n\n    static final String NAME = \"trino\";\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"trinodb/trino\");\n\n    static final String IMAGE = \"trinodb/trino\";\n\n    @VisibleForTesting\n    static final String DEFAULT_TAG = \"352\";\n\n    private static final int TRINO_PORT = 8080;\n\n    private String username = \"test\";\n\n    private String catalog = null;\n\n    public TrinoContainer(final String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    public TrinoContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n        addExposedPort(TRINO_PORT);\n    }\n\n    /**\n     * @return the ports on which to check if the container is ready\n     * @deprecated use {@link #getLivenessCheckPortNumbers()} instead\n     */\n    @NotNull\n    @Override\n    @Deprecated\n    protected Set<Integer> getLivenessCheckPorts() {\n        return super.getLivenessCheckPorts();\n    }\n\n    @Override\n    public String getDriverClassName() {\n        return \"io.trino.jdbc.TrinoDriver\";\n    }\n\n    @Override\n    public String getJdbcUrl() {\n        return String.format(\n            \"jdbc:trino://%s:%s/%s\",\n            getHost(),\n            getMappedPort(TRINO_PORT),\n            Strings.nullToEmpty(catalog)\n        );\n    }\n\n    @Override\n    public String getUsername() {\n        return username;\n    }\n\n    @Override\n    public String getPassword() {\n        return \"\";\n    }\n\n    @Override\n    public String getDatabaseName() {\n        return catalog;\n    }\n\n    @Override\n    public String getTestQueryString() {\n        return \"SELECT count(*) FROM tpch.tiny.nation\";\n    }\n\n    @Override\n    public TrinoContainer withUsername(final String username) {\n        this.username = username;\n        return this;\n    }\n\n    @Override\n    public TrinoContainer withDatabaseName(String dbName) {\n        this.catalog = dbName;\n        return this;\n    }\n\n    public Connection createConnection() throws SQLException, NoDriverFoundException {\n        return createConnection(\"\");\n    }\n}\n"
  },
  {
    "path": "modules/trino/src/main/java/org/testcontainers/containers/TrinoContainerProvider.java",
    "content": "package org.testcontainers.containers;\n\nimport org.testcontainers.utility.DockerImageName;\n\n/**\n * Factory for Trino containers.\n */\npublic class TrinoContainerProvider extends JdbcDatabaseContainerProvider {\n\n    @Override\n    public boolean supports(String databaseType) {\n        return databaseType.equals(TrinoContainer.NAME);\n    }\n\n    @Override\n    public JdbcDatabaseContainer newInstance() {\n        return newInstance(TrinoContainer.DEFAULT_TAG);\n    }\n\n    @Override\n    public JdbcDatabaseContainer newInstance(String tag) {\n        return new TrinoContainer(DockerImageName.parse(TrinoContainer.IMAGE).withTag(tag));\n    }\n}\n"
  },
  {
    "path": "modules/trino/src/main/java/org/testcontainers/trino/TrinoContainer.java",
    "content": "package org.testcontainers.trino;\n\nimport com.google.common.base.Strings;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.VisibleForTesting;\nimport org.testcontainers.containers.JdbcDatabaseContainer;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.sql.Connection;\nimport java.sql.SQLException;\nimport java.util.Set;\n\n/**\n * Testcontainers implementation for TrinoDB.\n * <p>\n * Supported image: {@code trinodb/trino}\n * <p>\n * Exposed ports: 8080\n */\npublic class TrinoContainer extends JdbcDatabaseContainer<TrinoContainer> {\n\n    static final String NAME = \"trino\";\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"trinodb/trino\");\n\n    static final String IMAGE = \"trinodb/trino\";\n\n    @VisibleForTesting\n    static final String DEFAULT_TAG = \"352\";\n\n    private static final int TRINO_PORT = 8080;\n\n    private String username = \"test\";\n\n    private String catalog = null;\n\n    public TrinoContainer(final String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    public TrinoContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n        addExposedPort(TRINO_PORT);\n    }\n\n    /**\n     * @return the ports on which to check if the container is ready\n     * @deprecated use {@link #getLivenessCheckPortNumbers()} instead\n     */\n    @NotNull\n    @Override\n    @Deprecated\n    protected Set<Integer> getLivenessCheckPorts() {\n        return super.getLivenessCheckPorts();\n    }\n\n    @Override\n    public String getDriverClassName() {\n        return \"io.trino.jdbc.TrinoDriver\";\n    }\n\n    @Override\n    public String getJdbcUrl() {\n        return String.format(\n            \"jdbc:trino://%s:%s/%s\",\n            getHost(),\n            getMappedPort(TRINO_PORT),\n            Strings.nullToEmpty(catalog)\n        );\n    }\n\n    @Override\n    public String getUsername() {\n        return username;\n    }\n\n    @Override\n    public String getPassword() {\n        return \"\";\n    }\n\n    @Override\n    public String getDatabaseName() {\n        return catalog;\n    }\n\n    @Override\n    public String getTestQueryString() {\n        return \"SELECT count(*) FROM tpch.tiny.nation\";\n    }\n\n    @Override\n    public TrinoContainer withUsername(final String username) {\n        this.username = username;\n        return this;\n    }\n\n    @Override\n    public TrinoContainer withDatabaseName(String dbName) {\n        this.catalog = dbName;\n        return this;\n    }\n\n    public Connection createConnection() throws SQLException, NoDriverFoundException {\n        return createConnection(\"\");\n    }\n}\n"
  },
  {
    "path": "modules/trino/src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider",
    "content": "org.testcontainers.containers.TrinoContainerProvider\n"
  },
  {
    "path": "modules/trino/src/test/java/org/testcontainers/TrinoTestImages.java",
    "content": "package org.testcontainers;\n\nimport org.testcontainers.utility.DockerImageName;\n\npublic interface TrinoTestImages {\n    DockerImageName TRINO_TEST_IMAGE = DockerImageName.parse(\"trinodb/trino:352\");\n\n    DockerImageName TRINO_PREVIOUS_VERSION_TEST_IMAGE = DockerImageName.parse(\"trinodb/trino:351\");\n}\n"
  },
  {
    "path": "modules/trino/src/test/java/org/testcontainers/jdbc/trino/TrinoJDBCDriverTest.java",
    "content": "package org.testcontainers.jdbc.trino;\n\nimport org.testcontainers.jdbc.AbstractJDBCDriverTest;\n\nimport java.util.Arrays;\nimport java.util.EnumSet;\n\nclass TrinoJDBCDriverTest extends AbstractJDBCDriverTest {\n\n    public static Iterable<Object[]> data() {\n        return Arrays.asList(\n            new Object[][] { //\n                { \"jdbc:tc:trino:352://hostname/\", EnumSet.of(Options.PmdKnownBroken) },\n            }\n        );\n    }\n}\n"
  },
  {
    "path": "modules/trino/src/test/java/org/testcontainers/trino/TrinoContainerTest.java",
    "content": "package org.testcontainers.trino;\n\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.TrinoTestImages;\n\nimport java.sql.Connection;\nimport java.sql.ResultSet;\nimport java.sql.Statement;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass TrinoContainerTest {\n\n    @Test\n    void testSimple() throws Exception {\n        try ( // container {\n            TrinoContainer trino = new TrinoContainer(\"trinodb/trino:352\")\n            // }\n        ) {\n            trino.start();\n            try (\n                Connection connection = trino.createConnection();\n                Statement statement = connection.createStatement();\n                ResultSet resultSet = statement.executeQuery(\"SELECT DISTINCT node_version FROM system.runtime.nodes\")\n            ) {\n                assertThat(resultSet.next()).as(\"results\").isTrue();\n                assertThat(resultSet.getString(\"node_version\")).as(\"Trino version\").isEqualTo(\"352\");\n                assertContainerHasCorrectExposedAndLivenessCheckPorts(trino);\n            }\n        }\n    }\n\n    @Test\n    void testSpecificVersion() throws Exception {\n        try (TrinoContainer trino = new TrinoContainer(TrinoTestImages.TRINO_PREVIOUS_VERSION_TEST_IMAGE)) {\n            trino.start();\n            try (\n                Connection connection = trino.createConnection();\n                Statement statement = connection.createStatement();\n                ResultSet resultSet = statement.executeQuery(\"SELECT DISTINCT node_version FROM system.runtime.nodes\")\n            ) {\n                assertThat(resultSet.next()).as(\"results\").isTrue();\n                assertThat(resultSet.getString(\"node_version\"))\n                    .as(\"Trino version\")\n                    .isEqualTo(TrinoTestImages.TRINO_PREVIOUS_VERSION_TEST_IMAGE.getVersionPart());\n            }\n        }\n    }\n\n    @Test\n    void testInitScript() throws Exception {\n        try (TrinoContainer trino = new TrinoContainer(TrinoTestImages.TRINO_TEST_IMAGE)) {\n            trino.withInitScript(\"initial.sql\");\n            trino.start();\n            try (\n                Connection connection = trino.createConnection();\n                Statement statement = connection.createStatement();\n                ResultSet resultSet = statement.executeQuery(\"SELECT a FROM memory.default.test_table\")\n            ) {\n                assertThat(resultSet.next()).as(\"results\").isTrue();\n                assertThat(resultSet.getObject(\"a\")).as(\"Value\").isEqualTo(12345678909324L);\n                assertThat(resultSet.next()).as(\"results\").isFalse();\n            }\n        }\n    }\n\n    private void assertContainerHasCorrectExposedAndLivenessCheckPorts(TrinoContainer trino) {\n        assertThat(trino.getExposedPorts()).containsExactly(8080);\n        assertThat(trino.getLivenessCheckPortNumbers()).containsExactly(trino.getMappedPort(8080));\n    }\n}\n"
  },
  {
    "path": "modules/trino/src/test/resources/initial.sql",
    "content": "CREATE TABLE memory.default.test_table(a bigint);\nINSERT INTO memory.default.test_table(a) VALUES (12345678909324);\n"
  },
  {
    "path": "modules/trino/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/typesense/build.gradle",
    "content": "description = \"Testcontainers :: Typesense\"\n\ndependencies {\n    api project(':testcontainers')\n\n    testImplementation 'org.typesense:typesense-java:2.0.0'\n}\n"
  },
  {
    "path": "modules/typesense/src/main/java/org/testcontainers/typesense/TypesenseContainer.java",
    "content": "package org.testcontainers.typesense;\n\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.utility.DockerImageName;\n\n/**\n * Testcontainers implementation for Typesense.\n * <p>\n * Supported image: {@code typesense/typesense}\n * <p>\n * Exposed ports: 8108\n */\npublic class TypesenseContainer extends GenericContainer<TypesenseContainer> {\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"typesense/typesense\");\n\n    private static final int PORT = 8108;\n\n    private static final String DEFAULT_API_KEY = \"testcontainers\";\n\n    private String apiKey = DEFAULT_API_KEY;\n\n    public TypesenseContainer(String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    public TypesenseContainer(DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n        withExposedPorts(PORT);\n        withEnv(\"TYPESENSE_DATA_DIR\", \"/tmp\");\n        waitingFor(\n            Wait\n                .forHttp(\"/health\")\n                .forStatusCode(200)\n                .forResponsePredicate(response -> response.contains(\"\\\"ok\\\":true\"))\n        );\n    }\n\n    @Override\n    protected void configure() {\n        withEnv(\"TYPESENSE_API_KEY\", this.apiKey);\n    }\n\n    public TypesenseContainer withApiKey(String apiKey) {\n        this.apiKey = apiKey;\n        return this;\n    }\n\n    public String getHttpPort() {\n        return String.valueOf(getMappedPort(PORT));\n    }\n\n    public String getApiKey() {\n        return this.apiKey;\n    }\n}\n"
  },
  {
    "path": "modules/typesense/src/test/java/org/testcontainers/typesense/TypesenseContainerTest.java",
    "content": "package org.testcontainers.typesense;\n\nimport org.junit.jupiter.api.Test;\nimport org.typesense.api.Client;\nimport org.typesense.api.Configuration;\nimport org.typesense.resources.Node;\n\nimport java.time.Duration;\nimport java.util.Collections;\nimport java.util.List;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass TypesenseContainerTest {\n\n    @Test\n    void query() throws Exception {\n        try ( // container {\n            TypesenseContainer typesense = new TypesenseContainer(\"typesense/typesense:27.1\")\n            // }\n        ) {\n            typesense.start();\n            List<Node> nodes = Collections.singletonList(\n                new Node(\"http\", typesense.getHost(), typesense.getHttpPort())\n            );\n\n            assertThat(typesense.getApiKey()).isEqualTo(\"testcontainers\");\n            Configuration configuration = new Configuration(nodes, Duration.ofSeconds(5), typesense.getApiKey());\n            Client client = new Client(configuration);\n            System.out.println(client.health.retrieve());\n            assertThat(client.health.retrieve()).containsEntry(\"ok\", true);\n        }\n    }\n\n    @Test\n    void withCustomApiKey() throws Exception {\n        try (TypesenseContainer typesense = new TypesenseContainer(\"typesense/typesense:27.1\").withApiKey(\"s3cr3t\")) {\n            typesense.start();\n            List<Node> nodes = Collections.singletonList(\n                new Node(\"http\", typesense.getHost(), typesense.getHttpPort())\n            );\n\n            assertThat(typesense.getApiKey()).isEqualTo(\"s3cr3t\");\n            Configuration configuration = new Configuration(nodes, Duration.ofSeconds(5), typesense.getApiKey());\n            Client client = new Client(configuration);\n            assertThat(client.health.retrieve()).containsEntry(\"ok\", true);\n        }\n    }\n}\n"
  },
  {
    "path": "modules/typesense/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/vault/AUTHORS",
    "content": "﻿Michael Oswald <michael.oswald@capitalone.com>"
  },
  {
    "path": "modules/vault/LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2017 Capital One Services, LLC and other authors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
  },
  {
    "path": "modules/vault/build.gradle",
    "content": "description = \"Testcontainers :: Vault\"\n\ndependencies {\n    api project(':testcontainers')\n\n    testImplementation 'com.bettercloud:vault-java-driver:5.1.0'\n    testImplementation 'io.rest-assured:rest-assured:5.5.6'\n}\n"
  },
  {
    "path": "modules/vault/src/main/java/org/testcontainers/vault/VaultContainer.java",
    "content": "package org.testcontainers.vault;\n\nimport com.github.dockerjava.api.command.InspectContainerResponse;\nimport com.github.dockerjava.api.model.Capability;\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.function.Consumer;\nimport java.util.stream.Collectors;\n\n/**\n * Testcontainers implementation for Vault.\n * <p>\n * Supported image: {@code hashicorp/vault}, {@code vault}\n * <p>\n * Exposure ports: 8200\n */\npublic class VaultContainer<SELF extends VaultContainer<SELF>> extends GenericContainer<SELF> {\n\n    private static final DockerImageName DEFAULT_OLD_IMAGE_NAME = DockerImageName.parse(\"vault\");\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"hashicorp/vault\");\n\n    private static final String DEFAULT_TAG = \"1.1.3\";\n\n    private static final int VAULT_PORT = 8200;\n\n    private Map<String, List<String>> secretsMap = new HashMap<>();\n\n    private List<String> initCommands = new ArrayList<>();\n\n    private int port = VAULT_PORT;\n\n    /**\n     * @deprecated use {@link #VaultContainer(DockerImageName)} instead\n     */\n    @Deprecated\n    public VaultContainer() {\n        this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG));\n    }\n\n    public VaultContainer(String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    public VaultContainer(final DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_OLD_IMAGE_NAME, DEFAULT_IMAGE_NAME);\n\n        // Use the vault healthcheck endpoint to check for readiness, per https://www.vaultproject.io/api/system/health.html\n        setWaitStrategy(Wait.forHttp(\"/v1/sys/health\").forStatusCode(200));\n\n        withCreateContainerCmdModifier(cmd -> cmd.withCapAdd(Capability.IPC_LOCK));\n        withEnv(\"VAULT_ADDR\", \"http://0.0.0.0:\" + port);\n        withExposedPorts(port);\n    }\n\n    public String getHttpHostAddress() {\n        return String.format(\"http://%s:%s\", getHost(), getMappedPort(port));\n    }\n\n    @Override\n    protected void containerIsStarted(InspectContainerResponse containerInfo) {\n        addSecrets();\n        runInitCommands();\n    }\n\n    private void addSecrets() {\n        if (!secretsMap.isEmpty()) {\n            try {\n                this.execInContainer(buildExecCommand(secretsMap)).getStdout().contains(\"Success\");\n            } catch (IOException | InterruptedException e) {\n                logger()\n                    .error(\n                        \"Failed to add these secrets {} into Vault via exec command. Exception message: {}\",\n                        secretsMap,\n                        e.getMessage()\n                    );\n            }\n        }\n    }\n\n    private String[] buildExecCommand(Map<String, List<String>> map) {\n        StringBuilder stringBuilder = new StringBuilder();\n        map.forEach((path, secrets) -> {\n            stringBuilder.append(\" && vault kv put \" + path);\n            secrets.forEach(item -> stringBuilder.append(\" \" + item));\n        });\n        return new String[] { \"/bin/sh\", \"-c\", stringBuilder.toString().substring(4) };\n    }\n\n    private void runInitCommands() {\n        if (!initCommands.isEmpty()) {\n            String commands = initCommands\n                .stream()\n                .map(command -> \"vault \" + command)\n                .collect(Collectors.joining(\" && \"));\n            try {\n                ExecResult execResult = this.execInContainer(new String[] { \"/bin/sh\", \"-c\", commands });\n                if (execResult.getExitCode() != 0) {\n                    logger()\n                        .error(\n                            \"Failed to execute these init commands {}. Exit code {}. Stdout {}. Stderr {}\",\n                            initCommands,\n                            execResult.getExitCode(),\n                            execResult.getStdout(),\n                            execResult.getStderr()\n                        );\n                }\n            } catch (IOException | InterruptedException e) {\n                logger()\n                    .error(\n                        \"Failed to execute these init commands {}. Exception message: {}\",\n                        initCommands,\n                        e.getMessage()\n                    );\n            }\n        }\n    }\n\n    /**\n     * Sets the Vault root token for the container so application tests can source secrets using the token\n     *\n     * @param token the root token value to set for Vault.\n     * @return this\n     */\n    public SELF withVaultToken(String token) {\n        withEnv(\"VAULT_DEV_ROOT_TOKEN_ID\", token);\n        withEnv(\"VAULT_TOKEN\", token);\n        return self();\n    }\n\n    /**\n     * Sets the Vault port in the container as well as the port bindings for the host to reach the container over HTTP.\n     *\n     * @param port the port number you want to have the Vault container listen on for tests.\n     * @return this\n     * @deprecated the exposed port will be randomized automatically. As calling this method provides no additional value, you are recommended to remove the call. getFirstMappedPort() may be used to obtain the listening vault port.\n     */\n    @Deprecated\n    public SELF withVaultPort(int port) {\n        this.port = port;\n        return self();\n    }\n\n    /**\n     * Sets the logging level for the Vault server in the container.\n     * Logs can be consumed through {@link #withLogConsumer(Consumer)}.\n     *\n     * @param level the logging level to set for Vault.\n     * @return this\n     * @deprecated use {@link #withEnv(String, String)} instead\n     */\n    @Deprecated\n    public SELF withLogLevel(VaultLogLevel level) {\n        return withEnv(\"VAULT_LOG_LEVEL\", level.config);\n    }\n\n    /**\n     * Pre-loads secrets into Vault container. User may specify one or more secrets and all will be added to each path\n     * that is specified. Thus this can be called more than once for multiple paths to be added to Vault.\n     * <p>\n     * The secrets are added to vault directly after the container is up via the\n     * {@link #addSecrets() addSecrets}, called from {@link #containerIsStarted(InspectContainerResponse) containerIsStarted}\n     *\n     * @param path             specific Vault path to store specified secrets\n     * @param firstSecret      first secret to add to specified path\n     * @param remainingSecrets var args list of secrets to add to specified path\n     * @return this\n     * @deprecated use {@link #withInitCommand(String...)} instead\n     */\n    @Deprecated\n    public SELF withSecretInVault(String path, String firstSecret, String... remainingSecrets) {\n        List<String> list = new ArrayList<>();\n        list.add(firstSecret);\n        for (String secret : remainingSecrets) {\n            list.add(secret);\n        }\n        if (secretsMap.containsKey(path)) {\n            list.addAll(list);\n        }\n        secretsMap.putIfAbsent(path, list);\n        return self();\n    }\n\n    /**\n     * Run initialization commands using the vault cli.\n     * <p>\n     * Useful for enabling more secret engines like:\n     * <pre>\n     *     .withInitCommand(\"secrets enable pki\")\n     *     .withInitCommand(\"secrets enable transit\")\n     * </pre>\n     * @param commands The commands to send to the vault cli\n     * @return this\n     */\n    public SELF withInitCommand(String... commands) {\n        initCommands.addAll(Arrays.asList(commands));\n        return self();\n    }\n}\n"
  },
  {
    "path": "modules/vault/src/main/java/org/testcontainers/vault/VaultLogLevel.java",
    "content": "package org.testcontainers.vault;\n\n/**\n * Vault preset of logging levels.\n */\npublic enum VaultLogLevel {\n    Trace(\"trace\"),\n    Debug(\"debug\"),\n    Info(\"info\"),\n    Warn(\"warn\"),\n    Error(\"err\");\n\n    public final String config;\n\n    VaultLogLevel(String config) {\n        this.config = config;\n    }\n}\n"
  },
  {
    "path": "modules/vault/src/test/java/org/testcontainers/vault/VaultClientTest.java",
    "content": "package org.testcontainers.vault;\n\nimport com.bettercloud.vault.Vault;\nimport com.bettercloud.vault.VaultConfig;\nimport com.bettercloud.vault.VaultException;\nimport com.bettercloud.vault.response.LogicalResponse;\nimport org.junit.jupiter.api.Test;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass VaultClientTest {\n\n    private static final String VAULT_TOKEN = \"my-root-token\";\n\n    @Test\n    void writeAndReadMultipleValues() throws VaultException {\n        try (VaultContainer<?> vaultContainer = new VaultContainer<>(\"vault:1.1.3\").withVaultToken(VAULT_TOKEN)) {\n            vaultContainer.start();\n\n            final VaultConfig config = new VaultConfig()\n                .address(\"http://\" + vaultContainer.getHost() + \":\" + vaultContainer.getFirstMappedPort())\n                .token(VAULT_TOKEN)\n                .build();\n\n            final Vault vault = new Vault(config);\n\n            final Map<String, Object> secrets = new HashMap<>();\n            secrets.put(\"value\", \"world\");\n            secrets.put(\"other_value\", \"another world\");\n\n            // Write operation\n            final LogicalResponse writeResponse = vault.logical().write(\"secret/hello\", secrets);\n\n            assertThat(writeResponse.getRestResponse().getStatus()).isEqualTo(200);\n\n            // Read operation\n            final Map<String, String> value = vault.logical().read(\"secret/hello\").getData();\n\n            assertThat(value).containsEntry(\"value\", \"world\").containsEntry(\"other_value\", \"another world\");\n        }\n    }\n}\n"
  },
  {
    "path": "modules/vault/src/test/java/org/testcontainers/vault/VaultContainerTest.java",
    "content": "package org.testcontainers.vault;\n\nimport com.bettercloud.vault.Vault;\nimport com.bettercloud.vault.VaultConfig;\nimport com.bettercloud.vault.response.LogicalResponse;\nimport io.restassured.response.Response;\nimport org.junit.jupiter.api.AfterAll;\nimport org.junit.jupiter.api.BeforeAll;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.GenericContainer;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static io.restassured.RestAssured.given;\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * This test shows the pattern to use the VaultContainer @ClassRule for a junit test. It also has tests that ensure\n * the secrets were added correctly by reading from Vault with the CLI, over HTTP and over Client Library.\n */\nclass VaultContainerTest {\n\n    private static final String VAULT_TOKEN = \"my-root-token\";\n\n    // vaultContainer {\n    public static VaultContainer<?> vaultContainer = new VaultContainer<>(\"hashicorp/vault:1.13\")\n        .withVaultToken(VAULT_TOKEN)\n        .withInitCommand(\n            \"secrets enable transit\",\n            \"write -f transit/keys/my-key\",\n            \"kv put secret/testing1 top_secret=password123\",\n            \"kv put secret/testing2 secret_one=password1 secret_two=password2 secret_three=password3 secret_three=password3 secret_four=password4\"\n        );\n\n    // }\n\n    @BeforeAll\n    static void setUp() {\n        vaultContainer.start();\n    }\n\n    @AfterAll\n    static void tearDown() {\n        vaultContainer.stop();\n    }\n\n    @Test\n    void readFirstSecretPathWithCli() throws Exception {\n        GenericContainer.ExecResult result = vaultContainer.execInContainer(\n            \"vault\",\n            \"kv\",\n            \"get\",\n            \"-format=json\",\n            \"secret/testing1\"\n        );\n        assertThat(result.getStdout()).contains(\"password123\");\n    }\n\n    @Test\n    void readSecondSecretPathWithCli() throws Exception {\n        GenericContainer.ExecResult result = vaultContainer.execInContainer(\n            \"vault\",\n            \"kv\",\n            \"get\",\n            \"-format=json\",\n            \"secret/testing2\"\n        );\n\n        final String output = result.getStdout().replaceAll(\"\\\\r?\\\\n\", \"\");\n        System.out.println(\"output = \" + output);\n        assertThat(output).contains(\"password1\");\n        assertThat(output).contains(\"password2\");\n        assertThat(output).contains(\"password3\");\n        assertThat(output).contains(\"password4\");\n    }\n\n    @Test\n    void readFirstSecretPathOverHttpApi() {\n        Response response = given()\n            .header(\"X-Vault-Token\", VAULT_TOKEN)\n            .when()\n            .get(vaultContainer.getHttpHostAddress() + \"/v1/secret/data/testing1\")\n            .thenReturn();\n        assertThat(response.body().jsonPath().getString(\"data.data.top_secret\")).isEqualTo(\"password123\");\n    }\n\n    @Test\n    void readSecondSecretPathOverHttpApi() throws InterruptedException {\n        Response response = given()\n            .header(\"X-Vault-Token\", VAULT_TOKEN)\n            .when()\n            .get(vaultContainer.getHttpHostAddress() + \"/v1/secret/data/testing2\")\n            .andReturn();\n\n        assertThat(response.body().jsonPath().getString(\"data.data.secret_one\")).contains(\"password1\");\n        assertThat(response.body().jsonPath().getString(\"data.data.secret_two\")).contains(\"password2\");\n        assertThat(response.body().jsonPath().getList(\"data.data.secret_three\")).contains(\"password3\");\n        assertThat(response.body().jsonPath().getString(\"data.data.secret_four\")).contains(\"password4\");\n    }\n\n    @Test\n    void readTransitKeyOverHttpApi() throws InterruptedException {\n        Response response = given()\n            .header(\"X-Vault-Token\", VAULT_TOKEN)\n            .when()\n            .get(vaultContainer.getHttpHostAddress() + \"/v1/transit/keys/my-key\")\n            .thenReturn();\n\n        assertThat(response.body().jsonPath().getString(\"data.name\")).isEqualTo(\"my-key\");\n    }\n\n    @Test\n    // readWithLibrary {\n    void readFirstSecretPathOverClientLibrary() throws Exception {\n        final VaultConfig config = new VaultConfig()\n            .address(vaultContainer.getHttpHostAddress())\n            .token(VAULT_TOKEN)\n            .build();\n\n        final Vault vault = new Vault(config);\n\n        final Map<String, String> value = vault.logical().read(\"secret/testing1\").getData();\n\n        assertThat(value).containsEntry(\"top_secret\", \"password123\");\n    }\n\n    // }\n\n    @Test\n    void readSecondSecretPathOverClientLibrary() throws Exception {\n        final VaultConfig config = new VaultConfig()\n            .address(vaultContainer.getHttpHostAddress())\n            .token(VAULT_TOKEN)\n            .build();\n\n        final Vault vault = new Vault(config);\n        final Map<String, String> value = vault.logical().read(\"secret/testing2\").getData();\n\n        assertThat(value)\n            .containsEntry(\"secret_one\", \"password1\")\n            .containsEntry(\"secret_two\", \"password2\")\n            .containsEntry(\"secret_three\", \"[\\\"password3\\\",\\\"password3\\\"]\")\n            .containsEntry(\"secret_four\", \"password4\");\n    }\n\n    @Test\n    void writeSecretOverClientLibrary() throws Exception {\n        final VaultConfig config = new VaultConfig()\n            .address(vaultContainer.getHttpHostAddress())\n            .token(VAULT_TOKEN)\n            .build();\n\n        final Vault vault = new Vault(config);\n\n        final Map<String, Object> secrets = new HashMap<>();\n        secrets.put(\"value\", \"world\");\n        secrets.put(\"other_value\", \"another world\");\n\n        // Write operation\n        final LogicalResponse writeResponse = vault.logical().write(\"secret/hello\", secrets);\n\n        assertThat(writeResponse.getRestResponse().getStatus()).isEqualTo(200);\n\n        // Read operation\n        final Map<String, String> value = vault.logical().read(\"secret/hello\").getData();\n\n        assertThat(value).containsEntry(\"value\", \"world\").containsEntry(\"other_value\", \"another world\");\n    }\n}\n"
  },
  {
    "path": "modules/vault/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/weaviate/build.gradle",
    "content": "description = \"Testcontainers :: Weaviate\"\n\ndependencies {\n    api project(':testcontainers')\n\n    testImplementation 'io.weaviate:client:5.5.0'\n}\n"
  },
  {
    "path": "modules/weaviate/src/main/java/org/testcontainers/weaviate/WeaviateContainer.java",
    "content": "package org.testcontainers.weaviate;\n\nimport org.testcontainers.containers.GenericContainer;\nimport org.testcontainers.containers.wait.strategy.Wait;\nimport org.testcontainers.utility.DockerImageName;\n\n/**\n * Testcontainers implementation of Weaviate.\n * <p>\n * Supported images: {@code cr.weaviate.io/semitechnologies/weaviate}, {@code semitechnologies/weaviate}\n * <p>\n * Exposed ports:\n * <ul>\n *     <li>HTTP: 8080</li>\n *     <li>gRPC: 50051</li>\n * </ul>\n */\npublic class WeaviateContainer extends GenericContainer<WeaviateContainer> {\n\n    private static final DockerImageName DEFAULT_WEAVIATE_IMAGE = DockerImageName.parse(\n        \"cr.weaviate.io/semitechnologies/weaviate\"\n    );\n\n    private static final DockerImageName DOCKER_HUB_WEAVIATE_IMAGE = DockerImageName.parse(\"semitechnologies/weaviate\");\n\n    public WeaviateContainer(String dockerImageName) {\n        this(DockerImageName.parse(dockerImageName));\n    }\n\n    public WeaviateContainer(DockerImageName dockerImageName) {\n        super(dockerImageName);\n        dockerImageName.assertCompatibleWith(DEFAULT_WEAVIATE_IMAGE, DOCKER_HUB_WEAVIATE_IMAGE);\n        withExposedPorts(8080, 50051);\n        withEnv(\"AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED\", \"true\");\n        withEnv(\"PERSISTENCE_DATA_PATH\", \"/var/lib/weaviate\");\n        waitingFor(Wait.forHttp(\"/v1/.well-known/ready\").forPort(8080).forStatusCode(200));\n    }\n\n    public String getHttpHostAddress() {\n        return getHost() + \":\" + getMappedPort(8080);\n    }\n\n    public String getGrpcHostAddress() {\n        return getHost() + \":\" + getMappedPort(50051);\n    }\n}\n"
  },
  {
    "path": "modules/weaviate/src/test/java/org/testcontainers/weaviate/WeaviateContainerTest.java",
    "content": "package org.testcontainers.weaviate;\n\nimport io.weaviate.client.Config;\nimport io.weaviate.client.WeaviateClient;\nimport io.weaviate.client.base.Result;\nimport io.weaviate.client.v1.misc.model.Meta;\nimport org.assertj.core.api.InstanceOfAssertFactories;\nimport org.junit.jupiter.api.Test;\n\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass WeaviateContainerTest {\n\n    @Test\n    void testWeaviate() {\n        try ( // container {\n            WeaviateContainer weaviate = new WeaviateContainer(\"cr.weaviate.io/semitechnologies/weaviate:1.29.0\")\n            // }\n        ) {\n            weaviate.start();\n            Config config = new Config(\"http\", weaviate.getHttpHostAddress());\n            config.setGRPCHost(weaviate.getGrpcHostAddress());\n            WeaviateClient client = new WeaviateClient(config);\n            Result<Meta> meta = client.misc().metaGetter().run();\n            assertThat(meta.getResult().getVersion()).isEqualTo(\"1.29.0\");\n        }\n    }\n\n    @Test\n    void testWeaviateWithModules() {\n        List<String> enableModules = Arrays.asList(\n            \"backup-filesystem\",\n            \"text2vec-openai\",\n            \"text2vec-cohere\",\n            \"text2vec-huggingface\",\n            \"generative-openai\"\n        );\n        Map<String, String> env = new HashMap<>();\n        env.put(\"ENABLE_MODULES\", String.join(\",\", enableModules));\n        env.put(\"BACKUP_FILESYSTEM_PATH\", \"/tmp/backups\");\n        try (WeaviateContainer weaviate = new WeaviateContainer(\"semitechnologies/weaviate:1.29.0\").withEnv(env)) {\n            weaviate.start();\n            Config config = new Config(\"http\", weaviate.getHttpHostAddress());\n            config.setGRPCHost(weaviate.getGrpcHostAddress());\n            WeaviateClient client = new WeaviateClient(config);\n            Result<Meta> meta = client.misc().metaGetter().run();\n            assertThat(meta.getResult().getVersion()).isEqualTo(\"1.29.0\");\n            Object modules = meta.getResult().getModules();\n            assertThat(modules)\n                .isNotNull()\n                .asInstanceOf(InstanceOfAssertFactories.map(String.class, Object.class))\n                .extracting(Map::keySet)\n                .satisfies(keys -> {\n                    assertThat(keys.size()).isEqualTo(enableModules.size());\n                    keys.forEach(key -> assertThat(enableModules.contains(key)).isTrue());\n                });\n        }\n    }\n}\n"
  },
  {
    "path": "modules/weaviate/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "modules/yugabytedb/build.gradle",
    "content": "description = \"Testcontainers :: JDBC :: YugabyteDB\"\n\ndependencies {\n    api project(':testcontainers-jdbc')\n\n    testImplementation project(':testcontainers-jdbc-test')\n    // YCQL driver\n    testImplementation 'com.yugabyte:java-driver-core:4.19.0-yb-1'\n    // YSQL driver\n    testRuntimeOnly 'com.yugabyte:jdbc-yugabytedb:42.7.3-yb-4'\n}\n"
  },
  {
    "path": "modules/yugabytedb/src/main/java/org/testcontainers/containers/YugabyteDBYCQLContainer.java",
    "content": "package org.testcontainers.containers;\n\nimport com.github.dockerjava.api.command.InspectContainerResponse;\nimport org.testcontainers.containers.delegate.YugabyteDBYCQLDelegate;\nimport org.testcontainers.containers.strategy.YugabyteDBYCQLWaitStrategy;\nimport org.testcontainers.ext.ScriptUtils;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.net.InetSocketAddress;\nimport java.time.Duration;\nimport java.util.Collections;\nimport java.util.Set;\n\n/**\n * Testcontainers implementation for YugabyteDB YCQL API.\n * <p>\n * Supported image: {@code yugabytedb/yugabyte}\n * <p>\n * Exposed ports:\n * <ul>\n *     <li>YCQL: 5433</li>\n *     <li>Master dashboard: 7000</li>\n *     <li>Tserver dashboard: 9000</li>\n * </ul>\n *\n * @see <a href=\"https://docs.yugabyte.com/stable/api/ycql/\">YCQL API</a>\n */\npublic class YugabyteDBYCQLContainer extends GenericContainer<YugabyteDBYCQLContainer> {\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"yugabytedb/yugabyte\");\n\n    private static final Integer YCQL_PORT = 9042;\n\n    private static final Integer MASTER_DASHBOARD_PORT = 7000;\n\n    private static final Integer TSERVER_DASHBOARD_PORT = 9000;\n\n    private static final String ENTRYPOINT = \"bin/yugabyted start --background=false\";\n\n    private static final String LOCAL_DC = \"datacenter1\";\n\n    private String keyspace;\n\n    private String username;\n\n    private String password;\n\n    private String initScript;\n\n    /**\n     * @param imageName image name\n     */\n    public YugabyteDBYCQLContainer(final String imageName) {\n        this(DockerImageName.parse(imageName));\n    }\n\n    /**\n     * @param imageName image name\n     */\n    public YugabyteDBYCQLContainer(final DockerImageName imageName) {\n        super(imageName);\n        imageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n        withExposedPorts(YCQL_PORT, MASTER_DASHBOARD_PORT, TSERVER_DASHBOARD_PORT);\n        waitingFor(new YugabyteDBYCQLWaitStrategy(this).withStartupTimeout(Duration.ofSeconds(60)));\n        withCommand(ENTRYPOINT);\n    }\n\n    @Override\n    public Set<Integer> getLivenessCheckPortNumbers() {\n        return Collections.singleton(getMappedPort(YCQL_PORT));\n    }\n\n    /**\n     * Configures the environment variables. Setting up these variables would create the\n     * custom objects. Setting {@link #withKeyspaceName(String)},\n     * {@link #withUsername(String)}, {@link #withPassword(String)} these parameters will\n     * initialize the database with those custom values\n     */\n    @Override\n    protected void configure() {\n        addEnv(\"YCQL_KEYSPACE\", keyspace);\n        addEnv(\"YCQL_USER\", username);\n        addEnv(\"YCQL_PASSWORD\", password);\n    }\n\n    /**\n     * @param initScript path of the initialization script file\n     * @return {@link YugabyteDBYCQLContainer} instance\n     */\n    public YugabyteDBYCQLContainer withInitScript(String initScript) {\n        this.initScript = initScript;\n        return this;\n    }\n\n    /**\n     * Setting this would create the keyspace\n     * @param keyspace keyspace\n     * @return {@link YugabyteDBYCQLContainer} instance\n     */\n    public YugabyteDBYCQLContainer withKeyspaceName(final String keyspace) {\n        this.keyspace = keyspace;\n        return this;\n    }\n\n    /**\n     * Setting this would create the custom user role\n     * @param username user name\n     * @return {@link YugabyteDBYCQLContainer} instance\n     */\n    public YugabyteDBYCQLContainer withUsername(final String username) {\n        this.username = username;\n        return this;\n    }\n\n    /**\n     * Setting this along with {@link #withUsername(String)} would enable authentication\n     * @param password password\n     * @return {@link YugabyteDBYCQLContainer} instance\n     */\n    public YugabyteDBYCQLContainer withPassword(final String password) {\n        this.password = password;\n        return this;\n    }\n\n    /**\n     * Executes the initialization script\n     * @param containerInfo containerInfo\n     */\n    @Override\n    protected void containerIsStarted(InspectContainerResponse containerInfo) {\n        if (this.initScript != null) {\n            ScriptUtils.runInitScript(new YugabyteDBYCQLDelegate(this), initScript);\n        }\n    }\n\n    /**\n     * Returns a {@link InetSocketAddress} representation of YCQL's contact point info\n     * @return contactpoint\n     */\n    public InetSocketAddress getContactPoint() {\n        return new InetSocketAddress(getHost(), getMappedPort(YCQL_PORT));\n    }\n\n    /**\n     * Returns the local datacenter name\n     * @return localdc name\n     */\n    public String getLocalDc() {\n        return LOCAL_DC;\n    }\n\n    /**\n     * Username getter method\n     * @return username\n     */\n    public String getUsername() {\n        return this.username;\n    }\n\n    /**\n     * Password getter method\n     * @return password\n     */\n    public String getPassword() {\n        return this.password;\n    }\n\n    /**\n     * Keyspace getter method\n     * @return keyspace\n     */\n    public String getKeyspace() {\n        return this.keyspace;\n    }\n}\n"
  },
  {
    "path": "modules/yugabytedb/src/main/java/org/testcontainers/containers/YugabyteDBYSQLContainer.java",
    "content": "package org.testcontainers.containers;\n\nimport org.testcontainers.containers.strategy.YugabyteDBYSQLWaitStrategy;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.time.Duration;\nimport java.util.Collections;\nimport java.util.Set;\n\n/**\n * Testcontainers implementation for YugabyteDB YSQL API.\n * <p>\n * Supported image: {@code yugabytedb/yugabyte}\n * <p>\n * Exposed ports:\n * <ul>\n *     <li>YSQL: 5433</li>\n *     <li>Master dashboard: 7000</li>\n *     <li>Tserver dashboard: 9000</li>\n * </ul>\n *\n * @see <a href=\"https://docs.yugabyte.com/stable/api/ysql/\">YSQL API</a>\n */\npublic class YugabyteDBYSQLContainer extends JdbcDatabaseContainer<YugabyteDBYSQLContainer> {\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"yugabytedb/yugabyte\");\n\n    private static final Integer YSQL_PORT = 5433;\n\n    private static final Integer MASTER_DASHBOARD_PORT = 7000;\n\n    private static final Integer TSERVER_DASHBOARD_PORT = 9000;\n\n    private static final String JDBC_DRIVER_CLASS = \"com.yugabyte.Driver\";\n\n    private static final String JDBC_CONNECT_PREFIX = \"jdbc:yugabytedb\";\n\n    private static final String ENTRYPOINT = \"bin/yugabyted start --background=false\";\n\n    private String database = \"yugabyte\";\n\n    private String username = \"yugabyte\";\n\n    private String password = \"yugabyte\";\n\n    /**\n     * @param imageName image name\n     */\n    public YugabyteDBYSQLContainer(final String imageName) {\n        this(DockerImageName.parse(imageName));\n    }\n\n    /**\n     * @param imageName image name\n     */\n    public YugabyteDBYSQLContainer(final DockerImageName imageName) {\n        super(imageName);\n        imageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);\n        withExposedPorts(YSQL_PORT, MASTER_DASHBOARD_PORT, TSERVER_DASHBOARD_PORT);\n        waitingFor(new YugabyteDBYSQLWaitStrategy(this).withStartupTimeout(Duration.ofSeconds(60)));\n        withCommand(ENTRYPOINT);\n    }\n\n    @Override\n    public Set<Integer> getLivenessCheckPortNumbers() {\n        return Collections.singleton(getMappedPort(YSQL_PORT));\n    }\n\n    /**\n     * Configures the environment variables. Setting up these variables would create the\n     * custom objects. Setting {@link #withDatabaseName(String)},\n     * {@link #withUsername(String)}, {@link #withPassword(String)} these parameters will\n     * initialize the database with those custom values\n     */\n\n    @Override\n    protected void configure() {\n        addEnv(\"YSQL_DB\", database);\n        addEnv(\"YSQL_USER\", username);\n        addEnv(\"YSQL_PASSWORD\", password);\n    }\n\n    @Override\n    public String getDriverClassName() {\n        return JDBC_DRIVER_CLASS;\n    }\n\n    @Override\n    public String getJdbcUrl() {\n        return (\n            JDBC_CONNECT_PREFIX +\n            \"://\" +\n            getHost() +\n            \":\" +\n            getMappedPort(YSQL_PORT) +\n            \"/\" +\n            database +\n            constructUrlParameters(\"?\", \"&\")\n        );\n    }\n\n    @Override\n    public String getDatabaseName() {\n        return database;\n    }\n\n    @Override\n    public String getUsername() {\n        return username;\n    }\n\n    @Override\n    public String getPassword() {\n        return password;\n    }\n\n    @Override\n    public String getTestQueryString() {\n        return \"SELECT 1\";\n    }\n\n    /**\n     * Setting this would create the keyspace\n     * @param database database name\n     * @return {@link YugabyteDBYSQLContainer} instance\n     */\n\n    @Override\n    public YugabyteDBYSQLContainer withDatabaseName(final String database) {\n        this.database = database;\n        return this;\n    }\n\n    /**\n     * Setting this would create the custom user role\n     * @param username user name\n     * @return {@link YugabyteDBYSQLContainer} instance\n     */\n\n    @Override\n    public YugabyteDBYSQLContainer withUsername(final String username) {\n        this.username = username;\n        return this;\n    }\n\n    /**\n     * Setting this along with {@link #withUsername(String)} would enable authentication\n     * @param password password\n     * @return {@link YugabyteDBYSQLContainer} instance\n     */\n\n    @Override\n    public YugabyteDBYSQLContainer withPassword(final String password) {\n        this.password = password;\n        return this;\n    }\n\n    @Override\n    protected void waitUntilContainerStarted() {\n        getWaitStrategy().waitUntilReady(this);\n    }\n}\n"
  },
  {
    "path": "modules/yugabytedb/src/main/java/org/testcontainers/containers/YugabyteDBYSQLContainerProvider.java",
    "content": "package org.testcontainers.containers;\n\nimport org.testcontainers.jdbc.ConnectionUrl;\nimport org.testcontainers.utility.DockerImageName;\n\n/**\n * YugabyteDB YSQL (Structured Query Language) JDBC container provider\n */\npublic class YugabyteDBYSQLContainerProvider extends JdbcDatabaseContainerProvider {\n\n    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse(\"yugabytedb/yugabyte\");\n\n    private static final String DEFAULT_TAG = \"2.14.4.0-b26\";\n\n    private static final String NAME = \"yugabyte\";\n\n    private static final String USER_PARAM = \"user\";\n\n    private static final String PASSWORD_PARAM = \"password\";\n\n    @Override\n    public boolean supports(String databaseType) {\n        return databaseType.equals(NAME);\n    }\n\n    @Override\n    public JdbcDatabaseContainer newInstance() {\n        return newInstance(DEFAULT_TAG);\n    }\n\n    @Override\n    public JdbcDatabaseContainer newInstance(String tag) {\n        return new YugabyteDBYSQLContainer(DEFAULT_IMAGE_NAME.withTag(tag));\n    }\n\n    @Override\n    public JdbcDatabaseContainer newInstance(ConnectionUrl connectionUrl) {\n        return newInstanceFromConnectionUrl(connectionUrl, USER_PARAM, PASSWORD_PARAM);\n    }\n}\n"
  },
  {
    "path": "modules/yugabytedb/src/main/java/org/testcontainers/containers/delegate/AbstractYCQLDelegate.java",
    "content": "package org.testcontainers.containers.delegate;\n\nimport org.testcontainers.delegate.DatabaseDelegate;\n\n/**\n * An abstract delegate do-nothing class\n */\npublic abstract class AbstractYCQLDelegate implements DatabaseDelegate {\n\n    @Override\n    public void execute(\n        String statement,\n        String scriptPath,\n        int lineNumber,\n        boolean continueOnError,\n        boolean ignoreFailedDrops\n    ) {\n        // do nothing\n    }\n\n    @Override\n    public void close() {\n        // do nothing\n    }\n}\n"
  },
  {
    "path": "modules/yugabytedb/src/main/java/org/testcontainers/containers/delegate/YugabyteDBYCQLDelegate.java",
    "content": "package org.testcontainers.containers.delegate;\n\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.apache.commons.lang3.StringUtils;\nimport org.testcontainers.containers.Container.ExecResult;\nimport org.testcontainers.containers.YugabyteDBYCQLContainer;\nimport org.testcontainers.ext.ScriptUtils.UncategorizedScriptException;\n\nimport java.util.Collection;\n\n/**\n * Query execution delegate class for YCQL API to delegate init-script statements. This\n * invokes the in-built <code>ycqlsh</code> cli within the container to execute the\n * statements at one go. It is recommended to use frameworks such as liquibase to manage\n * this requirement. This functionality is kept to address the initialization requirements\n * from standalone services that can't leverage liquibase or something similar.\n *\n * @see YugabyteDBYCQLContainer\n */\n@RequiredArgsConstructor\n@Slf4j\npublic final class YugabyteDBYCQLDelegate extends AbstractYCQLDelegate {\n\n    private static final String BIN_PATH = \"/home/yugabyte/tserver/bin/ycqlsh\";\n\n    private final YugabyteDBYCQLContainer container;\n\n    @Override\n    public void execute(\n        Collection<String> statements,\n        String scriptPath,\n        boolean continueOnError,\n        boolean ignoreFailedDrops\n    ) {\n        final String containerInterfaceIP = container\n            .getContainerInfo()\n            .getNetworkSettings()\n            .getNetworks()\n            .entrySet()\n            .stream()\n            .findFirst()\n            .get()\n            .getValue()\n            .getIpAddress();\n        try {\n            ExecResult result = container.execInContainer(\n                BIN_PATH,\n                containerInterfaceIP,\n                \"-u\",\n                container.getUsername(),\n                \"-p\",\n                container.getPassword(),\n                \"-k\",\n                container.getKeyspace(),\n                \"-e\",\n                StringUtils.join(statements, \";\")\n            );\n            if (result.getExitCode() != 0) {\n                throw new RuntimeException(result.getStderr());\n            }\n        } catch (Exception e) {\n            log.debug(e.getMessage(), e);\n            throw new UncategorizedScriptException(e.getMessage(), e);\n        }\n    }\n}\n"
  },
  {
    "path": "modules/yugabytedb/src/main/java/org/testcontainers/containers/strategy/YugabyteDBYCQLWaitStrategy.java",
    "content": "package org.testcontainers.containers.strategy;\n\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.testcontainers.containers.Container.ExecResult;\nimport org.testcontainers.containers.YugabyteDBYCQLContainer;\nimport org.testcontainers.containers.wait.strategy.AbstractWaitStrategy;\nimport org.testcontainers.containers.wait.strategy.WaitStrategyTarget;\n\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\nimport static org.rnorth.ducttape.unreliables.Unreliables.retryUntilSuccess;\n\n/**\n * Custom wait strategy for YCQL API.\n *\n * <p>\n * Though we can either use HTTP or PORT based wait strategy, when we create a custom\n * keyspace/role, it gets executed asynchronously. As the wait on container.start() on a\n * specific port wouldn't fully guarantee the custom object execution. It's better to\n * check the DB status with this way with a smoke test query that uses the underlying\n * custom objects and wait for the operation to complete.\n * </p>\n */\n@RequiredArgsConstructor\n@Slf4j\npublic final class YugabyteDBYCQLWaitStrategy extends AbstractWaitStrategy {\n\n    private static final String YCQL_TEST_QUERY = \"SELECT release_version FROM system.local\";\n\n    private static final String BIN_PATH = \"/home/yugabyte/tserver/bin/ycqlsh\";\n\n    private final WaitStrategyTarget target;\n\n    @Override\n    public void waitUntilReady(WaitStrategyTarget target) {\n        YugabyteDBYCQLContainer container = (YugabyteDBYCQLContainer) target;\n        AtomicBoolean status = new AtomicBoolean(true);\n        final String containerInterfaceIP = container\n            .getContainerInfo()\n            .getNetworkSettings()\n            .getNetworks()\n            .entrySet()\n            .stream()\n            .findFirst()\n            .get()\n            .getValue()\n            .getIpAddress();\n        retryUntilSuccess(\n            (int) startupTimeout.getSeconds(),\n            TimeUnit.SECONDS,\n            () -> {\n                YugabyteDBYCQLWaitStrategy.this.getRateLimiter()\n                    .doWhenReady(() -> {\n                        try {\n                            ExecResult result = container.execInContainer(\n                                BIN_PATH,\n                                containerInterfaceIP,\n                                \"-u\",\n                                container.getUsername(),\n                                \"-p\",\n                                container.getPassword(),\n                                \"-k\",\n                                container.getKeyspace(),\n                                \"-e\",\n                                YCQL_TEST_QUERY\n                            );\n                            if (result.getExitCode() != 0) {\n                                status.set(false);\n                                log.debug(result.getStderr());\n                            }\n                        } catch (Exception e) {\n                            status.set(false);\n                            log.debug(e.getMessage(), e);\n                        } finally {\n                            if (!status.getAndSet(true)) {\n                                throw new RuntimeException(\"container hasn't come up yet\");\n                            }\n                        }\n                    });\n                return status;\n            }\n        );\n    }\n\n    @Override\n    public void waitUntilReady() {\n        waitUntilReady(target);\n    }\n}\n"
  },
  {
    "path": "modules/yugabytedb/src/main/java/org/testcontainers/containers/strategy/YugabyteDBYSQLWaitStrategy.java",
    "content": "package org.testcontainers.containers.strategy;\n\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.testcontainers.containers.YugabyteDBYSQLContainer;\nimport org.testcontainers.containers.wait.strategy.AbstractWaitStrategy;\nimport org.testcontainers.containers.wait.strategy.WaitStrategyTarget;\n\nimport java.sql.Connection;\nimport java.sql.SQLException;\nimport java.sql.Statement;\nimport java.util.concurrent.TimeUnit;\n\nimport static org.rnorth.ducttape.unreliables.Unreliables.retryUntilSuccess;\n\n/**\n * Custom wait strategy for YSQL API.\n *\n * <p>\n * Though we can either use HTTP or PORT based wait strategy, when we create a custom\n * database/role, it gets executed asynchronously. As the wait on container.start() on a\n * specific port wouldn't fully guarantee the custom object execution. It's better to\n * check the DB status with this way with a smoke test query that uses the underlying\n * custom objects and wait for the operation to complete.\n * </p>\n */\n@RequiredArgsConstructor\n@Slf4j\npublic final class YugabyteDBYSQLWaitStrategy extends AbstractWaitStrategy {\n\n    private final WaitStrategyTarget target;\n\n    private static final String YSQL_EXTENDED_PROBE =\n        \"CREATE TABLE IF NOT EXISTS YB_SAMPLE(k int, v int, primary key(k, v))\";\n\n    private static final String YSQL_EXTENDED_PROBE_DROP_TABLE = \"DROP TABLE IF EXISTS YB_SAMPLE\";\n\n    @Override\n    public void waitUntilReady(WaitStrategyTarget target) {\n        YugabyteDBYSQLContainer container = (YugabyteDBYSQLContainer) target;\n        retryUntilSuccess(\n            (int) startupTimeout.getSeconds(),\n            TimeUnit.SECONDS,\n            () -> {\n                getRateLimiter()\n                    .doWhenReady(() -> {\n                        try (Connection con = container.createConnection(\"\"); Statement stmt = con.createStatement()) {\n                            stmt.execute(YSQL_EXTENDED_PROBE);\n                            stmt.execute(YSQL_EXTENDED_PROBE_DROP_TABLE);\n                        } catch (SQLException ex) {\n                            throw new RuntimeException(ex);\n                        }\n                    });\n                return true;\n            }\n        );\n    }\n\n    @Override\n    public void waitUntilReady() {\n        waitUntilReady(target);\n    }\n}\n"
  },
  {
    "path": "modules/yugabytedb/src/main/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider",
    "content": "org.testcontainers.containers.YugabyteDBYSQLContainerProvider\n"
  },
  {
    "path": "modules/yugabytedb/src/test/java/org/testcontainers/jdbc/yugabytedb/YugabyteDBYSQLJDBCDriverTest.java",
    "content": "package org.testcontainers.jdbc.yugabytedb;\n\nimport org.testcontainers.jdbc.AbstractJDBCDriverTest;\n\nimport java.util.Arrays;\nimport java.util.EnumSet;\n\n/**\n * YugabyteDB YSQL API JDBC connectivity driver test class\n */\nclass YugabyteDBYSQLJDBCDriverTest extends AbstractJDBCDriverTest {\n\n    public static Iterable<Object[]> data() {\n        return Arrays.asList(\n            new Object[][] {\n                {\n                    \"jdbc:tc:yugabyte://hostname/yugabyte?user=yugabyte&password=yugabyte\",\n                    EnumSet.noneOf(Options.class),\n                },\n            }\n        );\n    }\n}\n"
  },
  {
    "path": "modules/yugabytedb/src/test/java/org/testcontainers/junit/yugabytedb/YugabyteDBYCQLTest.java",
    "content": "package org.testcontainers.junit.yugabytedb;\n\nimport com.datastax.oss.driver.api.core.CqlSession;\nimport com.datastax.oss.driver.api.core.cql.ResultSet;\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.YugabyteDBYCQLContainer;\nimport org.testcontainers.utility.DockerImageName;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * YugabyteDB YCQL API unit test class\n */\nclass YugabyteDBYCQLTest {\n\n    private static final String IMAGE_NAME = \"yugabytedb/yugabyte:2.14.4.0-b26\";\n\n    private static final String IMAGE_NAME_2_18 = \"yugabytedb/yugabyte:2.18.3.0-b75\";\n\n    private static final DockerImageName YBDB_TEST_IMAGE = DockerImageName.parse(IMAGE_NAME);\n\n    @Test\n    void testSmoke() {\n        try (\n            // creatingYCQLContainer {\n            final YugabyteDBYCQLContainer ycqlContainer = new YugabyteDBYCQLContainer(\n                \"yugabytedb/yugabyte:2.14.4.0-b26\"\n            )\n                .withUsername(\"cassandra\")\n                .withPassword(\"cassandra\")\n            // }\n        ) {\n            ycqlContainer.start();\n            assertThat(performQuery(ycqlContainer, \"SELECT release_version FROM system.local\").wasApplied())\n                .as(\"A sample test query succeeds\")\n                .isTrue();\n        }\n    }\n\n    @Test\n    void testCustomKeyspace() {\n        String key = \"random\";\n        try (\n            final YugabyteDBYCQLContainer ycqlContainer = new YugabyteDBYCQLContainer(YBDB_TEST_IMAGE)\n                .withKeyspaceName(key)\n                .withUsername(\"cassandra\")\n                .withPassword(\"cassandra\")\n        ) {\n            ycqlContainer.start();\n            assertThat(\n                performQuery(\n                    ycqlContainer,\n                    \"SELECT keyspace_name FROM system_schema.keyspaces where keyspace_name='\" + key + \"'\"\n                )\n                    .one()\n                    .getString(0)\n            )\n                .as(\"Custom keyspace creation succeeds\")\n                .isEqualTo(key);\n        }\n    }\n\n    @Test\n    void testAuthenticationEnabled() {\n        String role = \"random\";\n        try (\n            final YugabyteDBYCQLContainer ycqlContainer = new YugabyteDBYCQLContainer(YBDB_TEST_IMAGE)\n                .withUsername(role)\n                .withPassword(role)\n        ) {\n            ycqlContainer.start();\n            assertThat(\n                performQuery(ycqlContainer, \"SELECT role FROM system_auth.roles where role='\" + role + \"'\")\n                    .one()\n                    .getString(0)\n            )\n                .as(\"Keyspace login with authentication enabled succeeds\")\n                .isEqualTo(role);\n        }\n    }\n\n    @Test\n    void testAuthenticationDisabled() {\n        try (\n            final YugabyteDBYCQLContainer ycqlContainer = new YugabyteDBYCQLContainer(YBDB_TEST_IMAGE)\n                .withPassword(\"cassandra\")\n                .withUsername(\"cassandra\")\n        ) {\n            ycqlContainer.start();\n            assertThat(performQuery(ycqlContainer, \"SELECT release_version FROM system.local\").wasApplied())\n                .as(\"Keyspace login with authentication disabled succeeds\")\n                .isTrue();\n        }\n    }\n\n    @Test\n    void testInitScript() {\n        String key = \"random\";\n        try (\n            final YugabyteDBYCQLContainer ycqlContainer = new YugabyteDBYCQLContainer(YBDB_TEST_IMAGE)\n                .withKeyspaceName(key)\n                .withUsername(key)\n                .withPassword(key)\n                .withInitScript(\"init/init_yql.sql\")\n        ) {\n            ycqlContainer.start();\n            ResultSet output = performQuery(ycqlContainer, \"SELECT greet FROM random.dsql\");\n            assertThat(output.wasApplied()).as(\"Statements from a custom script execution succeeds\").isTrue();\n            assertThat(output.one().getString(0)).as(\"A record match succeeds\").isEqualTo(\"Hello DSQL\");\n        }\n    }\n\n    @Test\n    void shouldStartWhenContainerIpIsUsedInWaitStrategy() {\n        try (\n            final YugabyteDBYCQLContainer ycqlContainer = new YugabyteDBYCQLContainer(IMAGE_NAME_2_18)\n                .withUsername(\"cassandra\")\n                .withPassword(\"cassandra\")\n        ) {\n            ycqlContainer.start();\n            boolean isQueryExecuted = performQuery(ycqlContainer, \"SELECT release_version FROM system.local\")\n                .wasApplied();\n            assertThat(isQueryExecuted).isTrue();\n        }\n    }\n\n    private ResultSet performQuery(YugabyteDBYCQLContainer ycqlContainer, String cql) {\n        try (\n            CqlSession session = CqlSession\n                .builder()\n                .withKeyspace(ycqlContainer.getKeyspace())\n                .withAuthCredentials(ycqlContainer.getUsername(), ycqlContainer.getPassword())\n                .withLocalDatacenter(ycqlContainer.getLocalDc())\n                .addContactPoint(ycqlContainer.getContactPoint())\n                .build()\n        ) {\n            return session.execute(cql);\n        }\n    }\n}\n"
  },
  {
    "path": "modules/yugabytedb/src/test/java/org/testcontainers/junit/yugabytedb/YugabyteDBYSQLTest.java",
    "content": "package org.testcontainers.junit.yugabytedb;\n\nimport org.junit.jupiter.api.Test;\nimport org.testcontainers.containers.YugabyteDBYSQLContainer;\nimport org.testcontainers.db.AbstractContainerDatabaseTest;\nimport org.testcontainers.utility.DockerImageName;\n\nimport java.sql.SQLException;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\n/**\n * YugabyteDB YSQL API unit test class\n */\nclass YugabyteDBYSQLTest extends AbstractContainerDatabaseTest {\n\n    private static final String IMAGE_NAME = \"yugabytedb/yugabyte:2.14.4.0-b26\";\n\n    private static final DockerImageName YBDB_TEST_IMAGE = DockerImageName.parse(IMAGE_NAME);\n\n    @Test\n    void testSmoke() throws SQLException {\n        try (\n            // creatingYSQLContainer {\n            final YugabyteDBYSQLContainer ysqlContainer = new YugabyteDBYSQLContainer(\n                \"yugabytedb/yugabyte:2.14.4.0-b26\"\n            )\n            // }\n        ) {\n            ysqlContainer.start();\n            assertThat(performQuery(ysqlContainer, \"SELECT 1\").getInt(1))\n                .as(\"A sample test query succeeds\")\n                .isEqualTo(1);\n        }\n    }\n\n    @Test\n    void testCustomDatabase() throws SQLException {\n        String key = \"random\";\n        try (\n            final YugabyteDBYSQLContainer ysqlContainer = new YugabyteDBYSQLContainer(YBDB_TEST_IMAGE)\n                .withDatabaseName(key)\n        ) {\n            ysqlContainer.start();\n            assertThat(performQuery(ysqlContainer, \"SELECT 1\").getInt(1))\n                .as(\"A test query on a custom database succeeds\")\n                .isEqualTo(1);\n        }\n    }\n\n    @Test\n    void testInitScript() throws SQLException {\n        try (\n            final YugabyteDBYSQLContainer ysqlContainer = new YugabyteDBYSQLContainer(YBDB_TEST_IMAGE)\n                .withInitScript(\"init/init_yql.sql\")\n        ) {\n            ysqlContainer.start();\n            assertThat(performQuery(ysqlContainer, \"SELECT greet FROM dsql\").getString(1))\n                .as(\"A record match succeeds\")\n                .isEqualTo(\"Hello DSQL\");\n        }\n    }\n\n    @Test\n    void testWithAdditionalUrlParamInJdbcUrl() {\n        try (\n            final YugabyteDBYSQLContainer ysqlContainer = new YugabyteDBYSQLContainer(YBDB_TEST_IMAGE)\n                .withUrlParam(\"sslmode\", \"disable\")\n                .withUrlParam(\"application_name\", \"yugabyte\")\n        ) {\n            ysqlContainer.start();\n            String jdbcUrl = ysqlContainer.getJdbcUrl();\n            assertThat(jdbcUrl)\n                .contains(\"?\")\n                .contains(\"&\")\n                .contains(\"sslmode=disable\")\n                .contains(\"application_name=yugabyte\")\n                .as(\"A JDBC connection string with additional parameter validation succeeds\");\n        }\n    }\n\n    @Test\n    void testWithCustomRole() throws SQLException {\n        try (\n            final YugabyteDBYSQLContainer ysqlContainer = new YugabyteDBYSQLContainer(YBDB_TEST_IMAGE)\n                .withDatabaseName(\"yugabyte\")\n                .withPassword(\"yugabyte\")\n                .withUsername(\"yugabyte\")\n        ) {\n            ysqlContainer.start();\n            assertThat(performQuery(ysqlContainer, \"SELECT 1\").getInt(1))\n                .as(\"A sample test query with a custom role succeeds\")\n                .isEqualTo(1);\n        }\n    }\n\n    @Test\n    void testWaitStrategy() throws SQLException {\n        try (final YugabyteDBYSQLContainer ysqlContainer = new YugabyteDBYSQLContainer(YBDB_TEST_IMAGE)) {\n            ysqlContainer.start();\n            assertThat(performQuery(ysqlContainer, \"SELECT 1\").getInt(1))\n                .as(\"A sample test query succeeds\")\n                .isEqualTo(1);\n            boolean tableExists = performQuery(\n                ysqlContainer,\n                \"SELECT EXISTS (SELECT FROM pg_tables WHERE tablename = 'YB_SAMPLE')\"\n            )\n                .getBoolean(1);\n            assertThat(tableExists).as(\"yb_sample table does not exists\").isFalse();\n        }\n    }\n}\n"
  },
  {
    "path": "modules/yugabytedb/src/test/resources/init/init_yql.sql",
    "content": "CREATE TABLE dsql(\n    greet text primary key\n);\n\nINSERT INTO dsql (greet) VALUES ('Hello DSQL');\n"
  },
  {
    "path": "modules/yugabytedb/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "requirements.txt",
    "content": "mkdocs==1.3.0\nmkdocs-codeinclude-plugin==0.2.0\nmkdocs-material==8.1.3\nmkdocs-markdownextradata-plugin==0.2.5\n"
  },
  {
    "path": "runtime.txt",
    "content": "3.8\n"
  },
  {
    "path": "settings.gradle",
    "content": "buildscript {\n    repositories {\n        maven {\n            url \"https://plugins.gradle.org/m2/\"\n        }\n    }\n    dependencies {\n        classpath \"com.gradle.enterprise:com.gradle.enterprise.gradle.plugin:3.19.2\"\n        classpath \"com.gradle:common-custom-user-data-gradle-plugin:2.4.0\"\n        classpath \"org.gradle.toolchains:foojay-resolver:0.8.0\"\n    }\n}\n\napply plugin: 'com.gradle.develocity'\napply plugin: \"com.gradle.common-custom-user-data-gradle-plugin\"\napply plugin: \"org.gradle.toolchains.foojay-resolver-convention\"\n\nrootProject.name = 'testcontainers-java'\n\ninclude \"bom\"\n\ninclude \"testcontainers\"\nproject(':testcontainers').projectDir = \"$rootDir/core\" as File\n\nfile('modules').eachDir { dir ->\n    include \"testcontainers-${dir.name}\"\n    project(\":testcontainers-${dir.name}\").projectDir = dir\n}\n\ninclude ':docs:examples:junit5:redis'\ninclude ':docs:examples:spock:redis'\n\ninclude 'test-support'\n\next.isCI = System.getenv(\"CI\") != null\n\nbuildCache {\n    local {\n        enabled = !isCI\n    }\n    remote(develocity.buildCache) {\n        push = isCI && !System.getenv(\"READ_ONLY_REMOTE_GRADLE_CACHE\") && System.getenv(\"DEVELOCITY_ACCESS_KEY\")\n        enabled = true\n    }\n}\n\ndevelocity {\n    buildScan {\n        server = \"https://ge.testcontainers.org/\"\n        publishing.onlyIf {\n            it.authenticated\n        }\n        uploadInBackground = !isCI\n        capture.fileFingerprints = true\n    }\n\n}\n"
  },
  {
    "path": "smoke-test/build.gradle",
    "content": "// empty build.gradle for dependabot\nplugins {\n    id 'com.diffplug.spotless' version '6.22.0' apply false\n}\n\napply from: \"$rootDir/../gradle/ci-support.gradle\"\n\nsubprojects {\n    apply plugin: \"java\"\n    apply from: \"$rootDir/../gradle/spotless.gradle\"\n    apply plugin: 'checkstyle'\n\n    repositories {\n        mavenCentral()\n    }\n\n    test {\n        defaultCharacterEncoding = \"UTF-8\"\n        testLogging {\n            displayGranularity 1\n            showStackTraces = true\n            exceptionFormat = 'full'\n            events \"STARTED\", \"PASSED\", \"FAILED\", \"SKIPPED\"\n        }\n    }\n\n    checkstyle {\n        toolVersion = \"10.23.0\"\n        configFile = rootProject.file('../config/checkstyle/checkstyle.xml')\n    }\n}\n"
  },
  {
    "path": "smoke-test/gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionSha256Sum=bd71102213493060956ec229d946beee57158dbd89d0e62b91bca0fa2c5f3531\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-8.14.3-bin.zip\nnetworkTimeout=10000\nvalidateDistributionUrl=true\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "smoke-test/gradlew",
    "content": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n# SPDX-License-Identifier: Apache-2.0\n#\n\n##############################################################################\n#\n#   Gradle start up script for POSIX generated by Gradle.\n#\n#   Important for running:\n#\n#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n#       noncompliant, but you have some other compliant shell such as ksh or\n#       bash, then to run this script, type that shell name before the whole\n#       command line, like:\n#\n#           ksh Gradle\n#\n#       Busybox and similar reduced shells will NOT work, because this script\n#       requires all of these POSIX shell features:\n#         * functions;\n#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n#         * compound commands having a testable exit status, especially «case»;\n#         * various built-in commands including «command», «set», and «ulimit».\n#\n#   Important for patching:\n#\n#   (2) This script targets any POSIX shell, so it avoids extensions provided\n#       by Bash, Ksh, etc; in particular arrays are avoided.\n#\n#       The \"traditional\" practice of packing multiple parameters into a\n#       space-separated string is a well documented source of bugs and security\n#       problems, so this is (mostly) avoided, by progressively accumulating\n#       options in \"$@\", and eventually passing that to Java.\n#\n#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n#       see the in-line comments for details.\n#\n#       There are tweaks for specific operating systems such as AIX, CygWin,\n#       Darwin, MinGW, and NonStop.\n#\n#   (3) This script is generated from the Groovy template\n#       https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n#       within the Gradle project.\n#\n#       You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n    APP_HOME=${app_path%\"${app_path##*/}\"}  # leaves a trailing /; empty if no leading path\n    [ -h \"$app_path\" ]\ndo\n    ls=$( ls -ld \"$app_path\" )\n    link=${ls#*' -> '}\n    case $link in             #(\n      /*)   app_path=$link ;; #(\n      *)    app_path=$APP_HOME$link ;;\n    esac\ndone\n\n# This is normally unused\n# shellcheck disable=SC2034\nAPP_BASE_NAME=${0##*/}\n# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)\nAPP_HOME=$( cd -P \"${APP_HOME:-./}\" > /dev/null && printf '%s\\n' \"$PWD\" ) || exit\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n    echo \"$*\"\n} >&2\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in                #(\n  CYGWIN* )         cygwin=true  ;; #(\n  Darwin* )         darwin=true  ;; #(\n  MSYS* | MINGW* )  msys=true    ;; #(\n  NONSTOP* )        nonstop=true ;;\nesac\n\nCLASSPATH=\"\\\\\\\"\\\\\\\"\"\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=$JAVA_HOME/jre/sh/java\n    else\n        JAVACMD=$JAVA_HOME/bin/java\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=java\n    if ! command -v java >/dev/null 2>&1\n    then\n        die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n    case $MAX_FD in #(\n      max*)\n        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        MAX_FD=$( ulimit -H -n ) ||\n            warn \"Could not query maximum file descriptor limit\"\n    esac\n    case $MAX_FD in  #(\n      '' | soft) :;; #(\n      *)\n        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        ulimit -n \"$MAX_FD\" ||\n            warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n    esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n#   * args from the command line\n#   * the main class name\n#   * -classpath\n#   * -D...appname settings\n#   * --module-path (only if needed)\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n    APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n    CLASSPATH=$( cygpath --path --mixed \"$CLASSPATH\" )\n\n    JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    for arg do\n        if\n            case $arg in                                #(\n              -*)   false ;;                            # don't mess with options #(\n              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath\n                    [ -e \"$t\" ] ;;                      #(\n              *)    false ;;\n            esac\n        then\n            arg=$( cygpath --path --ignore --mixed \"$arg\" )\n        fi\n        # Roll the args list around exactly as many times as the number of\n        # args, so each arg winds up back in the position where it started, but\n        # possibly modified.\n        #\n        # NB: a `for` loop captures its iteration list before it begins, so\n        # changing the positional parameters here affects neither the number of\n        # iterations, nor the values presented in `arg`.\n        shift                   # remove old arg\n        set -- \"$@\" \"$arg\"      # push replacement arg\n    done\nfi\n\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Collect all arguments for the java command:\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,\n#     and any embedded shellness will be escaped.\n#   * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be\n#     treated as '${Hostname}' itself on the command line.\n\nset -- \\\n        \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n        -classpath \"$CLASSPATH\" \\\n        -jar \"$APP_HOME/gradle/wrapper/gradle-wrapper.jar\" \\\n        \"$@\"\n\n# Stop when \"xargs\" is not available.\nif ! command -v xargs >/dev/null 2>&1\nthen\n    die \"xargs is not available\"\nfi\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n#   readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n#   set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n        printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n        xargs -n1 |\n        sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n        tr '\\n' ' '\n    )\" '\"$@\"'\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "smoke-test/gradlew.bat",
    "content": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\r\n@rem you may not use this file except in compliance with the License.\r\n@rem You may obtain a copy of the License at\r\n@rem\r\n@rem      https://www.apache.org/licenses/LICENSE-2.0\r\n@rem\r\n@rem Unless required by applicable law or agreed to in writing, software\r\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\r\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n@rem See the License for the specific language governing permissions and\r\n@rem limitations under the License.\r\n@rem\r\n@rem SPDX-License-Identifier: Apache-2.0\r\n@rem\r\n\r\n@if \"%DEBUG%\"==\"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\"==\"\" set DIRNAME=.\r\n@rem This is normally unused\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\r\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif %ERRORLEVEL% equ 0 goto execute\r\n\r\necho. 1>&2\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2\r\necho. 1>&2\r\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\r\necho location of your Java installation. 1>&2\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto execute\r\n\r\necho. 1>&2\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2\r\necho. 1>&2\r\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\r\necho location of your Java installation. 1>&2\r\n\r\ngoto fail\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=\r\n\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" -jar \"%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\" %*\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif %ERRORLEVEL% equ 0 goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nset EXIT_CODE=%ERRORLEVEL%\r\nif %EXIT_CODE% equ 0 set EXIT_CODE=1\r\nif not \"\"==\"%GRADLE_EXIT_CONSOLE%\" exit %EXIT_CODE%\r\nexit /b %EXIT_CODE%\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "smoke-test/settings.gradle",
    "content": "buildscript {\n    repositories {\n        maven {\n            url \"https://plugins.gradle.org/m2/\"\n        }\n    }\n    dependencies {\n        classpath \"gradle.plugin.ch.myniva.gradle:s3-build-cache:0.10.0\"\n        classpath \"com.gradle.enterprise:com.gradle.enterprise.gradle.plugin:3.17.4\"\n        classpath \"com.gradle:common-custom-user-data-gradle-plugin:2.0.1\"\n    }\n}\n\napply plugin: 'com.gradle.develocity'\napply plugin: \"com.gradle.common-custom-user-data-gradle-plugin\"\n\nrootProject.name = 'testcontainers-smoke-tests'\n\nincludeBuild '..'\n\n// explicit include to allow Dependabot to autodiscover subprojects\ninclude 'turbo-mode'\n\next.isCI = System.getenv(\"CI\") != null\n\nbuildCache {\n    local {\n        enabled = !isCI\n    }\n    remote(develocity.buildCache) {\n        push = isCI && !System.getenv(\"READ_ONLY_REMOTE_GRADLE_CACHE\") && System.getenv(\"DEVELOCITY_ACCESS_KEY\")\n        enabled = true\n    }\n}\n\ndevelocity {\n    buildScan {\n        server = \"https://ge.testcontainers.org/\"\n        publishing.onlyIf {\n            it.authenticated\n        }\n        uploadInBackground = !isCI\n        capture.fileFingerprints = true\n    }\n\n}\n"
  },
  {
    "path": "smoke-test/turbo-mode/build.gradle",
    "content": "plugins {\n    id 'java'\n}\n\ndependencies {\n    testImplementation 'org.testcontainers:testcontainers'\n    testImplementation 'org.junit.jupiter:junit-jupiter:5.13.4'\n    testImplementation 'ch.qos.logback:logback-classic:1.3.15'\n    testImplementation 'org.assertj:assertj-core:3.27.4'\n    testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.13.3'\n}\n\ntest {\n    useJUnitPlatform()\n    forkEvery = 1\n    maxParallelForks = 4\n}\n"
  },
  {
    "path": "smoke-test/turbo-mode/src/test/java/org/testcontainers/example/AbstractRedisContainer.java",
    "content": "package org.testcontainers.example;\n\nimport org.testcontainers.containers.GenericContainer;\n\nimport static org.assertj.core.api.Assertions.assertThat;\n\nclass AbstractRedisContainer {\n\n    private static final String REDIS_IMAGE = \"redis:7.0.12-alpine\";\n\n    void runRedisContainer() {\n        try (\n            GenericContainer<?> redis = new GenericContainer<>(REDIS_IMAGE)\n                .withExposedPorts(6379)\n                .withCreateContainerCmdModifier(cmd -> cmd.withName(\"tc-redis\"))\n        ) {\n            redis.start();\n            assertThat(redis.isRunning()).isTrue();\n        }\n    }\n}\n"
  },
  {
    "path": "smoke-test/turbo-mode/src/test/java/org/testcontainers/example/RedisContainer1Test.java",
    "content": "package org.testcontainers.example;\n\nimport org.junit.jupiter.api.Test;\n\nclass RedisContainer1Test extends AbstractRedisContainer {\n\n    @Test\n    void testSimple() {\n        runRedisContainer();\n    }\n}\n"
  },
  {
    "path": "smoke-test/turbo-mode/src/test/java/org/testcontainers/example/RedisContainer2Test.java",
    "content": "package org.testcontainers.example;\n\nimport org.junit.jupiter.api.Test;\n\nclass RedisContainer2Test extends AbstractRedisContainer {\n\n    @Test\n    void testSimple() {\n        runRedisContainer();\n    }\n}\n"
  },
  {
    "path": "smoke-test/turbo-mode/src/test/java/org/testcontainers/example/RedisContainer3Test.java",
    "content": "package org.testcontainers.example;\n\nimport org.junit.jupiter.api.Test;\n\nclass RedisContainer3Test extends AbstractRedisContainer {\n\n    @Test\n    void testSimple() {\n        runRedisContainer();\n    }\n}\n"
  },
  {
    "path": "smoke-test/turbo-mode/src/test/java/org/testcontainers/example/RedisContainer4Test.java",
    "content": "package org.testcontainers.example;\n\nimport org.junit.jupiter.api.Test;\n\nclass RedisContainer4Test extends AbstractRedisContainer {\n\n    @Test\n    void testSimple() {\n        runRedisContainer();\n    }\n}\n"
  },
  {
    "path": "smoke-test/turbo-mode/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"INFO\"/>\n</configuration>\n"
  },
  {
    "path": "test-support/build.gradle",
    "content": "dependencies {\n    implementation 'junit:junit:4.13.2'\n    implementation 'org.slf4j:slf4j-api:2.0.17'\n    testImplementation 'org.assertj:assertj-core:3.27.4'\n}\n"
  },
  {
    "path": "test-support/src/main/java/org/testcontainers/testsupport/Flaky.java",
    "content": "package org.testcontainers.testsupport;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * Annotation for test methods that should be retried in the event of failure. See {@link FlakyTestJUnit4RetryRule} for\n * more details.\n */\n@Retention(RetentionPolicy.RUNTIME)\n@Target({ ElementType.METHOD })\npublic @interface Flaky {\n    /**\n     * @return a URL for a GitHub issue where this flaky test can be discussed, and where actions to resolve it can be\n     * coordinated.\n     */\n    String githubIssueUrl();\n\n    /**\n     * @return a date at which this should be reviewed, in {@link java.time.format.DateTimeFormatter#ISO_LOCAL_DATE}\n     * format (e.g. {@code 2020-12-03}). Now + 3 months is suggested. Once this date has passed, retries will no longer\n     * be applied.\n     */\n    String reviewDate();\n\n    /**\n     * @return the total number of times to try running this test (default 3)\n     */\n    int maxTries() default 3;\n}\n"
  },
  {
    "path": "test-support/src/main/java/org/testcontainers/testsupport/FlakyTestJUnit4RetryRule.java",
    "content": "package org.testcontainers.testsupport;\n\nimport lombok.extern.slf4j.Slf4j;\nimport org.junit.rules.TestRule;\nimport org.junit.runner.Description;\nimport org.junit.runners.model.MultipleFailureException;\nimport org.junit.runners.model.Statement;\n\nimport java.time.LocalDate;\nimport java.time.format.DateTimeParseException;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * <p>\n *     JUnit 4 @Rule that implements retry for flaky tests (tests that suffer from sporadic random failures).\n * </p>\n * <p>\n *     This rule should be used in conjunction with the @{@link Flaky} annotation. When this Rule is applied to a test\n *     class, any test method with this annotation will be invoked up to 3 times or until it succeeds.\n * </p>\n * <p>\n *     Tests should <em>not</em> be marked @{@link Flaky} for a long period of time. Every usage should be\n *     accompanied by a GitHub issue URL, and should be subject to review at a suitable point in the (near) future.\n *     Should the review date pass without the test's instability being fixed, the retry behaviour will cease to have an\n *     effect and the test will be allowed to sporadically fail again.\n * </p>\n */\n@Slf4j\npublic class FlakyTestJUnit4RetryRule implements TestRule {\n\n    @Override\n    public Statement apply(Statement base, Description description) {\n        final Flaky annotation = description.getAnnotation(Flaky.class);\n\n        if (annotation == null) {\n            // leave the statement as-is\n            return base;\n        }\n\n        if (annotation.githubIssueUrl().trim().length() == 0) {\n            throw new IllegalArgumentException(\"A GitHub issue URL must be set for usages of the @Flaky annotation\");\n        }\n\n        final int maxTries = annotation.maxTries();\n\n        if (maxTries < 1) {\n            throw new IllegalArgumentException(\"@Flaky annotation maxTries must be at least one\");\n        }\n\n        final LocalDate reviewDate;\n        try {\n            reviewDate = LocalDate.parse(annotation.reviewDate());\n        } catch (DateTimeParseException e) {\n            throw new IllegalArgumentException(\n                \"@Flaky reviewDate could not be parsed. Please provide a date in yyyy-mm-dd format\"\n            );\n        }\n\n        // the annotation should only have an effect before the review date, to encourage review and resolution\n        if (LocalDate.now().isBefore(reviewDate)) {\n            return new RetryingStatement(base, description, maxTries);\n        } else {\n            return base;\n        }\n    }\n\n    private static class RetryingStatement extends Statement {\n\n        private final Statement base;\n\n        private final Description description;\n\n        private final int maxTries;\n\n        RetryingStatement(Statement base, Description description, int maxTries) {\n            this.base = base;\n            this.description = description;\n            this.maxTries = maxTries;\n        }\n\n        @Override\n        public void evaluate() {\n            int attempts = 0;\n            final List<Throwable> causes = new ArrayList<>();\n\n            while (++attempts <= maxTries) {\n                try {\n                    base.evaluate();\n                    return;\n                } catch (Throwable throwable) {\n                    log.warn(\"Retrying @Flaky-annotated test: {}\", description.getDisplayName());\n                    causes.add(throwable);\n                }\n            }\n\n            throw new IllegalStateException(\n                \"@Flaky-annotated test failed despite retries.\",\n                new MultipleFailureException(causes)\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "test-support/src/test/java/org/testcontainers/testsupport/FlakyRuleTest.java",
    "content": "package org.testcontainers.testsupport;\n\nimport org.junit.Test;\nimport org.junit.runner.Description;\nimport org.junit.runners.model.Statement;\n\nimport java.lang.annotation.Annotation;\n\nimport static org.assertj.core.api.Assertions.assertThat;\nimport static org.assertj.core.api.Assertions.fail;\n\npublic class FlakyRuleTest {\n\n    private static final String VALID_URL = \"http://some.url/here\";\n\n    private static final String INVALID_URL = \"\";\n\n    private static final String VALID_DATE_IN_FAR_FUTURE = \"2063-04-05\";\n\n    private static final String VALID_DATE_IN_PAST = \"1991-08-16\";\n\n    private static final String INVALID_DATE = \"91-01-45\";\n\n    private static final int DEFAULT_TRIES = 3;\n\n    private FlakyTestJUnit4RetryRule rule = new FlakyTestJUnit4RetryRule();\n\n    @Test\n    public void testIgnoresMethodWithoutAnnotation() throws Throwable {\n        final Description description = newDescriptionWithoutAnnotation();\n        final DummyStatement statement = newStatement(3);\n        try {\n            rule.apply(statement, description).evaluate();\n            fail(\"Should not reach here\");\n        } catch (Exception ignored) {}\n        assertThat(statement.invocationCount)\n            .as(\"The statement should only be invoked once, even if it throws\")\n            .isEqualTo(1);\n    }\n\n    @Test\n    public void testRetriesMethodWithAnnotationUntilFailure() throws Throwable {\n        final Description description = newDescriptionWithAnnotation();\n\n        final DummyStatement statement = newStatement(3);\n        try {\n            rule.apply(statement, description).evaluate();\n            fail(\"Should not reach here\");\n        } catch (Exception ignored) {}\n        assertThat(statement.invocationCount).as(\"The statement should be invoked three times\").isEqualTo(3);\n    }\n\n    @Test\n    public void testCustomRetryCount() throws Throwable {\n        final Description description = newDescriptionWithAnnotationAndCustomTries(10);\n\n        final DummyStatement statement = newStatement(10);\n        try {\n            rule.apply(statement, description).evaluate();\n            fail(\"Should not reach here\");\n        } catch (Exception ignored) {}\n        assertThat(statement.invocationCount).as(\"The statement should be invoked ten times\").isEqualTo(10);\n    }\n\n    @Test\n    public void testRetriesMethodWithAnnotationUntilSuccess() throws Throwable {\n        final Description description = newDescriptionWithAnnotation();\n\n        final DummyStatement statement = newStatement(2);\n\n        rule.apply(statement, description).evaluate();\n\n        assertThat(statement.invocationCount)\n            .as(\"The statement should be invoked three times, and succeed the third time\")\n            .isEqualTo(3);\n    }\n\n    @Test\n    public void testDoesNotRetryMethodWithAnnotationIfNotThrowing() throws Throwable {\n        final Description description = newDescriptionWithAnnotation();\n\n        final DummyStatement statement = newStatement(0);\n\n        rule.apply(statement, description).evaluate();\n\n        assertThat(statement.invocationCount).as(\"The statement should be invoked once\").isEqualTo(1);\n    }\n\n    @Test\n    public void testTreatsExpiredAnnotationAsNoAnnotation() throws Throwable {\n        final Description description = newDescriptionWithExpiredAnnotation();\n        final DummyStatement statement = newStatement(3);\n        try {\n            rule.apply(statement, description).evaluate();\n            fail(\"Should not reach here\");\n        } catch (Exception ignored) {}\n        assertThat(statement.invocationCount)\n            .as(\"The statement should only be invoked once, even if it throws\")\n            .isEqualTo(1);\n    }\n\n    @Test\n    public void testThrowsOnInvalidDateFormat() throws Throwable {\n        final Description description = newDescriptionWithAnnotation(INVALID_DATE, VALID_URL);\n        final DummyStatement statement = newStatement(3);\n        try {\n            rule.apply(statement, description).evaluate();\n            fail(\"Should not reach here\");\n        } catch (IllegalArgumentException ignored) {}\n        assertThat(statement.invocationCount)\n            .as(\"The statement should not be invoked if the annotation is invalid\")\n            .isEqualTo(0);\n    }\n\n    @Test\n    public void testThrowsOnInvalidGitHubUrl() throws Throwable {\n        final Description description = newDescriptionWithAnnotation(VALID_DATE_IN_FAR_FUTURE, INVALID_URL);\n        final DummyStatement statement = newStatement(3);\n        try {\n            rule.apply(statement, description).evaluate();\n            fail(\"Should not reach here\");\n        } catch (IllegalArgumentException ignored) {}\n        assertThat(statement.invocationCount)\n            .as(\"The statement should not be invoked if the annotation is invalid\")\n            .isEqualTo(0);\n    }\n\n    private Description newDescriptionWithAnnotation(String reviewDate, String gitHubUrl) {\n        return Description.createTestDescription(\n            \"SomeTestClass\",\n            \"someMethod\",\n            newAnnotation(reviewDate, gitHubUrl, DEFAULT_TRIES)\n        );\n    }\n\n    private Description newDescriptionWithoutAnnotation() {\n        return Description.createTestDescription(\"SomeTestClass\", \"someMethod\");\n    }\n\n    private Description newDescriptionWithAnnotation() {\n        return Description.createTestDescription(\n            \"SomeTestClass\",\n            \"someMethod\",\n            newAnnotation(VALID_DATE_IN_FAR_FUTURE, VALID_URL, DEFAULT_TRIES)\n        );\n    }\n\n    private Description newDescriptionWithAnnotationAndCustomTries(int maxTries) {\n        return Description.createTestDescription(\n            \"SomeTestClass\",\n            \"someMethod\",\n            newAnnotation(VALID_DATE_IN_FAR_FUTURE, VALID_URL, maxTries)\n        );\n    }\n\n    private Description newDescriptionWithExpiredAnnotation() {\n        return Description.createTestDescription(\n            \"SomeTestClass\",\n            \"someMethod\",\n            newAnnotation(VALID_DATE_IN_PAST, VALID_URL, DEFAULT_TRIES)\n        );\n    }\n\n    private Flaky newAnnotation(final String reviewDate, String gitHubUrl, int tries) {\n        return new Flaky() {\n            @Override\n            public Class<? extends Annotation> annotationType() {\n                return Flaky.class;\n            }\n\n            @Override\n            public String githubIssueUrl() {\n                return gitHubUrl;\n            }\n\n            @Override\n            public String reviewDate() {\n                return reviewDate;\n            }\n\n            @Override\n            public int maxTries() {\n                return tries;\n            }\n        };\n    }\n\n    private DummyStatement newStatement(int timesToThrow) {\n        final DummyStatement statement = new DummyStatement();\n        statement.shouldThrowTimes = timesToThrow;\n        return statement;\n    }\n\n    private static class DummyStatement extends Statement {\n\n        int shouldThrowTimes = -1;\n\n        int invocationCount = 0;\n\n        @Override\n        public void evaluate() {\n            invocationCount++;\n\n            if (shouldThrowTimes > 0) {\n                shouldThrowTimes--;\n                throw new RuntimeException();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "test-support/src/test/resources/logback-test.xml",
    "content": "<configuration>\n\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n        <!-- encoders are assigned the type\n             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->\n        <encoder>\n            <pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>\n        </encoder>\n    </appender>\n\n    <root level=\"INFO\">\n        <appender-ref ref=\"STDOUT\"/>\n    </root>\n\n    <logger name=\"org.testcontainers\" level=\"DEBUG\"/>\n</configuration>\n"
  }
]