looptime 0.1__tar.gz → 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.

Potentially problematic release.


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

Files changed (47) hide show
  1. {looptime-0.1 → looptime-0.3}/.github/workflows/ci.yaml +22 -16
  2. {looptime-0.1 → looptime-0.3}/.github/workflows/publish.yaml +3 -3
  3. {looptime-0.1 → looptime-0.3}/.github/workflows/thorough.yaml +15 -15
  4. {looptime-0.1 → looptime-0.3}/.pre-commit-config.yaml +3 -3
  5. {looptime-0.1/looptime.egg-info → looptime-0.3}/PKG-INFO +158 -32
  6. looptime-0.1/PKG-INFO → looptime-0.3/README.md +142 -46
  7. {looptime-0.1 → looptime-0.3}/looptime/__init__.py +2 -1
  8. {looptime-0.1 → looptime-0.3}/looptime/loops.py +71 -4
  9. {looptime-0.1 → looptime-0.3}/looptime/math.py +3 -1
  10. looptime-0.3/looptime/plugin.py +316 -0
  11. {looptime-0.1 → looptime-0.3}/looptime/timeproxies.py +0 -10
  12. looptime-0.1/README.md → looptime-0.3/looptime.egg-info/PKG-INFO +172 -26
  13. {looptime-0.1 → looptime-0.3}/looptime.egg-info/entry_points.txt +0 -1
  14. {looptime-0.1 → looptime-0.3}/requirements.txt +1 -2
  15. {looptime-0.1 → looptime-0.3}/setup.py +1 -1
  16. {looptime-0.1 → looptime-0.3}/tests/test_chronometers.py +2 -1
  17. looptime-0.3/tests/test_patching.py +92 -0
  18. {looptime-0.1 → looptime-0.3}/tests/test_plugin.py +46 -19
  19. {looptime-0.1 → looptime-0.3}/tests/test_time_moves.py +37 -0
  20. looptime-0.1/looptime/plugin.py +0 -45
  21. looptime-0.1/tests/test_patching.py +0 -44
  22. {looptime-0.1 → looptime-0.3}/.codecov.yml +0 -0
  23. {looptime-0.1 → looptime-0.3}/.github/CODEOWNERS +0 -0
  24. {looptime-0.1 → looptime-0.3}/.github/FUNDING.yml +0 -0
  25. {looptime-0.1 → looptime-0.3}/.github/ISSUE_TEMPLATE.md +0 -0
  26. {looptime-0.1 → looptime-0.3}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  27. {looptime-0.1 → looptime-0.3}/.gitignore +0 -0
  28. {looptime-0.1 → looptime-0.3}/.isort.cfg +0 -0
  29. {looptime-0.1 → looptime-0.3}/LICENSE +0 -0
  30. {looptime-0.1 → looptime-0.3}/MAINTAINERS +0 -0
  31. {looptime-0.1 → looptime-0.3}/SECURITY.md +0 -0
  32. {looptime-0.1 → looptime-0.3}/looptime/chronometers.py +0 -0
  33. {looptime-0.1 → looptime-0.3}/looptime/patchers.py +0 -0
  34. {looptime-0.1 → looptime-0.3}/looptime/py.typed +0 -0
  35. {looptime-0.1 → looptime-0.3}/looptime.egg-info/SOURCES.txt +0 -0
  36. {looptime-0.1 → looptime-0.3}/looptime.egg-info/dependency_links.txt +0 -0
  37. {looptime-0.1 → looptime-0.3}/looptime.egg-info/top_level.txt +0 -0
  38. {looptime-0.1 → looptime-0.3}/looptime.egg-info/zip-safe +0 -0
  39. {looptime-0.1 → looptime-0.3}/mypy.ini +0 -0
  40. {looptime-0.1 → looptime-0.3}/setup.cfg +0 -0
  41. {looptime-0.1 → looptime-0.3}/tests/conftest.py +0 -0
  42. {looptime-0.1 → looptime-0.3}/tests/test_initialisation.py +0 -0
  43. {looptime-0.1 → looptime-0.3}/tests/test_readme.py +0 -0
  44. {looptime-0.1 → looptime-0.3}/tests/test_subclassing.py +0 -0
  45. {looptime-0.1 → looptime-0.3}/tests/test_time_on_executors.py +0 -0
  46. {looptime-0.1 → looptime-0.3}/tests/test_time_on_io_idle.py +0 -0
  47. {looptime-0.1 → looptime-0.3}/tests/test_timeproxies.py +0 -0
