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.
Files changed (28) hide show
  1. {pytest_neon-3.0.0 → pytest_neon-3.0.1}/PKG-INFO +1 -1
  2. {pytest_neon-3.0.0 → pytest_neon-3.0.1}/pyproject.toml +1 -1
  3. {pytest_neon-3.0.0 → pytest_neon-3.0.1}/src/pytest_neon/__init__.py +1 -1
  4. {pytest_neon-3.0.0 → pytest_neon-3.0.1}/src/pytest_neon/plugin.py +73 -0
  5. {pytest_neon-3.0.0 → pytest_neon-3.0.1}/tests/test_branch_name_prefix.py +6 -3
  6. {pytest_neon-3.0.0 → pytest_neon-3.0.1}/tests/test_migrations.py +2 -1
  7. {pytest_neon-3.0.0 → pytest_neon-3.0.1}/uv.lock +1 -1
  8. {pytest_neon-3.0.0 → pytest_neon-3.0.1}/.config/wt.toml +0 -0
  9. {pytest_neon-3.0.0 → pytest_neon-3.0.1}/.env.example +0 -0
  10. {pytest_neon-3.0.0 → pytest_neon-3.0.1}/.github/workflows/release.yml +0 -0
  11. {pytest_neon-3.0.0 → pytest_neon-3.0.1}/.github/workflows/tests.yml +0 -0
  12. {pytest_neon-3.0.0 → pytest_neon-3.0.1}/.gitignore +0 -0
  13. {pytest_neon-3.0.0 → pytest_neon-3.0.1}/.neon +0 -0
  14. {pytest_neon-3.0.0 → pytest_neon-3.0.1}/CLAUDE.md +0 -0
  15. {pytest_neon-3.0.0 → pytest_neon-3.0.1}/LICENSE +0 -0
  16. {pytest_neon-3.0.0 → pytest_neon-3.0.1}/README.md +0 -0
  17. {pytest_neon-3.0.0 → pytest_neon-3.0.1}/src/pytest_neon/py.typed +0 -0
  18. {pytest_neon-3.0.0 → pytest_neon-3.0.1}/tests/conftest.py +0 -0
  19. {pytest_neon-3.0.0 → pytest_neon-3.0.1}/tests/test_branch_lifecycle.py +0 -0
  20. {pytest_neon-3.0.0 → pytest_neon-3.0.1}/tests/test_cli_options.py +0 -0
  21. {pytest_neon-3.0.0 → pytest_neon-3.0.1}/tests/test_default_branch_safety.py +0 -0
  22. {pytest_neon-3.0.0 → pytest_neon-3.0.1}/tests/test_env_var.py +0 -0
  23. {pytest_neon-3.0.0 → pytest_neon-3.0.1}/tests/test_fixture_errors.py +0 -0
  24. {pytest_neon-3.0.0 → pytest_neon-3.0.1}/tests/test_integration.py +0 -0
  25. {pytest_neon-3.0.0 → pytest_neon-3.0.1}/tests/test_reset_behavior.py +0 -0
  26. {pytest_neon-3.0.0 → pytest_neon-3.0.1}/tests/test_service_classes.py +0 -0
  27. {pytest_neon-3.0.0 → pytest_neon-3.0.1}/tests/test_skip_behavior.py +0 -0
  28. {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.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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "pytest-neon"
7
- version = "3.0.0"
7
+ version = "3.0.1"
8
8
  description = "Pytest plugin for Neon database branch isolation in tests"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -9,7 +9,7 @@ from pytest_neon.plugin import (
9
9
  neon_engine,
10
10
  )
11
11
 
12
- __version__ = "3.0.0"
12
+ __version__ = "3.0.1"
13
13
  __all__ = [
14
14
  "NeonBranch",
15
15
  "neon_apply_migrations",
@@ -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
- assert branch_create_count[0] == 1, f"Created {branch_create_count[0]} branches"
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.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