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
/// Contains types for creating tests
use dockertest::{Composition, DockerOperations, DockerTest};
use futures::Future;
use type_map::concurrent::TypeMap;
use crate::server::{Config, Server};
/// A single test which brings up one or more [Servers][Server].
///
/// A [Test][crate::test::Test] receives [Configs][Config] via `register` which
/// determines the servers that will be brought up when the test is executed.
/// The `run` method is used to bring up the [Servers][Server] and then run the
/// given test body.
///
/// The body of a test is determined by an async closure passed to the `run`
/// method. The closure will receive a [TestInstance] which can be used to
/// access the [Servers][Server] that were created when the test was setup.
pub struct Test {
pub configs: TypeMap,
pub compositions: Vec<Composition>,
}
impl Test {
/// Creates an empty [Test].
pub fn new() -> Self {
Test {
configs: TypeMap::new(),
compositions: Vec::new(),
}
}
/// Registers a [Config] with this test.
///
/// A [Test] can be configured with any number of [Configs][Config] for
/// determining which [Servers][Server] are brought up in a test. Each
/// [Config] passed will have it's respective [Server] created before the
/// test body is ran.
pub fn register(&mut self, config: impl Config + 'static) {
self.configs.insert(config.clone());
self.compositions.push(config.into_composition());
}
/// Brings up the [Servers][Server] registered with this test and then
/// runs the given test body.
///
/// The test body receives a [TestInstance] which can be used for accessing
/// any configured [Servers][Server] through the `server` method.
/// The test body is guaranteed to not execute until all configured
/// [Servers][Server] are verified to be running and available. The scope of
/// the test body determines the life of the [Servers][Server]: they are
/// created before the closure is run and destroyed after the closure exits.
pub fn run<T, F>(self, fun: T)
where
T: FnOnce(TestInstance) -> F + Send + 'static,
F: Future<Output = ()> + Send + 'static,
{
let mut test = DockerTest::new();
for comp in self.compositions {
test.add_composition(comp)
}
let configs = self.configs;
test.run(|ops| async move {
let instance = TestInstance::new(configs, ops);
(fun)(instance).await;
});
}
}
impl Default for Test {
fn default() -> Self {
Self::new()
}
}
/// Represents a running instance of a [Test].
///
/// Internally, this type keeps track of all [Configs][Config] that were
/// registered with a [Test] using a [TypeMap]. A [Server] can be fetched by
/// calling the `server` method with the desired type that implements [Server].
/// See the associated method documentation for more details.
pub struct TestInstance {
pub configs: TypeMap,
pub op: DockerOperations,
}
impl TestInstance {
/// Returns a new [TestInstance].
pub fn new(configs: TypeMap, op: DockerOperations) -> Self {
TestInstance { configs, op }
}
/// Returns an instance of the requested type that implements [Server].
///
/// Internally a [TestInstance] has a [TypeMap] which contains all of the
/// [Configs][Config] that were registered with a given [Test]. Since each
/// [Config] has a one-to-one relationship with a [Server], an instance of a
/// [Server] can be created by simply having a copy of it's associated
/// [Config]. This method performs that mapping for you, looking for a
/// [Config] of the type specified by [Server::Config] in the type map and
/// then generating and returning a new instance of the [Server] using the
/// [Config] and runtime data generated when the underlying container was
/// brought up.
///
/// Note that it then follows this method will fail if the passed [Server]
/// type did not have it's associated [Config] registered when the [Test]
/// was created.
pub fn server<S: Server>(&self) -> S {
let config = self.configs.get::<S::Config>().unwrap();
let container = self.op.handle(config.handle());
S::new(config, container)
}
}