|
9 | 9 | import sys |
10 | 10 | import threading |
11 | 11 | from concurrent.futures import ThreadPoolExecutor |
| 12 | +from contextlib import contextmanager |
12 | 13 |
|
13 | 14 | import numpy as np |
14 | 15 |
|
|
21 | 22 | import holoviews.plotting.mpl |
22 | 23 |
|
23 | 24 |
|
| 25 | +@contextmanager |
| 26 | +def fast_switch(): |
| 27 | + """ |
| 28 | + Sets sys.getswitchinterval to 1 microsecond to force thread interleaving in |
| 29 | + tests that check for id collisions with multiple threads |
| 30 | + """ |
| 31 | + old = sys.getswitchinterval() |
| 32 | + sys.setswitchinterval(1e-6) |
| 33 | + try: |
| 34 | + yield |
| 35 | + finally: |
| 36 | + sys.setswitchinterval(old) |
| 37 | + |
| 38 | + |
24 | 39 | @mpl_skip |
25 | 40 | class TestStoreOptionsMerge: |
26 | 41 | def setup_method(self): |
| 42 | + self._backend = hv.Store.current_backend |
27 | 43 | hv.Store.current_backend = "matplotlib" |
28 | 44 | self.expected = {"Image": {"plot": {"fig_size": 150}, "style": {"cmap": "Blues"}}} |
29 | 45 |
|
| 46 | + def teardown_method(self): |
| 47 | + hv.Store.current_backend = self._backend |
| 48 | + |
30 | 49 | def test_full_spec_format(self): |
31 | 50 | out = hv.StoreOptions.merge_options( |
32 | 51 | ["plot", "style"], |
@@ -58,7 +77,11 @@ class TestStoreOptsMethod: |
58 | 77 | """ |
59 | 78 |
|
60 | 79 | def setup_method(self): |
61 | | - hv.Store.current_backend = "matplotlib" |
| 80 | + self._backend = hv.Store.current_backend |
| 81 | + hv.Store.set_current_backend("matplotlib") |
| 82 | + |
| 83 | + def teardown_method(self): |
| 84 | + hv.Store.current_backend = self._backend |
62 | 85 |
|
63 | 86 | def test_overlay_options_partitioned(self): |
64 | 87 | """ |
@@ -171,48 +194,36 @@ def test_overlay_subset_update_keeps_ids_bounded(self): |
171 | 194 | assert max_ids[-1] < 500, f"id growth is not polynomial: {max_ids}" |
172 | 195 |
|
173 | 196 | def test_concurrent_recustomization_keeps_styles_distinct(self): |
174 | | - n_workers = 8 |
| 197 | + n_workers = 4 |
175 | 198 | barrier = threading.Barrier(n_workers) |
176 | 199 |
|
177 | 200 | def worker(w): |
178 | 201 | barrier.wait() |
179 | 202 | mismatches = 0 |
180 | | - for _ in range(120): |
| 203 | + for _ in range(60): |
181 | 204 | el = hv.Curve([1, 2, 3], label=f"w{w}").opts(linewidth=w + 1) |
182 | 205 | got = hv.Store.lookup_options("matplotlib", el, "style").kwargs.get("linewidth") |
183 | 206 | mismatches += got != w + 1 |
184 | 207 | return mismatches |
185 | 208 |
|
186 | | - # A normal opts() runs within one thread-scheduling quantum, so the id |
187 | | - # race stays hidden; a tiny switch interval forces interleaving. |
188 | | - old = sys.getswitchinterval() |
189 | | - sys.setswitchinterval(1e-6) |
190 | | - try: |
191 | | - with ThreadPoolExecutor(max_workers=n_workers) as executor: |
192 | | - mismatches = sum(executor.map(worker, range(n_workers))) |
193 | | - finally: |
194 | | - sys.setswitchinterval(old) |
| 209 | + with fast_switch(), ThreadPoolExecutor(max_workers=n_workers) as executor: |
| 210 | + mismatches = sum(executor.map(worker, range(n_workers))) |
195 | 211 |
|
196 | 212 | assert mismatches == 0, f"concurrent customizations collided on ids: {mismatches}" |
197 | 213 |
|
198 | 214 | def test_concurrent_id_reservation_yields_disjoint_blocks(self): |
199 | | - n_workers = 8 |
| 215 | + n_workers = 4 |
200 | 216 | barrier = threading.Barrier(n_workers) |
201 | 217 |
|
202 | 218 | def worker(_): |
203 | 219 | barrier.wait() |
204 | 220 | ids = [] |
205 | | - for n in range(1, 120): |
| 221 | + for n in range(10, 30): |
206 | 222 | start = hv.StoreOptions.reserve_ids(n) |
207 | 223 | ids.extend(range(start, start + n)) |
208 | 224 | return ids |
209 | 225 |
|
210 | | - old = sys.getswitchinterval() |
211 | | - sys.setswitchinterval(1e-6) # force interleaving of the read-modify-write |
212 | | - try: |
213 | | - with ThreadPoolExecutor(max_workers=n_workers) as executor: |
214 | | - reserved = [i for block in executor.map(worker, range(n_workers)) for i in block] |
215 | | - finally: |
216 | | - sys.setswitchinterval(old) |
| 226 | + with fast_switch(), ThreadPoolExecutor(max_workers=n_workers) as executor: |
| 227 | + reserved = [i for block in executor.map(worker, range(n_workers)) for i in block] |
217 | 228 |
|
218 | 229 | assert len(reserved) == len(set(reserved)), "reserved id blocks overlap" |
0 commit comments