Skip to content

Commit

Permalink
refactor: implement ReuseDirective enum
Browse files Browse the repository at this point in the history
  • Loading branch information
the-wondersmith committed Dec 11, 2024
1 parent 23bdbfd commit 75328e0
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 53 deletions.
2 changes: 2 additions & 0 deletions testcontainers/src/core.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#[cfg(feature = "reusable-containers")]
pub use self::image::ReuseDirective;
pub use self::{
containers::*,
image::{ContainerState, ExecCommand, Image, ImageExt},
Expand Down
22 changes: 17 additions & 5 deletions testcontainers/src/core/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -447,19 +447,31 @@ impl Client {
.collect::<HashMap<_, _>>();

let options = Some(ListContainersOptions {
filters,
all: false,
size: false,
limit: Some(1),
limit: None,
filters: filters.clone(),
});

Ok(self
let containers = self
.bollard
.list_containers(options)
.await
.map_err(ClientError::ListContainers)?
.map_err(ClientError::ListContainers)?;

if containers.len() > 1 {
log::warn!(
"Found {} containers matching filters: {:?}",
containers.len(),
filters
);
}

Ok(containers
.into_iter()
.next()
// Use `max_by_key()` instead of `next()` to ensure we're
// returning the id of most recently created container.
.max_by_key(|container| container.created.unwrap_or(i64::MIN))
.and_then(|container| container.id))
}
}
Expand Down
50 changes: 31 additions & 19 deletions testcontainers/src/core/containers/async_container.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ pub struct ContainerAsync<I: Image> {
network: Option<Arc<Network>>,
dropped: bool,
#[cfg(feature = "reusable-containers")]
reuse: bool,
reuse: crate::ReuseDirective,
}

impl<I> ContainerAsync<I>
Expand Down Expand Up @@ -356,11 +356,27 @@ where
I: fmt::Debug + Image,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ContainerAsync")
.field("id", &self.id)
.field("image", &self.image)
.field("command", &self.docker_client.config.command())
.finish()
let with_feature_flag_fields = {
#[cfg(not(feature = "reusable-containers"))]
{
std::fmt::DebugStruct::finish
}
#[cfg(feature = "reusable-containers")]
{
|repr: &mut std::fmt::DebugStruct<'_, '_>| -> std::fmt::Result {
repr.field("reuse", &self.reuse).finish()
}
}
};

with_feature_flag_fields(
f.debug_struct("ContainerAsync")
.field("id", &self.id)
.field("image", &self.image)
.field("command", &self.docker_client.config.command())
.field("network", &self.network)
.field("dropped", &self.dropped),
)
}
}

