Skip to content
Merged

Next #150

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .flake8
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ max-line-length = 120
# D103 Missing docstring in public function
# D104 Missing docstring in public package
# D107 Missing docstring in __init__
# W503 line break before binary operator
# W504 line break after binary operator
# W606 'async' and 'await' are reserved keywords starting with Python 3.7
ignore = D100, D101, D102, D103, D104, D107, W504, W606
ignore = D100, D101, D102, D103, D104, D107, W503, W504, W606
2 changes: 1 addition & 1 deletion tests/test_3141596.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def cleanup_package(package):


def prepare_package(package, output=None, syntax_test=False, syntax_compatibility=False,
color_scheme_test=False, delay=None):
color_scheme_test=False, delay=100):
def wrapper(func):
@wraps(func)
def real_wrapper(self):
Expand Down
47 changes: 47 additions & 0 deletions tests/test_await_worker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from functools import partial
import time

import sublime

from unittesting import DeferrableTestCase, AWAIT_WORKER


def run_in_worker(fn, *args, **kwargs):
sublime.set_timeout_async(partial(fn, *args, **kwargs))


class TestAwaitingWorkerInDeferredTestCase(DeferrableTestCase):

def test_ensure_plain_yield_is_faster_than_the_worker_thread(self):
messages = []

def work(message, worktime=None):
# simulate that a task might take some time
# this will not yield back but block
if worktime:
time.sleep(worktime)

messages.append(message)

run_in_worker(work, 1, 5)

yield

self.assertEqual(messages, [])

def test_await_worker(self):
messages = []

def work(message, worktime=None):
# simulate that a task might take some time
# this will not yield back but block
if worktime:
time.sleep(worktime)

messages.append(message)

run_in_worker(work, 1, 0.5)

yield AWAIT_WORKER

self.assertEqual(messages, [1])
81 changes: 81 additions & 0 deletions tests/test_deferred_timing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
from functools import partial
import time
from unittest.mock import patch

import sublime

from unittesting import DeferrableTestCase


def run_in_worker(fn, *args, **kwargs):
sublime.set_timeout_async(partial(fn, *args, **kwargs))


# When we swap `set_timeout_async` with `set_timeout` we basically run
# our program single-threaded.
# This has some benefits:
# - We avoid async/timing issues
# - We can use plain `yield` to run Sublime's task queue empty, see below
# - Every code we run will get correct coverage
#
# However note, that Sublime will just put all async events on the queue,
# avoiding the API. We cannot patch that. That means, the event handlers
# will *not* run using plain `yield` like below, you still have to await
# them using `yield AWAIT_WORKER`.
#

class TestTimingInDeferredTestCase(DeferrableTestCase):

def test_a(self):
# `patch` doesn't work as a decorator with generator functions so we
# use `with`
with patch.object(sublime, 'set_timeout_async', sublime.set_timeout):
messages = []

def work(message, worktime=None):
# simulate that a task might take some time
# this will not yield back but block
if worktime:
time.sleep(worktime)

messages.append(message)

def uut():
run_in_worker(work, 1, 0.5) # add task (A)
run_in_worker(work, 2) # add task (B)

uut() # after that task queue has: (A)..(B)
yield # add task (C) and wait for (C)
expected = [1, 2]
self.assertEqual(messages, expected)

def test_b(self):
# `patch` doesn't work as a decorator with generator functions so we
# use `with`
with patch.object(sublime, 'set_timeout_async', sublime.set_timeout):
messages = []

def work(message, worktime=None):
if worktime:
time.sleep(worktime)
messages.append(message)

def sub_task():
run_in_worker(work, 2, 0.5) # add task (D)

def uut():
run_in_worker(work, 1, 0.3) # add task (A)
run_in_worker(sub_task) # add task (B)

uut()
# task queue now: (A)..(B)

yield # add task (C) and wait for (C)
# (A) runs, (B) runs and adds task (D), (C) resolves
expected = [1]
self.assertEqual(messages, expected)
# task queue now: (D)
yield # add task (E) and wait for it
# (D) runs and (E) resolves
expected = [1, 2]
self.assertEqual(messages, expected)
26 changes: 26 additions & 0 deletions tests/test_ensure_do_cleanups.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from unittesting import DeferrableTestCase


class TestExplicitDoCleanups(DeferrableTestCase):

def test_manually_calling_do_cleanups_works(self):
messages = []

