experimaestro 2.0.0b8__py3-none-any.whl → 2.0.0b17__py3-none-any.whl

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.

Potentially problematic release.


This version of experimaestro might be problematic. Click here for more details.

Files changed (152) hide show
  1. experimaestro/__init__.py +12 -5
  2. experimaestro/cli/__init__.py +239 -126
  3. experimaestro/cli/filter.py +48 -23
  4. experimaestro/cli/jobs.py +253 -71
  5. experimaestro/cli/refactor.py +1 -2
  6. experimaestro/commandline.py +7 -4
  7. experimaestro/connectors/__init__.py +9 -1
  8. experimaestro/connectors/local.py +43 -3
  9. experimaestro/core/arguments.py +18 -18
  10. experimaestro/core/identifier.py +11 -11
  11. experimaestro/core/objects/config.py +96 -39
  12. experimaestro/core/objects/config_walk.py +3 -3
  13. experimaestro/core/{subparameters.py → partial.py} +16 -16
  14. experimaestro/core/partial_lock.py +394 -0
  15. experimaestro/core/types.py +12 -15
  16. experimaestro/dynamic.py +290 -0
  17. experimaestro/experiments/__init__.py +6 -2
  18. experimaestro/experiments/cli.py +217 -50
  19. experimaestro/experiments/configuration.py +24 -0
  20. experimaestro/generators.py +5 -5
  21. experimaestro/ipc.py +118 -1
  22. experimaestro/launcherfinder/__init__.py +2 -2
  23. experimaestro/launcherfinder/registry.py +6 -7
  24. experimaestro/launcherfinder/specs.py +2 -9
  25. experimaestro/launchers/slurm/__init__.py +2 -2
  26. experimaestro/launchers/slurm/base.py +62 -0
  27. experimaestro/locking.py +957 -1
  28. experimaestro/notifications.py +89 -201
  29. experimaestro/progress.py +63 -366
  30. experimaestro/rpyc.py +0 -2
  31. experimaestro/run.py +29 -2
  32. experimaestro/scheduler/__init__.py +8 -1
  33. experimaestro/scheduler/base.py +629 -53
  34. experimaestro/scheduler/dependencies.py +20 -16
  35. experimaestro/scheduler/experiment.py +732 -167
  36. experimaestro/scheduler/interfaces.py +316 -101
  37. experimaestro/scheduler/jobs.py +58 -20
  38. experimaestro/scheduler/remote/adaptive_sync.py +265 -0
  39. experimaestro/scheduler/remote/client.py +171 -117
  40. experimaestro/scheduler/remote/protocol.py +8 -193
  41. experimaestro/scheduler/remote/server.py +95 -71
  42. experimaestro/scheduler/services.py +53 -28
  43. experimaestro/scheduler/state_provider.py +663 -2430
  44. experimaestro/scheduler/state_status.py +1247 -0
  45. experimaestro/scheduler/transient.py +31 -0
  46. experimaestro/scheduler/workspace.py +1 -1
  47. experimaestro/scheduler/workspace_state_provider.py +1273 -0
  48. experimaestro/scriptbuilder.py +4 -4
  49. experimaestro/settings.py +36 -0
  50. experimaestro/tests/conftest.py +33 -5
  51. experimaestro/tests/connectors/bin/executable.py +1 -1
  52. experimaestro/tests/fixtures/pre_experiment/experiment_check_env.py +16 -0
  53. experimaestro/tests/fixtures/pre_experiment/experiment_check_mock.py +14 -0
  54. experimaestro/tests/fixtures/pre_experiment/experiment_simple.py +12 -0
  55. experimaestro/tests/fixtures/pre_experiment/pre_setup_env.py +5 -0
  56. experimaestro/tests/fixtures/pre_experiment/pre_setup_error.py +3 -0
  57. experimaestro/tests/fixtures/pre_experiment/pre_setup_mock.py +8 -0
  58. experimaestro/tests/launchers/bin/test.py +1 -0
  59. experimaestro/tests/launchers/test_slurm.py +9 -9
  60. experimaestro/tests/partial_reschedule.py +46 -0
  61. experimaestro/tests/restart.py +3 -3
  62. experimaestro/tests/restart_main.py +1 -0
  63. experimaestro/tests/scripts/notifyandwait.py +1 -0
  64. experimaestro/tests/task_partial.py +38 -0
  65. experimaestro/tests/task_tokens.py +2 -2
  66. experimaestro/tests/tasks/test_dynamic.py +6 -6
  67. experimaestro/tests/test_dependencies.py +3 -3
  68. experimaestro/tests/test_deprecated.py +15 -15
  69. experimaestro/tests/test_dynamic_locking.py +317 -0
  70. experimaestro/tests/test_environment.py +24 -14
  71. experimaestro/tests/test_experiment.py +171 -36
  72. experimaestro/tests/test_identifier.py +25 -25
  73. experimaestro/tests/test_identifier_stability.py +3 -5
  74. experimaestro/tests/test_multitoken.py +2 -4
  75. experimaestro/tests/{test_subparameters.py → test_partial.py} +25 -25
  76. experimaestro/tests/test_partial_paths.py +81 -138
  77. experimaestro/tests/test_pre_experiment.py +219 -0
  78. experimaestro/tests/test_progress.py +2 -8
  79. experimaestro/tests/test_remote_state.py +560 -99
  80. experimaestro/tests/test_stray_jobs.py +261 -0
  81. experimaestro/tests/test_tasks.py +1 -2
  82. experimaestro/tests/test_token_locking.py +52 -67
  83. experimaestro/tests/test_tokens.py +5 -6
  84. experimaestro/tests/test_transient.py +225 -0
  85. experimaestro/tests/test_workspace_state_provider.py +768 -0
  86. experimaestro/tests/token_reschedule.py +1 -3
  87. experimaestro/tests/utils.py +2 -7
  88. experimaestro/tokens.py +227 -372
  89. experimaestro/tools/diff.py +1 -0
  90. experimaestro/tools/documentation.py +4 -5
  91. experimaestro/tools/jobs.py +1 -2
  92. experimaestro/tui/app.py +438 -1966
  93. experimaestro/tui/app.tcss +162 -0
  94. experimaestro/tui/dialogs.py +172 -0
  95. experimaestro/tui/log_viewer.py +253 -3
  96. experimaestro/tui/messages.py +137 -0
  97. experimaestro/tui/utils.py +54 -0
  98. experimaestro/tui/widgets/__init__.py +23 -0
  99. experimaestro/tui/widgets/experiments.py +468 -0
  100. experimaestro/tui/widgets/global_services.py +238 -0
  101. experimaestro/tui/widgets/jobs.py +972 -0
  102. experimaestro/tui/widgets/log.py +156 -0
  103. experimaestro/tui/widgets/orphans.py +363 -0
  104. experimaestro/tui/widgets/runs.py +185 -0
  105. experimaestro/tui/widgets/services.py +314 -0
  106. experimaestro/tui/widgets/stray_jobs.py +528 -0
  107. experimaestro/utils/__init__.py +1 -1
  108. experimaestro/utils/environment.py +105 -22
  109. experimaestro/utils/fswatcher.py +124 -0
  110. experimaestro/utils/jobs.py +1 -2
  111. experimaestro/utils/jupyter.py +1 -2
  112. experimaestro/utils/logging.py +72 -0
  113. experimaestro/version.py +2 -2
  114. experimaestro/webui/__init__.py +9 -0
  115. experimaestro/webui/app.py +117 -0
  116. experimaestro/{server → webui}/data/index.css +66 -11
  117. experimaestro/webui/data/index.css.map +1 -0
  118. experimaestro/{server → webui}/data/index.js +82763 -87217
  119. experimaestro/webui/data/index.js.map +1 -0
  120. experimaestro/webui/routes/__init__.py +5 -0
  121. experimaestro/webui/routes/auth.py +53 -0
  122. experimaestro/webui/routes/proxy.py +117 -0
  123. experimaestro/webui/server.py +200 -0
  124. experimaestro/webui/state_bridge.py +152 -0
  125. experimaestro/webui/websocket.py +413 -0
  126. {experimaestro-2.0.0b8.dist-info → experimaestro-2.0.0b17.dist-info}/METADATA +5 -6
  127. experimaestro-2.0.0b17.dist-info/RECORD +219 -0
  128. experimaestro/cli/progress.py +0 -269
  129. experimaestro/scheduler/state.py +0 -75
  130. experimaestro/scheduler/state_db.py +0 -437
  131. experimaestro/scheduler/state_sync.py +0 -891
  132. experimaestro/server/__init__.py +0 -467
  133. experimaestro/server/data/index.css.map +0 -1
  134. experimaestro/server/data/index.js.map +0 -1
  135. experimaestro/tests/test_cli_jobs.py +0 -615
  136. experimaestro/tests/test_file_progress.py +0 -425
  137. experimaestro/tests/test_file_progress_integration.py +0 -477
  138. experimaestro/tests/test_state_db.py +0 -434
  139. experimaestro-2.0.0b8.dist-info/RECORD +0 -187
  140. /experimaestro/{server → webui}/data/1815e00441357e01619e.ttf +0 -0
  141. /experimaestro/{server → webui}/data/2463b90d9a316e4e5294.woff2 +0 -0
  142. /experimaestro/{server → webui}/data/2582b0e4bcf85eceead0.ttf +0 -0
  143. /experimaestro/{server → webui}/data/89999bdf5d835c012025.woff2 +0 -0
  144. /experimaestro/{server → webui}/data/914997e1bdfc990d0897.ttf +0 -0
  145. /experimaestro/{server → webui}/data/c210719e60948b211a12.woff2 +0 -0
  146. /experimaestro/{server → webui}/data/favicon.ico +0 -0
  147. /experimaestro/{server → webui}/data/index.html +0 -0
  148. /experimaestro/{server → webui}/data/login.html +0 -0
  149. /experimaestro/{server → webui}/data/manifest.json +0 -0
  150. {experimaestro-2.0.0b8.dist-info → experimaestro-2.0.0b17.dist-info}/WHEEL +0 -0
  151. {experimaestro-2.0.0b8.dist-info → experimaestro-2.0.0b17.dist-info}/entry_points.txt +0 -0
  152. {experimaestro-2.0.0b8.dist-info → experimaestro-2.0.0b17.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,261 @@
1
+ """Tests for stray job detection.
2
+
3
+ A stray job is a running job that is not associated with any active experiment.
4
+ This happens when an experiment plan changes (e.g., same experiment ID is relaunched
5
+ with different parameters).
6
+ """
7
+
8
+ import time
9
+ from pathlib import Path
10
+ import tempfile
11
+ import logging
12
+
13
+ from experimaestro import (
14
+ Task,
15
+ Param,
16
+ Meta,
17
+ field,
18
+ PathGenerator,
19
+ experiment,
20
+ GracefulExperimentExit,
21
+ )
22
+ from experimaestro.scheduler.workspace import RunMode
23
+ from experimaestro.scheduler import JobState
24
+ from experimaestro.scheduler.workspace_state_provider import WorkspaceStateProvider
25
+
26
+ logger = logging.getLogger(__name__)
27
+
28
+
29
+ class ControllableTask(Task):
30
+ """A task that can be controlled via files for testing purposes."""
31
+
32
+ value: Param[int]
33
+ touch: Meta[Path] = field(default_factory=PathGenerator("touch"))
34
+ wait: Meta[Path] = field(default_factory=PathGenerator("wait"))
35
+
36
+ def execute(self):
37
+ # Signal that the task has started
38
+ with open(self.touch, "w") as out:
39
+ out.write("started")
40
+
41
+ # Wait for external signal to continue
42
+ while not self.wait.is_file():
43
+ time.sleep(0.1)
44
+
45
+
46
+ MAX_WAIT_ITERATIONS = 50 # 5 seconds max wait
47
+
48
+
49
+ def test_stray_job_detection():
50
+ """Test that a running job becomes stray when experiment is relaunched with different params.
51
+
52
+ Scenario:
53
+ 1. Start experiment with task(value=1)
54
+ 2. Wait for task to start running
55
+ 3. Exit experiment without waiting (using GracefulExperimentExit)
56
+ 4. Start same experiment (same ID) with task(value=2)
57
+ 5. The first job should now be detected as stray
58
+ 6. Signal first task to finish
59
+ 7. Verify stray detection works correctly
60
+ """
61
+ with tempfile.TemporaryDirectory(prefix="xpm_stray_test_") as workdir:
62
+ workdir_path = Path(workdir)
63
+ experiment_id = "stray_test"
64
+
65
+ # Phase 1: Start experiment with value=1
66
+ with experiment(workdir_path, experiment_id) as _:
67
+ task1 = ControllableTask.C(value=1)
68
+ # First do a dry run to get the file paths
69
+ task1.submit(run_mode=RunMode.DRY_RUN)
70
+ touch_path = task1.touch
71
+ wait_path = task1.wait
72
+
73
+ # Now submit for real
74
+ task1 = ControllableTask.C(value=1)
75
+ task1.submit()
76
+
77
+ # Wait for task to start
78
+ counter = 0
79
+ while not touch_path.exists():
80
+ time.sleep(0.1)
81
+ counter += 1
82
+ if counter >= MAX_WAIT_ITERATIONS:
83
+ raise AssertionError("Timeout waiting for task1 to start")
84
+
85
+ raise GracefulExperimentExit()
86
+
87
+ # Phase 2: Start same experiment with value=2
88
+ try:
89
+ with experiment(workdir_path, experiment_id) as _:
90
+ task2 = ControllableTask.C(value=2)
91
+ task2.submit()
92
+
93
+ # Give the scheduler a moment to process
94
+ time.sleep(0.5)
95
+
96
+ # Check for stray jobs using a fresh state provider
97
+ provider = WorkspaceStateProvider(workdir_path)
98
+
99
+ stray_jobs = provider.get_stray_jobs()
100
+
101
+ # At least the first task should be stray (running but not in current experiment)
102
+ # Note: task2 might also appear as stray temporarily because status.json
103
+ # hasn't been flushed yet
104
+ assert len(stray_jobs) >= 1, (
105
+ f"Expected at least 1 stray job, found {len(stray_jobs)}"
106
+ )
107
+
108
+ # Verify that at least one stray job is actually running
109
+ running_stray = [j for j in stray_jobs if j.state == JobState.RUNNING]
110
+ assert len(running_stray) >= 1, (
111
+ f"Expected at least 1 running stray job, found {len(running_stray)}"
112
+ )
113
+
114
+ # Signal first task to finish
115
+ with open(wait_path, "w") as f:
116
+ f.write("done")
117
+
118
+ # Give it a moment to finish
119
+ time.sleep(0.5)
120
+
121
+ # After task1 finishes, it should no longer be stray (it's not running)
122
+ # task1 should no longer be stray (it finished)
123
+ # task2 might still appear as stray if status hasn't been flushed
124
+ # So we just verify that the count decreased by at least 1
125
+ # (or at least, that task1's specific job is no longer in the list)
126
+
127
+ # Exit gracefully to not wait for task2
128
+ raise GracefulExperimentExit()
129
+ finally:
130
+ # Clean up: signal task2 to finish if it started
131
+ with experiment(workdir_path, experiment_id, run_mode=RunMode.DRY_RUN):
132
+ task2_dry = ControllableTask.C(value=2)
133
+ task2_dry.submit(run_mode=RunMode.DRY_RUN)
134
+ task2_wait = task2_dry.wait
135
+
136
+ if task2_wait and task2_wait.exists() is False:
137
+ # task2 might have started
138
+ task2_touch = task2_dry.touch
139
+ if task2_touch.exists():
140
+ with open(task2_wait, "w") as f:
141
+ f.write("done")
142
+ time.sleep(0.5)
143
+
144
+
145
+ def test_running_state_detection():
146
+ """Test that running jobs are correctly detected from PID files."""
147
+ with tempfile.TemporaryDirectory(prefix="xpm_running_test_") as workdir:
148
+ workdir_path = Path(workdir)
149
+ experiment_id = "running_test"
150
+
151
+ with experiment(workdir_path, experiment_id) as _:
152
+ task = ControllableTask.C(value=1)
153
+ task.submit(run_mode=RunMode.DRY_RUN)
154
+ touch_path = task.touch
155
+ wait_path = task.wait
156
+
157
+ task = ControllableTask.C(value=1)
158
+ task.submit()
159
+
160
+ # Wait for task to start
161
+ counter = 0
162
+ while not touch_path.exists():
163
+ time.sleep(0.1)
164
+ counter += 1
165
+ if counter >= MAX_WAIT_ITERATIONS:
166
+ raise AssertionError("Timeout waiting for task to start")
167
+
168
+ # Create a fresh provider to check the running state
169
+ provider = WorkspaceStateProvider(workdir_path)
170
+
171
+ # Get all jobs on disk
172
+ jobs_base = workdir_path / "jobs"
173
+ job_paths = list(jobs_base.glob("*/*"))
174
+ assert len(job_paths) == 1, (
175
+ f"Expected 1 job on disk, found {len(job_paths)}"
176
+ )
177
+
178
+ # Check that the job is detected as running
179
+ job_path = job_paths[0]
180
+ task_id = job_path.parent.name
181
+ job_id = job_path.name
182
+
183
+ mock_job = provider._create_mock_job_from_path(job_path, task_id, job_id)
184
+ assert mock_job.state == JobState.RUNNING, (
185
+ f"Expected job state RUNNING, got {mock_job.state}"
186
+ )
187
+
188
+ # Signal task to finish
189
+ with open(wait_path, "w") as f:
190
+ f.write("done")
191
+
192
+
193
+ def test_completed_job_not_stray():
194
+ """Test that completed jobs are not detected as stray.
195
+
196
+ Scenario:
197
+ 1. Start experiment, run and complete a job
198
+ 2. Start a new run of the same experiment with a different job
199
+ 3. The first job should be orphan (not in the new run) but NOT stray (not running)
200
+ """
201
+ with tempfile.TemporaryDirectory(prefix="xpm_completed_test_") as workdir:
202
+ workdir_path = Path(workdir)
203
+ experiment_id = "completed_test"
204
+
205
+ # Phase 1: Create and complete a job
206
+ with experiment(workdir_path, experiment_id) as _:
207
+ task = ControllableTask.C(value=1)
208
+ task.submit(run_mode=RunMode.DRY_RUN)
209
+ touch_path = task.touch
210
+ wait_path = task.wait
211
+
212
+ task = ControllableTask.C(value=1)
213
+ task.submit()
214
+
215
+ # Wait for task to start
216
+ counter = 0
217
+ while not touch_path.exists():
218
+ time.sleep(0.1)
219
+ counter += 1
220
+ if counter >= MAX_WAIT_ITERATIONS:
221
+ raise AssertionError("Timeout waiting for task to start")
222
+
223
+ # Signal to finish immediately
224
+ with open(wait_path, "w") as f:
225
+ f.write("done")
226
+
227
+ # Phase 2: Start a new run with a different job
228
+ # This makes the old job an orphan (not in current run)
229
+ with experiment(workdir_path, experiment_id) as _:
230
+ task2 = ControllableTask.C(value=2)
231
+ task2.submit(run_mode=RunMode.DRY_RUN)
232
+ touch_path2 = task2.touch
233
+ wait_path2 = task2.wait
234
+
235
+ task2 = ControllableTask.C(value=2)
236
+ task2.submit()
237
+
238
+ # Wait for task2 to start
239
+ counter = 0
240
+ while not touch_path2.exists():
241
+ time.sleep(0.1)
242
+ counter += 1
243
+ if counter >= MAX_WAIT_ITERATIONS:
244
+ raise AssertionError("Timeout waiting for task2 to start")
245
+
246
+ # Now check that the first job is NOT stray (it's completed)
247
+ provider = WorkspaceStateProvider(workdir_path)
248
+
249
+ stray_jobs = provider.get_stray_jobs()
250
+
251
+ # Task1 is not running, so it should not be stray
252
+ # Task2 might appear as stray because status.json hasn't been flushed
253
+ # Filter to check that no DONE jobs are stray
254
+ done_stray = [j for j in stray_jobs if j.state == JobState.DONE]
255
+ assert len(done_stray) == 0, (
256
+ f"Expected 0 completed stray jobs, found {len(done_stray)}"
257
+ )
258
+
259
+ # Signal task2 to finish
260
+ with open(wait_path2, "w") as f:
261
+ f.write("done")
@@ -132,14 +132,13 @@ def test_restart(terminate):
132
132
  def test_submitted_twice():
133
133
  """Check that a job cannot be submitted twice within the same experiment"""
134
134
  with TemporaryExperiment("duplicate", maxwait=20):
135
-
136
135
  task1 = SimpleTask.C(x=1)
137
136
  o1 = task1.submit()
138
137
 
139
138
  task2 = SimpleTask.C(x=1)
140
139
  o2 = task2.submit()
141
140
 
142
- print(o1)
141
+ print(o1) # noqa: T201
143
142
  assert o1.task is not o2.task
144
143
  assert task1.__xpm__.job is task2.__xpm__.job, f"{id(task1)} != {id(task2)}"
145
144
 
@@ -9,6 +9,7 @@ import pytest
9
9
  import tempfile
10
10
  from pathlib import Path
11
11
  import time
12
+ import hashlib
12
13
 
13
14
  from experimaestro.tokens import CounterToken
14
15
  from experimaestro.locking import LockError
@@ -16,22 +17,54 @@ from experimaestro.locking import LockError
16
17
  pytestmark = pytest.mark.anyio
17
18
 
18
19
 
20
+ class MockIdentifier:
21
+ """Mock identifier with hex() method."""
22
+
23
+ def __init__(self, value: str):
24
+ self._hex = hashlib.sha256(value.encode()).hexdigest()
25
+
26
+ def hex(self):
27
+ return self._hex
28
+
29
+
30
+ class MockXPM:
31
+ """Mock __xpm__ object."""
32
+
33
+ def __init__(self, name: str):
34
+ self.identifier = type("Identifier", (), {"main": MockIdentifier(name)})()
35
+
36
+
37
+ class MockConfig:
38
+ """Mock config object."""
39
+
40
+ def __init__(self, name: str):
41
+ self.__xpm__ = MockXPM(name)
42
+
43
+
44
+ def create_mock_job(name: str, tmpdir: str):
45
+ """Create a mock job with all required attributes."""
46
+
47
+ class MockJob:
48
+ task_id = "mock-task"
49
+ config = MockConfig(name)
50
+
51
+ @property
52
+ def identifier(self):
53
+ return f"mock-job-{name}"
54
+
55
+ @property
56
+ def basepath(self):
57
+ return Path(tmpdir) / name
58
+
59
+ return MockJob()
60
+
61
+
19
62
  async def test_token_acquire_release():
20
63
  """Test basic token acquire and release"""
21
64
  with tempfile.TemporaryDirectory() as tmpdir:
22
65
  token = CounterToken("test-basic", Path(tmpdir) / "token", count=1)
23
66
 
24
- # Create a mock job target
25
- class MockJob:
26
- @property
27
- def identifier(self):
28
- return "mock-job-1"
29
-
30
- @property
31
- def basepath(self):
32
- return Path(tmpdir) / "job1"
33
-
34
- job = MockJob()
67
+ job = create_mock_job("1", tmpdir)
35
68
 
36
69
  # Create dependency
37
70
  dep = token.dependency(1)
@@ -52,20 +85,8 @@ async def test_token_blocking():
52
85
  with tempfile.TemporaryDirectory() as tmpdir:
53
86
  token = CounterToken("test-blocking", Path(tmpdir) / "token", count=1)
54
87
 
55
- class MockJob:
56
- def __init__(self, name):
57
- self.name = name
58
-
59
- @property
60
- def identifier(self):
61
- return f"mock-job-{self.name}"
62
-
63
- @property
64
- def basepath(self):
65
- return Path(tmpdir) / self.name
66
-
67
- job1 = MockJob("1")
68
- job2 = MockJob("2")
88
+ job1 = create_mock_job("1", tmpdir)
89
+ job2 = create_mock_job("2", tmpdir)
69
90
 
70
91
  dep1 = token.dependency(1)
71
92
  dep1.target = job1
@@ -99,20 +120,8 @@ async def test_token_notification():
99
120
  with tempfile.TemporaryDirectory() as tmpdir:
100
121
  token = CounterToken("test-notify", Path(tmpdir) / "token", count=1)
101
122
 
102
- class MockJob:
103
- def __init__(self, name):
104
- self.name = name
105
-
106
- @property
107
- def identifier(self):
108
- return f"mock-job-{self.name}"
109
-
110
- @property
111
- def basepath(self):
112
- return Path(tmpdir) / self.name
113
-
114
- job1 = MockJob("1")
115
- job2 = MockJob("2")
123
+ job1 = create_mock_job("1", tmpdir)
124
+ job2 = create_mock_job("2", tmpdir)
116
125
 
117
126
  dep1 = token.dependency(1)
118
127
  dep1.target = job1
@@ -151,20 +160,8 @@ async def test_token_multiple_waiting():
151
160
  with tempfile.TemporaryDirectory() as tmpdir:
152
161
  token = CounterToken("test-multiple", Path(tmpdir) / "token", count=1)
153
162
 
154
- class MockJob:
155
- def __init__(self, name):
156
- self.name = name
157
-
158
- @property
159
- def identifier(self):
160
- return f"mock-job-{self.name}"
161
-
162
- @property
163
- def basepath(self):
164
- return Path(tmpdir) / self.name
165
-
166
163
  # Acquire the token
167
- job1 = MockJob("1")
164
+ job1 = create_mock_job("1", tmpdir)
168
165
  dep1 = token.dependency(1)
169
166
  dep1.target = job1
170
167
  lock1 = await dep1.aio_lock(timeout=0.5)
@@ -173,7 +170,7 @@ async def test_token_multiple_waiting():
173
170
  acquired_order = []
174
171
 
175
172
  async def acquire_task(name):
176
- job = MockJob(name)
173
+ job = create_mock_job(name, tmpdir)
177
174
  dep = token.dependency(1)
178
175
  dep.target = job
179
176
  lock = await dep.aio_lock(timeout=10.0)
@@ -206,20 +203,8 @@ async def test_token_timeout_zero():
206
203
  with tempfile.TemporaryDirectory() as tmpdir:
207
204
  token = CounterToken("test-timeout-zero", Path(tmpdir) / "token", count=1)
208
205
 
209
- class MockJob:
210
- def __init__(self, name):
211
- self.name = name
212
-
213
- @property
214
- def identifier(self):
215
- return f"mock-job-{self.name}"
216
-
217
- @property
218
- def basepath(self):
219
- return Path(tmpdir) / self.name
220
-
221
- job1 = MockJob("1")
222
- job2 = MockJob("2")
206
+ job1 = create_mock_job("1", tmpdir)
207
+ job2 = create_mock_job("2", tmpdir)
223
208
 
224
209
  dep1 = token.dependency(1)
225
210
  dep1.target = job1
@@ -8,7 +8,7 @@ from pathlib import Path
8
8
 
9
9
  import subprocess
10
10
  from experimaestro import Task, Param
11
- from experimaestro.tokens import CounterToken, TokenFile
11
+ from experimaestro.tokens import CounterToken, TokenLockFile
12
12
  from experimaestro.scheduler import JobState
13
13
  from .utils import (
14
14
  TemporaryExperiment,
@@ -101,7 +101,7 @@ def test_token_cleanup():
101
101
  # Just lock directly (but without process)
102
102
  # The absence of process should be detected right away
103
103
  logging.info("Lock without process")
104
- TokenFile.create(dependency)
104
+ TokenLockFile.from_dependency(dependency)
105
105
  task2 = dummy_task.C(x=2)
106
106
  task2.add_dependencies(token.dependency(1)).submit()
107
107
  xp.wait()
@@ -111,7 +111,7 @@ def test_token_cleanup():
111
111
  job = dependency.target
112
112
  with fasteners.InterProcessLock(job.lockpath):
113
113
  logging.info("Creating dependency %s", dependency)
114
- TokenFile.create(dependency)
114
+ TokenLockFile.from_dependency(dependency)
115
115
  lockingpath = job.path / "testtoken.signal"
116
116
  command = [
117
117
  sys.executable,
@@ -145,7 +145,7 @@ def test_token_monitor():
145
145
  )
146
146
  return task
147
147
 
148
- with TemporaryExperiment("tokens1", maxwait=20, port=0) as xp1:
148
+ with TemporaryExperiment("tokens1", maxwait=20) as xp1:
149
149
  # Use the same workspace for both experiments
150
150
  with TemporaryExperiment(
151
151
  "tokens2", workdir=xp1.workspace.path, maxwait=20
@@ -203,8 +203,7 @@ def test_token_reschedule():
203
203
 
204
204
  # Create the locking path
205
205
  logging.info(
206
- "Both processes are ready:"
207
- "allowing tasks to finish by writing in %s",
206
+ "Both processes are ready:allowing tasks to finish by writing in %s",
208
207
  lockingpath,
209
208
  )
210
209
  lockingpath.write_text("Let's go")