pytest-isolate 0.0.2__tar.gz → 0.0.3__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.
@@ -1,12 +1,12 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: pytest-isolate
3
- Version: 0.0.2
3
+ Version: 0.0.3
4
+ Summary: Run pytest tests in isolated subprocesses
4
5
  Project-URL: Documentation, https://github.com/gilfree/pytest-isolate#readme
5
6
  Project-URL: Issues, https://github.com/gilfree/pytest-isolate/issues
6
7
  Project-URL: Source, https://github.com/gilfree/pytest-isolate
7
8
  Author-email: gilfree <gilfree@users.noreply.github.com>
8
- License-Expression: MIT
9
- License-File: LICENSE.txt
9
+ License: MIT
10
10
  Classifier: Development Status :: 4 - Beta
11
11
  Classifier: Programming Language :: Python
12
12
  Classifier: Programming Language :: Python :: 3.7
@@ -60,9 +60,9 @@ To run every test in its own **forked** subprocess.
60
60
 
61
61
  Or:
62
62
 
63
- pytest --isolate-timeout 10 --isolate-mem-limit 1000000
63
+ pytest --isolate-timeout 10 --isolate-mem-limit 1000000 --isolate-cpu-limit 10
64
64
 
65
- To set a timeout to every test in addition to forking.
65
+ To set a timeout to every test in addition to forking, and limit to 10 cpu seconds.
66
66
 
67
67
  > Note:
68
68
  >
@@ -75,10 +75,10 @@ they will take precedence. Uninstall them to use `pytest-isolate`.
75
75
 
76
76
  Unlike `pytest-timeout`, timeout in `pytest-isolate` is implemented by forking the test to a separate subprocess, and setting timeout for that subprocess.
77
77
 
78
- You can also use a mark to isolate or time limit the memory test:
78
+ You can also use a mark to isolate or time limit the memory and/or cpu usage test:
79
79
 
80
80
  ```python
81
- @pytest.mark.isolate(timeout=10,mem_limit=10**6)
81
+ @pytest.mark.isolate(timeout=10, mem_limit=10**6, cpu_limit=10)
82
82
  def test_something():
83
83
  pass
84
84
  ```
@@ -89,6 +89,7 @@ The options can also be set in an pytest configuration file, e.g:
89
89
  [tool.pytest.ini_options]
90
90
  isolate_timeout=10
91
91
  isolate_mem_limit=1000000
92
+ isolate_cpu_limit=10
92
93
  ```
93
94
 
94
95
  ## Contributing
@@ -36,9 +36,9 @@ To run every test in its own **forked** subprocess.
36
36
 
37
37
  Or:
38
38
 
39
- pytest --isolate-timeout 10 --isolate-mem-limit 1000000
39
+ pytest --isolate-timeout 10 --isolate-mem-limit 1000000 --isolate-cpu-limit 10
40
40
 
41
- To set a timeout to every test in addition to forking.
41
+ To set a timeout to every test in addition to forking, and limit to 10 cpu seconds.
42
42
 
43
43
  > Note:
44
44
  >
@@ -51,10 +51,10 @@ they will take precedence. Uninstall them to use `pytest-isolate`.
51
51
 
52
52
  Unlike `pytest-timeout`, timeout in `pytest-isolate` is implemented by forking the test to a separate subprocess, and setting timeout for that subprocess.
53
53
 
54
- You can also use a mark to isolate or time limit the memory test:
54
+ You can also use a mark to isolate or time limit the memory and/or cpu usage test:
55
55
 
56
56
  ```python
57
- @pytest.mark.isolate(timeout=10,mem_limit=10**6)
57
+ @pytest.mark.isolate(timeout=10, mem_limit=10**6, cpu_limit=10)
58
58
  def test_something():
59
59
  pass
60
60
  ```
@@ -65,6 +65,7 @@ The options can also be set in an pytest configuration file, e.g:
65
65
  [tool.pytest.ini_options]
66
66
  isolate_timeout=10
67
67
  isolate_mem_limit=1000000
68
+ isolate_cpu_limit=10
68
69
  ```
69
70
 
70
71
  ## Contributing
@@ -4,14 +4,12 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "pytest-isolate"
7
- description = ''
7
+ description = 'Run pytest tests in isolated subprocesses'
8
8
  readme = "README.md"
9
9
  requires-python = ">=3.7"
10
10
  license = "MIT"
11
11
  keywords = []