def work(message):
messages.append(message)

self.addCleanup(work, 1)
yield from self.doCleanups()

self.assertEqual(messages, [1])


cleanup_called = []


class TestImplicitDoCleanupsOnTeardown(DeferrableTestCase):
def test_a_prepare(self):
self.addCleanup(lambda: cleanup_called.append(1))

def test_b_assert(self):
self.assertEqual(cleanup_called, [1])
30 changes: 30 additions & 0 deletions tests/test_yield_condition.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from unittesting import DeferrableTestCase


class TestYieldConditionsHandlingInDeferredTestCase(DeferrableTestCase):
def test_reraises_errors_raised_in_conditions(self):
try:
yield lambda: 1 / 0
self.fail('Did not reraise the exception from the condition')
except ZeroDivisionError:
pass
except Exception:
self.fail('Did not throw the original exception')

def test_returns_condition_value(self):
rv = yield lambda: 'Hans Peter'

self.assertEqual(rv, 'Hans Peter')

def test_handle_condition_timeout_as_failure(self):
try:
yield {
'condition': lambda: True is False,
'timeout': 100
}
self.fail('Unmet condition should have thrown')
except TimeoutError as e:
self.assertEqual(
str(e),
'Condition not fulfilled within 0.10 seconds'
)
1 change: 1 addition & 0 deletions unittesting.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"pattern" : "test*.py",
"async": false,
"deferred": true,
"legacy_runner": false,
"verbosity": 2,
"capture_console": false,
"reload_package_on_testing": true,
Expand Down
5 changes: 3 additions & 2 deletions unittesting/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .core import DeferrableTestCase
from .core import DeferrableTestCase, AWAIT_WORKER
from .scheduler import UnitTestingRunSchedulerCommand
from .scheduler import run_scheduler
from .test_package import UnitTestingCommand
Expand All @@ -22,5 +22,6 @@
"UnitTestingCurrentPackageCoverageCommand",
"UnitTestingSyntaxCommand",
"UnitTestingSyntaxCompatibilityCommand",
"UnitTestingColorSchemeCommand"
"UnitTestingColorSchemeCommand",
"AWAIT_WORKER"
]
12 changes: 10 additions & 2 deletions unittesting/core/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
from .st3.runner import DeferringTextTestRunner
from .st3.runner import DeferringTextTestRunner, AWAIT_WORKER
from .st3.legacy_runner import LegacyDeferringTextTestRunner
from .st3.case import DeferrableTestCase
from .st3.suite import DeferrableTestSuite
from .loader import UnitTestingLoader as TestLoader

__all__ = ["TestLoader", "DeferringTextTestRunner", "DeferrableTestCase", "DeferrableTestSuite"]
__all__ = [
"TestLoader",
"DeferringTextTestRunner",
"LegacyDeferringTextTestRunner",
"DeferrableTestCase",
"DeferrableTestSuite",
"AWAIT_WORKER"
]
29 changes: 19 additions & 10 deletions unittesting/core/st3/case.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,18 +63,12 @@ def run(self, result=None):
outcome = _Outcome()
self._outcomeForDoCleanups = outcome

deferred = self._executeTestPart(self.setUp, outcome)
if isiterable(deferred):
yield from deferred
yield from self._executeTestPart(self.setUp, outcome)
if outcome.success:
deferred = self._executeTestPart(testMethod, outcome, isTest=True)
if isiterable(deferred):
yield from deferred
deferred = self._executeTestPart(self.tearDown, outcome)
if isiterable(deferred):
yield from deferred
yield from self._executeTestPart(testMethod, outcome, isTest=True)
yield from self._executeTestPart(self.tearDown, outcome)

self.doCleanups()
yield from self.doCleanups()
if outcome.success:
result.addSuccess(self)
else:
Expand Down Expand Up @@ -110,3 +104,18 @@ def run(self, result=None):
stopTestRun = getattr(result, 'stopTestRun', None)
if stopTestRun is not None:
stopTestRun()

def doCleanups(self):
"""Execute all cleanup functions.

Normally called for you after tearDown.
"""
outcome = self._outcomeForDoCleanups or _Outcome()
while self._cleanups:
function, args, kwargs = self._cleanups.pop()
part = lambda: function(*args, **kwargs) # noqa: E731
yield from self._executeTestPart(part, outcome)

# return this for backwards compatibility
# even though we no longer us it internally
return outcome.success
Loading