Skip to content

Instantly share code, notes, and snippets.

@rnorth
Last active November 24, 2018 11:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rnorth/3b31471ac29ba34c9ad266450f205cdd to your computer and use it in GitHub Desktop.
Save rnorth/3b31471ac29ba34c9ad266450f205cdd to your computer and use it in GitHub Desktop.

Testcontainers general principles

Tips for developing/maintaining Testcontainers forks:

  • Some general tips (most important at the top):

    • Have an easy-to-use API that is simple for people to use and read
    • Make common tasks intuitive and nice to use. Make less common tasks possible, but not at the expense of the common tasks. It's better not to support a niche feature than to degrade the common features.
    • Guide people towards doing sensible things, rather than letting them fall into traps.
    • Be idiomatic for the language and framework
    • Give users coarse-grained tools rather than a fine-grained bag of components to figure out how to assemble
  • IDE autocomplete is the first, and usually the last, place that people will look to work out how to do things. Make the most of autocompletion and in-IDE hints.

  • Configurability: Don't make people choose or configure things if we can work out a sensible default.

    • Let people configure things only if it's important to configure.
    • Don't require them to know the option is there if they're not going to use it.

Connecting to Docker

It's near certain that the Docker daemon will run differently between developers' machines and CI. Therefore, Testcontainers should not use checked-in configuration to know how to connect. Configuration should come from the environment as much as possible, or by inference.

Instead, we try and do the right thing automatically. Testcontainers should try multiple discovery strategies for finding and connecting to Docker, in an order which has proven to be sensible. (TODO: insert list of strategies).

Users should always be able to set a specific strategy in config that sits in their home directory. (TODO: describe ~/testcontainers.properties. Maybe we can reuse this config file for *nix-like forks?)

While users can maintain this configuration file, they should not have to.

Testcontainers will update the user config file automatically as a way of speeding up discovery of the Docker daemon on subsequent test runs. If the configured discovery strategy fails, Testcontainers should revert to trying the other strategies rather than fail.

Docker-machine is a special case, whereby we will try and launch the default/configured machine VM via docker-machine start ...

Startup strategies and wait strategies

The naming is potentially confusing, but there are two separate concepts:

  • 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.

  • 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.

Wait strategy nuances

In no particular order:

  • by default, Testcontainers automatically checks that all exposed ports are listening. Because we don't know the protocol, the best we can do by default is check that we can open a TCP connection.
  • there are more sophisticated options available, such as making an HTTP call or executing a JDBC/SQL test query for databases.
  • sometimes a network based approach simply isn't good enough: for example, some daemons start accepting connections before they are actually usable. For these cases, we support a wait strategy that looks for a particular pattern in the container's stdout (e.g. a 'ready' log message)
  • for the default TCP-based wait strategy: we must always try opening a docker from inside the container and from outside. This is because different Docker setups behave differently and have race conditions. E.g. Docker for Mac has it's own TCP proxy, which may accept a connection before the container is actually ready. Conversely, the proxy may start listening after the process inside the container starts listening.
  • to check whether the TCP port is listening from within the container we must try a variety of shell command invocations, which are designed so that at least one will work in any given Linux-based container. (TODO: link to the list of commands)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment