Skip to content

Commit a3aa638

Browse files
committed
Update release tooling; make test files unique
1 parent ac84d18 commit a3aa638

File tree

6 files changed

+267
-165
lines changed

6 files changed

+267
-165
lines changed

RELEASE.md

Lines changed: 31 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,48 +2,49 @@
22

33
## Bump the package version and build documentation
44

5-
Bump the version number in `setup.cfg` and `singlestoredb/__init__.py` using
6-
semantic versioning rules: minor bump for new features, patch bump for
7-
bug fixes. Add release notes to `docs/src/whatsnew.rst`. Run `make html` in
8-
`docs/src` to generate documentation.
9-
10-
You will need `sphinx` and `sphinx_rtd_theme` installed for this step. You
11-
also need a SingleStoreDB server running at the given IP and port to run
12-
samples against.
13-
14-
There is a utility to do this process for you, but you should check the
15-
`docs/src/whatsnew.rst` to verify the release summary. Use the following
16-
to run it:
5+
Run the following command:
176
```
187
resources/bump_version.py < major | minor | patch >
198
209
```
2110

22-
## Commit and push the changes
11+
This will bump the version number in `setup.cfg` and `singlestoredb/__init__.py`
12+
using semantic versioning rules: minor bump for new features, patch bump for
13+
bug fixes. It will genarete a list of changes since the last version and
14+
ask for confirmation of the release notes in `docs/src/whatsnew.rst`.
15+
It will then run `make html` in `docs/src` to generate documentation.
16+
You will need `sphinx` and `sphinx_rtd_theme` installed for this step.
17+
The documentation contains source which runs against a live server that is
18+
created using Docker.
2319