@@ -9,13 +9,13 @@ on:
9
9
  jobs:
10
10
  linters:
11
11
  name: Linting and static analysis
12
- runs-on: ubuntu-20.04
12
+ runs-on: ubuntu-24.04
13
13
  timeout-minutes: 5
14
14
  steps:
15
- - uses: actions/checkout@v2
16
- - uses: actions/setup-python@v2
15
+ - uses: actions/checkout@v4
16
+ - uses: actions/setup-python@v5
17
17
  with:
18
- python-version: "3.10"
18
+ python-version: "3.13"
19
19
  - run: pip install -r requirements.txt
20
20
  - run: pre-commit run --all-files
21
21
  - run: mypy looptime --strict
@@ -24,18 +24,24 @@ jobs:
24
24
  strategy:
25
25
  fail-fast: false
26
26
  matrix:
27
- python-version: [ "3.7", "3.8", "3.9", "3.10" ]
28
- name: Python ${{ matrix.python-version }}
29
- runs-on: ubuntu-20.04
27
+ python-version: [ "3.9", "3.10", "3.11", "3.12" ]
28
+ include:
29
+ - python-version: "3.13"
30
+ - python-version: "3.13"
31
+ install-extras: "pytest-asyncio<1.0.0"
32
+ name: Python ${{ matrix.python-version }}${{ matrix.install-extras && ', ' || '' }}${{ matrix.install-extras }}
33
+ runs-on: ubuntu-24.04
30
34
  timeout-minutes: 5
31
35
  steps:
32
- - uses: actions/checkout@v2
33
- - uses: actions/setup-python@v2
36
+ - uses: actions/checkout@v4
37
+ - uses: actions/setup-python@v5
34
38
  with:
35
39
  python-version: ${{ matrix.python-version }}
36
40
 
37
41
  - run: pip install -r requirements.txt
38
42
  - run: pip install -e .
43
+ - run: pip install "${{ matrix.install-extras }}"
44
+ if: ${{ matrix.install-extras }}
39
45
  - run: pytest --color=yes --cov=looptime --cov-branch
40
46
 
41
47
  - name: Publish coverage to Coveralls.io
@@ -45,7 +51,7 @@ jobs:
45
51
  GITHUB_TOKEN: ${{ secrets.github_token }}
46
52
  continue-on-error: true
47
53
  - name: Publish coverage to CodeCov.io
48
- uses: codecov/codecov-action@v1
54
+ uses: codecov/codecov-action@v3
49
55
  if: success()
50
56
  env:
51
57
  PYTHON: ${{ matrix.python-version }}
@@ -59,13 +65,13 @@ jobs:
59
65
  strategy:
60
66
  fail-fast: false
61
67
  matrix:
62
- python-version: [ "pypy-3.7", "pypy-3.8" ]
68
+ python-version: [ "pypy-3.9", "pypy-3.10", "pypy-3.11" ]
63
69
  name: Python ${{ matrix.python-version }}
64
- runs-on: ubuntu-20.04
70
+ runs-on: ubuntu-24.04
65
71
  timeout-minutes: 5
66
72
  steps:
67
- - uses: actions/checkout@v2
68
- - uses: actions/setup-python@v2
73
+ - uses: actions/checkout@v4
74
+ - uses: actions/setup-python@v5
69
75
  with:
70
76
  python-version: ${{ matrix.python-version }}
71
77
 
@@ -76,9 +82,9 @@ jobs:
76
82
  coveralls-finish:
77
83
  name: Finalize coveralls.io
78
84
  needs: [unit-tests]
79
- runs-on: ubuntu-20.04
85
+ runs-on: ubuntu-24.04
80
86
  steps:
81
- - uses: actions/setup-python@v2
87
+ - uses: actions/setup-python@v5
82
88
  - run: pip install coveralls
83
89
  - run: coveralls --service=github --finish
84
90
  env:
@@ -11,10 +11,10 @@ on:
11
11
  jobs:
12
12
  publish:
13
13
  name: Build and publish
14
- runs-on: ubuntu-20.04
14
+ runs-on: ubuntu-24.04
15
15
  steps:
16
- - uses: actions/checkout@v2
17
- - uses: actions/setup-python@v2
16
+ - uses: actions/checkout@v4
17
+ - uses: actions/setup-python@v5
18
18
  with:
19
19
  python-version: "3.10"
20
20
  - run: pip install --upgrade setuptools wheel twine
@@ -11,13 +11,13 @@ on:
11
11
  jobs:
12
12
  linters:
13
13
  name: Linting and static analysis
14
- runs-on: ubuntu-20.04
14
+ runs-on: ubuntu-24.04
15
15
  timeout-minutes: 5
16
16
  steps:
17
- - uses: actions/checkout@v2
18
- - uses: actions/setup-python@v2
17
+ - uses: actions/checkout@v4
18
+ - uses: actions/setup-python@v5
19
19
  with:
20
- python-version: "3.10"
20
+ python-version: "3.13"
21
21
  - run: pip install -r requirements.txt
22
22
  - run: pre-commit run --all-files
23
23
  - run: mypy looptime --strict
@@ -26,13 +26,13 @@ jobs:
26
26
  strategy:
27
27
  fail-fast: false
28
28
  matrix:
29
- python-version: [ "3.7", "3.8", "3.9", "3.10" ]
29
+ python-version: [ "3.9", "3.10", "3.11", "3.12", "3.13" ]
30
30
  name: Python ${{ matrix.python-version }}
31
- runs-on: ubuntu-20.04
31
+ runs-on: ubuntu-24.04
32
32
  timeout-minutes: 5
33
33
  steps:
34
- - uses: actions/checkout@v2
35
- - uses: actions/setup-python@v2
34
+ - uses: actions/checkout@v4
35
+ - uses: actions/setup-python@v5
36
36
  with:
37
37
  python-version: ${{ matrix.python-version }}
38
38
 
@@ -47,7 +47,7 @@ jobs:
47
47
  GITHUB_TOKEN: ${{ secrets.github_token }}
48
48
  continue-on-error: true
49
49
  - name: Publish coverage to CodeCov.io
50
- uses: codecov/codecov-action@v1
50
+ uses: codecov/codecov-action@v3
51
51
  if: success()
52
52
  env:
53
53
  PYTHON: ${{ matrix.python-version }}
@@ -61,13 +61,13 @@ jobs:
61
61
  strategy:
62
62
  fail-fast: false
63
63
  matrix:
64
- python-version: [ "pypy-3.7", "pypy-3.8" ]
64
+ python-version: [ "pypy-3.9", "pypy-3.10", "pypy-3.11" ]
65
65
  name: Python ${{ matrix.python-version }}
66
- runs-on: ubuntu-20.04
66
+ runs-on: ubuntu-24.04
67
67
  timeout-minutes: 5
68
68
  steps:
69
- - uses: actions/checkout@v2
70
- - uses: actions/setup-python@v2
69
+ - uses: actions/checkout@v4
70
+ - uses: actions/setup-python@v5
71
71
  with:
72
72
  python-version: ${{ matrix.python-version }}
73
73
 
@@ -78,9 +78,9 @@ jobs:
78
78
  coveralls-finish:
79
79
  name: Finalize coveralls.io
80
80
  needs: [unit-tests]
81
- runs-on: ubuntu-20.04
81
+ runs-on: ubuntu-24.04
82
82
  steps:
