diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000..376119f --- /dev/null +++ b/.pylintrc @@ -0,0 +1,396 @@ +[MASTER] + +# Specify a configuration file. +#rcfile= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +init-hook="import crochet" + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=_version.py, + tests + +# Pickle collected data for later comparisons. +persistent=no + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + +# Use multiple processes to speed up Pylint. +jobs=1 + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code +extension-pkg-whitelist= + +# Allow optimization of some AST trees. This will activate a peephole AST +# optimizer, which will apply various small optimizations. For instance, it can +# be used to obtain the result of joining multiple strings with the addition +# operator. Joining a lot of strings can lead to a maximum recursion error in +# Pylint and this flag can prevent that. It has one side effect, the resulting +# AST will be different than the one from reality. +optimize-ast=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED +confidence= + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time. See also the "--disable" option for examples. +disable=all + +enable=import-error, + import-self, + reimported, + wildcard-import, + misplaced-future, + relative-import, + deprecated-module, + unpacking-non-sequence, + invalid-all-object, + undefined-all-variable, + used-before-assignment, + cell-var-from-loop, + global-variable-undefined, + redefined-builtin, + redefine-in-handler, + unused-import, + unused-wildcard-import, + global-variable-not-assigned, + undefined-loop-variable, + global-statement, + global-at-module-level, + bad-open-mode, + redundant-unittest-assert, + boolean-datetime, + redefined-outer-name, + undefined-variable, + no-name-in-module, + unused-argument + +[REPORTS] + +# Set the output format. Available formats are text, parseable, colorized, msvs +# (visual studio) and html. You can also give a reporter class, eg +# mypackage.mymodule.MyReporterClass. +output-format=parseable + +# Put messages in a separate file for each module / package specified on the +# command line instead of printing them on stdout. Reports (if any) will be +# written in a file name "pylint_global.[txt|html]". +files-output=no + +# Tells whether to display a full report or only the messages +reports=no + +# Python expression which should return a note less than 10 (10 is the highest +# note). You have access to the variables errors warning, statement which +# respectively contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details +#msg-template= + + +[LOGGING] + +# Logging modules to check that the string format arguments are in logging +# function parameter format +logging-modules=logging + + +[FORMAT] + +# Maximum number of characters on a single line. +max-line-length=100 + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + +# List of optional constructs for which whitespace checking is disabled. `dict- +# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. +# `trailing-comma` allows a space between comma and closing bracket: (a, ). +# `empty-line` allows space-only lines. +no-space-check=trailing-comma,dict-separator + +# Maximum number of lines in a module +max-module-lines=1000 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + + +[TYPECHECK] + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis. It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# List of classes names for which member attributes should not be checked +# (useful for classes with attributes dynamically set). This supports can work +# with qualified names. +ignored-classes= + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + + +[VARIABLES] + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# A regular expression matching the name of dummy variables (i.e. expectedly +# not used). +dummy-variables-rgx=^_|^dummy + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_,_cb + + +[SIMILARITIES] + +# Minimum lines number of a similarity. +min-similarity-lines=4 + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + + +[SPELLING] + +# Spelling dictionary name. Available dictionaries: none. To make it working +# install python-enchant package. +spelling-dict= + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to indicated private dictionary in +# --spelling-private-dict-file option instead of raising a message. +spelling-store-unknown-words=no + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME,XXX,TODO + + +[BASIC] + +# List of builtins function names that should not be used, separated by a comma +bad-functions=map,filter,input + +# Good variable names which should always be accepted, separated by a comma +good-names=i,j,k,ex,Run,_ + +# Bad variable names which should always be refused, separated by a comma +bad-names=foo,bar,baz,toto,tutu,tata + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Include a hint for the correct naming format with invalid-name +include-naming-hint=no + +# Regular expression matching correct function names +function-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for function names +function-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct variable names +variable-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for variable names +variable-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct constant names +const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Naming hint for constant names +const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Regular expression matching correct attribute names +attr-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for attribute names +attr-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct argument names +argument-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for argument names +argument-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct class attribute names +class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ + +# Naming hint for class attribute names +class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ + +# Regular expression matching correct inline iteration names +inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ + +# Naming hint for inline iteration names +inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ + +# Regular expression matching correct class names +class-rgx=[A-Z_][a-zA-Z0-9]+$ + +# Naming hint for class names +class-name-hint=[A-Z_][a-zA-Z0-9]+$ + +# Regular expression matching correct module names +module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Naming hint for module names +module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Regular expression matching correct method names +method-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for method names +method-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + + +[ELIF] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + + +[IMPORTS] + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=regsub,TERMIOS,Bastion,rexec + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled) +import-graph= + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled) +int-import-graph= + + +[DESIGN] + +# Maximum number of arguments for function / method +max-args=5 + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore +ignored-argument-names=_.* + +# Maximum number of locals for function / method body +max-locals=15 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of branch for function / method body +max-branches=12 + +# Maximum number of statements in function / method body +max-statements=50 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of boolean expressions in a if statement +max-bool-expr=5 + + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__,__new__,setUp + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict,_fields,_replace,_source,_make + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "Exception" +overgeneral-exceptions=Exception diff --git a/.style.yapf b/.style.yapf new file mode 100644 index 0000000..95f9eb0 --- /dev/null +++ b/.style.yapf @@ -0,0 +1,3 @@ +[style] +DEDENT_CLOSING_BRACKETS = true +COALESCE_BRACKETS = true \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 3777fa6..c59111b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,12 +15,13 @@ python: install: - python setup.py --version - - pip install -q $TWISTED pyflakes + - pip install -q $TWISTED flake8 pylint - python setup.py -q install script: - trial crochet.tests - - pyflakes crochet + - flake8 crochet + - pylint crochet notifications: email: false diff --git a/crochet/__init__.py b/crochet/__init__.py index 32948d9..af91279 100644 --- a/crochet/__init__.py +++ b/crochet/__init__.py @@ -8,6 +8,12 @@ from twisted.python.log import startLoggingWithObserver from twisted.python.runtime import platform + +from ._shutdown import _watchdog, register +from ._eventloop import ( + EventualResult, TimeoutError, EventLoop, _store, ReactorStopped) +from ._version import get_versions + if platform.type == "posix": try: from twisted.internet.process import reapAllProcesses @@ -17,15 +23,12 @@ else: # Process support is still not ported to Python 3 on some versions # of Twisted. - reapAllProcesses = lambda: None + def reapAllProcesses(): pass else: # waitpid() is only necessary on POSIX: - reapAllProcesses = lambda: None + def reapAllProcesses(): pass + -from ._shutdown import _watchdog, register -from ._eventloop import (EventualResult, TimeoutError, EventLoop, _store, - ReactorStopped) -from ._version import get_versions __version__ = get_versions()['version'] del get_versions @@ -33,8 +36,11 @@ def _importReactor(): from twisted.internet import reactor return reactor -_main = EventLoop(_importReactor, register, startLoggingWithObserver, - _watchdog, reapAllProcesses) + + +_main = EventLoop( + _importReactor, register, startLoggingWithObserver, _watchdog, + reapAllProcesses) setup = _main.setup no_setup = _main.no_setup run_in_reactor = _main.run_in_reactor @@ -48,9 +54,17 @@ def _importReactor(): # Backwards compatibility with 1.1.0 and earlier: wait_for_reactor = _main.wait_for_reactor -__all__ = ["setup", "run_in_reactor", "EventualResult", "TimeoutError", - "retrieve_result", "no_setup", "wait_for", - "ReactorStopped", "__version__", - # Backwards compatibility: - "DeferredResult", "in_reactor", "wait_for_reactor", - ] +__all__ = [ + "setup", + "run_in_reactor", + "EventualResult", + "TimeoutError", + "retrieve_result", + "no_setup", + "wait_for", + "ReactorStopped", + "__version__", + # Backwards compatibility: + "DeferredResult", + "in_reactor", + "wait_for_reactor", ] diff --git a/crochet/_eventloop.py b/crochet/_eventloop.py index ece299f..47ce9eb 100644 --- a/crochet/_eventloop.py +++ b/crochet/_eventloop.py @@ -24,14 +24,15 @@ _store = ResultStore() - if hasattr(weakref, "WeakSet"): WeakSet = weakref.WeakSet else: + class WeakSet(object): """ Minimal WeakSet emulation. """ + def __init__(self): self._items = weakref.WeakKeyDictionary() @@ -67,7 +68,8 @@ class ResultRegistry(object): ReactorStopped exception to unblock any remaining EventualResult.wait() calls. """ - def __init__(self, reactor): + + def __init__(self): self._results = WeakSet() self._stopped = False self._lock = threading.Lock() @@ -127,6 +129,7 @@ def _connect_deferred(self, deferred): Should only be run in Twisted thread, and only called once. """ self._deferred = deferred + # Because we use __del__, we need to make sure there are no cycles # involving this object, which is why we use a weakref: def put(result, eventual=weakref.ref(self)): @@ -135,6 +138,7 @@ def put(result, eventual=weakref.ref(self)): eventual._set_result(result) else: err(result, "Unhandled error in EventualResult") + deferred.addBoth(put) def _set_result(self, result): @@ -182,10 +186,12 @@ def _result(self, timeout=None): result. """ if timeout is None: - warnings.warn("Unlimited timeouts are deprecated.", - DeprecationWarning, stacklevel=3) + warnings.warn( + "Unlimited timeouts are deprecated.", + DeprecationWarning, + stacklevel=3) # Queue.get(None) won't get interrupted by Ctrl-C... - timeout = 2 ** 31 + timeout = 2**31 self._result_set.wait(timeout) # In Python 2.6 we can't rely on the return result of wait(), so we # have to check manually: @@ -220,11 +226,13 @@ def wait(self, timeout=None): pass else: # If EventualResult.wait() is run during module import, if the - # Twisted code that is being run also imports something the result - # will be a deadlock. Even if that is not an issue it would - # prevent importing in other threads until the call returns. + # Twisted code that is being run also imports something the + # result will be a deadlock. Even if that is not an issue it + # would prevent importing in other threads until the call + # returns. raise RuntimeError( - "EventualResult.wait() must not be run at module import time.") + "EventualResult.wait() must not be run at module " + "import time.") result = self._result(timeout) if isinstance(result, Failure): @@ -267,6 +275,7 @@ class ThreadLogObserver(object): In particular, used to wrap PythonLoggingObserver, so that blocking logging.py Handlers don't block the event loop. """ + def __init__(self, observer): self._observer = observer if getattr(select, "epoll", None): @@ -280,8 +289,8 @@ def __init__(self, observer): reactorFactory = SelectReactor self._logWritingReactor = reactorFactory() self._logWritingReactor._registerAsIOThread = False - self._thread = threading.Thread(target=self._reader, - name="CrochetLogWriter") + self._thread = threading.Thread( + target=self._reader, name="CrochetLogWriter") self._thread.start() def _reader(self): @@ -301,6 +310,7 @@ def __call__(self, msg): """ A log observer that writes to a queue. """ + def log(): try: self._observer(msg) @@ -316,10 +326,15 @@ class EventLoop(object): """ Initialization infrastructure for running a reactor in a thread. """ - def __init__(self, reactorFactory, atexit_register, - startLoggingWithObserver=None, - watchdog_thread=None, - reapAllProcesses=None): + + def __init__( + self, + reactorFactory, + atexit_register, + startLoggingWithObserver=None, + watchdog_thread=None, + reapAllProcesses=None + ): """ reactorFactory: Zero-argument callable that returns a reactor. atexit_register: atexit.register, or look-alike. @@ -351,7 +366,7 @@ def _common_setup(self): """ self._started = True self._reactor = self._reactorFactory() - self._registry = ResultRegistry(self._reactor) + self._registry = ResultRegistry() # We want to unblock EventualResult regardless of how the reactor is # run, so we always register this: self._reactor.addSystemEventTrigger( @@ -375,6 +390,7 @@ def setup(self): self._reactor.callFromThread(self._startReapingProcesses) if self._startLoggingWithObserver: observer = ThreadLogObserver(PythonLoggingObserver().emit) + def start(): # Twisted is going to override warnings.showwarning; let's # make sure that has no effect: @@ -383,18 +399,18 @@ def start(): log.showwarning = warnings.showwarning self._startLoggingWithObserver(observer, False) log.showwarning = original + self._reactor.callFromThread(start) # We only want to stop the logging thread once the reactor has # shut down: - self._reactor.addSystemEventTrigger("after", "shutdown", - observer.stop) + self._reactor.addSystemEventTrigger( + "after", "shutdown", observer.stop) t = threading.Thread( target=lambda: self._reactor.run(installSignalHandlers=False), name="CrochetReactor") t.start() - self._atexit_register(self._reactor.callFromThread, - self._reactor.stop) + self._atexit_register(self._reactor.callFromThread, self._reactor.stop) self._atexit_register(_store.log_errors) if self._watchdog_thread is not None: self._watchdog_thread.start() @@ -412,17 +428,20 @@ def no_setup(self): If no_setup() is called after setup(), a RuntimeError is raised. """ if self._started: - raise RuntimeError("no_setup() is intended to be called once, by a" - " Twisted application, before any libraries " - "using crochet are imported and call setup().") + raise RuntimeError( + "no_setup() is intended to be called once, by a" + " Twisted application, before any libraries " + "using crochet are imported and call setup().") self._common_setup() def run_in_reactor(self, function): """ - A decorator that ensures the wrapped function runs in the reactor thread. + A decorator that ensures the wrapped function runs in the reactor + thread. When the wrapped function is called, an EventualResult is returned. """ + def runs_in_reactor(result, args, kwargs): d = maybeDeferred(function, *args, **kwargs) result._connect_deferred(d) @@ -433,6 +452,7 @@ def wrapper(*args, **kwargs): self._registry.register(result) self._reactor.callFromThread(runs_in_reactor, result, args, kwargs) return result + wrapper.wrapped_function = function return wrapper @@ -440,16 +460,19 @@ def wait_for_reactor(self, function): """ DEPRECATED, use wait_for(timeout) instead. - A decorator that ensures the wrapped function runs in the reactor thread. + A decorator that ensures the wrapped function runs in the reactor + thread. When the wrapped function is called, its result is returned or its exception raised. Deferreds are handled transparently. """ - warnings.warn("@wait_for_reactor is deprecated, use @wait_for instead", - DeprecationWarning, stacklevel=2) + warnings.warn( + "@wait_for_reactor is deprecated, use @wait_for instead", + DeprecationWarning, + stacklevel=2) # This will timeout, in theory. In practice the process will be dead # long before that. - return self.wait_for(2 ** 31)(function) + return self.wait_for(2**31)(function) def wait_for(self, timeout): """ @@ -461,35 +484,43 @@ def wait_for(self, timeout): timeout after the given number of seconds (a float), raising a crochet.TimeoutError, and cancelling the Deferred being waited on. """ + def decorator(function): @wraps(function) def wrapper(*args, **kwargs): @self.run_in_reactor def run(): return function(*args, **kwargs) + eventual_result = run() try: return eventual_result.wait(timeout) except TimeoutError: eventual_result.cancel() raise + wrapper.wrapped_function = function return wrapper + return decorator def in_reactor(self, function): """ DEPRECATED, use run_in_reactor. - A decorator that ensures the wrapped function runs in the reactor thread. + A decorator that ensures the wrapped function runs in the reactor + thread. The wrapped function will get the reactor passed in as a first argument, in addition to any arguments it is called with. When the wrapped function is called, an EventualResult is returned. """ - warnings.warn("@in_reactor is deprecated, use @run_in_reactor", - DeprecationWarning, stacklevel=2) + warnings.warn( + "@in_reactor is deprecated, use @run_in_reactor", + DeprecationWarning, + stacklevel=2) + @self.run_in_reactor @wraps(function) def add_reactor(*args, **kwargs): diff --git a/crochet/_resultstore.py b/crochet/_resultstore.py index 266bed8..3661eff 100644 --- a/crochet/_resultstore.py +++ b/crochet/_resultstore.py @@ -20,6 +20,7 @@ class ResultStore(object): EventualResults that are not retrieved by shutdown will be logged if they have an error result. """ + def __init__(self): self._counter = 0 self._stored = {} @@ -53,4 +54,3 @@ def log_errors(self): failure = result.original_failure() if failure is not None: log.err(failure, "Unhandled error in stashed EventualResult:") - diff --git a/crochet/_shutdown.py b/crochet/_shutdown.py index 834c1e8..f0e026c 100644 --- a/crochet/_shutdown.py +++ b/crochet/_shutdown.py @@ -33,6 +33,7 @@ class FunctionRegistry(object): """ A registry of functions that can be called all at once. """ + def __init__(self): self._functions = [] @@ -56,10 +57,7 @@ def run(self): # This is... fragile. Not sure how else to do it though. _registry = FunctionRegistry() _watchdog = Watchdog( - [ - t for t in threading.enumerate() - if isinstance(t, threading._MainThread) - ][0], - _registry.run, -) + [t for t in threading.enumerate() + if isinstance(t, threading._MainThread)][0], + _registry.run, ) register = _registry.register diff --git a/crochet/_util.py b/crochet/_util.py index 43b4a7f..c886f26 100644 --- a/crochet/_util.py +++ b/crochet/_util.py @@ -9,9 +9,11 @@ def synchronized(method): """ Decorator that wraps a method with an acquire/release of self._lock. """ + @wraps(method) def synced(self, *args, **kwargs): with self._lock: return method(self, *args, **kwargs) + synced.synchronized = True return synced diff --git a/crochet/_version.py b/crochet/_version.py index 4665551..d073220 100644 --- a/crochet/_version.py +++ b/crochet/_version.py @@ -1,4 +1,3 @@ - # This file helps to compute a version number in source trees obtained from # git-archive tarball (such as those provided by githubs download-from-tag # feature). Distribution tarballs (built by setup.py sdist) and build @@ -7,7 +6,6 @@ # This file is released into the public domain. Generated by # versioneer-0.16 (https://github.com/warner/python-versioneer) - """Git implementation of _version.py.""" import errno @@ -57,12 +55,14 @@ class NotThisMethod(Exception): def register_vcs_handler(vcs, method): # decorator """Decorator to mark a method as the handler for a particular VCS.""" + def decorate(f): """Store f in HANDLERS[vcs][method].""" if vcs not in HANDLERS: HANDLERS[vcs] = {} HANDLERS[vcs][method] = f return f + return decorate @@ -74,9 +74,11 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False): try: dispcmd = str([c] + args) # remember shell=False, so use git.cmd on windows, not just git - p = subprocess.Popen([c] + args, cwd=cwd, stdout=subprocess.PIPE, - stderr=(subprocess.PIPE if hide_stderr - else None)) + p = subprocess.Popen( + [c] + args, + cwd=cwd, + stdout=subprocess.PIPE, + stderr=(subprocess.PIPE if hide_stderr else None)) break except EnvironmentError: e = sys.exc_info()[1] @@ -88,7 +90,7 @@ def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False): return None else: if verbose: - print("unable to find command, tried %s" % (commands,)) + print("unable to find command, tried %s" % (commands, )) return None stdout = p.communicate()[0].strip() if sys.version_info[0] >= 3: @@ -109,12 +111,15 @@ def versions_from_parentdir(parentdir_prefix, root, verbose): dirname = os.path.basename(root) if not dirname.startswith(parentdir_prefix): if verbose: - print("guessing rootdir is '%s', but '%s' doesn't start with " - "prefix '%s'" % (root, dirname, parentdir_prefix)) + print( + "guessing rootdir is '%s', but '%s' doesn't start with " + "prefix '%s'" % (root, dirname, parentdir_prefix)) raise NotThisMethod("rootdir doesn't start with parentdir_prefix") - return {"version": dirname[len(parentdir_prefix):], - "full-revisionid": None, - "dirty": False, "error": None} + return { + "version": dirname[len(parentdir_prefix):], + "full-revisionid": None, + "dirty": False, + "error": None} @register_vcs_handler("git", "get_keywords") @@ -167,7 +172,7 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): # "stabilization", as well as "HEAD" and "master". tags = set([r for r in refs if re.search(r'\d', r)]) if verbose: - print("discarding '%s', no digits" % ",".join(refs-tags)) + print("discarding '%s', no digits" % ",".join(refs - tags)) if verbose: print("likely tags: %s" % ",".join(sorted(tags))) for ref in sorted(tags): @@ -176,16 +181,19 @@ def git_versions_from_keywords(keywords, tag_prefix, verbose): r = ref[len(tag_prefix):] if verbose: print("picking %s" % r) - return {"version": r, - "full-revisionid": keywords["full"].strip(), - "dirty": False, "error": None - } + return { + "version": r, + "full-revisionid": keywords["full"].strip(), + "dirty": False, + "error": None} # no suitable tags, so version is "0+unknown", but full hex is still there if verbose: print("no suitable tags, using unknown + full revision id") - return {"version": "0+unknown", - "full-revisionid": keywords["full"].strip(), - "dirty": False, "error": "no suitable tags"} + return { + "version": "0+unknown", + "full-revisionid": keywords["full"].strip(), + "dirty": False, + "error": "no suitable tags"} @register_vcs_handler("git", "pieces_from_vcs") @@ -206,10 +214,11 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): GITS = ["git.cmd", "git.exe"] # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] # if there isn't one, this yields HEX[-dirty] (no NUM) - describe_out = run_command(GITS, ["describe", "--tags", "--dirty", - "--always", "--long", - "--match", "%s*" % tag_prefix], - cwd=root) + describe_out = run_command( + GITS, [ + "describe", "--tags", "--dirty", "--always", "--long", "--match", + "%s*" % tag_prefix], + cwd=root) # --long was added in git-1.5.5 if describe_out is None: raise NotThisMethod("'git describe' failed") @@ -241,8 +250,8 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) if not mo: # unparseable. Maybe git-describe is misbehaving? - pieces["error"] = ("unable to parse git-describe output: '%s'" - % describe_out) + pieces["error"] = ( + "unable to parse git-describe output: '%s'" % describe_out) return pieces # tag @@ -251,8 +260,9 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): if verbose: fmt = "tag '%s' doesn't start with prefix '%s'" print(fmt % (full_tag, tag_prefix)) - pieces["error"] = ("tag '%s' doesn't start with prefix '%s'" - % (full_tag, tag_prefix)) + pieces["error"] = ( + "tag '%s' doesn't start with prefix '%s'" % + (full_tag, tag_prefix)) return pieces pieces["closest-tag"] = full_tag[len(tag_prefix):] @@ -265,8 +275,8 @@ def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): else: # HEX: no tags pieces["closest-tag"] = None - count_out = run_command(GITS, ["rev-list", "HEAD", "--count"], - cwd=root) + count_out = run_command( + GITS, ["rev-list", "HEAD", "--count"], cwd=root) pieces["distance"] = int(count_out) # total number of commits return pieces @@ -297,8 +307,7 @@ def render_pep440(pieces): rendered += ".dirty" else: # exception #1 - rendered = "0+untagged.%d.g%s" % (pieces["distance"], - pieces["short"]) + rendered = "0+untagged.%d.g%s" % (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" return rendered @@ -412,10 +421,11 @@ def render_git_describe_long(pieces): def render(pieces, style): """Render the given version pieces into the requested style.""" if pieces["error"]: - return {"version": "unknown", - "full-revisionid": pieces.get("long"), - "dirty": None, - "error": pieces["error"]} + return { + "version": "unknown", + "full-revisionid": pieces.get("long"), + "dirty": None, + "error": pieces["error"]} if not style or style == "default": style = "pep440" # the default @@ -435,8 +445,11 @@ def render(pieces, style): else: raise ValueError("unknown style '%s'" % style) - return {"version": rendered, "full-revisionid": pieces["long"], - "dirty": pieces["dirty"], "error": None} + return { + "version": rendered, + "full-revisionid": pieces["long"], + "dirty": pieces["dirty"], + "error": None} def get_versions(): @@ -450,8 +463,8 @@ def get_versions(): verbose = cfg.verbose try: - return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, - verbose) + return git_versions_from_keywords( + get_keywords(), cfg.tag_prefix, verbose) except NotThisMethod: pass @@ -463,9 +476,11 @@ def get_versions(): for i in cfg.versionfile_source.split('/'): root = os.path.dirname(root) except NameError: - return {"version": "0+unknown", "full-revisionid": None, - "dirty": None, - "error": "unable to find root of source tree"} + return { + "version": "0+unknown", + "full-revisionid": None, + "dirty": None, + "error": "unable to find root of source tree"} try: pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose) @@ -479,6 +494,8 @@ def get_versions(): except NotThisMethod: pass - return {"version": "0+unknown", "full-revisionid": None, - "dirty": None, - "error": "unable to compute version"} + return { + "version": "0+unknown", + "full-revisionid": None, + "dirty": None, + "error": "unable to compute version"} diff --git a/crochet/tests/test_api.py b/crochet/tests/test_api.py index feb7849..a3b109b 100644 --- a/crochet/tests/test_api.py +++ b/crochet/tests/test_api.py @@ -19,6 +19,15 @@ from twisted.python.failure import Failure from twisted.python import threadable from twisted.python.runtime import platform + +from .._eventloop import ( + EventLoop, EventualResult, TimeoutError, ResultRegistry, ReactorStopped) +from .test_setup import FakeReactor +from .. import ( + _main, setup, in_reactor, retrieve_result, _store, no_setup, + run_in_reactor, wait_for_reactor, wait_for) +from ..tests import crochet_directory + if platform.type == "posix": try: from twisted.internet.process import reapAllProcesses @@ -33,24 +42,18 @@ # waitpid() is only necessary on POSIX: reapAllProcesses = None -from .._eventloop import (EventLoop, EventualResult, TimeoutError, - ResultRegistry, ReactorStopped) -from .test_setup import FakeReactor -from .. import (_main, setup, in_reactor, retrieve_result, _store, no_setup, - run_in_reactor, wait_for_reactor, wait_for) -from ..tests import crochet_directory - class ResultRegistryTests(TestCase): """ Tests for ResultRegistry. """ + def test_stopped_registered(self): """ ResultRegistery.stop() fires registered EventualResult with ReactorStopped. """ - registry = ResultRegistry(FakeReactor()) + registry = ResultRegistry() er = EventualResult(None, None) registry.register(er) registry.stop() @@ -61,7 +64,7 @@ def test_stopped_new_registration(self): After ResultRegistery.stop() is called subsequent register() calls raise ReactorStopped. """ - registry = ResultRegistry(FakeReactor()) + registry = ResultRegistry() er = EventualResult(None, None) registry.stop() self.assertRaises(ReactorStopped, registry.register, er) @@ -71,7 +74,7 @@ def test_stopped_already_have_result(self): ResultRegistery.stop() has no impact on registered EventualResult which already have a result. """ - registry = ResultRegistry(FakeReactor()) + registry = ResultRegistry() er = EventualResult(succeed(123), None) registry.register(er) registry.stop() @@ -84,7 +87,7 @@ def test_weakref(self): Registering an EventualResult with a ResultRegistry does not prevent it from being garbage collected. """ - registry = ResultRegistry(FakeReactor()) + registry = ResultRegistry() er = EventualResult(None, None) registry.register(er) ref = weakref.ref(er) @@ -111,6 +114,7 @@ def append_in_thread(l, f, *args, **kwargs): """ started = threading.Event() done = threading.Event() + def go(): started.set() try: @@ -120,6 +124,7 @@ def go(): else: l.extend([True, result]) done.set() + threading.Thread(target=go).start() started.wait() return done @@ -238,6 +243,7 @@ def test_cancel(self): """ reactor = FakeReactor() cancelled = [] + def error(f): cancelled.append(reactor.in_call_from_thread) cancelled.append(f) @@ -262,7 +268,7 @@ def test_original_failure(self): wrapped by the EventualResult. """ try: - 1/0 + 1 / 0 except: f = Failure() dr = EventualResult(fail(f), None) @@ -366,7 +372,7 @@ def interrupt(): except KeyboardInterrupt: sys.exit(23) """ - kw = { 'cwd': crochet_directory } + kw = {'cwd': crochet_directory} # on Windows the only way to interrupt a subprocess reliably is to # create a new process group: # http://docs.python.org/2/library/subprocess.html#subprocess.CREATE_NEW_PROCESS_GROUP @@ -481,8 +487,9 @@ def run(): else: sys.exit(3) """ - process = subprocess.Popen([sys.executable, "-c", program], - cwd=crochet_directory,) + process = subprocess.Popen( + [sys.executable, "-c", program], + cwd=crochet_directory, ) self.assertEqual(process.wait(), 23) def test_noWaitingDuringImport(self): @@ -497,15 +504,18 @@ def test_noWaitingDuringImport(self): """ if sys.version_info[0] > 2: from unittest import SkipTest - raise SkipTest("This test is too fragile (and insufficient) on " - "Python 3 - see " - "https://github.com/itamarst/crochet/issues/43") + raise SkipTest( + "This test is too fragile (and insufficient) on " + "Python 3 - see " + "https://github.com/itamarst/crochet/issues/43") directory = tempfile.mktemp() os.mkdir(directory) sys.path.append(directory) self.addCleanup(sys.path.remove, directory) - with open(os.path.join(directory, "shouldbeunimportable.py"), "w") as f: - f.write("""\ + with open(os.path.join(directory, "shouldbeunimportable.py"), + "w") as f: + f.write( + """\ from crochet import EventualResult from twisted.internet.defer import Deferred @@ -573,6 +583,7 @@ def test_name(self): @c.in_reactor def some_name(reactor): pass + self.assertEqual(some_name.__name__, "some_name") def test_in_reactor_thread(self): @@ -607,6 +618,7 @@ def wrapper(*args, **kwargs): result = function(*args, **kwargs) wrapped[0] = False return result + return wrapper myreactor = FakeReactor() @@ -614,7 +626,6 @@ def wrapper(*args, **kwargs): c.no_setup() c.run_in_reactor = fake_run_in_reactor - @c.in_reactor def func(reactor): self.assertTrue(wrapped[0]) @@ -629,6 +640,7 @@ class RunInReactorTests(TestCase): """ Tests for the run_in_reactor decorator. """ + def test_name(self): """ The function decorated with run_in_reactor has the same name as the @@ -639,6 +651,7 @@ def test_name(self): @c.run_in_reactor def some_name(): pass + self.assertEqual(some_name.__name__, "some_name") def test_run_in_reactor_thread(self): @@ -671,6 +684,7 @@ def make_wrapped_function(self): @c.run_in_reactor def passthrough(argument): return argument + return passthrough def test_deferred_success_result(self): @@ -715,7 +729,7 @@ def test_exception_result(self): @c.run_in_reactor def raiser(): - 1/0 + 1 / 0 result = raiser() self.assertIsInstance(result, EventualResult) @@ -742,8 +756,10 @@ def test_wrapped_function(self): `wrapped_function` attribute. """ c = EventLoop(lambda: None, lambda f, g: None) + def func(): pass + wrapper = c.run_in_reactor(func) self.assertIdentical(wrapper.wrapped_function, func) @@ -752,6 +768,7 @@ class WaitTestsMixin(object): """ Tests mixin for the wait_for_reactor/wait_for decorators. """ + def setUp(self): self.reactor = FakeReactor() self.eventloop = EventLoop(lambda: self.reactor, lambda f, g: None) @@ -770,11 +787,13 @@ def make_wrapped_function(self): its first argument, or raises it if it's an exception. """ decorator = self.decorator() + @decorator def passthrough(argument): if isinstance(argument, Exception): raise argument return argument + return passthrough def test_name(self): @@ -783,9 +802,11 @@ def test_name(self): original function. """ decorator = self.decorator() + @decorator def some_name(argument): pass + self.assertEqual(some_name.__name__, "some_name") def test_wrapped_function(self): @@ -794,8 +815,10 @@ def test_wrapped_function(self): `wrapped_function` attribute. """ decorator = self.decorator() + def func(): pass + wrapper = decorator(func) self.assertIdentical(wrapper.wrapped_function, func) @@ -914,8 +937,8 @@ def wait(): wait() except KeyboardInterrupt: sys.exit(23) -""" % (self.DECORATOR_CALL,) - kw = { 'cwd': crochet_directory } +""" % (self.DECORATOR_CALL, ) + kw = {'cwd': crochet_directory} if platform.type.startswith('win'): kw['creationflags'] = subprocess.CREATE_NEW_PROCESS_GROUP process = subprocess.Popen([sys.executable, "-c", program], **kw) @@ -944,7 +967,7 @@ def run(): er = run() except crochet.ReactorStopped: sys.exit(23) -""" % (self.DECORATOR_CALL,) +""" % (self.DECORATOR_CALL, ) process = subprocess.Popen([sys.executable, "-c", program], cwd=crochet_directory) self.assertEqual(process.wait(), 23) @@ -974,6 +997,7 @@ def test_timeoutRaises(self): If a function wrapped with wait_for hits the timeout, it raises TimeoutError. """ + @self.eventloop.wait_for(timeout=0.5) def times_out(): return Deferred().addErrback(lambda f: f.trap(CancelledError)) @@ -994,6 +1018,7 @@ def test_timeoutCancels(self): @self.eventloop.wait_for(timeout=0.0) def times_out(): return result + self.assertRaises(TimeoutError, times_out) self.assertIsInstance(error[0].value, CancelledError) @@ -1002,13 +1027,18 @@ class PublicAPITests(TestCase): """ Tests for the public API. """ + def test_no_sideeffects(self): """ Creating an EventLoop object, as is done in crochet.__init__, does not call any methods on the objects it is created with. """ - c = EventLoop(lambda: None, lambda f, g: 1/0, lambda *args: 1/0, - watchdog_thread=object(), reapAllProcesses=lambda: 1/0) + c = EventLoop( + lambda: None, + lambda f, g: 1 / 0, + lambda *args: 1 / 0, + watchdog_thread=object(), + reapAllProcesses=lambda: 1 / 0) del c def test_eventloop_api(self): @@ -1026,13 +1056,14 @@ def test_eventloop_api(self): self.assertEqual(_main.wait_for_reactor, wait_for_reactor) self.assertEqual(_main.wait_for, wait_for) self.assertIdentical(_main._atexit_register, _shutdown.register) - self.assertIdentical(_main._startLoggingWithObserver, - startLoggingWithObserver) + self.assertIdentical( + _main._startLoggingWithObserver, startLoggingWithObserver) self.assertIdentical(_main._watchdog_thread, _shutdown._watchdog) def test_eventloop_api_reactor(self): """ - The publicly exposed EventLoop will, when setup, use the global reactor. + The publicly exposed EventLoop will, when setup, use the global + reactor. """ from twisted.internet import reactor _main.no_setup() @@ -1052,6 +1083,7 @@ def test_reapAllProcesses(self): plaforms. """ self.assertIdentical(_main._reapAllProcesses, reapAllProcesses) + if platform.type != "posix": test_reapAllProcesses.skip = "Only relevant on POSIX platforms" if reapAllProcesses is None: diff --git a/crochet/tests/test_logging.py b/crochet/tests/test_logging.py index 9dc351b..641800e 100644 --- a/crochet/tests/test_logging.py +++ b/crochet/tests/test_logging.py @@ -17,6 +17,7 @@ class ThreadLogObserverTest(SynchronousTestCase): We use Twisted's SyncTestCase to ensure that unhandled logged errors get reported as errors, in particular for test_error. """ + def test_stop(self): """ ThreadLogObserver.stop() stops the thread started in __init__. @@ -33,6 +34,7 @@ def test_emit(self): the given message. """ messages = [] + def observer(msg): messages.append((threading.current_thread().ident, msg)) @@ -54,6 +56,7 @@ def test_errors(self): """ messages = [] counter = [] + def observer(msg): counter.append(1) if len(counter) == 2: @@ -80,7 +83,8 @@ def test_ioThreadUnchanged(self): threadLog = ThreadLogObserver(None) threadLog.stop() threadLog._thread.join() - self.assertIn(threadable.ioThread, - # Either reactor was never run, or run in thread running - # the tests: - (None, threading.current_thread().ident)) + self.assertIn( + threadable.ioThread, + # Either reactor was never run, or run in thread running + # the tests: + (None, threading.current_thread().ident)) diff --git a/crochet/tests/test_process.py b/crochet/tests/test_process.py index e4c305c..dbac593 100644 --- a/crochet/tests/test_process.py +++ b/crochet/tests/test_process.py @@ -10,10 +10,12 @@ from ..tests import crochet_directory + class ProcessTests(TestCase): """ Tests for process support. """ + def test_processExit(self): """ A Crochet-managed reactor notice when a process it started exits. @@ -61,5 +63,6 @@ def run(): stdout=subprocess.PIPE) result = process.stdout.read() self.assertEqual(result, b"abc") + if platform.type != "posix": test_processExit.skip = "SIGCHLD is a POSIX-specific issue" diff --git a/crochet/tests/test_setup.py b/crochet/tests/test_setup.py index 5bc7850..ef40950 100644 --- a/crochet/tests/test_setup.py +++ b/crochet/tests/test_setup.py @@ -72,7 +72,8 @@ def test_first_runs_reactor(self): EventLoop(lambda: reactor, lambda f, *g: None).setup() reactor.started.wait(5) self.assertNotEqual(reactor.thread_id, None) - self.assertNotEqual(reactor.thread_id, threading.current_thread().ident) + self.assertNotEqual( + reactor.thread_id, threading.current_thread().ident) self.assertFalse(reactor.installSignalHandlers) def test_second_does_nothing(self): @@ -93,19 +94,20 @@ def test_stop_on_exit(self): """ atexit = [] reactor = FakeReactor() - s = EventLoop(lambda: reactor, lambda f, *args: atexit.append((f, args))) + s = EventLoop( + lambda: reactor, lambda f, *args: atexit.append((f, args))) s.setup() self.assertEqual(len(atexit), 2) self.assertFalse(reactor.stopping) f, args = atexit[0] self.assertEqual(f, reactor.callFromThread) - self.assertEqual(args, (reactor.stop,)) + self.assertEqual(args, (reactor.stop, )) f(*args) self.assertTrue(reactor.stopping) f, args = atexit[1] self.assertEqual(f, _store.log_errors) self.assertEqual(args, ()) - f(*args) # make sure it doesn't throw an exception + f(*args) # make sure it doesn't throw an exception def test_runs_with_lock(self): """ @@ -120,6 +122,7 @@ def test_logging(self): ThreadLogObserver, removing the default log observer. """ logging = [] + def fakeStartLoggingWithObserver(observer, setStdout=1): self.assertIsInstance(observer, ThreadLogObserver) wrapped = observer._observer @@ -132,20 +135,23 @@ def fakeStartLoggingWithObserver(observer, setStdout=1): logging.append(observer) reactor = FakeReactor() - loop = EventLoop(lambda: reactor, lambda f, *g: None, - fakeStartLoggingWithObserver) + loop = EventLoop( + lambda: reactor, lambda f, *g: None, fakeStartLoggingWithObserver) loop.setup() self.assertTrue(logging) logging[0].stop() def test_stop_logging_on_exit(self): """ - setup() registers a reactor shutdown event that stops the logging thread. + setup() registers a reactor shutdown event that stops the logging + thread. """ observers = [] reactor = FakeReactor() - s = EventLoop(lambda: reactor, lambda f, *arg: None, - lambda observer, setStdout=1: observers.append(observer)) + s = EventLoop( + lambda: reactor, + lambda f, *arg: None, + lambda observer, setStdout=1: observers.append(observer)) s.setup() self.addCleanup(observers[0].stop) self.assertIn(("after", "shutdown", observers[0].stop), reactor.events) @@ -155,13 +161,15 @@ def test_warnings_untouched(self): setup() ensure the warnings module's showwarning is unmodified, overriding the change made by normal Twisted logging setup. """ + def fakeStartLoggingWithObserver(observer, setStdout=1): warnings.showwarning = log.showwarning self.addCleanup(observer.stop) + original = warnings.showwarning reactor = FakeReactor() - loop = EventLoop(lambda: reactor, lambda f, *g: None, - fakeStartLoggingWithObserver) + loop = EventLoop( + lambda: reactor, lambda f, *g: None, fakeStartLoggingWithObserver) loop.setup() self.assertIs(warnings.showwarning, original) @@ -171,8 +179,8 @@ def test_start_watchdog_thread(self): """ thread = FakeThread() reactor = FakeReactor() - loop = EventLoop(lambda: reactor, lambda *args: None, - watchdog_thread=thread) + loop = EventLoop( + lambda: reactor, lambda *args: None, watchdog_thread=thread) loop.setup() self.assertTrue(thread.started) @@ -185,9 +193,11 @@ def test_no_setup(self): atexit = [] thread = FakeThread() reactor = FakeReactor() - loop = EventLoop(lambda: reactor, lambda f, *arg: atexit.append(f), - lambda observer, *a, **kw: observers.append(observer), - watchdog_thread=thread) + loop = EventLoop( + lambda: reactor, + lambda f, *arg: atexit.append(f), + lambda observer, *a, **kw: observers.append(observer), + watchdog_thread=thread) loop.no_setup() loop.setup() @@ -213,9 +223,8 @@ def test_setup_registry_shutdown(self): reactor = FakeReactor() s = EventLoop(lambda: reactor, lambda f, *g: None) s.setup() - self.assertEqual(reactor.events, - [("before", "shutdown", s._registry.stop)]) - + self.assertEqual( + reactor.events, [("before", "shutdown", s._registry.stop)]) def test_no_setup_registry_shutdown(self): """ @@ -225,14 +234,15 @@ def test_no_setup_registry_shutdown(self): reactor = FakeReactor() s = EventLoop(lambda: reactor, lambda f, *g: None) s.no_setup() - self.assertEqual(reactor.events, - [("before", "shutdown", s._registry.stop)]) + self.assertEqual( + reactor.events, [("before", "shutdown", s._registry.stop)]) class ProcessSetupTests(TestCase): """ setup() enables support for IReactorProcess on POSIX plaforms. """ + def test_posix(self): """ On POSIX systems, setup() installs a LoopingCall that runs @@ -240,8 +250,10 @@ def test_posix(self): """ reactor = FakeReactor() reaps = [] - s = EventLoop(lambda: reactor, lambda f, *g: None, - reapAllProcesses=lambda: reaps.append(1)) + s = EventLoop( + lambda: reactor, + lambda f, *g: None, + reapAllProcesses=lambda: reaps.append(1)) s.setup() reactor.advance(0.1) self.assertEquals(reaps, [1]) @@ -249,6 +261,7 @@ def test_posix(self): self.assertEquals(reaps, [1, 1]) reactor.advance(0.1) self.assertEquals(reaps, [1, 1, 1]) + if platform.type != "posix": test_posix.skip = "SIGCHLD is a POSIX-specific issue" @@ -273,6 +286,7 @@ class ReactorImportTests(TestCase): doesn't work if reactor is imported (https://twistedmatrix.com/trac/ticket/7105). """ + def test_crochet_import_no_reactor(self): """ Importing crochet should not import the reactor. @@ -306,6 +320,7 @@ def test_crochet_import_no_reactor(self): log.msg("log-error", isError=True) """ + class LoggingTests(TestCase): """ End-to-end tests for Twisted->stdlib logging bridge. @@ -320,10 +335,12 @@ def test_old_logging(self): if tuple(map(int, twisted.__version__.split("."))) >= (15, 2, 0): raise SkipTest("This test is for Twisted < 15.2.") - program = LOGGING_PROGRAM % ("",) + program = LOGGING_PROGRAM % ("", ) output = subprocess.check_output([sys.executable, "-u", "-c", program], cwd=crochet_directory) - self.assertTrue(output.startswith("""\ + self.assertTrue( + output.startswith( + """\ INFO Log opened. INFO log-info ERROR log-error @@ -331,13 +348,14 @@ def test_old_logging(self): def test_new_logging(self): """ - Messages from both new and old Twisted logging APIs are emitted to Python - standard library logging. + Messages from both new and old Twisted logging APIs are emitted to + Python standard library logging. """ if tuple(map(int, twisted.__version__.split("."))) < (15, 2, 0): raise SkipTest("This test is for Twisted 15.2 and later.") - program = LOGGING_PROGRAM % ("""\ + program = LOGGING_PROGRAM % ( + """\ from twisted.logger import Logger l2 = Logger() import time @@ -346,10 +364,11 @@ def test_new_logging(self): l2.critical("logger-critical") l2.warn("logger-warning") l2.debug("logger-debug") -""",) +""", ) output = subprocess.check_output([sys.executable, "-u", "-c", program], cwd=crochet_directory) - self.assertIn("""\ + self.assertIn( + """\ INFO logger-info CRITICAL logger-critical WARNING logger-warning @@ -357,4 +376,3 @@ def test_new_logging(self): INFO log-info CRITICAL log-error """, output.decode("utf-8")) - diff --git a/crochet/tests/test_shutdown.py b/crochet/tests/test_shutdown.py index 81e7ec8..97eff49 100644 --- a/crochet/tests/test_shutdown.py +++ b/crochet/tests/test_shutdown.py @@ -10,8 +10,8 @@ from twisted.trial.unittest import TestCase -from crochet._shutdown import (Watchdog, FunctionRegistry, _watchdog, register, - _registry) +from crochet._shutdown import ( + Watchdog, FunctionRegistry, _watchdog, register, _registry) from ..tests import crochet_directory @@ -19,6 +19,7 @@ class ShutdownTests(TestCase): """ Tests for shutdown registration. """ + def test_shutdown(self): """ A function registered with _shutdown.register() is called when the @@ -116,7 +117,7 @@ def test_log_errors(self): result = [] registry = FunctionRegistry() registry.register(lambda: result.append(2)) - registry.register(lambda: 1/0) + registry.register(lambda: 1 / 0) registry.register(lambda: result.append(1)) registry.run() self.assertEqual(result, [1, 2]) diff --git a/crochet/tests/test_util.py b/crochet/tests/test_util.py index c22ac41..fddffa5 100644 --- a/crochet/tests/test_util.py +++ b/crochet/tests/test_util.py @@ -11,8 +11,10 @@ class FakeLock(object): locked = False + def __enter__(self): self.locked = True + def __exit__(self, type, value, traceback): self.locked = False @@ -38,6 +40,7 @@ class SynchronizedTests(TestCase): """ Tests for the synchronized decorator. """ + def test_return(self): """ A method wrapped with @synchronized is called with the lock acquired,