Skip to content

Commit 9cbc3f1

Browse files
authored
Merge pull request #150 from kaste/next
2 parents 27a8902 + 830fcbb commit 9cbc3f1

15 files changed

+430
-41
lines changed

.flake8

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ max-line-length = 120
77
# D103 Missing docstring in public function
88
# D104 Missing docstring in public package
99
# D107 Missing docstring in __init__
10+
# W503 line break before binary operator
1011
# W504 line break after binary operator
1112
# W606 'async' and 'await' are reserved keywords starting with Python 3.7
12-
ignore = D100, D101, D102, D103, D104, D107, W504, W606
13+
ignore = D100, D101, D102, D103, D104, D107, W503, W504, W606

tests/test_3141596.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def cleanup_package(package):
3838

3939

4040
def prepare_package(package, output=None, syntax_test=False, syntax_compatibility=False,
41-
color_scheme_test=False, delay=None):
41+
color_scheme_test=False, delay=100):
4242
def wrapper(func):
4343
@wraps(func)
4444
def real_wrapper(self):

tests/test_await_worker.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
from functools import partial
2+
import time
3+
4+
import sublime
5+
6+
from unittesting import DeferrableTestCase, AWAIT_WORKER
7+
8+
9+
def run_in_worker(fn, *args, **kwargs):
10+
sublime.set_timeout_async(partial(fn, *args, **kwargs))
11+
12+
13+
class TestAwaitingWorkerInDeferredTestCase(DeferrableTestCase):
14+
15+
def test_ensure_plain_yield_is_faster_than_the_worker_thread(self):
16+
messages = []
17+
18+
def work(message, worktime=None):
19+
# simulate that a task might take some time
20+
# this will not yield back but block
21+
if worktime:
22+
time.sleep(worktime)
23+
24+
messages.append(message)
25+
26+
run_in_worker(work, 1, 5)
27+
28+
yield
29+
30+
self.assertEqual(messages, [])
31+
32+
def test_await_worker(self):
33+
messages = []
34+
35+
def work(message, worktime=None):
36+
# simulate that a task might take some time
37+
# this will not yield back but block
38+
if worktime:
39+
time.sleep(worktime)
40+
41+
messages.append(message)
42+
43+
run_in_worker(work, 1, 0.5)
44+
45+
yield AWAIT_WORKER
46+
47+
self.assertEqual(messages, [1])

tests/test_deferred_timing.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
from functools import partial
2+
import time
3+
from unittest.mock import patch
4+
5+
import sublime
6+
7+
from unittesting import DeferrableTestCase
8+
9+
10+
def run_in_worker(fn, *args, **kwargs):
11+
sublime.set_timeout_async(partial(fn, *args, **kwargs))
12+
13+
14+
# When we swap `set_timeout_async` with `set_timeout` we basically run
15+
# our program single-threaded.
16+
# This has some benefits:
17+
# - We avoid async/timing issues
18+
# - We can use plain `yield` to run Sublime's task queue empty, see below
19+
# - Every code we run will get correct coverage
20+
#
21+
# However note, that Sublime will just put all async events on the queue,
22+
# avoiding the API. We cannot patch that. That means, the event handlers
23+
# will *not* run using plain `yield` like below, you still have to await
24+
# them using `yield AWAIT_WORKER`.
25+
#
26+
27+
class TestTimingInDeferredTestCase(DeferrableTestCase):
28+
29+
def test_a(self):
30+
# `patch` doesn't work as a decorator with generator functions so we
31+
# use `with`
32+
with patch.object(sublime, 'set_timeout_async', sublime.set_timeout):
33+
messages = []
34+
35+
def work(message, worktime=None):
36+
# simulate that a task might take some time
37+
# this will not yield back but block
38+
if worktime:
39+
time.sleep(worktime)
40+
41+
messages.append(message)
42+
43+
def uut():
44+
run_in_worker(work, 1, 0.5) # add task (A)
45+
run_in_worker(work, 2) # add task (B)
46+
47+
uut() # after that task queue has: (A)..(B)
48+
yield # add task (C) and wait for (C)
49+
expected = [1, 2]
50+
self.assertEqual(messages, expected)
51+
52+
def test_b(self):
53+
# `patch` doesn't work as a decorator with generator functions so we
54+
# use `with`
55+
with patch.object(sublime, 'set_timeout_async', sublime.set_timeout):
56+
messages = []
57+
58+
def work(message, worktime=None):
59+
if worktime:
60+
time.sleep(worktime)
61+
messages.append(message)
62+
63+
def sub_task():
64+
run_in_worker(work, 2, 0.5) # add task (D)
65+
66+
def uut():
67+
run_in_worker(work, 1, 0.3) # add task (A)
68+
run_in_worker(sub_task) # add task (B)
69+
70+
uut()
71+
# task queue now: (A)..(B)
72+
73+
yield # add task (C) and wait for (C)
74+
# (A) runs, (B) runs and adds task (D), (C) resolves
75+
expected = [1]
76+
self.assertEqual(messages, expected)
77+
# task queue now: (D)
78+
yield # add task (E) and wait for it
79+
# (D) runs and (E) resolves
80+
expected = [1, 2]
81+
self.assertEqual(messages, expected)