83
- - uses: actions/setup-python@v2
83
+ - uses: actions/setup-python@v5
84
84
  - run: pip install coveralls
85
85
  - run: coveralls --service=github --finish
86
86
  env:
@@ -7,7 +7,7 @@ exclude: |
7
7
  )
8
8
  repos:
9
9
  - repo: https://github.com/pre-commit/pre-commit-hooks
10
- rev: v3.4.0
10
+ rev: v5.0.0
11
11
  hooks:
12
12
  - id: check-ast
13
13
  - id: trailing-whitespace
@@ -46,7 +46,7 @@ repos:
46
46
  # - id: double-quote-string-fixer
47
47
 
48
48
  - repo: https://github.com/pre-commit/pygrep-hooks
49
- rev: v1.8.0
49
+ rev: v1.10.0
50
50
  hooks:
51
51
  - id: python-check-blanket-noqa
52
52
  - id: python-check-mock-methods
@@ -59,7 +59,7 @@ repos:
59
59
  - id: text-unicode-replacement-char
60
60
 
61
61
  - repo: https://github.com/PyCQA/isort
62
- rev: 5.8.0
62
+ rev: 6.0.1
63
63
  hooks:
64
64
  - id: isort
65
65
  name: isort-source-code
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: looptime
3
- Version: 0.1
3
+ Version: 0.3
4
4
  Summary: Fast-forward asyncio event loop time (in tests)
5
5
  Home-page: https://github.com/nolar/looptime
6
6
  Author: Sergey Vasilyev
@@ -11,18 +11,28 @@ License: MIT
11
11
  Project-URL: Bug Tracker, https://github.com/nolar/looptime/issues
12
12
  Project-URL: Source Code, https://github.com/nolar/looptime
13
13
  Keywords: asyncio,event loop,time,python,pytest
14
- Platform: UNKNOWN
15
- Requires-Python: >=3.7
14
+ Requires-Python: >=3.9
16
15
  Description-Content-Type: text/markdown
17
16
  License-File: LICENSE
17
+ Dynamic: author
18
+ Dynamic: author-email
19
+ Dynamic: description
20
+ Dynamic: description-content-type
21
+ Dynamic: home-page
22
+ Dynamic: keywords
23
+ Dynamic: license
24
+ Dynamic: license-file
25
+ Dynamic: maintainer
26
+ Dynamic: maintainer-email
27
+ Dynamic: project-url
28
+ Dynamic: requires-python
29
+ Dynamic: summary
18
30
 
19
31
  # Fast-forward asyncio event loop time (in tests)
20
32
 