12
- authors = [
13
- { name = "gilfree", email = "gilfree@users.noreply.github.com" },
14
- ]
12
+ authors = [{ name = "gilfree", email = "gilfree@users.noreply.github.com" }]
15
13
  classifiers = [
16
14
  "Development Status :: 4 - Beta",
17
15
  "Programming Language :: Python",
@@ -23,7 +21,7 @@ classifiers = [
23
21
  "Programming Language :: Python :: Implementation :: CPython",
24
22
  "Programming Language :: Python :: Implementation :: PyPy",
25
23
  ]
26
- dependencies = ["dill","tblib","pytest"]
24
+ dependencies = ["dill", "tblib", "pytest"]
27
25
  dynamic = ["version"]
28
26
 
29
27
  [project.urls]
@@ -38,22 +36,26 @@ pytest_isolate = "pytest_isolate.plugin"
38
36
  path = "src/pytest_isolate/__about__.py"
39
37
 
40
38
  [tool.hatch.envs.default]
41
- dependencies = [
42
- "pytest",
43
- "pytest-cov",
44
- ]
39
+ dependencies = ["pytest", "pytest-cov", "numpy", "pytest-xdist"]
40
+
41
+
42
+ [tool.pytest.ini_options]
43
+ isolate_timeout = 2
45
44
 
46
45
  [tool.ruff]
46
+ fix = true
47
+
48
+ # Assume Python 3.11.
49
+ target-version = "py311"
50
+
51
+ [tool.ruff.lint]
47
52
  # Enable Pyflakes `E` and `F` codes by default.
48
- select = ["E", "F","I"]
53
+ select = ["E", "F", "I"]
49
54
  ignore = []
50
- fix=true
51
55
  # Allow unused variables when underscore-prefixed.
52
56
  dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
53
57
 
54
- # Assume Python 3.11.
55
- target-version = "py311"
56
58
 
57
- [tool.ruff.mccabe]
59
+ [tool.ruff.lint.mccabe]
58
60
  # Unlike Flake8, default to a complexity level of 10.
59
- max-complexity = 10
61
+ max-complexity = 10
@@ -0,0 +1 @@
1
+ __version__ = '0.0.3'
@@ -1,7 +1,7 @@
1
1
  # This file is was originally based on pytest forked:
2
2
  # see: https://github.com/pytest-dev/pytest-forked/blob/master/src/pytest_forked/__init__.py
3
- # Since pytest forked is un unmaintained,
4
- # I decided to make my changes in a different project.
3
+ # Since pytest forked is un unmaintained I decided to make my
4
+ # changes in a different project
5
5
  # Please see README.md
6
6
 
7
7
 
@@ -13,7 +13,6 @@ import resource
13
13
  import sys
14
14
  import warnings
15
15
  from contextlib import contextmanager
16
- from datetime import datetime
17
16
  from queue import Empty
18
17
  from typing import Any, List, Optional, Tuple
19
18
 
@@ -28,6 +27,11 @@ try:
28
27
  except ImportError:
29
28
  pass
30
29
 
30
+ try:
31
+ import setproctitle # noqa F401
32
+ except ImportError:
33
+ pass
34
+
31
35
  try:
32
36
  from tblib import pickling_support
33
37
 
@@ -37,26 +41,33 @@ except ImportError:
37
41
 
38
42
 
39
43
  def forked_subprocess(
40
- target, args=(), timeout=None, memlimt=None
44
+ target, args=(), timeout=None, memlimt=None, cpulimit=None
41
45
  ) -> Tuple[Any, int, bool]:
42
46
  sub = ForkedSubprocess()
43
- exitcode, timed_out, result = sub.run_in_subprocess(timeout, memlimt, target, args)
47
+ exitcode, timed_out, result = sub.run_in_subprocess(
48
+ timeout, memlimt, cpulimit, target, args
49
+ )
44
50
  return result, exitcode, timed_out
45
51
 
46
52
 
47
53
  @contextmanager
48
- def limits(max_mem):
49
- if not max_mem:
54
+ def limits(max_mem, max_cpu):
55
+ if not max_mem and not max_cpu:
50
56
  yield
51
57
  return
52
- (soft, hard) = resource.getrlimit(resource.RLIMIT_DATA)
53
- resource.setrlimit(resource.RLIMIT_DATA, (max_mem, hard))
54
- (soft, hard) = resource.getrlimit(resource.RLIMIT_DATA)
58
+ (soft_mem, hard_mem) = resource.getrlimit(resource.RLIMIT_DATA)
59
+ (soft_cpu, hard_cpu) = resource.getrlimit(resource.RLIMIT_CPU)
60
+ if max_mem:
61
+ resource.setrlimit(resource.RLIMIT_DATA, (max_mem, hard_mem))
62
+ if max_cpu:
63
+ resource.setrlimit(resource.RLIMIT_CPU, (max_cpu, hard_cpu))
55
64
  try:
56
65
  yield
57
66
  finally:
58
- pass
59
- resource.setrlimit(resource.RLIMIT_DATA, (soft, hard))
67
+ if max_mem:
68
+ resource.setrlimit(resource.RLIMIT_DATA, (soft_mem, hard_mem))
69
+ if max_cpu:
70
+ resource.setrlimit(resource.RLIMIT_CPU, (soft_cpu, hard_cpu))
60
71
  return
61
72
 
62
73
 
@@ -91,7 +102,7 @@ class ForkedSubprocess:
91
102
  os.close(self.w_out)
92
103
  os.close(self.w_err)
93
104
 
94
- def run_in_subprocess(self, timeout, memlimit, target, args=()):
105
+ def run_in_subprocess(self, timeout, memlimit, cpulimit, target, args=()):
95
106
  ctx = mp.get_context("fork")
96
107
  q = ctx.Queue()
97
108
  timed_out = None
@@ -99,9 +110,8 @@ class ForkedSubprocess:
99
110
  def run_subprocess():
100
111
  # Close the read fd, redirect output to write fd
101
112
  self.child_redirect_streams()
102
-
103
113
  try:
104
- with limits(memlimit):
114
+ with limits(memlimit, cpulimit):
105
115
  q.put(dill.dumps(target(*args)))
106
116
  except BaseException as e:
107
117
  q.put(dill.dumps(e))
@@ -183,12 +193,21 @@ def pytest_addoption(parser):
183
193
  default=None,
184
194
  help="Limit the memory usage of the test ",
185
195
  )
