-
Notifications
You must be signed in to change notification settings - Fork 22
Automatically invoke Docker when running pytest #106
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
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 |
|---|---|---|
|
|
@@ -86,21 +86,43 @@ def node_name() -> Iterator[str]: | |
|
|
||
|
|
||
| class _TestContainerManager(): | ||
| """Manages the setup and teardown of a SingleStoreDB Dev Container""" | ||
| """Manages the setup and teardown of a SingleStoreDB Dev Container | ||
|
|
||
| If SINGLESTOREDB_URL environment variable is set, the manager will use | ||
| the existing server instead of starting a Docker container. This allows | ||
| tests to run against either an existing server or an automatically | ||
| managed Docker container. | ||
| """ | ||
|
|
||
| def __init__(self) -> None: | ||
| # Check if SINGLESTOREDB_URL is already set - if so, use existing server | ||
| self.existing_url = os.environ.get('SINGLESTOREDB_URL') | ||
| self.use_existing = self.existing_url is not None | ||
|
|
||
| if self.use_existing: | ||
| logger.info('Using existing SingleStore server from SINGLESTOREDB_URL') | ||
| self.url = self.existing_url | ||
| # No need to initialize Docker-related attributes | ||
| return | ||
|
|
||
| logger.info('SINGLESTOREDB_URL not set, will start Docker container') | ||
|
|
||
| # Generate unique container name using UUID and worker ID | ||
| worker = os.environ.get('PYTEST_XDIST_WORKER', 'master') | ||
| unique_id = uuid.uuid4().hex[:8] | ||
| self.container_name = f'singlestoredb-test-{worker}-{unique_id}' | ||
|
|
||
| self.dev_image_name = 'ghcr.io/singlestore-labs/singlestoredb-dev' | ||
|
|
||
| assert 'SINGLESTORE_LICENSE' in os.environ, 'SINGLESTORE_LICENSE not set' | ||
| # Use SINGLESTORE_LICENSE from environment, or empty string as fallback | ||
| # Empty string works for the client SDK | ||
| license = os.environ.get('SINGLESTORE_LICENSE', '') | ||
| if not license: | ||
| logger.info('SINGLESTORE_LICENSE not set, using empty string') | ||
|
|
||
| self.root_password = 'Q8r4D7yXR8oqn' | ||
| self.environment_vars = { | ||
| 'SINGLESTORE_LICENSE': None, | ||
| 'SINGLESTORE_LICENSE': license, | ||
| 'ROOT_PASSWORD': f"\"{self.root_password}\"", | ||
| 'SINGLESTORE_SET_GLOBAL_DEFAULT_PARTITIONS_PER_LEAF': '1', | ||
| } | ||
|
|
@@ -111,12 +133,23 @@ def __init__(self) -> None: | |
| self.studio_port = _find_free_port() | ||
| self.ports = [ | ||
| (self.mysql_port, '3306'), # External port -> Internal port | ||
| (self.http_port, '8080'), | ||
| (self.studio_port, '9000'), | ||
| (self.studio_port, '8080'), # Studio | ||
| (self.http_port, '9000'), # Data API | ||
| ] | ||
|
|
||
| self.url = f'root:{self.root_password}@127.0.0.1:{self.mysql_port}' | ||
|
|
||
| @property | ||
| def http_connection_url(self) -> Optional[str]: | ||
| """HTTP connection URL for the SingleStoreDB server using Data API.""" | ||
| if self.use_existing: | ||
| # If using existing server, HTTP URL not available from manager | ||
| return None | ||
| return ( | ||
| f'singlestoredb+http://root:{self.root_password}@' | ||
| f'127.0.0.1:{self.http_port}' | ||
| ) | ||
|
|
||
| def _container_exists(self) -> bool: | ||
| """Check if a container with this name already exists.""" | ||
| try: | ||
|
|
@@ -169,11 +202,16 @@ def start(self) -> None: | |
| f'{self.http_port}, {self.studio_port}', | ||
| ) | ||
| try: | ||
| license = os.environ['SINGLESTORE_LICENSE'] | ||
| license = os.environ.get('SINGLESTORE_LICENSE', '') | ||
| env = { | ||
| 'SINGLESTORE_LICENSE': license, | ||
| } | ||
|
Comment on lines
206
to
208
|
||
| subprocess.check_call(command, shell=True, env=env) | ||
| # Capture output to avoid printing the container ID hash | ||
| subprocess.check_call( | ||
| command, shell=True, env=env, | ||
| stdout=subprocess.DEVNULL, | ||
| ) | ||
|
|
||
| except Exception as e: | ||
| logger.exception(e) | ||
| raise RuntimeError( | ||
|
|
@@ -250,30 +288,51 @@ def stop(self) -> None: | |
| logger.info('Cleaning up SingleStore DB dev container') | ||
| logger.debug('Stopping container') | ||
| try: | ||
| subprocess.check_call(f'docker stop {self.container_name}', shell=True) | ||
| subprocess.check_call( | ||
| f'docker stop {self.container_name}', | ||
| shell=True, | ||
| stdout=subprocess.DEVNULL, | ||
| ) | ||
|
|
||
| except Exception as e: | ||
| logger.exception(e) | ||
| raise RuntimeError('Failed to stop container.') from e | ||
|
|
||
| logger.debug('Removing container') | ||
| try: | ||
| subprocess.check_call(f'docker rm {self.container_name}', shell=True) | ||
| subprocess.check_call( | ||
| f'docker rm {self.container_name}', | ||
| shell=True, | ||
| stdout=subprocess.DEVNULL, | ||
| ) | ||
|
|
||
| except Exception as e: | ||
| logger.exception(e) | ||
| raise RuntimeError('Failed to stop container.') from e | ||
| raise RuntimeError('Failed to remove container.') from e | ||
|
|
||
|
|
||
| @pytest.fixture(scope='session') | ||
| def singlestoredb_test_container( | ||
| execution_mode: ExecutionMode, | ||
| ) -> Iterator[_TestContainerManager]: | ||
| """Sets up and tears down the test container""" | ||
| """Sets up and tears down the test container | ||
|
|
||
| If SINGLESTOREDB_URL is set in the environment, uses the existing server | ||
| and skips Docker container lifecycle management. Otherwise, automatically | ||
| starts a Docker container for testing. | ||
| """ | ||
|
|
||
| if not isinstance(execution_mode, ExecutionMode): | ||
| raise TypeError(f"Invalid execution mode '{execution_mode}'") | ||
|
|
||
| container_manager = _TestContainerManager() | ||
|
|
||
| # If using existing server, skip all Docker lifecycle management | ||
| if container_manager.use_existing: | ||
| logger.info('Using existing server, skipping Docker container lifecycle') | ||
| yield container_manager | ||
| return | ||
|
|
||
| # In sequential operation do all the steps | ||
| if execution_mode == ExecutionMode.SEQUENTIAL: | ||
| logger.debug('Not distributed') | ||
|
|
||
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.
The
licensevariable retrieved on line 119 is never actually used. Line 125 sets'SINGLESTORE_LICENSE': Nonein theenvironment_varsdictionary, ignoring the license value that was just retrieved. This appears to be a bug.If the intent is to use the retrieved license value, the code should be:
or simply remove the license retrieval code if it's not meant to be used here (since it's set separately in the
start()method'senvparameter).