21
33
  [![CI](https://github.com/nolar/looptime/workflows/Thorough%20tests/badge.svg)](https://github.com/nolar/looptime/actions/workflows/thorough.yaml)
22
34
  [![codecov](https://codecov.io/gh/nolar/looptime/branch/main/graph/badge.svg)](https://codecov.io/gh/nolar/looptime)
23
35
  [![Coverage Status](https://coveralls.io/repos/github/nolar/looptime/badge.svg?branch=main)](https://coveralls.io/github/nolar/looptime?branch=main)
24
- [![Total alerts](https://img.shields.io/lgtm/alerts/g/nolar/looptime.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/nolar/looptime/alerts/)
25
- [![Language grade: Python](https://img.shields.io/lgtm/grade/python/g/nolar/looptime.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/nolar/looptime/context:python)
26
36
  [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)](https://github.com/pre-commit/pre-commit)
27
37
 
28
38
  ## What?
@@ -37,11 +47,11 @@ The effects of time removal can be seen from both sides:
37
47
  This hides the code overhead and network latencies from the time measurements,
38
48
  making the loop time sharply and predictably advancing in configured steps.
39
49
 
40
- * From the **observer's (i.e. your) point of view,**
50
+ * From the **observer's (i.e. your personal) point of view,**
41
51
  all activities of the event loop, such as sleeps, events/conditions waits,
42
52
  timeouts, "later" callbacks, happen in near-zero amount of the real time
43
- (above the usual code overhead).
44
- This speeds up the tests execution without breaking the tests' time-based
53
+ (due to the usual code execution overhead).
54
+ This speeds up the execution of tests without breaking the tests' time-based
45
55
  design, even if they are designed to run in seconds or minutes.
46
56
 
47
57
  For the latter case, there are a few exceptions when the event loop's activities
@@ -77,28 +87,28 @@ at the same time:
77
87
  * One is for the coroutine-under-test which moves between states
78
88
  in the background.
79
89
  * Another one is for the test itself, which controls the flow
80
- of that coroutine-under-test: it sets events, injects data, etc.
90
+ of that coroutine-under-test: it schedules events, injects data, etc.
81
91
 
82
92
  In textbook cases with simple coroutines that are more like regular functions,
83
93
  it is possible to design a test so that it runs straight to the end in one hop
84
94
  — with all the preconditions set and data prepared in advance in the test setup.
85
95
 
86
96
  However, in the real-world cases, the tests often must verify that
87
- the corotuine stops at some point, waits for a condition for some limited time,
97
+ the coroutine stops at some point, waits for a condition for some limited time,
88
98
  and then passes or fails.
89
99
 
90
100
  The problem is often "solved" by mocking the low-level coroutines of sleep/wait
91
101
  that we expect the coroutine-under-test to call. But this violates the main
92
- principle of good unit-tests: test the promise, not the implementation.
102
+ principle of good unit-tests: **test the promise, not the implementation.**
93
103
  Mocking and checking the low-level coroutines is based on the assumptions
94
104
  of how the coroutine is implemented internally, which can change over time.
95
105
  Good tests do not change on refactoring if the protocol remains the same.
96
106
 
97
107
  Another (straightforward) approach is to not mock the low-level routines, but
98
- to spend the real-world time, just in short bursts as hard-cded in the test.
99
- Not only it makes the whole test-suite slower, it also brings the exection
100
- time close to the values where the code overhead affects the timing
101
- and makes it difficult to assert on the coroutine's pure time.
108
+ to spend the real-world time, just in short bursts as hard-coded in the test.
109
+ Not only it makes the whole test-suite slower, it also brings the execution
110
+ time close to the values where the code overhead or measurement errors affect
111
+ the timing, which makes it difficult to assert on the coroutine's pure time.
102
112
 
103
113
 
104
114
  ## Solution
@@ -125,7 +135,7 @@ pip install pytest-asyncio
125
135
  pip install looptime
126
136
  ```
127
137
 
128
- Nothing is needed to make async tests to run with the fake time, it just works:
138
+ Nothing is needed to make async tests run with the fake time, it just works:
129
139
 
130
140
  ```python
131
141
  import asyncio
@@ -221,14 +231,14 @@ for each setting separately (i.e. not the closest marker as a whole).
221
231
  marked as using the fake loop time —including those not marked at all—
222
232
  as if all tests were implicitly marked.
223
233
 
224
- `--no-looptime` runs all tests —both marked and unmarked— with real time.
234
+ `--no-looptime` runs all tests —both marked and unmarked— with the real time.
225
235
  This flag effectively disables the plugin.
226
236
 
227
237
 
228
238
  ## Settings
229
239
 
230
240
  The marker accepts several settings for the test. The closest to the test
231
- function applies. This lets you to define the test-suite defaults
241
+ function applies. This lets you define the test-suite defaults
232
242
  and override them on the directory, module, class, function, or test level:
233
243
 
234
244
  ```python
@@ -256,7 +266,20 @@ or `start=lambda: random.random() * 100` to add some unpredictability.
256
266
 
257
267
  `None` is treated the same as `0.0`.
258
268
 
259
- The default is `0.0`.
269
+ The default is `0.0`. For reusable event loops, the default is to keep
270
+ the time untouched, which means `0.0` or the explicit value for the first test,
271
+ but then an ever-increasing value for the 2nd, 3rd, and further tests.
272
+
273
+ Note: pytest-asyncio 1.0.0+ introduced event loops with higher scopes,
274
+ e.g. class-, module-, packages-, session-scoped event loops used in tests.
275
+ Such event loops are reused, so their time continues growing through many tests.
276
+ However, if the test is explicitly configured with the start time,
277
+ that time is enforced to the event loop when the test function starts —
278
+ to satisfy the clearly declared intentions — even if the time moves backwards,
279
+ which goes against the nature of the time itself (monotonically growing).
280
+ This might lead to surprises in time measurements outside of the test,
281
+ e.g. in fixtures: the code durations can become negative, or the events can
282
+ happen (falsely) before they are scheduled (loop-clock-wise). Be careful.
260
283
 
261
284
 
262
285
  ### The end of time
@@ -273,6 +296,8 @@ e.g. with `asyncio.sleep(0)`, simple `await` statements, etc.
273
296
 
274
297
  If set to `None`, there is no end of time, and the event loop runs
275
298
  as long as needed. Note: `0` means ending the time immediately on start.
299
+ Be careful with the explicit ending time in higher-scoped event loops
300
+ of pytest-asyncio>=1.0.0, since they time increases through many tests.
276
301
 
277
302
  If it is a callable, it is called once per event loop to get the value:
278
303
  e.g. `end=lambda: time.monotonic() + 10`.
@@ -304,7 +329,7 @@ Normally, it should not fail. However, with fake time (without workarounds)
304
329
  the following scenario is possible:
305
330
 
306
331
  * `async_timeout` library sets its delayed timer at 9 seconds since now.
307
- * the event loop notices that there is and and only one timer at T0+9s.
332
+ * the event loop notices that there is only one timer at T0+9s.
308
333
  * the event loop fast-forwards time to be `9`.
309
334
  * since there are no other handles/timers, that timer is executed.
310
335
  * `async_timeout` fails the test with `asyncio.TimeoutError`
@@ -359,11 +384,11 @@ However, with the fake time (with no workarounds), the following happens:
359
384
  * The test suppresses the timeout, checks the assertion, and fails:
360
385
  the sync event is still unset.
361
386
  * A fraction of a second (e.g. `0.001` second) later, the thread starts,
362
- calls the function, and sets the sync event, but it is too late.
387
+ calls the function and sets the sync event, but it is too late.
363
388
 
364
389
  Compared to the fake fast-forwarding time, even such fast things as threads
365
390
  are too slow to start. Unfortunately, `looptime` and the event loop can
366
- neither control what is happening outside of the event loop, nor predict
391
+ neither control what is happening outside of the event loop nor predict
367
392
  how long it will take.
368
393
 
369
394
  To work around this, `looptime` remembers all calls to executors and then
@@ -375,11 +400,11 @@ So, the fake time and real time move along while waiting for executors.
375
400
  Luckily for this case, in 1 or 2 such steps, the executor's thread will
376
401
  do its job, the event will be set, so as the synchronous & asynchronous
377
402
  futures of the executor. The latter one (the async future) will also
378
- let the `await` to move on.
403
+ let the `await` move on.
379
404
 
380
405
  The `idle_step` (`float` or `None`) setting is the duration of a single
381
406
  time step when fast-forwarding the time if there are executors used —
382
- i.e. if there are some synchronous tasks running in the thread pools.
407
+ i.e. if some synchronous tasks are running in the thread pools.
383
408
 
384
409
  Note that the steps are both true-time and fake-time: they spend the same
385
410
  amount of the observer's true time as they increment the loop's fake time.
@@ -413,7 +438,7 @@ With no workarounds, the test will hang forever waiting for the i/o to happen.
413
438
  This mostly happens when the only thing left in the event loop is the i/o,
414
439
  all internal scheduled callbacks are gone.
415
440
 
416
- `looptime` can artifically limit the lifetime of the event loop.
441
+ `looptime` can artificially limit the lifetime of the event loop.
417
442
  This can be done as a default setting for the whole test suite, for example.
418
443
 
419
444
  The `idle_timeout` (`float` or `None`) setting is the true-time limit
@@ -459,7 +484,7 @@ while `looptime(end=N)` applies to the lifecycle of the whole event loop,
459
484
  which is usually the duration of the whole test and monotonically increases.
460
485
 
461
486
  Second, `looptime(end=N)` syncs the loop time with the real time for N seconds,
462
- i.e. it does not instantly fast-forwards the loop time when the loops
487
+ i.e. it does not instantly fast-forward the loop time when the loops
463
488
  attempts to make an "infinite sleep" (technically, `selector.select(None)`).
464
489
  `async_timeout.timeout()` and `asyncio.wait_for()` set a delayed callback,
465
490
  so the time fast-forwards to it on the first possible occasion.
@@ -530,6 +555,63 @@ For example, with the resolution `0.001`, the time
530
555
  everything smaller than `0.001` becomes `0` and probably misbehaves._
531
556
 
532
557
 
558
+ ### Time magic coverage
559
+
560
+ The time compaction magic is enabled only for the duration of the test,
561
+ i.e. the test function — but not the fixtures.
562
+ The fixtures run in the real (wall-clock) time.
563
+
564
+ The options (including the force starting time) are applied at the test function
565
+ starting moment, not when it is setting up the fixtures (even function-scoped).
566
+
567
+ This is caused by a new concept of multiple co-existing event loops
568
+ in pytest-asyncio>=1.0.0:
569
+
570
+ - It is unclear which options to apply to higher-scoped fixtures
571
+ used by many tests, which themselves use higher-scoped event loops —
572
+ especially in selective partial runs. Technically, it is the 1st test,
573
+ with the options of 2nd and further tests simply ignored.
574
+ - It is impossible to guess which event loop will be the running loop
575
+ in the test until we reach the test itself, i.e. we do not know this
576
+ when setting up the fixtures, even function-scoped fixtures.
577
+ - There is no way to cover the fixture teardown (no hook in pytest),
578
+ only for the fixture setup and post-teardown cleanup.
579
+
580
+ As such, this functionality (covering of function-scoped fixtures)
581
+ was abandoned — since it was never promised, tested, or documented —
582
+ plus an assumption that it was never used by anyone (it should not be).
583
+ It was rather a side effect of the previous implemention,
584
+ which is not available or possible anymore.
585
+
586
+
587
+ ### pytest-asyncio>=1.0.0
588
+
589
+ As it is said above, pytest-asyncio>=1.0.0 introduced several co-existing
590
+ event loops of different scopes. The time compaction in these event loops
591
+ is NOT activated. Only the running loop of the test function is activated.
592
+
593
+ Configuring and activating multiple co-existing event loops brings a few
594
+ conceptual challenges, which require a good sample case to look into,
595
+ and some time to think.
596
+
597
+ Would you need time compaction in your fixtures of higher scopes,
598
+ do it explicitly:
599
+
600
+ ```python
601
+ import asyncio
602
+ import pytest
603
+
604
+ @pytest.fixture
605
+ async def fixt():
606
+ loop = asyncio.get_running_loop()
607
+ loop.setup_looptime(start=123, end=456)
608
+ with loop.looptime_enabled():
609
+ await do_things()
610
+ ```
611
+
612
+ There is #11 to add a feature to do this automatically, but it is not yet done.
613
+
614
+
533
615
  ## Extras
534
616
 
535
617
  ### Chronometers
@@ -578,7 +660,7 @@ async def test_me(chronometer, event_loop):
578
660
 
579
661
  ### Loop time assertions
580
662
 
581
- The `looptime` **fixture** is a syntax sugar for easy loop time assertions::
663
+ The `looptime` **fixture** is syntax sugar for easy loop time assertions::
582
664
 
583
665
  ```python
584
666
  import asyncio
@@ -593,13 +675,13 @@ async def test_me(looptime):
593
675
 
594
676
  Technically, it is a proxy object to `asyncio.get_running_loop().time()`.
595
677
  The proxy object supports the direct comparison with numbers (integers/floats),
596
- so as some basic arithmetics (adding, substracting, multiplication, etc).
678
+ so as some basic arithmetics (adding, subtracting, multiplication, etc).
597
679
  However, it adjusts to the time precision of 1 nanosecond (1e-9): every digit
598
680
  beyond that precision is ignored — so you can be not afraid of
599
681
  `123.456/1.2` suddenly becoming `102.88000000000001` and not equal to `102.88`
600
682
  (as long as the time proxy object is used and not converted to a native float).
601
683
 
602
- The proxy object can be used to create a new proxy which is bound to a specific
684
+ The proxy object can be used to create a new proxy that is bound to a specific
603
685
  event loop (it works for loops both with fake- and real-world time)::
604
686
 
605
687
  ```python
@@ -623,6 +705,8 @@ the loop time also includes the time of all fixtures setups.
623
705
  Do you use a custom event loop? No problem! Create a test-specific descendant
624
706
  with the provided mixin — and it will work the same as the default event loop.
625
707
 
708
+ For `pytest-asyncio<1.0.0`:
709
+
626
710
  ```python
627
711
  import looptime
628
712
  import pytest
@@ -638,6 +722,29 @@ def event_loop():
638
722
  return LooptimeCustomEventLoop()
639
723
  ```
640
724
 
725
+ For `pytest-asyncio>=1.0.0`:
726
+
727
+ ```python
728
+ import asyncio
729
+ import looptime
730
+ import pytest
731
+ from wherever import CustomEventLoop
732
+
733
+
734
+ class LooptimeCustomEventLoop(looptime.LoopTimeEventLoop, CustomEventLoop):
735
+ pass
736
+
737
+
738
+ class LooptimeCustomEventLoopPolicy(asyncio.DefaultEventLoopPolicy):
739
+ def new_event_loop(self):
740
+ return LooptimeCustomEventLoop()
741
+
742
+
743
+ @pytest.fixture(scope='session')
744
+ def event_loop_policy():
745
+ return LooptimeCustomEventLoopPolicy()
746
+ ```
747
+
641
748
  Only selector-based event loops are supported: the event loop must rely on
642
749
  `self._selector.select(timeout)` to sleep for `timeout` true-time seconds.
643
750
  Everything that inherits from `asyncio.BaseEventLoop` should work.
@@ -645,6 +752,8 @@ Everything that inherits from `asyncio.BaseEventLoop` should work.
645
752
  You can also patch almost any event loop class or event loop object
646
753
  the same way as `looptime` does that (via some dirty hackery):
647
754
 
755
+ For `pytest-asyncio<1.0.0`:
756
+
648
757
  ```python
649
758
  import asyncio
650
759
  import looptime
@@ -657,6 +766,25 @@ def event_loop():
657
766
  return looptime.patch_event_loop(loop)
658
767
  ```
659
768
 
769
+ For `pytest-asyncio>=1.0.0`:
770
+
771
+ ```python
772
+ import asyncio
773
+ import looptime
774
+ import pytest
775
+
776
+
777
+ class LooptimeEventLoopPolicy(asyncio.DefaultEventLoopPolicy):
778
+ def new_event_loop(self):
779
+ loop = super().new_event_loop()
780
+ return looptime.patch_event_loop(loop)
781
+
782
+
783
+ @pytest.fixture(scope='session')
784
+ def event_loop_policy():
785
+ return LooptimeEventLoopPolicy()
786
+ ```
787
+
660
788
  `looptime.make_event_loop_class(cls)` constructs a new class that inherits
661
789
  from the referenced class and the specialised event loop class mentioned above.
662
790
  The resulting classes are cached, so it can be safely called multiple times.
@@ -668,5 +796,3 @@ constructed one. For those who care, it is an equivalent of the following hack
668
796
  ```python
669
797
  loop.__class__ = looptime.make_event_loop_class(loop.__class__)
670
798
  ```
671
-
672
-