testcontainers / testcontainers-rs

A library for integration-testing against docker containers from within Rust.
https://rust.testcontainers.org
Apache License 2.0
770 stars 141 forks source link

Running container cannot be moved #378

Closed lcmgh closed 2 years ago

lcmgh commented 2 years ago

The following does not work because ls does not live long enough. This is really a hurdle.

  // LOCAL ONLY - Spawn up localstack with dynamo db and kinesis
  let container_opt = if local.spawn_localstack {
          info!("⌛ Spawning localstack instance.. this may take some seconds");
          let ls = LocalstackDocker::default();
          let container = ls.run();
          let ls_uri = format!("http://127.0.0.1:{}", container.get_host_port(4566));
          info!("✅ Local stack running on {}", ls_uri);
          kinesis_settings.aws_local_endpoint = Some(ls_uri);
          Some(container)
      } else {
          None
      }

As a workaround I'm currently spawning a tokio thread and return a signal via an oneshot channel once container is ready...

thomaseizinger commented 2 years ago

If you assign ls before the if, it will work :)

This is actually by design because the client holds resources like the created networks which must not be cleaned up before the container.

Can you elaborate on your design with the tokio thread? In all my uses, I've created the client and container at the top of the test, which gives it the longest lifetime of all variables. Note that you can move ls_uri without problems (even to a different thread) because #227 is not yet solved.

lcmgh commented 2 years ago

Hi!

Background: I was not able to put the container and client into a common struct with a field for each variable. I'm using test containers outside of tests to spawn up attached resources and run app in a "local mode" that enables devs to run it without the need of manually setting up Docker containers. This setup ends up in a simple cargo run command for a good dev experience :)

If you assign ls before the if, it will work :) My localstack code is tagged with a Rust cfg feature 'local'. Thereby I cannot use testcontainer code outside of such a "local block".

As I do use the same code for integration tests I'd like to refactor the init and start of a container into a function. But that will drop the container once the function call exited. So my workaround was to spawn a tokio for keeping it alive.

I think the best for my scenario would be to have a struct that can hold ls and container if that is somehow possible.

thomaseizinger commented 2 years ago

I'm using test containers outside of tests to spawn up attached resources and run app in a "local mode" that enables devs to run it without the need of manually setting up Docker containers.

This isn't really a usecase that we are striving to actively support. For example, testcontainers very liberally uses panic! because it doesn't matter in a test environment but that is problematic for an app, even in local mode.

We can only provide the API that we are currently providing because we are limiting the usecases we are supporting. Your request is very specific and I think the best option would be to write your own little harness for what you want to do. The internals of testcontainers are surprisingly simple and the license is permissive. Feel free to take inspiration / copy what you need.

As I do use the same code for integration tests I'd like to refactor the init and start of a container into a function. But that will drop the container once the function call exited. So my workaround was to spawn a tokio for keeping it alive.

If you really need to re-use testcontainers, then I'd suggest to only refactor the starting of the containers into a function and take the client as an argument. You should even be able to declare it inline and still have things work:

pub fn start_containers(client: &clients::Cli) -> Containers<'_> {}

In general, because testcontainers is optimising for tests, we tend to design our APIs such that factoring out common functions is not necessary / worth it because it is so simple that you can just duplicate it. Perhaps you can follow a similar strategy here and just duplicate some of the code between your local-mode and your integration tests.