tests/test_ensure_do_cleanups.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from unittesting import DeferrableTestCase
2+
3+
4+
class TestExplicitDoCleanups(DeferrableTestCase):
5+
6+
def test_manually_calling_do_cleanups_works(self):
7+
messages = []
8+
9+
def work(message):
10+
messages.append(message)
11+
12+
self.addCleanup(work, 1)
13+
yield from self.doCleanups()
14+
15+
self.assertEqual(messages, [1])
16+
17+
18+
cleanup_called = []
19+
20+
21+
class TestImplicitDoCleanupsOnTeardown(DeferrableTestCase):
22+
def test_a_prepare(self):
23+
self.addCleanup(lambda: cleanup_called.append(1))
24+
25+
def test_b_assert(self):
26+
self.assertEqual(cleanup_called, [1])

tests/test_yield_condition.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from unittesting import DeferrableTestCase
2+
3+
4+
class TestYieldConditionsHandlingInDeferredTestCase(DeferrableTestCase):
5+
def test_reraises_errors_raised_in_conditions(self):
6+
try:
7+
yield lambda: 1 / 0
8+
self.fail('Did not reraise the exception from the condition')
9+
except ZeroDivisionError:
10+
pass
11+
except Exception:
12+
self.fail('Did not throw the original exception')
13+
14+
def test_returns_condition_value(self):
15+
rv = yield lambda: 'Hans Peter'
16+
17+
self.assertEqual(rv, 'Hans Peter')
18+
19+
def test_handle_condition_timeout_as_failure(self):
20+
try:
21+
yield {
22+
'condition': lambda: True is False,
23+
'timeout': 100
24+
}
25+
self.fail('Unmet condition should have thrown')
26+
except TimeoutError as e:
27+
self.assertEqual(
28+
str(e),
29+
'Condition not fulfilled within 0.10 seconds'
30+
)

unittesting.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"pattern" : "test*.py",
44
"async": false,
55
"deferred": true,
6+
"legacy_runner": false,
67
"verbosity": 2,
78
"capture_console": false,
89
"reload_package_on_testing": true,

unittesting/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from .core import DeferrableTestCase
1+
from .core import DeferrableTestCase, AWAIT_WORKER
22
from .scheduler import UnitTestingRunSchedulerCommand
33
from .scheduler import run_scheduler
44
from .test_package import UnitTestingCommand
@@ -22,5 +22,6 @@
2222
"UnitTestingCurrentPackageCoverageCommand",
2323
"UnitTestingSyntaxCommand",
2424
"UnitTestingSyntaxCompatibilityCommand",
25-
"UnitTestingColorSchemeCommand"
25+
"UnitTestingColorSchemeCommand",
26+
"AWAIT_WORKER"
2627
]

unittesting/core/__init__.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
1-
from .st3.runner import DeferringTextTestRunner
1+
from .st3.runner import DeferringTextTestRunner, AWAIT_WORKER
2+
from .st3.legacy_runner import LegacyDeferringTextTestRunner
23
from .st3.case import DeferrableTestCase
34
from .st3.suite import DeferrableTestSuite
45
from .loader import UnitTestingLoader as TestLoader
56

6-
__all__ = ["TestLoader", "DeferringTextTestRunner", "DeferrableTestCase", "DeferrableTestSuite"]
7+
__all__ = [
8+
"TestLoader",
9+
"DeferringTextTestRunner",
10+
"LegacyDeferringTextTestRunner",
11+
"DeferrableTestCase",
12+
"DeferrableTestSuite",
13+
"AWAIT_WORKER"
14+
]

unittesting/core/st3/case.py

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -63,18 +63,12 @@ def run(self, result=None):
6363
outcome = _Outcome()
6464
self._outcomeForDoCleanups = outcome
6565

66-
deferred = self._executeTestPart(self.setUp, outcome)
67-
if isiterable(deferred):
68-
yield from deferred
66+
yield from self._executeTestPart(self.setUp, outcome)
6967
if outcome.success:
70-
deferred = self._executeTestPart(testMethod, outcome, isTest=True)
71-
if isiterable(deferred):
72-
yield from deferred
73-
deferred = self._executeTestPart(self.tearDown, outcome)
74-
if isiterable(deferred):
75-
yield from deferred
68+
yield from self._executeTestPart(testMethod, outcome, isTest=True)
69+
yield from self._executeTestPart(self.tearDown, outcome)
7670

77-
self.doCleanups()
71+
yield from self.doCleanups()
7872
if outcome.success:
7973
result.addSuccess(self)
8074
else:
@@ -110,3 +104,18 @@ def run(self, result=None):
110104
stopTestRun = getattr(result, 'stopTestRun', None)
111105
if stopTestRun is not None:
112106
stopTestRun()
107+
108+
def doCleanups(self):
109+
"""Execute all cleanup functions.
110+
111+
Normally called for you after tearDown.
112+
"""
113+
outcome = self._outcomeForDoCleanups or _Outcome()
114+
while self._cleanups:
115+
function, args, kwargs = self._cleanups.pop()
116+
part = lambda: function(*args, **kwargs) # noqa: E731
117+
yield from self._executeTestPart(part, outcome)
118+
119+
# return this for backwards compatibility
120+
# even though we no longer us it internally
121+
return outcome.success

0 commit comments

Comments
 (0)