Return-Path: X-Original-To: archive-asf-public-internal@cust-asf2.ponee.io Delivered-To: archive-asf-public-internal@cust-asf2.ponee.io Received: from cust-asf.ponee.io (cust-asf.ponee.io [163.172.22.183]) by cust-asf2.ponee.io (Postfix) with ESMTP id 8549F200CB0 for ; Fri, 9 Jun 2017 03:25:30 +0200 (CEST) Received: by cust-asf.ponee.io (Postfix) id 83FFF160BE5; Fri, 9 Jun 2017 01:25:30 +0000 (UTC) Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by cust-asf.ponee.io (Postfix) with SMTP id 29F5D160BFA for ; Fri, 9 Jun 2017 03:25:28 +0200 (CEST) Received: (qmail 24088 invoked by uid 500); 9 Jun 2017 01:25:27 -0000 Mailing-List: contact commits-help@qpid.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@qpid.apache.org Delivered-To: mailing list commits@qpid.apache.org Received: (qmail 22938 invoked by uid 99); 9 Jun 2017 01:25:26 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 09 Jun 2017 01:25:26 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id 73AE0F4A5A; Fri, 9 Jun 2017 01:25:25 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: aconway@apache.org To: commits@qpid.apache.org Date: Fri, 09 Jun 2017 01:26:02 -0000 Message-Id: In-Reply-To: <84083d2e38454e98bc64db339a7349a8@git.apache.org> References: <84083d2e38454e98bc64db339a7349a8@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [39/50] [abbrv] qpid-proton git commit: PROTON-1495: move examples/exampletest.py to tools/py/proctest.py archived-at: Fri, 09 Jun 2017 01:25:30 -0000 PROTON-1495: move examples/exampletest.py to tools/py/proctest.py It is not an example but part of our test framework. Want to use it for non-example tests as well. Project: http://git-wip-us.apache.org/repos/asf/qpid-proton/repo Commit: http://git-wip-us.apache.org/repos/asf/qpid-proton/commit/08505261 Tree: http://git-wip-us.apache.org/repos/asf/qpid-proton/tree/08505261 Diff: http://git-wip-us.apache.org/repos/asf/qpid-proton/diff/08505261 Branch: refs/heads/go1 Commit: 08505261f4008c43b2399cb4a72bd887bbadd7df Parents: d480600 Author: Alan Conway Authored: Fri Jun 2 09:33:43 2017 -0400 Committer: Alan Conway Committed: Wed Jun 7 09:59:23 2017 -0400 ---------------------------------------------------------------------- config.sh.in | 2 +- examples/CMakeLists.txt | 4 +- examples/c/proactor/example_test.py | 4 +- examples/cpp/example_test.py | 4 +- examples/exampletest.py | 193 ----------------------------- tools/py/proctest.py | 204 +++++++++++++++++++++++++++++++ 6 files changed, 211 insertions(+), 200 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/08505261/config.sh.in ---------------------------------------------------------------------- diff --git a/config.sh.in b/config.sh.in index 72d4ea9..d9debd3 100755 --- a/config.sh.in +++ b/config.sh.in @@ -39,7 +39,7 @@ RUBY_BINDINGS=$PROTON_BINDINGS/ruby PERL_BINDINGS=$PROTON_BINDINGS/perl # Python -COMMON_PYPATH=$PROTON_HOME/tests/python:$PROTON_HOME/proton-c/bindings/python:$PROTON_HOME/examples +COMMON_PYPATH=$PROTON_HOME/tests/python:$PROTON_HOME/proton-c/bindings/python:$PROTON_HOME/tools/py export PYTHONPATH=$COMMON_PYPATH:$PYTHON_BINDINGS # PHP http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/08505261/examples/CMakeLists.txt ---------------------------------------------------------------------- diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 4d744d2..8a8327a 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -29,8 +29,8 @@ macro(set_search_path result) # args after result are directories or search pat file(TO_NATIVE_PATH "${${result}}" ${result}) # native slash separators endmacro() -# Some non-python examples use exampletest.py to drive their self-tests. -set_search_path(EXAMPLE_PYTHONPATH "${CMAKE_CURRENT_SOURCE_DIR}" "$ENV{PYTHON_PATH}") +# Add the tools directory for the 'proctest' module +set_search_path(EXAMPLE_PYTHONPATH "${CMAKE_SOURCE_DIR}/tools/py" "$ENV{PYTHON_PATH}") set(EXAMPLE_ENV "PYTHONPATH=${EXAMPLE_PYTHONPATH}") add_subdirectory(c) http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/08505261/examples/c/proactor/example_test.py ---------------------------------------------------------------------- diff --git a/examples/c/proactor/example_test.py b/examples/c/proactor/example_test.py index 38f7fc8..02bb1fd 100644 --- a/examples/c/proactor/example_test.py +++ b/examples/c/proactor/example_test.py @@ -20,7 +20,7 @@ # This is a test script to run the examples and verify that they behave as expected. import unittest, sys, time -from exampletest import * +from proctest import * def python_cmd(name): dir = os.path.dirname(__file__) @@ -49,7 +49,7 @@ class Broker(object): raise ProcError(b, "broker crash") b.kill() -class CExampleTest(ExampleTestCase): +class CExampleTest(ProcTestCase): def test_send_receive(self): """Send first then receive""" http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/08505261/examples/cpp/example_test.py ---------------------------------------------------------------------- diff --git a/examples/cpp/example_test.py b/examples/cpp/example_test.py index 5773142..0ae929c 100644 --- a/examples/cpp/example_test.py +++ b/examples/cpp/example_test.py @@ -21,7 +21,7 @@ import unittest import os, sys, socket, time, re, inspect -from exampletest import * +from proctest import * from random import randrange from subprocess import Popen, PIPE, STDOUT, call from copy import copy @@ -82,7 +82,7 @@ def ensureCanTestExtendedSASL(): raise Skipped("Can't Test Extended SASL: Couldn't create auth db") -class BrokerTestCase(ExampleTestCase): +class BrokerTestCase(ProcTestCase): """ ExampleTest that starts a broker in setUpClass and kills it in tearDownClass. Subclasses must set `broker_exe` class variable with the name of the broker executable. http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/08505261/examples/exampletest.py ---------------------------------------------------------------------- diff --git a/examples/exampletest.py b/examples/exampletest.py deleted file mode 100644 index 5c53616..0000000 --- a/examples/exampletest.py +++ /dev/null @@ -1,193 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License -# - -# A test library to make it easy to run unittest tests that start, -# monitor, and report output from sub-processes. In particular -# it helps with starting processes that listen on random ports. - -import unittest -import os, sys, socket, time, re, inspect, errno, threading -from random import randrange -from subprocess import Popen, PIPE, STDOUT -from copy import copy -import platform -from os.path import dirname as dirname - -DEFAULT_TIMEOUT=10 - -class TestPort(object): - """Get an unused port using bind(0) and SO_REUSEADDR and hold it till close()""" - def __init__(self): - self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - self.sock.bind(('127.0.0.1', 0)) # Testing exampless is local only - self.host, self.port = socket.getnameinfo(self.sock.getsockname(), 0) - self.addr = "%s:%s" % (self.host, self.port) - - def __enter__(self): - return self - - def __exit__(self, *args): - self.close() - - def close(self): - self.sock.close() - -class ProcError(Exception): - """An exception that captures failed process output""" - def __init__(self, proc, what="bad exit status"): - out = proc.out.strip() - if out: - out = "\nvvvvvvvvvvvvvvvv\n%s\n^^^^^^^^^^^^^^^^\n" % out - else: - out = ", no output)" - super(Exception, self, ).__init__( - "%s %s, code=%s%s" % (proc.args, what, getattr(proc, 'returncode', 'noreturn'), out)) - -class NotFoundError(ProcError): - pass - -class Proc(Popen): - """A example process that stores its stdout and can scan it for a 'ready' pattern'""" - - if "VALGRIND" in os.environ and os.environ["VALGRIND"]: - vg_args = [os.environ["VALGRIND"], "--error-exitcode=42", "--quiet", "--leak-check=full"] - else: - vg_args = [] - - @property - def out(self): - self._out.seek(0) - # Normalize line endings, os.tmpfile() opens in binary mode. - return self._out.read().replace('\r\n','\n').replace('\r','\n') - - def __init__(self, args, skip_valgrind=False, **kwargs): - """Start an example process""" - args = list(args) - if skip_valgrind: - self.args = args - else: - self.args = self.vg_args + args - self.kwargs = kwargs - self._out = os.tmpfile() - try: - Popen.__init__(self, self.args, stdout=self._out, stderr=STDOUT, **kwargs) - except OSError, e: - if e.errno == errno.ENOENT: - raise NotFoundError(self, str(e)) - raise ProcError(self, str(e)) - except Exception, e: - raise ProcError(self, str(e)) - - def kill(self): - try: - if self.poll() is None: - Popen.kill(self) - except: - pass # Already exited. - return self.out - - def wait_exit(self, timeout=DEFAULT_TIMEOUT, expect=0): - """Wait for process to exit, return output. Raise ProcError on failure.""" - t = threading.Thread(target=self.wait) - t.start() - t.join(timeout) - if self.poll() is None: # Still running - self.kill() - raise ProcError(self, "still running after %ss" % timeout) - if expect is not None and self.poll() != expect: - raise ProcError(self) - return self.out - - def wait_re(self, regexp, timeout=DEFAULT_TIMEOUT): - """ - Wait for regexp to appear in the output, returns the re.search match result. - The target process should flush() important output to ensure it appears. - """ - if timeout: - deadline = time.time() + timeout - while timeout is None or time.time() < deadline: - match = re.search(regexp, self.out) - if match: - return match - time.sleep(0.01) # Not very efficient - raise ProcError(self, "gave up waiting for '%s' after %ss" % (regexp, timeout)) - -def _tc_missing(attr): - return not hasattr(unittest.TestCase, attr) - -class TestCase(unittest.TestCase): - """ - Roughly provides setUpClass() and tearDownClass() and other features missing - in python 2.6. If subclasses override setUp() or tearDown() they *must* - call the superclass. - """ - - if _tc_missing('setUpClass') and _tc_missing('tearDownClass'): - - @classmethod - def setUpClass(cls): - pass - - @classmethod - def tearDownClass(cls): - pass - - def setUp(self): - super(TestCase, self).setUp() - cls = type(self) - if not hasattr(cls, '_setup_class_count'): # First time - def is_test(m): - return inspect.ismethod(m) and m.__name__.startswith('test_') - cls._setup_class_count = len(inspect.getmembers(cls, predicate=is_test)) - cls.setUpClass() - - def tearDown(self): - self.assertTrue(self._setup_class_count > 0) - self._setup_class_count -= 1 - if self._setup_class_count == 0: - type(self).tearDownClass() - super(TestCase, self).tearDown() - - if _tc_missing('assertIn'): - def assertIn(self, a, b): - self.assertTrue(a in b, "%r not in %r" % (a, b)) - - if _tc_missing('assertMultiLineEqual'): - def assertMultiLineEqual(self, a, b): - self.assertEqual(a, b) - -class ExampleTestCase(TestCase): - """TestCase that manages started processes""" - def setUp(self): - super(ExampleTestCase, self).setUp() - self.procs = [] - - def tearDown(self): - for p in self.procs: - p.kill() - super(ExampleTestCase, self).tearDown() - - def proc(self, *args, **kwargs): - p = Proc(*args, **kwargs) - self.procs.append(p) - return p - -if __name__ == "__main__": - unittest.main() http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/08505261/tools/py/proctest.py ---------------------------------------------------------------------- diff --git a/tools/py/proctest.py b/tools/py/proctest.py new file mode 100644 index 0000000..ba83c7d --- /dev/null +++ b/tools/py/proctest.py @@ -0,0 +1,204 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License +# + +"""Unit test library to simplify tests that start, monitor, check and report +output from sub-processes. Provides safe port allocation for processes that +listen on a port. Allows executables to be run under a debugging tool like +valgrind. +""" + +import unittest +import os, sys, socket, time, re, inspect, errno, threading +from random import randrange +from subprocess import Popen, PIPE, STDOUT +from copy import copy +import platform +from os.path import dirname as dirname + +DEFAULT_TIMEOUT=10 + +class TestPort(object): + """Get an unused port using bind(0) and SO_REUSEADDR and hold it till close() + Can be used as `with TestPort() as tp:` Provides tp.host, tp.port and tp.addr + (a "host:port" string) + """ + def __init__(self): + self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self.sock.bind(('127.0.0.1', 0)) # Testing exampless is local only + self.host, self.port = socket.getnameinfo(self.sock.getsockname(), 0) + self.addr = "%s:%s" % (self.host, self.port) + + def __enter__(self): + return self + + def __exit__(self, *args): + self.close() + + def close(self): + self.sock.close() + +class ProcError(Exception): + """An exception that displays failed process output""" + def __init__(self, proc, what="bad exit status"): + self.out = proc.out.strip() + if self.out: + msgtail = "\nvvvvvvvvvvvvvvvv\n%s\n^^^^^^^^^^^^^^^^\n" % self.out + else: + msgtail = ", no output" + super(Exception, self, ).__init__( + "%s %s, code=%s%s" % (proc.args, what, getattr(proc, 'returncode', 'noreturn'), msgtail)) + +class NotFoundError(ProcError): + pass + +class Proc(Popen): + """Subclass of suprocess.Popen that stores its output and can scan it for a + 'ready' pattern' Use self.out to access output (combined stdout and stderr). + You can't set the Popen stdout and stderr arguments, they will be overwritten. + """ + + if "VALGRIND" in os.environ and os.environ["VALGRIND"]: + vg_args = [os.environ["VALGRIND"], "--error-exitcode=42", "--quiet", "--leak-check=full"] + else: + vg_args = [] + + @property + def out(self): + self._out.seek(0) + # Normalize line endings, os.tmpfile() opens in binary mode. + return self._out.read().replace('\r\n','\n').replace('\r','\n') + + def __init__(self, args, skip_valgrind=False, **kwargs): + """Start an example process""" + args = list(args) + if skip_valgrind: + self.args = args + else: + self.args = self.vg_args + args + self.kwargs = kwargs + self._out = os.tmpfile() + try: + Popen.__init__(self, self.args, stdout=self._out, stderr=STDOUT, **kwargs) + except OSError, e: + if e.errno == errno.ENOENT: + raise NotFoundError(self, str(e)) + raise ProcError(self, str(e)) + except Exception, e: + raise ProcError(self, str(e)) + + def kill(self): + try: + if self.poll() is None: + Popen.kill(self) + except: + pass # Already exited. + return self.out + + def wait_exit(self, timeout=DEFAULT_TIMEOUT, expect=0): + """Wait for process to exit, return output. Raise ProcError on failure.""" + t = threading.Thread(target=self.wait) + t.start() + t.join(timeout) + if self.poll() is None: # Still running + self.kill() + raise ProcError(self, "still running after %ss" % timeout) + if expect is not None and self.poll() != expect: + raise ProcError(self) + return self.out + + def wait_re(self, regexp, timeout=DEFAULT_TIMEOUT): + """ + Wait for regexp to appear in the output, returns the re.search match result. + The target process should flush() important output to ensure it appears. + """ + if timeout: + deadline = time.time() + timeout + while timeout is None or time.time() < deadline: + match = re.search(regexp, self.out) + if match: + return match + if self.poll() is not None: + raise ProcError(self, "process exited while waiting for '%s'" % (regexp)) + time.sleep(0.01) # Not very efficient + raise ProcError(self, "gave up waiting for '%s' after %ss" % (regexp, timeout)) + +def _tc_missing(attr): + return not hasattr(unittest.TestCase, attr) + +class ProcTestCase(unittest.TestCase): + """TestCase that manages started processes + + Also roughly provides setUpClass() and tearDownClass() and other features + missing in python 2.6. If subclasses override setUp() or tearDown() they + *must* call the superclass. + """ + + def setUp(self): + super(ProcTestCase, self).setUp() + self.procs = [] + + def tearDown(self): + for p in self.procs: + p.kill() + super(ProcTestCase, self).tearDown() + + def proc(self, *args, **kwargs): + """Return a Proc() that will be automatically killed on teardown""" + p = Proc(*args, **kwargs) + self.procs.append(p) + return p + + if _tc_missing('setUpClass') and _tc_missing('tearDownClass'): + + @classmethod + def setUpClass(cls): + pass + + @classmethod + def tearDownClass(cls): + pass + + def setUp(self): + super(ProcTestCase, self).setUp() + cls = type(self) + if not hasattr(cls, '_setup_class_count'): # First time + def is_test(m): + return inspect.ismethod(m) and m.__name__.startswith('test_') + cls._setup_class_count = len(inspect.getmembers(cls, predicate=is_test)) + cls.setUpClass() + + def tearDown(self): + self.assertTrue(self._setup_class_count > 0) + self._setup_class_count -= 1 + if self._setup_class_count == 0: + type(self).tearDownClass() + super(ProcTestCase, self).tearDown() + + if _tc_missing('assertIn'): + def assertIn(self, a, b): + self.assertTrue(a in b, "%r not in %r" % (a, b)) + + if _tc_missing('assertMultiLineEqual'): + def assertMultiLineEqual(self, a, b): + self.assertEqual(a, b) + +from unittest import main +if __name__ == "__main__": + main() --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org For additional commands, e-mail: commits-help@qpid.apache.org