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
use dockertest::{Composition, DockerOperations, DockerTest};
use futures::future::Future;

/// A server configuration which can be converted into a [Composition] to be
/// ran as a container.
///
/// Types implementing this trait should collect all required configuration data
/// and use [ServerConfig::to_comp] to convert it into a [Composition] to be run
/// by a [TestInstance]. The associated [Server] for this configuration should
/// later recieve a copy of this configuration to perform any additional steps
/// required.
pub trait ServerConfig {
    /// Creates a [Composition] from this configuration
    fn to_comp(&self) -> Composition;

    /// Creates a new [TestInstance] and automatically adds the [Composition]
    /// from this configuration to it.
    fn to_instance(&self) -> TestInstance;
}

/// An instance of a server that is created after it's associated [Composition]
/// has been brought up.
///
/// This trait is the main vehicle for tests to interact with the running
/// container. It should encompass all logic necessary for tests to successfully
/// interact with it.
pub trait Server {
    type Config;
    fn new(ops: &DockerOperations, config: &Self::Config) -> Self;
}

/// A single test instance made up of several [Composition]s which
/// are brought up for executing tests.
///
/// Use the `run` method to run the containers and perform the test logic.
pub struct TestInstance {
    pub instance: DockerTest,
}

impl TestInstance {
    /// Returns a new [TestInstance] configured with the given [Composition]s.
    pub fn new(mut servers: Vec<Composition>) -> TestInstance {
        let mut instance = DockerTest::new();
        servers.drain(..).for_each(|s| instance.add_composition(s));

        TestInstance { instance }
    }

    /// Adds a [Composition] to this [TestInstance].
    pub fn add(&mut self, comp: Composition) {
        self.instance.add_composition(comp);
    }

    /// Runs all containers from the associated [Composition]s, verifying they
    /// are running according to their startup conditions, and then calls the
    /// passed closure with runtime details.
    ///
    /// This is the main method for running the [TestInstance]. All test logic
    /// should be encompassed within the passed closure. Creating and destroying
    /// containers happens upon entering/leaving the closure.
    pub fn run<T, F>(self, fun: T)
    where
        T: FnOnce(DockerOperations) -> F + Send + 'static,
        F: Future<Output = ()> + Send + 'static,
    {
        self.instance.run(|ops| async move {
            (fun)(ops).await;
        });
    }
}