Skip to content

Commit d461e77

Browse files
Merge pull request #3 from InformaticsMatters/test-groups
Adds initial support for 'test-groups'
2 parents b7c98d8 + 96f60aa commit d461e77

File tree

6 files changed

+825
-281
lines changed

6 files changed

+825
-281
lines changed

.pylintrc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66
# R : (Refactoring) warnings
77
disable = import-error,
88
too-many-arguments,
9+
too-many-lines,
910
too-many-locals,
1011
too-many-branches,
1112
too-many-instance-attributes,
1213
too-many-nested-blocks,
14+
too-many-return-statements,
1315
too-many-statements

DEVELOPER-README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ to PyPI automatically from the main branch using GitHub Actions.
3131

3232
To build the package distribution manually run: -
3333

34+
pip install --upgrade pip
3435
python -m pip install --upgrade build
3536
python -m build --sdist --wheel --outdir dist/
3637

README.rst

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,12 @@ You can display the utility's help with::
100100

101101
jote --help
102102

103+
Jote container network
104+
----------------------
105+
106+
``jote`` tests are executed on the network ``data-manager_jote``. This is
107+
defined in the docker-compose file that it generates to run your tests.
108+
103109
Built-in variables
104110
------------------
105111

@@ -190,6 +196,81 @@ You should try and avoid creating too many long-running tests. If you cannot,
190196
consider whether it's a appropriate to use ``run-level`` to avoid ``jote``
191197
running them by default.
192198

199+
Test groups
200+
-----------
201+
202+
Tests are normally executed and the environment torn-down between them.
203+
If you have tests that depend on the results from a prior test you can run
204+
tests as a **group**, which preserves the project directory between the tests.
205+
206+
To run a sequence of test (as a **group**) you need to define a ``test-group``
207+
in your Job Definition file and then refer to that group in your test. Here,
208+
we define a test group called ``experiment-a``, at the top of the
209+
definition file::
210+
211+
test-groups:
212+
- name: experiment-a
213+
214+
215+
We then place a test in that group with a ``run-group`` declaration
216+
in the corresponding test block::
217+
218+
jobs:
219+
job-a:
220+
[...]
221+
tests:
222+
test-1:
223+
run-groups:
224+
- name: experiment-a
225+
ordinal: 1
226+
227+
We need to provide an ``ordinal`` value. This numeric value (from 1 ..N)
228+
puts the test in a specific position in the test sequence. When tests are
229+
placed in a ``run-group`` you have to order your tests so that ``a`` follows
230+
``b``. This is done with unique ordinals for each test in each group. A test
231+
with ordinal ``1`` will run before a test with ordinal ``2``.
232+
233+
You can run just the tests for a specific group by using the ``--run-group``
234+
option::
235+
236+
jote --run-group experiment-a
237+
238+
Running additional containers (group testing)
239+
---------------------------------------------
240+
241+
Test groups provide an ability to launch additional support containers during
242+
testing. You might want to start a background database for example, that can
243+
be used by tests in your ``test-group``. To take advantage of this feature
244+
you just need to provide a ``docker-compose`` file (in the Job definition
245+
``data-manager`` directory) and name that file in you r``test-groups``
246+
declaration.
247+
248+
Here we declare a docker-compose file called
249+
``docker-compose-experiment-a.yaml``::
250+
251+
test-groups:
252+
- name: experiment-a
253+
compose:
254+
file: docker-compose-experiment-a.yaml
255+
256+
The compose filename must begin ``docker-compose`` and end ``.yaml``.
257+
258+
The compose file is run before any tests in the corresponding test group
259+
have been run and will be stopped after the last test in the group.
260+
261+
The compose file you provide is run in a *detached* state so ``jote`` does
262+
not wait for the containers to start (or initialise). As the first test
263+
in the test group can begin very soon after the compose file is started
264+
you can minimise the risk that your containers are not ready for the tests
265+
by adding a fixed delay between ``jote`` starting the compose file and
266+
running the first test::
267+
268+
test-groups:
269+
- name: experiment-a
270+
compose:
271+
file: docker-compose-experiment-a.yaml
272+
delay-seconds: 10
273+
193274
Nextflow test execution
194275
-----------------------
195276

@@ -208,7 +289,7 @@ constraints.
208289