196
+ group.addoption(
197
+ "--isolate-cpu-limit",
198
+ dest="isolate_cpu_limit",
199
+ default=None,
200
+ help="Limit the CPU usage of the test ",
201
+ )
186
202
  parser.addini(
187
203
  "isolate_timeout", "Default timeout for isolated tests", type="string"
188
204
  )
189
205
  parser.addini(
190
206
  "isolate_mem_limit", "Default memory limit for isolated tests", type="string"
191
207
  )
208
+ parser.addini(
209
+ "isolate_cpu_limit", "Default cpu limit for isolated tests", type="string"
210
+ )
192
211
 
193
212
 
194
213
  @pytest.hookimpl(trylast=True)
@@ -208,8 +227,8 @@ def pytest_configure(config):
208
227
  warnings.warn(
209
228
  "Isolate is a replacement for pytest-timeout. "
210
229
  "Pytest-timeout will take precedence while installed, "
211
- "tests with timeout will not be isolated. please uninstall one of "
212
- "the plugins"
230
+ "tests with timeout will not be isolated. "
231
+ "Please uninstall one of the plugins"
213
232
  )
214
233
  else:
215
234
  config.addinivalue_line(
@@ -221,7 +240,6 @@ def pytest_configure(config):
221
240
  # Taken from pytest 7.2:
222
241
  @contextmanager
223
242
  def catch_warnings(item: pytest.Item):
224
-
225
243
  """Context manager that catches warnings generated in the contained execution block.
226
244
 
227
245
  ``item`` can be None if we are not in the context of an item execution.
@@ -254,14 +272,25 @@ def catch_warnings(item: pytest.Item):
254
272
 
255
273
 
256
274
  def run_subprocess(item: pytest.Item):
257
-
258
275
  item.config.pluginmanager.unregister(name="capturemanager")
276
+ try:
277
+ if not os.getenv("PYTEST_ISOLATE_NO_SETPROCTITLE"):
278
+ import setproctitle # noqa F811
259
279
 
280
+ setproctitle.setproctitle(f"pytest {item.nodeid}")
281
+ except ImportError:
282
+ pass
260
283
  try:
261
284
  with catch_warnings(item) as warnings:
262
285
  reports = runtestprotocol(item, log=False)
263
286
  s_reports = []
264
287
  for report in reports:
288
+ usage = resource.getrusage(resource.RUSAGE_SELF)
289
+ cpu_usage = usage.ru_utime + usage.ru_stime
290
+ usage = resource.getrusage(resource.RUSAGE_CHILDREN)
291
+ cpu_usage += usage.ru_utime + usage.ru_stime
292
+ report.user_properties.append(("cpu_usage", cpu_usage))
293
+ report.cpu_usage = cpu_usage
265
294
  s_reports.append(
266
295
  item.config.hook.pytest_report_to_serializable(
267
296
  config=item.config, report=report
@@ -273,28 +302,29 @@ def run_subprocess(item: pytest.Item):
273
302
 
274
303
 
275
304
  def run_in_subprocess(
276
- item: pytest.Item, timeout: Optional[float], mem_limit: Optional[int]
305
+ item: pytest.Item,
306
+ timeout: Optional[float],
307
+ mem_limit: Optional[int],
308
+ cpu_limit: Optional[int],
277
309
  ) -> List[pytest.TestReport]:
278
-
279
- cap: _pytest.capture.CaptureManager = item.config.pluginmanager.getplugin(
280
- "capturemanager"
281
- )
310
+ cap = item.config.pluginmanager.getplugin("capturemanager")
311
+ assert isinstance(cap, _pytest.capture.CaptureManager)
282
312
  cap.resume_global_capture()
283
313
  cap.activate_fixture()
284
- current_time = datetime.now().strftime("%Y%m%d-%H%M%S")
285
- print(f"{current_time}: Test lunched {item.nodeid} in subprocess ", file=sys.stderr)
286
314
  result, exitcode, timed_out = forked_subprocess(
287
- run_subprocess, (item,), timeout, mem_limit
315
+ run_subprocess, (item,), timeout, mem_limit, cpu_limit
288
316
  )
289
317
  cap.deactivate_fixture()
290
318
  cap.suspend_global_capture(in_=False)
291
319
  out, err = cap.read_global_capture()
292
320
 
321
+ warnings_captured = None
322
+
293
323
  # The result might be an exception, that comes
294
324
  # form current process
295
325
  if result and not isinstance(result, BaseException):
296
326
  # The result came from child process
297
- result, warnings_caputred = result
327
+ result, warnings_captured = result
298
328
 
299
329
  if result and not isinstance(result, BaseException):
300
330
  results: List[pytest.TestReport] = []
@@ -308,9 +338,9 @@ def run_in_subprocess(
308
338
  results[-1].sections.append(("Captured stderr", err))
309
339
  results[-1].sections.append(("Captured stdout", out))
310
340
 
311
- if warnings_caputred:
341
+ if warnings_captured:
312
342
  try:
313
- for warning_message in warnings_caputred:
343
+ for warning_message in warnings_captured:
314
344
  item.ihook.pytest_warning_recorded.call_historic(
315
345
  kwargs=dict(
316
346
  warning_message=warning_message,
@@ -343,10 +373,12 @@ def report_process_crash(
343
373
 
344
374
  if timed_out is not None:
345
375
  info += f"Timeout > {timed_out}"
376
+ elif exitcode == -24:
377
+ info += "SIGXCPU: CPU time limit exceeded"
346
378
  elif exitcode != 0:
347
- info += f"Running the test CRASHED with exit code {exitcode}"
379
+ info += f"Crashed with exit code {exitcode}"
348
380
  else:
349
- info += "Exited with no result, probably hit memory limit"
381
+ info += "Exited with no result, memory limit exceeded (probably)"
350
382
 
351
383
  call = runner.CallInfo.from_call(lambda: 0 / 0, "???")
352
384
  call.excinfo = info
@@ -421,13 +453,17 @@ def pytest_runtest_protocol(item):
421
453
  return
422
454
  if item.config.pluginmanager.get_plugin("timeout"):
423
455
  return
424
-
425
- isolate, timeout, mem_limit = get_isolation_options(item)
426
-
427
- if isolate is not None or mem_limit is not None or timeout is not None:
456
+ isolate, timeout, mem_limit, cpu_limit = get_isolation_options(item)
457
+
458
+ if (
459
+ isolate is not None
460
+ or mem_limit is not None
461
+ or timeout is not None
462
+ or cpu_limit is not None
463
+ ):
428
464
  ihook = item.ihook
429
465
  ihook.pytest_runtest_logstart(nodeid=item.nodeid, location=item.location)
430
- reports = run_in_subprocess(item, timeout, mem_limit)
466
+ reports = run_in_subprocess(item, timeout, mem_limit, cpu_limit)
431
467
 
432
468
  for rep in reports:
433
469
  ihook.pytest_runtest_logreport(report=rep)
@@ -436,22 +472,8 @@ def pytest_runtest_protocol(item):
436
472
  return True
437
473
 
438
474
 
439
- def get_isolation_options(item):
440
- # Should we fork?
441
- forked = get_global_value(item, "forked")
442
- forked_marker = get_marker(item, "forked")
443
- isolate = get_global_value(item, "isolate")
444
- isolate_marker = get_marker(item, "isolate")
445
- if isolate_marker is not None:
446
- isolate = isolate_marker
447
- elif forked_marker is not None:
448
- isolate = forked_marker
449
- elif isolate is not None:
450
- pass
451
- else:
452
- isolate = forked
453
-
454
- # Should we timeout
475
+ def get_timeout(item):
476
+ # Should we timeout?
455
477
  timeout = get_global_value(item, "timeout")
456
478
  timeout_marker = get_marker(item, "timeout", "timeout", 0)
457
479
 
@@ -465,10 +487,15 @@ def get_isolation_options(item):
465
487
  elif isolate_timeout is not None:
466
488
  timeout = isolate_timeout
467
489
 
468
- if timeout and isinstance(timeout, str) and str.isnumeric(timeout):
490
+ try:
469
491
  timeout = float(timeout)
492
+ except (ValueError, TypeError):
493
+ timeout = None
494
+ return timeout
495
+
470
496
 
471
- # Should we memlimit?
497
+ def get_memory_limit(item):
498
+ # Should we limit memory?
472
499
  mem_limit = None
473
500
  isolate_mem_limit_marker = get_marker(item, "isolate", "mem_limit", 1)
474
501
  isolate_mem_limit = get_global_value(item, "isolate_mem_limit")
@@ -477,4 +504,88 @@ def get_isolation_options(item):
477
504
  mem_limit = isolate_mem_limit_marker
478
505
  else:
479
506
  mem_limit = isolate_mem_limit
480
- return isolate, timeout, mem_limit
507
+
508
+ try:
509
+ mem_limit = int(mem_limit)
510
+ if mem_limit < 1 or round(mem_limit) != mem_limit:
511
+ raise ValueError("Memory limit must be in whole bytes")
512
+ mem_limit = int(mem_limit)
513
+ except (ValueError, TypeError):
514
+ mem_limit = None
515
+ return mem_limit
516
+
517
+
518
+ def get_cpu_limit(item):
519
+ # Should we limit cpu?
520
+ cpu_limit = None
521
+ isolate_cpu_limit_marker = get_marker(item, "isolate", "cpu_limit", 2)
522
+ isolate_cpu_limit = get_global_value(item, "isolate_cpu_limit")
523
+
524
+ if isolate_cpu_limit_marker is not None:
525
+ cpu_limit = isolate_cpu_limit_marker
526
+ else:
527
+ cpu_limit = isolate_cpu_limit
528
+
529
+ try:
530
+ cpu_limit = float(cpu_limit)
531
+ if cpu_limit < 1 or round(cpu_limit) != cpu_limit:
532
+ raise ValueError("Cpu limit must be in whole seconds")
533
+ cpu_limit = int(cpu_limit)
534
+ except (ValueError, TypeError):
535
+ cpu_limit = None
536
+ return cpu_limit
537
+
538
+
539
+ def get_isolation_options(item):
540
+ # Should we fork?
541
+ forked = get_global_value(item, "forked")
542
+ forked_marker = get_marker(item, "forked")
543
+ isolate = get_global_value(item, "isolate")
544
+ isolate_marker = get_marker(item, "isolate")
545
+ if isolate_marker is not None:
546
+ isolate = isolate_marker
547
+ elif forked_marker is not None:
548
+ isolate = forked_marker
549
+ elif isolate is not None:
550
+ pass
551
+ else:
552
+ isolate = forked
553
+ timeout = get_timeout(item)
554
+ mem_limit = get_memory_limit(item)
555
+ cpu_limit = get_cpu_limit(item)
556
+
557
+ return isolate, timeout, mem_limit, cpu_limit
558
+
559
+
560
+ @pytest.hookimpl(tryfirst=True)
561
+ def pytest_terminal_summary(terminalreporter) -> None:
562
+ durations = terminalreporter.config.option.durations
563
+ durations_min = terminalreporter.config.option.durations_min
564
+ verbose = terminalreporter.config.getvalue("verbose")
565
+ if durations is None:
566
+ return
567
+ tr = terminalreporter
568
+ dlist = []
569
+ for replist in tr.stats.values():
570
+ for rep in replist:
571
+ if hasattr(rep, "cpu_usage"):
572
+ dlist.append(rep)
573
+ if not dlist:
574
+ return
575
+ dlist.sort(key=lambda x: x.cpu_usage, reverse=True) # type: ignore[no-any-return]
576
+ if not durations:
577
+ tr.write_sep("=", "highest cpu_usage")
578
+ else:
579
+ tr.write_sep("=", "highest %s cpu_usage" % durations)
580
+ dlist = dlist[:durations]
581
+
582
+ for i, rep in enumerate(dlist):
583
+ if verbose < 2 and rep.cpu_usage < durations_min:
584
+ tr.write_line("")
585
+ tr.write_line(
586
+ "(%s cpu_usage < %gs hidden. Use -vv to show these durations.)"
587
+ % (len(dlist) - i, durations_min)
588
+ )
589
+ break
590
+ if rep.when == "teardown":
591
+ tr.write_line(f"{rep.cpu_usage:02.2f}s {rep.nodeid}")
@@ -43,7 +43,7 @@ def test_bare_ok():
43
43
  print(f"isolated in {os.getpid()}")
44
44
 
45
45
 
46
- @pytest.mark.xfail
46
+ @pytest.mark.xfail(strict=True)
47
47
  def test_bare_fail():
48
48
  print("Hello")
49
49
  print("Hello")
@@ -54,17 +54,27 @@ def test_bare_fail():
54
54
 
55
55
  @pytest.mark.isolate(mem_limit=10**9)
56
56
  def test_rss_limit_ok():
57
- a = bytearray(10**7)
57
+ a = bytearray(10**5)
58
58
  print(a[random.randint(0, len(a))])
59
59
 
60
60
 
61
61
  @pytest.mark.xfail
62
- @pytest.mark.isolate(mem_limit=10**6)
62
+ @pytest.mark.isolate(mem_limit=10**5)
63
63
  def test_rss_limit_fail():
64
- a = bytearray(10**7)
64
+ a = bytearray(10**6)
65
65
  print(a[random.randint(0, len(a))])
66
66
 
67
67
 
68
68
  @pytest.mark.isolate()
69
69
  def test_warn():
70
70
  warnings.warn("Boo")
71
+
72
+
73
+ @pytest.mark.xfail
74
+ @pytest.mark.isolate(cpu_limit=1)
75
+ def test_isolate_cpu():
76
+ import numpy as np
77
+ # hog CPU:
78
+ x=0
79
+ for i in range(100):
80
+ x+=(np.random.rand(1000,1000)@np.random.rand(1000,1000)).sum()
@@ -0,0 +1,25 @@
1
+ # For more information about tox, see https://tox.readthedocs.io/en/latest/
2
+ [tox]
3
+ requires =
4
+ tox >=4
5
+ envlist = py{37,311}
6
+ isolated_build = True
7
+
8
+ [testenv]
9
+ deps =
10
+ pytest>6.0
11
+ numpy
12
+ pytest-xdist
13
+ pytest-sugar
14
+
15
+ commands = pytest --isolate --import-mode importlib {posargs:tests}
16
+
17
+ [testenv:lint]
18
+ skip_install = true
19
+ deps = ruff
20
+ commands = ruff check {postargs:.}
21
+
22
+ [gh]
23
+ python =
24
+ 3.7 = py37
25
+ 3.11 = py311
@@ -1 +0,0 @@
1
- __version__ = '0.0.2'
@@ -1,25 +0,0 @@
1
- # For more information about tox, see https://tox.readthedocs.io/en/latest/
2
- [tox]
3
- envlist = py{37,311}
4
- isolated_build = True
5
-
6
- [tox:.package]
7
- # note tox will use the same python version as under what tox is installed to package
8
- # so unless this is python 3 you can require a given python version for the packaging
9
- # environment via the basepython key
10
- basepython = python3
11
-
12
-
13
- [testenv]
14
- deps = pytest>6.0
15
- commands = pytest --isolate --import-mode importlib {posargs:tests}
16
-
17
- [testenv:ruff]
18
- skip_install = true
19
- deps = ruff
20
- commands = ruff .
21
-
22
- [gh]
23
- python =
24
- 3.7 = py37
25
- 3.11 = py311, ruff