Expand All @@ -371,7 +387,9 @@ where
fn drop(&mut self) {
#[cfg(feature = "reusable-containers")]
{
if self.reuse && !self.dropped {
use crate::ReuseDirective::{Always, CurrentSession};

if !self.dropped && matches!(self.reuse, Always | CurrentSession) {
log::debug!("Declining to reap container marked for reuse: {}", &self.id);

return;
Expand Down Expand Up @@ -506,13 +524,13 @@ mod tests {
];

let initial_image = GenericImage::new("testcontainers/helloworld", "1.1.0")
.with_reuse(true)
.with_reuse(crate::ReuseDirective::CurrentSession)
.with_labels(labels);

let reused_image = initial_image
.image
.clone()
.with_reuse(true)
.with_reuse(crate::ReuseDirective::CurrentSession)
.with_labels(labels);

let initial_container = initial_image.start().await?;
Expand All @@ -532,7 +550,6 @@ mod tests {
.iter()
.map(|(key, value)| format!("{key}={value}"))
.chain([
"org.testcontainers.reuse=true".to_string(),
"org.testcontainers.managed-by=testcontainers".to_string(),
format!(
"org.testcontainers.session-id={}",
Expand Down Expand Up @@ -594,13 +611,7 @@ mod tests {
labels
.iter()
.map(|(key, value)| format!("{key}={value}"))
.chain([
"org.testcontainers.managed-by=testcontainers".to_string(),
format!(
"org.testcontainers.session-id={}",
crate::runners::async_runner::session_id()
),
])
.chain(["org.testcontainers.managed-by=testcontainers".to_string()])
.collect(),
)]),
};
Expand All @@ -626,7 +637,7 @@ mod tests {
#[cfg(feature = "reusable-containers")]
#[tokio::test]
async fn async_reusable_containers_are_not_dropped() -> anyhow::Result<()> {
use crate::ImageExt;
use crate::{ImageExt, ReuseDirective};

let client = crate::core::client::docker_client_instance().await?;

Expand All @@ -641,7 +652,8 @@ mod tests {
let container_id = {
let container = image.start().await?;

assert!(!container.dropped && container.reuse);
assert!(!container.dropped);
assert_eq!(container.reuse, ReuseDirective::Always);

container.id().to_string()
};
Expand Down
6 changes: 3 additions & 3 deletions testcontainers/src/core/containers/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ pub struct ContainerRequest<I: Image> {
pub(crate) working_dir: Option<String>,
pub(crate) log_consumers: Vec<Box<dyn LogConsumer + 'static>>,
#[cfg(feature = "reusable-containers")]
pub(crate) reuse: bool,
pub(crate) reuse: crate::ReuseDirective,
}

/// Represents a port mapping between a host's external port and the internal port of a container.
Expand Down Expand Up @@ -189,7 +189,7 @@ impl<I: Image> ContainerRequest<I> {

/// Indicates that the container will not be stopped when it is dropped
#[cfg(feature = "reusable-containers")]
pub fn reuse(&self) -> bool {
pub fn reuse(&self) -> crate::ReuseDirective {
self.reuse
}
}
Expand Down Expand Up @@ -220,7 +220,7 @@ impl<I: Image> From<I> for ContainerRequest<I> {
working_dir: None,
log_consumers: vec![],
#[cfg(feature = "reusable-containers")]
reuse: false,
reuse: false.into(),
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions testcontainers/src/core/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ use std::{borrow::Cow, fmt::Debug};

pub use exec::ExecCommand;
pub use image_ext::ImageExt;
#[cfg(feature = "reusable-containers")]
pub use image_ext::ReuseDirective;

use crate::{
core::{
Expand Down
47 changes: 44 additions & 3 deletions testcontainers/src/core/image/image_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,47 @@ use crate::{
ContainerRequest, Image,
};

#[cfg(feature = "reusable-containers")]
#[derive(Eq, Copy, Clone, Debug, Default, PartialEq)]
pub enum ReuseDirective {
#[default]
Never,
Always,
CurrentSession,
}

#[cfg(feature = "reusable-containers")]
impl std::fmt::Display for ReuseDirective {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str(match self {
Self::Never => "never",
Self::Always => "always",
Self::CurrentSession => "current-session",
})
}
}

#[cfg(feature = "reusable-containers")]
impl From<bool> for ReuseDirective {
fn from(value: bool) -> Self {
match value {
true => Self::Always,
false => Self::Never,
}
}
}

#[cfg(feature = "reusable-containers")]
impl From<&str> for ReuseDirective {
fn from(value: &str) -> Self {
match value.trim().to_ascii_lowercase().as_str() {
"true" | "always" => Self::Always,
value if value.ends_with("session") => Self::CurrentSession,
_ => Self::Never,
}
}
}

/// Represents an extension for the [`Image`] trait.
/// Allows to override image defaults and container configuration.
pub trait ImageExt<I: Image> {
Expand Down Expand Up @@ -145,7 +186,7 @@ pub trait ImageExt<I: Image> {
/// to change. Containers marked as `reuse` **_will not_** be stopped or cleaned up when their associated
/// `Container` or `ContainerAsync` is dropped.
#[cfg(feature = "reusable-containers")]
fn with_reuse(self, reuse: bool) -> ContainerRequest<I>;
fn with_reuse(self, reuse: impl Into<ReuseDirective>) -> ContainerRequest<I>;
}

/// Implements the [`ImageExt`] trait for the every type that can be converted into a [`ContainerRequest`].
Expand Down Expand Up @@ -354,9 +395,9 @@ impl<RI: Into<ContainerRequest<I>>, I: Image> ImageExt<I> for RI {
}

#[cfg(feature = "reusable-containers")]
fn with_reuse(self, reuse: bool) -> ContainerRequest<I> {
fn with_reuse(self, reuse: impl Into<ReuseDirective>) -> ContainerRequest<I> {
ContainerRequest {
reuse,
reuse: reuse.into(),
..self.into()
}
}
Expand Down
2 changes: 2 additions & 0 deletions testcontainers/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ pub mod core;
#[cfg(feature = "blocking")]
#[cfg_attr(docsrs, doc(cfg(feature = "blocking")))]
pub use crate::core::Container;
#[cfg(feature = "reusable-containers")]
pub use crate::core::ReuseDirective;
pub use crate::core::{
copy::{CopyDataSource, CopyToContainer, CopyToContainerError},
error::TestcontainersError,
Expand Down
57 changes: 34 additions & 23 deletions testcontainers/src/runners/async_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,21 +93,25 @@ where
"testcontainers".into(),
),
#[cfg(feature = "reusable-containers")]
(
"org.testcontainers.reuse".to_string(),
container_req.reuse().to_string(),
),
#[cfg(feature = "reusable-containers")]
(
"org.testcontainers.session-id".to_string(),
session_id().to_string(),
),
]),
{
if container_req.reuse() != crate::ReuseDirective::CurrentSession {
Default::default()
} else {
(
"org.testcontainers.session-id".to_string(),
session_id().to_string(),
)
}
},
])
.filter(|(_, value): &(_, String)| !value.is_empty()),
);

#[cfg(feature = "reusable-containers")]
{
if container_req.reuse() {
use crate::ReuseDirective::{Always, CurrentSession};

if matches!(container_req.reuse(), Always | CurrentSession) {
if let Some(container_id) = client
.get_running_container_id(
container_req.container_name().as_deref(),
Expand Down Expand Up @@ -382,10 +386,20 @@ mod tests {
),
]);

let container = GenericImage::new("hello-world", "latest")
.with_labels(&labels)
.start()
.await?;
let image = GenericImage::new("hello-world", "latest").with_labels(&labels);

let container = {
#[cfg(not(feature = "reusable-containers"))]
{
image
}
#[cfg(feature = "reusable-containers")]
{
image.with_reuse(crate::ReuseDirective::CurrentSession)
}
}
.start()
.await?;

let client = Client::lazy_client().await?;

Expand All @@ -411,17 +425,14 @@ mod tests {
);

#[cfg(feature = "reusable-containers")]
labels.extend([
("org.testcontainers.reuse".to_string(), false.to_string()),
(
"org.testcontainers.session-id".to_string(),
session_id().to_string(),
),
]);
labels.extend([(
"org.testcontainers.session-id".to_string(),
session_id().to_string(),
)]);

assert_eq!(labels, container_labels);

Ok(())
container.rm().await.map_err(anyhow::Error::from)
}

#[tokio::test]
Expand Down

0 comments on commit 75328e0

Please sign in to comment.