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

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.

@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 :)

@gruuya
Copy link
Contributor Author

gruuya commented Feb 4, 2025

Hey @ZENOTME @Fokko, can you also take a look at this proposal?

@gruuya gruuya force-pushed the concurrent-integration-tests branch from 7587992 to ac1821f Compare February 5, 2025 13:05
@gruuya gruuya force-pushed the concurrent-integration-tests branch from ac1821f to 3d9efa5 Compare February 5, 2025 13:09
@ZENOTME
Copy link
Contributor

ZENOTME commented Feb 6, 2025

Hey @ZENOTME @Fokko, can you also take a look at this proposal?

Thanks! @gruuya It's a great job to improve our ci. the idea and code change LGTM.

ns
}

fn test_schema() -> Schema {
Copy link
Contributor

Choose a reason for hiding this comment

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

Each test is an independent test and I think it's common for them to have different schemas, e.g. scal_all_type. Do we really need to extract this as a public function? 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hey, thanks for the review! So initially I found that append_data_file_test.rs, append_partition_data_file_test.rs and conflict_commit_test.rs use the same schema, and I figured it made sense to extract it simply for de-duplication (and allowing any future tests where schema isn't that important to re-use it as well).

I don't have any strong opinions on it though—would you prefer for the schemas to be constructed directly in each of those tests as before?

Copy link
Contributor

Choose a reason for hiding this comment

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

I figured it made sense to extract it simply for de-duplication (and allowing any future tests where schema isn't that important to re-use it as well).

Both way looks good to me. I'm fine to keep it now for reuse by 3 case and we can remove them when need to evolve schema indenpently.

@ZENOTME
Copy link
Contributor

ZENOTME commented Feb 7, 2025

cc @liurenjie1024 @Xuanwo @sdd

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
4 participants