1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
#![deny(missing_docs)]
#![deny(warnings)]
#![deny(rust_2018_idioms)]
#![deny(rustdoc::broken_intra_doc_links)]
//! _dockertest_ is a testing and automation abstraction for Docker.
//!
//! This testing library enables control and lifecycle management with docker containers
//! from an integration test, taking full care of start and cleanup phases. Containers can
//! be backed by various container registries, even behind authentication barrier.
//!
//! The main entrypoint to construct and a run a dockertest powered integration test starts
//! with [DockerTest].
//!
//! The underlying docker engine to use can be configured through:
//! - UNIX domain socket (linux)
//! - Named pipes (windows)
//! - TCP with TLS
//! - Piped through a docker-in-docker container where the execution occurs, to run on the
//! underlying docker engine.
//!
//! The main bread-and-butter of this library is the ability to specify which containers are
//! required for a test, and how one should ensure that the container is properly running prior
//! to starting the actual test body. Furthermore, dockertest supports interacting with existing
//! containers, without interfering with its lifecycle management.
//!
//! ## Lifecycle management
//!
//! There are multiple ways to control and interact with containers, with various trade-offs.
//!
//! ### Full isolation - `TestBodySpecification`
//!
//! The container is created, started, and cleaned up entirely within a single test body. This is
//! the highest level of isolation, and no other tests can interfere with this container. The main
//! drawback of this method is the high wall-time needed to start these containers. If there are
//! many integration tests in this mode, the total test run-time will be made primarily by the
//! container management overhead.
//!
//! ### Externally managed - `ExternalSpecification`
//!
//! The container is _never_ touched by dockertest. It is assumed to be started by some external
//! entity, and the container is never teared down by dockertest. Prior to running the test body,
//! the existence of the external container is verified.
//!
//! ### Conditionally managed - `DynamicSpecification`
//!
//! The containers lifecycle is only managed for creation, but not deletion. When multiple
//! specifications relate to the same underlying container, dockertest will attempt to locate
//! an existing container, and reuse this container to fulfill the specification. Once the
//! test body exists, the container will be left running.
//!
//! This mode is useful in when there are multiple tests, over a long period of development time,
//! that can utilize the same underlying container without causing cross-test contamination.
//! This will lead to significantly faster test execution time.
//!
//! # `WaitFor` - determining when a container is ready
//!
//! Each container that dockertest creates and starts must also have a policy to detect
//! when the container it has started is actually ready to start serving the test body.
//! This is achived through a trait object [WaitFor], where an implementation can be provided
//! outside of dockertest.
//!
//! The batteries included implementations are:
//! * [RunningWait] - wait for the container to report _running_ status.
//! * [ExitedWait] - wait for the container to report _exited_ status.
//! * [NoWait] - don't wait for anything
//! * [MessageWait] - wait for the following message to appear in the log stream.
//!
//! # Environment variables
//!
//! The following set of environment variables can impact running tests utilizing dockertest.
//!
//! ## Prune policy
//!
//! By default, dockertest will stop and remove all containers and created volumes
//! regardless of execution result. You can control this policy by setting the environment variable
//! `DOCKERTEST_PRUNE`:
//! * `always`: default remove everything
//! * `never`: leave all containers running
//! * `stop_on_failure`: stop containers on execution failure
//! * `running_on_failure`: leave containers running on execution failure
//!
//! ## Dockertest in Docker
//!
//! If the execution environment of running dockertest is itself a docker-in-docker container, one
//! may have connectivity issues between the test body code, and the container dependencies.
//! To ensure connectivity, dockertest must be instructed about the name/identifier of the
//! execution container to include it into the docker network of the test containers.
//! This is usually because the docker-in-docker docker daemon connection is routed to
//! the underlying host itself.
//!
//! `DOCKERTEST_CONTAINER_ID_INJECT_TO_NETWORK=your_container_id/name`
//!
//! # Example
//!
//! ```no_run
//! use dockertest::{TestBodySpecification, DockerTest};
//! use std::sync::{Arc, Mutex};
//!
//! #[test]
//! fn hello_world_test() {
//! // Define our test instance
//! let mut test = DockerTest::new();
//!
//! // A container specification can have multiple properties, depending on how the
//! // lifecycle management of the container should be handled by dockertest.
//! //
//! // For any container specification where dockertest needs to create and start the container,
//! // we must provide enough information to construct a composition of
//! // an Image configured with provided environment variables, arguments, StartPolicy, etc.
//! let hello = TestBodySpecification::with_repository("hello-world");
//!
//! // Populate the test instance.
//! // The order of compositions added reflect the execution order (depending on StartPolicy).
//! test.provide_container(hello);
//!
//! let has_ran: Arc<Mutex<bool>> = Arc::new(Mutex::new(false));
//! let has_ran_test = has_ran.clone();
//! test.run(|ops| async move {
//! // A handle to operate on the Container.
//! let _container = ops.handle("hello-world");
//!
//! // The container is in a running state at this point.
//! // Depending on the Image, it may exit on its own (like this hello-world image)
//! let mut ran = has_ran_test.lock().unwrap();
//! *ran = true;
//! });
//!
//! let ran = has_ran.lock().unwrap();
//! assert!(*ran);
//! }
//! ```
//!
//! [WaitFor]: crate::waitfor::WaitFor
//! [RunningWait]: crate::waitfor::RunningWait
//! [ExitedWait]: crate::waitfor::ExitedWait
//! [NoWait]: crate::waitfor::NoWait
//! [MessageWait]: crate::waitfor::MessageWait
mod composition;
mod container;
mod docker;
mod dockertest;
mod engine;
mod error;
mod image;
mod runner;
mod specification;
mod static_container;
// We only make this public because a function is used in our integration test
#[doc(hidden)]
pub mod utils;
pub mod waitfor;
pub use crate::composition::{LogAction, LogOptions, LogPolicy, LogSource, StartPolicy};
pub use crate::container::{OperationalContainer, PendingContainer};
pub use crate::docker::ContainerState;
pub use crate::dockertest::DockerTest;
pub use crate::dockertest::Network;
pub use crate::error::DockerTestError;
pub use crate::image::{Image, PullPolicy, RegistryCredentials, Source};
pub use crate::runner::DockerOperations;
pub use crate::specification::{
ContainerSpecification, DynamicSpecification, ExternalSpecification, TestBodySpecification,
TestSuiteSpecification,
};