Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: use shared containers for integration tests #924

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

gruuya
Copy link
Contributor

@gruuya gruuya commented Jan 30, 2025

Closes #923

This PR makes integration tests re-use the same set of docker containers, hence allowing them to be run concurrently

  1. all existing tests are moved to a shared_tests module, with a single top-level shared.rs file, thus requiring only one compilation step (as opposed to compiling every test file individually)
  2. set_test_fixture is made sync, since there's no async code in it
  3. given 1 and 2, the test fixture is wrapped in a OnceLock, so that it is invoked only once for the resulting (shared) test binary; it also has a corresponding destructor to spin down the containers after the tests run
  4. if there's a need for integration tests which don't use the shared docker containers, their test files should be placed alongside shared.rs (top-level); otherwise they should be created in the shared_tests module
  5. one (shared) file can now contain an arbitrary number of tests that utilize the shared set of containers (without having to resort to decorating them with #[serial])

Finally, with this change my integration test runs go from taking ~3.5 minutes down to 30 seconds.

EDIT: The CI unit workflow run duration also seems ~10min shorter now.

pub rest_catalog: RestCatalog,
pub catalog_config: RestCatalogConfig,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the tests now share this fixture but they're all executed in different runtimes, they can't share the same client, as those can get dropped prematurely when a test ends, resulting in dispatch task is gone: runtime dropped the dispatch task

Comment on lines 80 to 81
.name("t1".to_string())
.name("t2".to_string())
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Making the write tests use different tables each allows for them to also use the shared docker stack.

Copy link
Contributor

@kevinjqliu kevinjqliu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

generally lgtm. Since we're using a shared catalog, are there any concerns with side effects?
In pyiceberg we use a table identifier fixture to generate table names so they dont conflict

@gruuya
Copy link
Contributor Author

gruuya commented Jan 30, 2025

generally lgtm. Since we're using a shared catalog, are there any concerns with side effects? In pyiceberg we use a table identifier fixture to generate table names so they dont conflict

Good point, I could also extract the namespace as a shared fixture among the tests (in the PR i just ignore the result of create_namespace assuming it was already created if it was an error).

EDIT: I've extracted two common fixtures now: the apple-ios namespace and the foo-bar-baz schema.

@gruuya gruuya force-pushed the concurrent-integration-tests branch from fcf379f to 7fb4d98 Compare January 31, 2025 08:56
Copy link
Contributor

@xxchan xxchan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if I understand it correctly:

  • Before this PR, tests are compiled into separated binaries. In theory they can run concurrently (e.g., if use cargo nextest), but just cargo test will run them in serial.
  • In this PR, we put them into 1 binary. And then they are run concurrently by #[tokio::tests].
    • We use "shared container" to also save the time of spinning up and down containers. But need to take care of potential conflicts.

Comment on lines 53 to 56
let ns = Namespace::with_properties(
NamespaceIdent::from_strs(["apple", "ios"]).unwrap(),
HashMap::from([
("owner".to_string(), "ray".to_string()),
("community".to_string(), "apache".to_string()),
]),
);
let fixture = get_shared_containers();
let rest_catalog = RestCatalog::new(fixture.catalog_config.clone());
let ns = apple_ios_ns().await;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about creating a per-test randomized namespace instead? I don't get why do we want a shared namespace. I guess before this PR, we just didn't consider the concurrency and conflict problem.

Copy link
Contributor Author

@gruuya gruuya Feb 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, that makes sense, I'll change it.

EDIT: Done, the randomness is introduced via uuids.

@gruuya
Copy link
Contributor Author

gruuya commented Feb 2, 2025

  • Before this PR, tests are compiled into separated binaries. In theory they can run concurrently (e.g., if use cargo nextest), but just cargo test will run them in serial.

Correct; at present the tests can't be run concurrently because they depend on separate docker container sets. (Also even if they didn't, cargo nextest would compile each top-level test file separately.)

  • In this PR, we put them into 1 binary. And then they are run concurrently by #[tokio::tests].

    • We use "shared container" to also save the time of spinning up and down containers. But need to take care of potential conflicts.

Yep, that's it in a nutshell. Using the shared container set is the biggest time-saver (eliminating the docker build-start-stop overhead for each test). Having made them use shared containers, the next simple improvement is to make them all compile to one test binary (thus eliminating multiple compilations), as is done through having a single top-level shared.rs file, and consequently run them concurrently as well.

Copy link
Contributor

@xxchan xxchan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code changes and the idea LGTM 👍 Maybe we could rename the PR title to "use shared container for integration tests" instead as it's the biggest time-saver?

@kevinjqliu
Copy link
Contributor

thanks! generally lgtm.
Does this also affect CI runs? if so, would be great to also include how much time we saved there too.

@gruuya gruuya changed the title chore: make integration tests run concurrently chore: use shared containers for integration tests Feb 3, 2025
@gruuya
Copy link
Contributor Author

gruuya commented Feb 3, 2025

thanks! generally lgtm. Does this also affect CI runs? if so, would be great to also include how much time we saved there too.

Good question; a brief glance suggests it may have shaved about ~5 minutes from the unit CI workflow, e.g. by comparing the Test step duration from a job on this PR(3m 51s) and a job on another PR(8m 45s).

The difference would certainly grow as more and more integration tests are added.

EDIT: It's actually closer to 10 minutes, since the integration tests are run twice, once in the Test step and once in the Async-std Test step.

@kevinjqliu
Copy link
Contributor

thats amazing! Thanks for looking into that, i like faster CI :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

chore: Improve efficiency of Docker fixtures in integration_tests
3 participants