Skip to content

Commit

Permalink
Add integration test support for volume mounts
Browse files Browse the repository at this point in the history
  • Loading branch information
runesoerensen committed Oct 21, 2024
1 parent a3174bc commit 74ed252
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 0 deletions.
33 changes: 33 additions & 0 deletions libcnb-test/src/container_config.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::collections::{HashMap, HashSet};
use std::path::{Path, PathBuf};

/// Config used when starting a container.
///
Expand Down Expand Up @@ -31,6 +32,7 @@ pub struct ContainerConfig {
pub(crate) command: Option<Vec<String>>,
pub(crate) env: HashMap<String, String>,
pub(crate) exposed_ports: HashSet<u16>,
pub(crate) volumes: HashMap<PathBuf, PathBuf>,
}

impl ContainerConfig {
Expand Down Expand Up @@ -169,6 +171,37 @@ impl ContainerConfig {
self
}

/// Mounts a named volume `source` into the container `destination`. Useful for integration
/// tests that depend on persistent storage shared between container executions.
///
/// See: [Docker CLI, Mount Volume](https://docs.docker.com/reference/cli/docker/container/run/#volume)
///
/// # Example
/// ```no_run
/// use libcnb_test::{BuildConfig, ContainerConfig, TestRunner};
/// use std::path::PathBuf;
///
/// TestRunner::default().build(
/// BuildConfig::new("heroku/builder:22", "tests/fixtures/app"),
/// |context| {
/// // ...
/// context.start_container(
/// ContainerConfig::new().volume(PathBuf::from("/shared/cache"), PathBuf::from("/workspace/cache")),
/// |container| {
/// // ...
/// },
/// );
/// },
/// );
/// ```
pub fn volume<P: AsRef<Path>>(&mut self, source: P, destination: P) -> &mut Self {
self.volumes.insert(
source.as_ref().to_path_buf(),
destination.as_ref().to_path_buf(),
);
self
}

/// Adds or updates multiple environment variable mappings for the container.
///
/// # Example
Expand Down
25 changes: 25 additions & 0 deletions libcnb-test/src/docker.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::collections::{BTreeMap, BTreeSet};
use std::path::PathBuf;
use std::process::Command;

/// Represents a `docker run` command.
Expand All @@ -13,6 +14,7 @@ pub(crate) struct DockerRunCommand {
image_name: String,
platform: Option<String>,
remove: bool,
volumes: BTreeMap<PathBuf, PathBuf>,
}

impl DockerRunCommand {
Expand All @@ -27,6 +29,7 @@ impl DockerRunCommand {
image_name: image_name.into(),
platform: None,
remove: false,
volumes: BTreeMap::new(),
}
}

Expand Down Expand Up @@ -67,6 +70,11 @@ impl DockerRunCommand {
self.remove = remove;
self
}

pub(crate) fn volume<P: Into<PathBuf>>(&mut self, source: P, destination: P) -> &mut Self {
self.volumes.insert(source.into(), destination.into());
self
}
}

impl From<DockerRunCommand> for Command {
Expand Down Expand Up @@ -98,6 +106,17 @@ impl From<DockerRunCommand> for Command {
command.args(["--publish", &format!("127.0.0.1::{port}")]);
}

for (source, destination) in &docker_run_command.volumes {
command.args([
"--volume",
&format!(
"{}:{}",
source.to_string_lossy(),
destination.to_string_lossy()
),
]);
}

command.arg(docker_run_command.image_name);

if let Some(container_command) = docker_run_command.command {
Expand Down Expand Up @@ -315,6 +334,8 @@ mod tests {
docker_run_command.expose_port(55555);
docker_run_command.platform("linux/amd64");
docker_run_command.remove(true);
docker_run_command.volume(PathBuf::from("./test-cache"), PathBuf::from("/cache"));
docker_run_command.volume(PathBuf::from("foo"), PathBuf::from("/bar"));

let command: Command = docker_run_command.clone().into();
assert_eq!(
Expand All @@ -337,6 +358,10 @@ mod tests {
"127.0.0.1::12345",
"--publish",
"127.0.0.1::55555",
"--volume",
"./test-cache:/cache",
"--volume",
"foo:/bar",
"my-image",
"echo",
"hello",
Expand Down
4 changes: 4 additions & 0 deletions libcnb-test/src/test_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ impl<'a> TestContext<'a> {
docker_run_command.expose_port(*port);
});

config.volumes.iter().for_each(|(source, destination)| {
docker_run_command.volume(source, destination);
});

// We create the ContainerContext early to ensure the cleanup in ContainerContext::drop
// is still performed even if the Docker command panics.
let container_context = ContainerContext {
Expand Down

0 comments on commit 74ed252

Please sign in to comment.