209290
When running nextflow jobs ``jote`` writes a ``nextflow.config`` to the
210291
test's simulated project directory prior to executing the command, and
211-
this is the curent-workign directory when the test starts.
292+
this is the current-working directory when the test starts.
212293
``jote`` *will not* let you have a nextflow config in your home directory
213294
as any settings found there would be merged with the file ``jote`` writes,
214295
potentially disturbing the execution behaviour.

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
docker-compose == 1.29.2
2-
im-data-manager-job-decoder == 1.12.0
2+
im-data-manager-job-decoder == 1.12.3
33
munch == 2.5.0
44
pyyaml == 5.4.1
55
yamllint == 1.28.0

src/jote/compose.py

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import os
1313
import shutil
1414
import subprocess
15+
import time
1516
from typing import Any, Dict, Optional, Tuple
1617

1718
# The 'simulated' instance directory,
@@ -31,8 +32,12 @@
3132
# because we're relying on 'mem_limit' and 'cpus',
3233
# which are ignored (moved to swarm) in v3.
3334
version: '2.4'
35+
networks:
36+
jote:
3437
services:
3538
job:
39+
networks:
40+
- jote
3641
image: {image}
3742
container_name: {job}-{test}-jote
3843
user: '{uid}:{gid}'
@@ -222,10 +227,16 @@ def run(
222227

223228
try:
224229
# Run the container
225-
# and then cleanup
230+
# and then cleanup.
231+
# By using '-p' ('--project-name')
232+
# we set the prefix for the network name and can use compose files
233+
# from different directories. Without this the network name
234+
# is prefixed by the directory the compose file is in.
226235
test = subprocess.run(
227236
[
228237
"docker-compose",
238+
"-p",
239+
"data-manager",
229240
"up",
230241
"--exit-code-from",
231242
"job",
@@ -257,3 +268,87 @@ def delete(self) -> None:
257268
shutil.rmtree(test_path)
258269

259270
print("# Compose: Deleted")
271+
272+
@staticmethod
273+
def run_group_compose_file(compose_file: str, delay_seconds: int = 0) -> bool:
274+
"""Starts a group compose file in a detached state.
275+
The file is expected to be a compose file in the 'data-manager' directory.
276+
We pull the continer imag to reduce the 'docker-compose up' time
277+
and then optionally wait for a period of seconds.
278+
"""
279+
280+
print("# Compose: Starting test group containers...")
281+
282+
# Runs a group compose file in a detached state.
283+
# The file is expected to be resident in the 'data-manager' directory.
284+
try:
285+
# Pre-pull the docker-compose images.
286+
# This saves start-up execution time.
287+
_ = subprocess.run(
288+
[
289+
"docker-compose",
290+
"-f",
291+
os.path.join("data-manager", compose_file),
292+
"pull",
293+
],
294+
capture_output=False,
295+
check=False,
296+
)
297+
298+
# Bring the group-test compose file up.
299+
# By using '-p' ('--project-name')
300+
# we set the prefix for the network name and services from this container
301+
# are visible to the test container. Without this the network name
302+
# is prefixed by the directory the compose file is in.
303+
_ = subprocess.run(
304+
[
305+
"docker-compose",
306+
"-f",
307+
os.path.join("data-manager", compose_file),
308+
"-p",
309+
"data-manager",
310+
"up",
311+
"-d",
312+
],
313+
capture_output=False,
314+
check=False,
315+
)
316+
except: # pylint: disable=bare-except
317+
return False
318+
319+
# Wait for a period of seconds after launching?
320+
if delay_seconds:
321+
print(f"# Compose: Post-bring-up test group sleep ({delay_seconds})...")
322+
time.sleep(delay_seconds)
323+
324+
print("# Compose: Started test group containers")
325+
return True
326+
327+
@staticmethod
328+
def stop_group_compose_file(compose_file: str) -> bool:
329+
"""Stops a group compose file.
330+
The file is expected to be a compose file in the 'data-manager' directory.
331+
"""
332+
333+
print("# Compose: Stopping test group containers...")
334+
335+
try:
336+
# Bring the compose file down...
337+
_ = subprocess.run(
338+
[
339+
"docker-compose",
340+
"-f",
341+
os.path.join("data-manager", compose_file),
342+
"down",
343+
"--remove-orphans",
344+
],
345+
capture_output=False,
346+
timeout=240,
347+
check=False,
348+
)
349+
except: # pylint: disable=bare-except
350+
return False
351+
352+
print("# Compose: Stopped test group containers")
353+
354+
return True

0 commit comments

Comments
 (0)