-
Couldn't load subscription status.
- Fork 344
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
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] | ||
|
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. For some reason mypy didn't like passing in the mock and I had to add the ignore despite other places in the same file not having this problem with mock. No idea why :-( |
||
|
|
||
| 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") | ||
|
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.
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.