Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 380
feat(core, postgres): Add RunFunctionWaitStrategy and use it in pg#908
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
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -15,6 +15,7 @@ | ||
| LogMessageWaitStrategy, | ||
| PortWaitStrategy, | ||
| WaitStrategy, | ||
| RunFunctionWaitStrategy, | ||
| ) | ||
| @@ -550,6 +551,60 @@ def test_wait_until_ready(self, mock_sleep, mock_time, mock_is_file, file_exists | ||
| strategy.wait_until_ready(mock_container) | ||
| class TestRunFunctionWaitStrategy: | ||
| """Test the RunFunctionWaitStrategy class.""" | ||
| def test_run_function_wait_strategy_initialization(self): | ||
| func = lambda x: True | ||
| strategy = RunFunctionWaitStrategy(func) | ||
| assert strategy.func == func | ||
| def test_run_function_wait_strategy_wait_until_ready(self): | ||
| returns = [False, False, True] | ||
| mock_container = object() | ||
| def func(target) -> bool: | ||
| assert target is mock_container | ||
| return returns.pop(0) | ||
| strategy = RunFunctionWaitStrategy(func).with_poll_interval(0) | ||
| strategy.wait_until_ready(mock_container) # type: ignore[arg-type] | ||
| ||
| def test_run_function_wait_strategy_wait_until_ready_with_unknown_exception(self): | ||
| mock_container = object() | ||
| def func(target) -> bool: | ||
| assert target is mock_container | ||
| raise RuntimeError("Unknown error, abort!") | ||
| strategy = RunFunctionWaitStrategy(func).with_poll_interval(0) | ||
| with pytest.raises(RuntimeError, match="Unknown error, abort!"): | ||
| strategy.wait_until_ready(mock_container) # type: ignore[arg-type] | ||
| @pytest.mark.parametrize("transient_exception", [ConnectionError, NotImplementedError]) | ||
| def test_run_function_wait_strategy_wait_until_ready_with_transient_exception(self, transient_exception): | ||
| mock_container = object() | ||
| returns = [False, False, True] | ||
| def func(target) -> bool: | ||
| assert target is mock_container | ||
| if returns.pop(0): | ||
| return True | ||
| raise transient_exception("Go on") | ||
| # ConnectionError should be in the default transient exceptions, but NotImplementedError ist not | ||
| strategy = ( | ||
| RunFunctionWaitStrategy(func).with_poll_interval(0.001).with_transient_exceptions(NotImplementedError) | ||
| ) | ||
| strategy.wait_until_ready(mock_container) # type: ignore[arg-type] | ||
| def test_run_function_wait_strategy_wait_until_ready_with_timeout(self): | ||
| mock_container = object() | ||
| strategy = RunFunctionWaitStrategy(lambda x: False).with_poll_interval(0).with_startup_timeout(0) | ||
| with pytest.raises(TimeoutError, match=r"Wait time (.*) exceeded for"): | ||
| strategy.wait_until_ready(mock_container) # type: ignore[arg-type] | ||
| class TestCompositeWaitStrategy: | ||
| """Test the CompositeWaitStrategy class.""" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -15,7 +15,8 @@ | ||
| from testcontainers.core.generic import DbContainer | ||
| from testcontainers.core.utils import raise_for_deprecated_parameter | ||
| from testcontainers.core.waiting_utils import wait_container_is_ready | ||
| from testcontainers.core.wait_strategies import RunFunctionWaitStrategy | ||
| from testcontainers.core.waiting_utils import WaitStrategyTarget | ||
| _UNSET = object() | ||
| @@ -64,6 +65,7 @@ def __init__( | ||
| self.driver = f"+{driver}" if driver else "" | ||
| self.with_exposed_ports(self.port) | ||
| self.waiting_for(RunFunctionWaitStrategy(_check_postgres_ready)) | ||
| def _configure(self) -> None: | ||
| self.with_env("POSTGRES_USER", self.username) | ||
| @@ -87,7 +89,6 @@ def get_connection_url(self, host: Optional[str] = None, driver: Optional[str] = | ||
| port=self.port, | ||
| ) | ||
| @wait_container_is_ready() | ||
| def _connect(self) -> None: | ||
| escaped_single_password = self.password.replace("'", "'\"'\"'") | ||
| result = self.exec( | ||
| @@ -99,3 +100,11 @@ def _connect(self) -> None: | ||
| ) | ||
| if result.exit_code: | ||
| raise ConnectionError("pg_isready is not ready yet") | ||
ContributorAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I assume that the | ||
| def _check_postgres_ready(container: WaitStrategyTarget) -> bool: | ||
| if not isinstance(container, PostgresContainer): | ||
| raise AssertionError("This check can only wait for postgres containers to start up") | ||
| # This raises a ConnectionError if not ready yet | ||
| container._connect() | ||
| return True | ||
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I actually found no reason why this shouldn't be a tuple already on
self-> might be an nice refactoring onWaitStrategyand where it's used.