24-
After verifying the release summary in the documentation, commit the changes:
25-
```
26-
# Make sure newly generated docs get added
27-
git add docs
2820

29-
# Commit changes
30-
git commit -am "Prepare for vX.X.X release".
21+
## Commit and push the changes
3122

32-
git push
23+
After verifying the release summary in the documentation, commit the changes.
24+
All modified files should be staged by `bump_version.py` already.
25+
```
26+
git commit -m "Prepare for vX.X.X release" && git push
3327
3428
```
3529

36-
## Run smoke tests
30+
## Final testing
31+
32+
The coverage tests will be triggered by the push, but it will likely only
33+
be a subset since Management API tests only get executed if the Management API
34+
source files are changed. You should run the full set of [
35+
Coverage tests at](https://github.com/singlestore-labs/singlestoredb-python/actions/workflows/coverage.yml)
36+
and then run the Smoke tests which verify the code works at all of the advertised Python versions:
37+
[Smoke test](https://github.com/singlestore-labs/singlestoredb-python/actions/workflows/smoke-test.yml).
3738

38-
The coverage tests will be triggered by the push, but you should also run
39-
[Smoke test](https://github.com/singlestore-labs/singlestoredb-python/actions/workflows/smoke-test.yml)
40-
workflow manually which does basic tests on all supported versions of Python.
4139

4240
## Create the release on Github
4341

44-
Once all workflows are clean, create a new Github release with the name
45-
"SingleStoreDB vX.X.X" at <https://github.com/singlestore-labs/singlestoredb-python/releases>
46-
and set the generated tag to the matching version
47-
number. Add the release notes from the `whatsnew.rst` file to the release
48-
notes. Creating the release will run the [Publish packages](https://github.com/singlestore-labs/singlestoredb-python/actions/workflows/publish.yml)
49-
workflow which builds the packages and pubsishes them to PyPI.
42+
To create the release, run:
43+
```
44+
resources/create_release.py
45+
46+
```
47+
48+
This will generate a tag and start the release process in the
49+
[Publish packages](https://github.com/singlestore-labs/singlestoredb-python/actions/workflows/publish.yml)
50+
Github workflow.

resources/bump_version.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,11 @@ def main() -> None:
424424
print('=' * 50, file=sys.stderr)
425425
print(f'🎉 Version bump completed successfully in {total_elapsed:.1f}s!', file=sys.stderr)
426426
print(f'📝 Version: {current_version}{new_version}', file=sys.stderr)
427-
print('📄 Next step: git commit -m "Bump version to {}"'.format(new_version), file=sys.stderr)
427+
print('🚀 Next steps:', file=sys.stderr)
428+
print(' 📄 git commit -m "Prepare for v{} release" && git push'.format(new_version), file=sys.stderr)
429+
print(' 📄 Run Coverage tests <https://github.com/singlestore-labs/singlestoredb-python/actions/workflows/coverage.yml>', file=sys.stderr)
430+
print(' 📄 Run Smoke test <https://github.com/singlestore-labs/singlestoredb-python/actions/workflows/smoke-test.yml>', file=sys.stderr)
431+
print(' 📄 Run resources/create_release.py', file=sys.stderr)
428432

429433

430434
if __name__ == '__main__':

singlestoredb/pytest.py

Lines changed: 77 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
"""Pytest plugin"""
33
import logging
44
import os
5+
import socket
56
import subprocess
67
import time
8+
import uuid
79
from enum import Enum
810
from typing import Iterator
911
from typing import Optional
@@ -28,6 +30,14 @@
2830
TEARDOWN_WAIT_SECONDS = 2
2931

3032

33+
def _find_free_port() -> int:
34+
"""Find a free port by binding to port 0 and getting the assigned port."""
35+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
36+
s.bind(('', 0))
37+
s.listen(1)
38+
return s.getsockname()[1]
39+
40+
3141
class ExecutionMode(Enum):
3242
SEQUENTIAL = 1
3343
LEADER = 2
@@ -79,7 +89,11 @@ class _TestContainerManager():
7989
"""Manages the setup and teardown of a SingleStoreDB Dev Container"""
8090

8191
def __init__(self) -> None:
82-
self.container_name = 'singlestoredb-test-container'
92+
# Generate unique container name using UUID and worker ID
93+
worker = os.environ.get('PYTEST_XDIST_WORKER', 'master')
94+
unique_id = uuid.uuid4().hex[:8]
95+
self.container_name = f'singlestoredb-test-{worker}-{unique_id}'
96+
8397
self.dev_image_name = 'ghcr.io/singlestore-labs/singlestoredb-dev'
8498

8599
assert 'SINGLESTORE_LICENSE' in os.environ, 'SINGLESTORE_LICENSE not set'
@@ -91,14 +105,69 @@ def __init__(self) -> None:
91105
'SINGLESTORE_SET_GLOBAL_DEFAULT_PARTITIONS_PER_LEAF': '1',
92106
}
93107

94-
self.ports = ['3306', '8080', '9000']
108+
# Use dynamic port allocation to avoid conflicts
109+
self.mysql_port = _find_free_port()
110+
self.http_port = _find_free_port()
111+
self.studio_port = _find_free_port()
112+
self.ports = [
113+
(self.mysql_port, '3306'), # External port -> Internal port
114+
(self.http_port, '8080'),
115+
(self.studio_port, '9000'),
116+
]
117+
118+
self.url = f'root:{self.root_password}@127.0.0.1:{self.mysql_port}'
119+
120+
def _container_exists(self) -> bool:
121+
"""Check if a container with this name already exists."""
122+
try:
123+
result = subprocess.run(
124+
[
125+
'docker', 'ps', '-a', '--filter',
126+
f'name={self.container_name}',
127+
'--format', '{{.Names}}',
128+
],
129+
capture_output=True,
130+
text=True,
131+
check=True,
132+
)
133+
return self.container_name in result.stdout
134+
except subprocess.CalledProcessError:
135+
return False
136+
137+
def _cleanup_existing_container(self) -> None:
138+
"""Stop and remove any existing container with the same name."""
139+
if not self._container_exists():
140+
return
95141

96-
self.url = f'root:{self.root_password}@127.0.0.1:3306'
142+
logger.info(f'Found existing container {self.container_name}, cleaning up')
143+
try:
144+
# Try to stop the container (ignore if it's already stopped)
145+
subprocess.run(
146+
['docker', 'stop', self.container_name],
147+
capture_output=True,
148+
check=False,
149+
)
150+
# Remove the container
151+
subprocess.run(
152+
['docker', 'rm', self.container_name],
153+
capture_output=True,
154+
check=True,
155+
)
156+
logger.debug(f'Cleaned up existing container {self.container_name}')
157+
except subprocess.CalledProcessError as e:
158+
logger.warning(f'Failed to cleanup existing container: {e}')
159+
# Continue anyway - the unique name should prevent most conflicts
97160

98161
def start(self) -> None:
162+
# Clean up any existing container with the same name
163+
self._cleanup_existing_container()
164+
99165
command = ' '.join(self._start_command())
100166

101-
logger.info(f'Starting container {self.container_name}')
167+
logger.info(
168+
f'Starting container {self.container_name} on ports {self.mysql_port}, '
169+
f'{self.http_port}, {self.studio_port}',
170+
)
102171
try:
103172
license = os.environ['SINGLESTORE_LICENSE']
104173
env = {
@@ -108,8 +177,8 @@ def start(self) -> None:
108177
except Exception as e:
109178
logger.exception(e)
110179
raise RuntimeError(
111-
'Failed to start container. '
112-
'Is one already running?',
180+
f'Failed to start container {self.container_name}. '
181+
f'Command: {command}',
113182
) from e
114183
logger.debug('Container started')
115184

@@ -123,9 +192,9 @@ def _start_command(self) -> Iterator[str]:
123192
else:
124193
yield f'{key}={value}'
125194

126-
for port in self.ports:
195+
for external_port, internal_port in self.ports:
127196
yield '-p'
128-
yield f'{port}:{port}'
197+
yield f'{external_port}:{internal_port}'
129198

130199
yield self.dev_image_name
131200

0 commit comments

Comments
 (0)