pytest-neon 3.0.0__tar.gz → 3.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pytest_neon-3.0.0 → pytest_neon-3.0.1}/PKG-INFO +1 -1
- {pytest_neon-3.0.0 → pytest_neon-3.0.1}/pyproject.toml +1 -1
- {pytest_neon-3.0.0 → pytest_neon-3.0.1}/src/pytest_neon/__init__.py +1 -1
- {pytest_neon-3.0.0 → pytest_neon-3.0.1}/src/pytest_neon/plugin.py +73 -0
- {pytest_neon-3.0.0 → pytest_neon-3.0.1}/tests/test_branch_name_prefix.py +6 -3
- {pytest_neon-3.0.0 → pytest_neon-3.0.1}/tests/test_migrations.py +2 -1
- {pytest_neon-3.0.0 → pytest_neon-3.0.1}/uv.lock +1 -1
- {pytest_neon-3.0.0 → pytest_neon-3.0.1}/.config/wt.toml +0 -0
- {pytest_neon-3.0.0 → pytest_neon-3.0.1}/.env.example +0 -0
- {pytest_neon-3.0.0 → pytest_neon-3.0.1}/.github/workflows/release.yml +0 -0
- {pytest_neon-3.0.0 → pytest_neon-3.0.1}/.github/workflows/tests.yml +0 -0
- {pytest_neon-3.0.0 → pytest_neon-3.0.1}/.gitignore +0 -0
- {pytest_neon-3.0.0 → pytest_neon-3.0.1}/.neon +0 -0
- {pytest_neon-3.0.0 → pytest_neon-3.0.1}/CLAUDE.md +0 -0
- {pytest_neon-3.0.0 → pytest_neon-3.0.1}/LICENSE +0 -0
- {pytest_neon-3.0.0 → pytest_neon-3.0.1}/README.md +0 -0
- {pytest_neon-3.0.0 → pytest_neon-3.0.1}/src/pytest_neon/py.typed +0 -0
- {pytest_neon-3.0.0 → pytest_neon-3.0.1}/tests/conftest.py +0 -0
- {pytest_neon-3.0.0 → pytest_neon-3.0.1}/tests/test_branch_lifecycle.py +0 -0
- {pytest_neon-3.0.0 → pytest_neon-3.0.1}/tests/test_cli_options.py +0 -0
- {pytest_neon-3.0.0 → pytest_neon-3.0.1}/tests/test_default_branch_safety.py +0 -0
- {pytest_neon-3.0.0 → pytest_neon-3.0.1}/tests/test_env_var.py +0 -0
- {pytest_neon-3.0.0 → pytest_neon-3.0.1}/tests/test_fixture_errors.py +0 -0
- {pytest_neon-3.0.0 → pytest_neon-3.0.1}/tests/test_integration.py +0 -0
- {pytest_neon-3.0.0 → pytest_neon-3.0.1}/tests/test_reset_behavior.py +0 -0
- {pytest_neon-3.0.0 → pytest_neon-3.0.1}/tests/test_service_classes.py +0 -0
- {pytest_neon-3.0.0 → pytest_neon-3.0.1}/tests/test_skip_behavior.py +0 -0
- {pytest_neon-3.0.0 → pytest_neon-3.0.1}/tests/test_xdist_worker_support.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pytest-neon
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.1
|
|
4
4
|
Summary: Pytest plugin for Neon database branch isolation in tests
|
|
5
5
|
Project-URL: Homepage, https://github.com/ZainRizvi/pytest-neon
|
|
6
6
|
Project-URL: Repository, https://github.com/ZainRizvi/pytest-neon
|
|
@@ -577,6 +577,7 @@ class XdistCoordinator:
|
|
|
577
577
|
def __init__(self, tmp_path_factory: pytest.TempPathFactory):
|
|
578
578
|
self.worker_id = _get_xdist_worker_id()
|
|
579
579
|
self.is_xdist = self.worker_id != "main"
|
|
580
|
+
self._worker_count: int | None = None
|
|
580
581
|
|
|
581
582
|
if self.is_xdist:
|
|
582
583
|
root_tmp_dir = tmp_path_factory.getbasetemp().parent
|
|
@@ -584,6 +585,21 @@ class XdistCoordinator:
|
|
|
584
585
|
else:
|
|
585
586
|
self._lock_dir = None
|
|
586
587
|
|
|
588
|
+
def _get_worker_count(self) -> int:
|
|
589
|
+
"""Get the total number of xdist workers."""
|
|
590
|
+
if self._worker_count is not None:
|
|
591
|
+
return self._worker_count
|
|
592
|
+
|
|
593
|
+
# PYTEST_XDIST_WORKER_COUNT is set by xdist
|
|
594
|
+
count_str = os.environ.get("PYTEST_XDIST_WORKER_COUNT")
|
|
595
|
+
if count_str:
|
|
596
|
+
self._worker_count = int(count_str)
|
|
597
|
+
else:
|
|
598
|
+
# Fallback: count from worker ID pattern (gw0, gw1, etc.)
|
|
599
|
+
# This shouldn't happen in normal xdist runs
|
|
600
|
+
self._worker_count = 1
|
|
601
|
+
return self._worker_count
|
|
602
|
+
|
|
587
603
|
def coordinate_resource(
|
|
588
604
|
self,
|
|
589
605
|
resource_name: str,
|
|
@@ -642,6 +658,53 @@ class XdistCoordinator:
|
|
|
642
658
|
signal_file = self._lock_dir / f"neon_{signal_name}"
|
|
643
659
|
signal_file.write_text("done")
|
|
644
660
|
|
|
661
|
+
def signal_worker_done(self) -> None:
|
|
662
|
+
"""Signal that this worker has completed all tests."""
|
|
663
|
+
if not self.is_xdist or self._lock_dir is None:
|
|
664
|
+
return
|
|
665
|
+
|
|
666
|
+
done_file = self._lock_dir / f"neon_worker_done_{self.worker_id}"
|
|
667
|
+
done_file.write_text("done")
|
|
668
|
+
|
|
669
|
+
def wait_for_all_workers_done(self, timeout: float = 300) -> None:
|
|
670
|
+
"""
|
|
671
|
+
Wait for all xdist workers to signal completion.
|
|
672
|
+
|
|
673
|
+
This ensures the branch isn't deleted while other workers are still
|
|
674
|
+
running tests.
|
|
675
|
+
|
|
676
|
+
Args:
|
|
677
|
+
timeout: Maximum time to wait in seconds (default: 5 minutes)
|
|
678
|
+
"""
|
|
679
|
+
if not self.is_xdist or self._lock_dir is None:
|
|
680
|
+
return
|
|
681
|
+
|
|
682
|
+
worker_count = self._get_worker_count()
|
|
683
|
+
waited = 0.0
|
|
684
|
+
poll_interval = 0.5
|
|
685
|
+
|
|
686
|
+
while waited < timeout:
|
|
687
|
+
done_count = 0
|
|
688
|
+
for i in range(worker_count):
|
|
689
|
+
done_file = self._lock_dir / f"neon_worker_done_gw{i}"
|
|
690
|
+
if done_file.exists():
|
|
691
|
+
done_count += 1
|
|
692
|
+
|
|
693
|
+
if done_count >= worker_count:
|
|
694
|
+
return
|
|
695
|
+
|
|
696
|
+
time.sleep(poll_interval)
|
|
697
|
+
waited += poll_interval
|
|
698
|
+
|
|
699
|
+
# Timeout - log warning but proceed with cleanup anyway
|
|
700
|
+
# This prevents infinite hangs if a worker crashes
|
|
701
|
+
warnings.warn(
|
|
702
|
+
f"Timeout waiting for all workers to complete after {timeout}s. "
|
|
703
|
+
f"Only {done_count}/{worker_count} workers signaled completion. "
|
|
704
|
+
f"Proceeding with branch cleanup.",
|
|
705
|
+
stacklevel=2,
|
|
706
|
+
)
|
|
707
|
+
|
|
645
708
|
|
|
646
709
|
class EnvironmentManager:
|
|
647
710
|
"""Manages DATABASE_URL environment variable lifecycle."""
|
|
@@ -870,6 +933,10 @@ def _neon_test_branch(
|
|
|
870
933
|
This creates a single branch with expiry that all tests share.
|
|
871
934
|
The first worker creates the branch, others reuse it.
|
|
872
935
|
|
|
936
|
+
Branch cleanup is coordinated so the creator waits for ALL workers
|
|
937
|
+
to complete before deleting the branch, preventing connection errors
|
|
938
|
+
for workers that finish later.
|
|
939
|
+
|
|
873
940
|
Yields:
|
|
874
941
|
Tuple of (branch, is_creator) where is_creator indicates if this
|
|
875
942
|
worker created the branch (and should run migrations/cleanup).
|
|
@@ -893,7 +960,13 @@ def _neon_test_branch(
|
|
|
893
960
|
yield branch, is_creator
|
|
894
961
|
finally:
|
|
895
962
|
env_manager.restore()
|
|
963
|
+
# Signal that this worker is done with all tests
|
|
964
|
+
_neon_xdist_coordinator.signal_worker_done()
|
|
965
|
+
|
|
896
966
|
if is_creator:
|
|
967
|
+
# Wait for all other workers to finish before deleting the branch
|
|
968
|
+
# This prevents "endpoint not found" errors for slower workers
|
|
969
|
+
_neon_xdist_coordinator.wait_for_all_workers_done()
|
|
897
970
|
_neon_branch_manager.delete_branch(branch.branch_id)
|
|
898
971
|
|
|
899
972
|
|
|
@@ -101,9 +101,10 @@ class TestNeonBranchManagerCreateBranch:
|
|
|
101
101
|
|
|
102
102
|
def test_branch_name_includes_git_branch(self):
|
|
103
103
|
"""Branch name includes git branch when in a repo."""
|
|
104
|
-
from pytest_neon.plugin import NeonBranchManager, NeonConfig
|
|
105
104
|
from neon_api.schema import EndpointState
|
|
106
105
|
|
|
106
|
+
from pytest_neon.plugin import NeonBranchManager, NeonConfig
|
|
107
|
+
|
|
107
108
|
mock_config = NeonConfig(
|
|
108
109
|
api_key="test-api-key",
|
|
109
110
|
project_id="test-project",
|
|
@@ -160,9 +161,10 @@ class TestNeonBranchManagerCreateBranch:
|
|
|
160
161
|
|
|
161
162
|
def test_branch_name_truncates_long_git_branch(self):
|
|
162
163
|
"""Git branch name is truncated to 15 characters."""
|
|
163
|
-
from pytest_neon.plugin import NeonBranchManager, NeonConfig
|
|
164
164
|
from neon_api.schema import EndpointState
|
|
165
165
|
|
|
166
|
+
from pytest_neon.plugin import NeonBranchManager, NeonConfig
|
|
167
|
+
|
|
166
168
|
mock_config = NeonConfig(
|
|
167
169
|
api_key="test-api-key",
|
|
168
170
|
project_id="test-project",
|
|
@@ -218,9 +220,10 @@ class TestNeonBranchManagerCreateBranch:
|
|
|
218
220
|
|
|
219
221
|
def test_branch_name_without_git(self):
|
|
220
222
|
"""Branch name uses old format when not in a git repo."""
|
|
221
|
-
from pytest_neon.plugin import NeonBranchManager, NeonConfig
|
|
222
223
|
from neon_api.schema import EndpointState
|
|
223
224
|
|
|
225
|
+
from pytest_neon.plugin import NeonBranchManager, NeonConfig
|
|
226
|
+
|
|
224
227
|
mock_config = NeonConfig(
|
|
225
228
|
api_key="test-api-key",
|
|
226
229
|
project_id="test-project",
|
|
@@ -167,7 +167,8 @@ class TestSharedBranchBehavior:
|
|
|
167
167
|
|
|
168
168
|
def pytest_sessionfinish(session, exitstatus):
|
|
169
169
|
# Should only create ONE branch for entire session
|
|
170
|
-
|
|
170
|
+
count = branch_create_count[0]
|
|
171
|
+
assert count == 1, f"Created {count} branches"
|
|
171
172
|
"""
|
|
172
173
|
)
|
|
173
174
|
|
|
@@ -1212,7 +1212,7 @@ wheels = [
|
|
|
1212
1212
|
|
|
1213
1213
|
[[package]]
|
|
1214
1214
|
name = "pytest-neon"
|
|
1215
|
-
version = "3.0.
|
|
1215
|
+
version = "3.0.1"
|
|
1216
1216
|
source = { editable = "." }
|
|
1217
1217
|
dependencies = [
|
|
1218
1218
|
{ name = "filelock", version = "3.19.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|