Hello community, here is the log from the commit of package python-testflo for openSUSE:Factory checked in at 2020-03-11 18:54:39 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-testflo (Old) and /work/SRC/openSUSE:Factory/.python-testflo.new.3160 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-testflo" Wed Mar 11 18:54:39 2020 rev:5 rq:783804 version:1.4.1 Changes: -------- --- /work/SRC/openSUSE:Factory/python-testflo/python-testflo.changes 2019-01-11 14:04:33.827855914 +0100 +++ /work/SRC/openSUSE:Factory/.python-testflo.new.3160/python-testflo.changes 2020-03-11 18:56:15.783699474 +0100 @@ -1,0 +2,8 @@ +Wed Mar 11 12:15:00 UTC 2020 - pgaj...@suse.com + +- version update to 1.4.1 + * bugfix +- deleted patches + - use_setuptools.patch (upstreamed) + +------------------------------------------------------------------- Old: ---- testflo-1.3.4.tar.gz use_setuptools.patch New: ---- testflo-1.4.1.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-testflo.spec ++++++ --- /var/tmp/diff_new_pack.Qz9OND/_old 2020-03-11 18:56:16.307699709 +0100 +++ /var/tmp/diff_new_pack.Qz9OND/_new 2020-03-11 18:56:16.311699710 +0100 @@ -1,7 +1,7 @@ # # spec file for package python-testflo # -# Copyright (c) 2019 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2020 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -18,32 +18,30 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} Name: python-testflo -Version: 1.3.4 +Version: 1.4.1 Release: 0 Summary: A flow-based testing framework License: Apache-2.0 Group: Development/Languages/Python -Url: https://github.com/OpenMDAO/testflo +URL: https://github.com/OpenMDAO/testflo Source: https://files.pythonhosted.org/packages/source/t/testflo/testflo-%{version}.tar.gz -# PATCH-FIX-OPENSUSE use_setuptools.patch -- some of the optional features we want need setuptools -Patch0: use_setuptools.patch BuildRequires: %{python_module setuptools} BuildRequires: %{python_module six} BuildRequires: fdupes BuildRequires: python-rpm-macros -# SECTION test requirements -BuildRequires: %{python_module coverage} -BuildRequires: %{python_module mpi4py} -BuildRequires: %{python_module psutil} -# /SECTION +Requires: python-setuptools Requires: python-six +Requires(post): update-alternatives +Requires(preun): update-alternatives Recommends: python-coverage Recommends: python-mpi4py Recommends: python-psutil BuildArch: noarch -Requires(post): update-alternatives -Requires(preun): update-alternatives - +# SECTION test requirements +BuildRequires: %{python_module coverage} +BuildRequires: %{python_module mpi4py} +BuildRequires: %{python_module psutil} +# /SECTION %python_subpackages %description @@ -55,7 +53,6 @@ %prep %setup -q -n testflo-%{version} -%patch0 -p1 %build %python_build ++++++ testflo-1.3.4.tar.gz -> testflo-1.4.1.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testflo-1.3.4/MANIFEST.in new/testflo-1.4.1/MANIFEST.in --- old/testflo-1.3.4/MANIFEST.in 1970-01-01 01:00:00.000000000 +0100 +++ new/testflo-1.4.1/MANIFEST.in 2020-01-16 17:15:46.000000000 +0100 @@ -0,0 +1,4 @@ +include README.md +include LICENSE.txt +include RELEASE_NOTES.txt +include DESIGN.txt \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testflo-1.3.4/PKG-INFO new/testflo-1.4.1/PKG-INFO --- old/testflo-1.3.4/PKG-INFO 2018-12-06 16:34:08.000000000 +0100 +++ new/testflo-1.4.1/PKG-INFO 2020-02-28 19:44:32.330716800 +0100 @@ -1,6 +1,6 @@ -Metadata-Version: 1.0 +Metadata-Version: 1.1 Name: testflo -Version: 1.3.4 +Version: 1.4.1 Summary: A simple flow-based testing framework Home-page: UNKNOWN Author: UNKNOWN @@ -68,3 +68,11 @@ running in a subprocess (MPI and isolated). Platform: UNKNOWN +Classifier: Development Status :: 4 - Beta +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Natural Language :: English +Classifier: Operating System :: MacOS :: MacOS X +Classifier: Operating System :: POSIX :: Linux +Classifier: Operating System :: Microsoft :: Windows +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: Implementation :: CPython diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testflo-1.3.4/README.md new/testflo-1.4.1/README.md --- old/testflo-1.3.4/README.md 2018-11-16 20:41:08.000000000 +0100 +++ new/testflo-1.4.1/README.md 2020-02-28 14:43:11.000000000 +0100 @@ -97,9 +97,7 @@ Ran 258 tests using 8 processes -Sum of test times: 00:00:6.09 Wall clock time: 00:00:1.82 -Speedup: 3.347731 ``` @@ -151,9 +149,7 @@ Ran 30 tests using 8 processes -Sum of test times: 00:00:1.24 Wall clock time: 00:00:1.17 -Speedup: 1.054168 ``` @@ -161,8 +157,8 @@ ------------------------------------- testflo is used to test OpenMDAO as part of its CI process, -so we run it nearly every day on linux, Windows and OS X under -python 2.7 and 3.5. +so we run it nearly every day on linux, Windows and OS X. It requires +python 3.5 or higher. You can install testflo directly from github using the following command: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testflo-1.3.4/RELEASE_NOTES.txt new/testflo-1.4.1/RELEASE_NOTES.txt --- old/testflo-1.3.4/RELEASE_NOTES.txt 2018-12-06 16:29:30.000000000 +0100 +++ new/testflo-1.4.1/RELEASE_NOTES.txt 2020-02-13 17:42:08.000000000 +0100 @@ -1,3 +1,15 @@ +testflo version 1.3.6 Release Notes +Feb 13, 2020 + +* add option to show skipped tests (even if not verbose) + +testflo version 1.3.5 Release Notes +Jan 6, 2020 + +* use setuptools +* filter out expected fails from failtests.in +* added msg when there are out-of-sync collective MPI calls +* require coverage <5.0 testflo version 1.3.4 Release Notes Dec 6, 2018 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testflo-1.3.4/setup.cfg new/testflo-1.4.1/setup.cfg --- old/testflo-1.3.4/setup.cfg 2018-11-16 19:02:04.000000000 +0100 +++ new/testflo-1.4.1/setup.cfg 2020-02-28 19:44:32.330716800 +0100 @@ -1,2 +1,7 @@ [metadata] description-file = README.md + +[egg_info] +tag_build = +tag_date = 0 + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testflo-1.3.4/setup.py new/testflo-1.4.1/setup.py --- old/testflo-1.3.4/setup.py 2018-11-30 18:53:36.000000000 +0100 +++ new/testflo-1.4.1/setup.py 2020-02-28 14:43:11.000000000 +0100 @@ -1,4 +1,4 @@ -from distutils.core import setup +from setuptools import setup import re @@ -71,10 +71,19 @@ takes longer than timeout. Only works for tests running in a subprocess (MPI and isolated). """, + classifiers=[ + 'Development Status :: 4 - Beta', + 'License :: OSI Approved :: Apache Software License', + 'Natural Language :: English', + 'Operating System :: MacOS :: MacOS X', + 'Operating System :: POSIX :: Linux', + 'Operating System :: Microsoft :: Windows', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: Implementation :: CPython', + ], license='Apache 2.0', install_requires=[ - 'six', - 'coverage' + 'coverage<5.0' ], packages=['testflo'], entry_points=""" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testflo-1.3.4/testflo/__init__.py new/testflo-1.4.1/testflo/__init__.py --- old/testflo-1.3.4/testflo/__init__.py 2018-12-06 16:28:46.000000000 +0100 +++ new/testflo-1.4.1/testflo/__init__.py 2020-02-28 19:42:36.000000000 +0100 @@ -1 +1 @@ -__version__ = '1.3.4' +__version__ = '1.4.1' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testflo-1.3.4/testflo/benchmark.py new/testflo-1.4.1/testflo/benchmark.py --- old/testflo-1.3.4/testflo/benchmark.py 2017-05-30 16:08:07.000000000 +0200 +++ new/testflo-1.4.1/testflo/benchmark.py 2020-02-28 14:43:11.000000000 +0100 @@ -23,8 +23,8 @@ result.status, result.elapsed(), result.memory_usage, - result.load1m, - result.load5m, - result.load15m + result.load[0], + result.load[1], + result.load[2] )) stream.flush() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testflo-1.3.4/testflo/discover.py new/testflo-1.4.1/testflo/discover.py --- old/testflo-1.3.4/testflo/discover.py 2018-11-16 19:02:04.000000000 +0100 +++ new/testflo-1.4.1/testflo/discover.py 2020-02-28 14:43:11.000000000 +0100 @@ -1,8 +1,8 @@ import traceback from inspect import getmembers, isclass, isfunction +from importlib import import_module from unittest import TestCase -import six from os.path import basename, dirname, isdir @@ -21,9 +21,10 @@ class TestDiscoverer(object): - def __init__(self, module_pattern=six.text_type('test*.py'), + def __init__(self, options, module_pattern='test*.py', func_match=lambda f: fnmatchcase(f, 'test*'), dir_exclude=None): + self.options = options self.module_pattern = module_pattern self.func_match = func_match self.dir_exclude = dir_exclude @@ -54,7 +55,7 @@ yield result # Every test left has been group together either by module or - # TestCase or both, due to the presense of module or testcase class level + # TestCase or both, due to the presence of module or testcase class level # setup/teardown, and we need to run each group on the same # process so that we can execute the module or class level setup/teardown # only once while impacting all of the tests in that group. @@ -68,7 +69,7 @@ tests[-1]._tcase_fixture_last = True # check to see if this TestCase is part of a module with setUpModule/tearDownModule - if tests[0].mod in self._mod_fixture_groups: + if tests[0].modpath in self._mod_fixture_groups: # these tests are already part of a module fixture, so we # don't want to execute them a second time continue @@ -95,18 +96,24 @@ and/or part of a TestCase with setUpClass/tearDownClass, then save it for later, else return it. """ + if test.status is not None: + return test - if test.mod in self._mod_fixture_groups: - self._mod_fixture_groups[test.mod].append(test) - elif hasattr(test.mod, 'setUpModule') or hasattr(test.mod, 'tearDownModule'): - self._mod_fixture_groups[test.mod] = [test] - - if test.tcase in self._tcase_fixture_groups: - self._tcase_fixture_groups[test.tcase].append(test) - elif _has_class_fixture(test.tcase): - self._tcase_fixture_groups[test.tcase] = [test] + mod = import_module(test.modpath) + if test.modpath in self._mod_fixture_groups: + self._mod_fixture_groups[test.modpath].append(test) + elif hasattr(mod, 'setUpModule') or hasattr(mod, 'tearDownModule'): + self._mod_fixture_groups[test.modpath] = [test] + + full_tcase = (test.modpath, test.tcasename) + testcase = getattr(mod, test.tcasename) if test.tcasename else None + if full_tcase in self._tcase_fixture_groups: + self._tcase_fixture_groups[full_tcase].append(test) + elif _has_class_fixture(testcase): + self._tcase_fixture_groups[full_tcase] = [test] - if not (test.mod in self._mod_fixture_groups or test.tcase in self._tcase_fixture_groups): + if not (test.modpath in self._mod_fixture_groups or + full_tcase in self._tcase_fixture_groups): return test def _dir_iter(self, dname): @@ -116,7 +123,7 @@ """ for f in find_files(dname, match=self.module_pattern, direxclude=self.dir_exclude): - if not basename(f).startswith(six.text_type('__init__.')): + if not basename(f).startswith('__init__.'): for result in self._module_iter(f): yield result @@ -128,9 +135,12 @@ try: fname, mod = get_module(filename) except: - yield Test(filename, 'FAIL', err_msg=traceback.format_exc()) + t = Test(filename, self.options) + t.status = 'FAIL' + t.err_msg=traceback.format_exc() + yield t else: - if basename(fname).startswith(six.text_type('__init__.')): + if basename(fname).startswith('__init__.'): for result in self._dir_iter(dirname(fname)): yield result else: @@ -140,7 +150,7 @@ yield result elif isfunction(obj) and self.func_match(name): - yield Test(':'.join((filename, obj.__name__))) + yield Test(':'.join((filename, obj.__name__)), self.options) def _testcase_iter(self, fname, testcase): """Returns an iterator of Test objects coming from a given @@ -149,7 +159,7 @@ tcname = ':'.join((fname, testcase.__name__)) for name, method in getmembers(testcase, ismethod): if self.func_match(name): - yield Test('.'.join((tcname, method.__name__))) + yield Test('.'.join((tcname, method.__name__)), self.options) def _testspec_iter(self, testspec): """Returns an iterator of Test objects found in the @@ -168,17 +178,22 @@ if rest: tcasename, _, method = rest.partition('.') if method: - yield Test(testspec) + yield Test(testspec, self.options) else: # could be a test function or a TestCase try: fname, mod = get_module(module) except: - yield Test(testspec, 'FAIL', err_msg=traceback.format_exc()) + t = Test(testspec, self.options) + t.status = 'FAIL' + t.err_msg = traceback.format_exc() return try: tcase = get_testcase(fname, mod, tcasename) except (AttributeError, TypeError): - yield Test(testspec) + t = Test(testspec, self.options) + t.status = 'FAIL' + t.err_msg = traceback.format_exc() + yield t else: for test in self._testcase_iter(fname, tcase): yield test diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testflo-1.3.4/testflo/filters.py new/testflo-1.4.1/testflo/filters.py --- old/testflo-1.3.4/testflo/filters.py 2017-05-30 16:08:07.000000000 +0200 +++ new/testflo-1.4.1/testflo/filters.py 2020-02-13 16:44:22.000000000 +0100 @@ -29,6 +29,6 @@ def get_iter(self, input_iter): with open(self.outfile, 'w') as f: for result in input_iter: - if result.status == 'FAIL': + if result.status == 'FAIL' and not result.expected_fail: print(result.spec, file=f) yield result diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testflo-1.3.4/testflo/isolatedrun.py new/testflo-1.4.1/testflo/isolatedrun.py --- old/testflo-1.3.4/testflo/isolatedrun.py 2018-06-25 18:23:46.000000000 +0200 +++ new/testflo-1.4.1/testflo/isolatedrun.py 2020-02-28 19:42:36.000000000 +0100 @@ -12,13 +12,16 @@ from testflo.test import Test from testflo.cover import save_coverage from testflo.qman import get_client_queue + from testflo.options import get_options queue = get_client_queue() os.environ['TESTFLO_QUEUE'] = '' + options = get_options() + try: try: - test = Test(sys.argv[1]) + test = Test(sys.argv[1], options) test.nocapture = True # so we don't lose stdout test.run() except: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testflo-1.3.4/testflo/main.py new/testflo-1.4.1/testflo/main.py --- old/testflo-1.3.4/testflo/main.py 2018-11-16 19:02:04.000000000 +0100 +++ new/testflo-1.4.1/testflo/main.py 2020-02-28 14:43:11.000000000 +0100 @@ -26,8 +26,9 @@ import os import sys -import six import time +import warnings +import multiprocessing from fnmatch import fnmatch, fnmatchcase @@ -72,7 +73,7 @@ return_code = 0 - # iterate over the last iter in the pipline and we're done + # iterate over the last iter in the pipeline and we're done for result in iters[-1]: if result.status == 'FAIL' and not result.expected_fail: return_code = 1 @@ -85,6 +86,7 @@ args = sys.argv[1:] options = get_options(args) + nprocs = options.num_procs options.skip_dirs = [] @@ -104,6 +106,16 @@ if options.cfg: read_config_file(options.cfg, options) + if nprocs is None and options.num_procs is None: + try: + options.num_procs = multiprocessing.cpu_count() + except: + warnings.warn('CPU count could not be determined. Defaulting to 1') + options.num_procs = 1 + + if nprocs is not None: + options.num_procs = nprocs + tests = options.tests if options.testfile: tests += list(read_test_file(options.testfile)) @@ -136,12 +148,12 @@ if options.benchmark: options.num_procs = 1 options.isolated = True - discoverer = TestDiscoverer(module_pattern=six.text_type('benchmark*.py'), + discoverer = TestDiscoverer(options, module_pattern='benchmark*.py', func_match=lambda f: fnmatchcase(f, 'benchmark*'), dir_exclude=dir_exclude) benchmark_file = open(options.benchmarkfile, 'a') else: - discoverer = TestDiscoverer(dir_exclude=dir_exclude, + discoverer = TestDiscoverer(options, dir_exclude=dir_exclude, func_match=func_matcher) benchmark_file = open(os.devnull, 'a') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testflo-1.3.4/testflo/mpirun.py new/testflo-1.4.1/testflo/mpirun.py --- old/testflo-1.3.4/testflo/mpirun.py 2017-05-30 16:08:07.000000000 +0200 +++ new/testflo-1.4.1/testflo/mpirun.py 2020-02-28 14:43:11.000000000 +0100 @@ -22,12 +22,13 @@ queue = get_client_queue() os.environ['TESTFLO_QUEUE'] = '' - setup_coverage(get_options()) + options = get_options() + setup_coverage(options) try: try: comm = MPI.COMM_WORLD - test = Test(sys.argv[1]) + test = Test(sys.argv[1], options) test.nocapture = True # so we don't lose stdout test.run() except: @@ -38,7 +39,10 @@ # collect results results = comm.gather(test, root=0) if comm.rank == 0: - total_mem_usage = sum(r.memory_usage for r in results) + if not all([isinstance(r, Test) for r in results]): + print("\nNot all results gathered are Test objects. " + "You may have out-of-sync collective MPI calls.\n") + total_mem_usage = sum(r.memory_usage for r in results if isinstance(r, Test)) test.memory_usage = total_mem_usage # check for errors and record error message diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testflo-1.3.4/testflo/printer.py new/testflo-1.4.1/testflo/printer.py --- old/testflo-1.3.4/testflo/printer.py 2018-11-16 19:02:04.000000000 +0100 +++ new/testflo-1.4.1/testflo/printer.py 2020-02-28 14:43:11.000000000 +0100 @@ -2,9 +2,6 @@ import sys from testflo.util import elapsed_str -from testflo.options import get_options - -options = get_options() _result_map = { ('FAIL', False): 'F', diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testflo-1.3.4/testflo/qman.py new/testflo-1.4.1/testflo/qman.py --- old/testflo-1.3.4/testflo/qman.py 2017-05-30 16:08:07.000000000 +0200 +++ new/testflo-1.4.1/testflo/qman.py 2020-02-28 14:43:11.000000000 +0100 @@ -1,9 +1,7 @@ import os import sys -import multiprocessing import socket -from multiprocessing.managers import SyncManager, RebuildProxy, AutoProxy, Token # pickling the queue proxy gets rid of the authkey, so use a fixed authkey here @@ -11,6 +9,7 @@ _testflo_authkey = b'foobarxxxx' def get_server_queue(): + from multiprocessing.managers import SyncManager #FIXME: some OSX users were getting "Can't assign requested address" errors # if we use socket.gethostname() for the address. Changing it to # 'localhost' seems to fix the issue, but I don't know why. We had to @@ -27,6 +26,7 @@ return manager, manager.Queue() def get_client_queue(): + from multiprocessing.managers import RebuildProxy, AutoProxy, Token qstr = os.environ.get('TESTFLO_QUEUE') if qstr: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testflo-1.3.4/testflo/runner.py new/testflo-1.4.1/testflo/runner.py --- old/testflo-1.3.4/testflo/runner.py 2018-11-16 19:02:04.000000000 +0100 +++ new/testflo-1.4.1/testflo/runner.py 2020-02-28 14:43:11.000000000 +0100 @@ -6,13 +6,11 @@ import sys import os -from six import advance_iterator from multiprocessing import Queue, Process from testflo.cover import save_coverage from testflo.test import Test from testflo.options import get_options -from testflo.qman import get_client_queue def worker(test_queue, done_queue, subproc_queue, worker_id): @@ -107,7 +105,7 @@ numtests = 0 try: for proc in self.procs: - self.task_queue.put(advance_iterator(it)) + self.task_queue.put(next(it)) numtests += 1 except StopIteration: pass @@ -126,7 +124,7 @@ if stop: break numtests -= 1 - self.task_queue.put(advance_iterator(it)) + self.task_queue.put(next(it)) numtests += 1 except StopIteration: pass diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testflo-1.3.4/testflo/summary.py new/testflo-1.4.1/testflo/summary.py --- old/testflo-1.3.4/testflo/summary.py 2018-11-16 19:02:04.000000000 +0100 +++ new/testflo-1.4.1/testflo/summary.py 2020-02-13 17:42:08.000000000 +0100 @@ -48,7 +48,7 @@ yield test # now summarize the run - if skips and self.options.verbose: # only list skips in verbose mode + if skips and (self.options.verbose or self.options.show_skipped): write("\n\nThe following tests were skipped:\n") for s in sorted(skips): write(s) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testflo-1.3.4/testflo/test.py new/testflo-1.4.1/testflo/test.py --- old/testflo-1.3.4/testflo/test.py 2018-12-06 16:30:55.000000000 +0100 +++ new/testflo-1.4.1/testflo/test.py 2020-02-28 14:43:11.000000000 +0100 @@ -5,32 +5,21 @@ import time import traceback from inspect import isclass -from subprocess import Popen, PIPE +import subprocess from tempfile import mkstemp +from importlib import import_module from types import FunctionType, ModuleType -from six.moves import cStringIO -from six import PY2, PY3 +from io import StringIO from unittest import TestCase, SkipTest -if PY2: - from unittest.case import _ExpectedFailure, _UnexpectedSuccess -else: - from unittest.case import _UnexpectedSuccess +from unittest.case import _UnexpectedSuccess from testflo.cover import start_coverage, stop_coverage from testflo.util import get_module, ismethod, get_memory_usage, \ - _get_testflo_subproc_args + _options2args from testflo.devnull import DevNull -from testflo.options import get_options - -try: - from mpi4py import MPI -except ImportError: - MPI = None - -options = get_options() from distutils import spawn @@ -86,45 +75,31 @@ start/end times and resource usage data. """ - def __init__(self, testspec, status=None, err_msg=''): + def __init__(self, testspec, options): self.spec = testspec - self.status = status - self.err_msg = err_msg + self.options = options + self.test_dir = os.path.dirname(testspec.split(':',1)[0]) + + self.status = None + self.err_msg = '' + self.mpi = False + self.memory_usage = 0 self.nprocs = 0 + self.isolated = False self.start_time = 0 self.end_time = 0 - self.load1m = 0.0 - self.load5m = 0.0 - self.load15m = 0.0 - self.nocapture = options.nocapture - self.isolated = options.isolated - self.mpi = not options.nompi - self.timeout = options.timeout + self.modpath = None + self.tcasename = None + self.funcname = None + self.load = (0.0, 0.0, 0.0) self.expected_fail = False - self.test_dir = os.path.dirname(testspec.split(':',1)[0]) self._mod_fixture_first = False self._mod_fixture_last = False self._tcase_fixture_first = False self._tcase_fixture_last = False - if not err_msg: - with TestContext(self): - self.mod, self.tcase, self.funcname, self.nprocs, isolated = self._get_test_info() - if isolated: - self.isolated = isolated - else: - self.mod = self.tcase = self.funcname = None - - if self.err_msg: - self.start_time = self.end_time = time.time() - - def __getstate__(self): - """ Get rid of module and testcase so we don't pickle them. """ - state = self.__dict__.copy() - state['mod'] = None - state['tcase'] = None - return state + self._get_test_info() def __iter__(self): """Allows Test to be iterated over so we don't have to check later @@ -134,80 +109,52 @@ def _get_test_info(self): """Get the test's module, testcase (if any), function name, - N_PROCS (for mpi tests) and ISOLATED. + N_PROCS (for mpi tests) and ISOLATED and set our attributes. """ - parent = funcname = mod = testcase = None - nprocs = 0 - isolated = False - - try: - mod, testcase, funcname = _parse_test_path(self.spec) - except Exception: - self.status = 'FAIL' - self.err_msg = traceback.format_exc() - else: - if funcname is None: + with TestContext(self): + try: + mod, self.tcasename, self.funcname = _parse_test_path(self.spec) + self.modpath = mod.__name__ + except Exception: self.status = 'FAIL' - self.err_msg = 'ERROR: test function not specified.' + self.err_msg = traceback.format_exc() else: - if testcase is not None: - parent = testcase - nprocs = getattr(testcase, 'N_PROCS', 0) - isolated = getattr(testcase, 'ISOLATED', False) + if self.funcname is None: + self.status = 'FAIL' + self.err_msg = 'ERROR: test function not specified.' else: - parent = mod + if self.tcasename is not None: + testcase = getattr(mod, self.tcasename) + self.nprocs = getattr(testcase, 'N_PROCS', 0) + self.isolated = getattr(testcase, 'ISOLATED', False) - return mod, testcase, funcname, nprocs, isolated + if self.err_msg: + self.start_time = self.end_time = time.time() - def _run_sub(self, cmd, queue): + def _run_sub(self, cmd, queue, env): """ Run a command in a subprocess. """ try: add_queue_to_env(queue) - if self.nocapture: - out = sys.stdout + if self.options.nocapture: + stdout = subprocess.PIPE + stderr = subprocess.STDOUT else: - out = open(os.devnull, 'w') + stdout = subprocess.DEVNULL + stderr = subprocess.PIPE - errfd, tmperr = mkstemp() - err = os.fdopen(errfd, 'w') + p = subprocess.run(cmd, stdout=stdout, stderr=stderr, env=env, + timeout=self.options.timeout, universal_newlines=True) - p = Popen(cmd, stdout=out, stderr=err, env=os.environ, - universal_newlines=True) # text mode - count = 0 - timedout = False - - if self.timeout < 0.0: # infinite timeout - p.wait() - else: - poll_interval = 0.2 - while p.poll() is None: - if count * poll_interval > self.timeout: - p.terminate() - timedout = True - break - time.sleep(poll_interval) - count += 1 - - err.close() - - with open(tmperr, 'r') as f: - errmsg = f.read() - os.remove(tmperr) - - os.environ['TESTFLO_QUEUE'] = '' - - if timedout: - result = self + if p.returncode != 0: self.status = 'FAIL' - self.err_msg = 'TIMEOUT after %s sec. ' % self.timeout - if errmsg: - self.err_msg += errmsg + self.err_msg = p.stdout if self.options.nocapture else p.stderr + result = self else: - if p.returncode != 0: - print(errmsg) + if self.options.nocapture: + print(p.stdout) result = queue.get() except: # we generally shouldn't get here, but just in case, @@ -217,13 +164,6 @@ self.err_msg = traceback.format_exc() result = self - err.close() - finally: - if not self.nocapture: - out.close() - sys.stdout.flush() - sys.stderr.flush() - return result def _run_isolated(self, queue): @@ -236,7 +176,7 @@ self.spec] try: - result = self._run_sub(cmd, queue) + result = self._run_sub(cmd, queue, os.environ) except: # we generally shouldn't get here, but just in case, # handle it so that the main process doesn't hang at the @@ -253,17 +193,16 @@ """This runs the test using mpirun in a subprocess, then returns the Test object. """ - try: if mpirun_exe is None: raise Exception("mpirun or mpiexec was not found in the system path.") - cmd = [mpirun_exe, '-n', str(self.nprocs), + cmd = [mpirun_exe, '-n', str(self.nprocs), sys.executable, os.path.join(os.path.dirname(__file__), 'mpirun.py'), - self.spec] + _get_testflo_subproc_args() + self.spec] + _options2args() - result = self._run_sub(cmd, queue) + result = self._run_sub(cmd, queue, os.environ) except: # we generally shouldn't get here, but just in case, @@ -273,6 +212,8 @@ self.err_msg = traceback.format_exc() result = self + result.mpi = True + return result def run(self, queue=None): @@ -281,17 +222,22 @@ # premature failure occurred (or dry run), just return return self - if queue is not None: - if MPI is not None and self.mpi and self.nprocs > 0: + MPI = None + if queue is not None and self.nprocs > 0 and not self.options.nompi: + try: + from mpi4py import MPI + except ImportError: + pass + else: return self._run_mpi(queue) - elif self.isolated: - return self._run_isolated(queue) + elif self.options.isolated: + return self._run_isolated(queue) with TestContext(self): - if self.tcase is None: - mod, testcase, funcname, nprocs, _ = self._get_test_info() - else: - mod, testcase, funcname, nprocs = (self.mod, self.tcase, self.funcname, self.nprocs) + mod = import_module(self.modpath) + + testcase = getattr(mod, self.tcasename) if self.tcasename is not None else None + funcname, nprocs = (self.funcname, self.nprocs) mod_setup = mod_teardown = tcase_setup = tcase_teardown = None @@ -307,10 +253,10 @@ tcase_teardown = getattr(testcase, 'tearDownClass', None) parent = testcase(methodName=funcname) - # if we get here an nprocs > 0, we need + # if we get here and nprocs > 0, we need # to set .comm in our TestCase instance. if nprocs > 0: - if MPI is not None and self.mpi: + if MPI is not None and not self.options.nompi: parent.comm = MPI.COMM_WORLD else: parent.comm = FakeComm() @@ -321,11 +267,11 @@ parent = mod setup = teardown = None - if self.nocapture: + if self.options.nocapture: outstream = sys.stdout else: outstream = DevNull() - errstream = cStringIO() + errstream = StringIO() done = False expected = expected2 = expected3 = False @@ -388,9 +334,9 @@ self.expected_fail = expected or expected2 or expected3 if sys.platform == 'win32': - self.load1m, self.load5m, self.load15m = (0.0, 0.0, 0.0) + self.load = (0.0, 0.0, 0.0) else: - self.load1m, self.load5m, self.load15m = os.getloadavg() + self.load = os.getloadavg() finally: stop_coverage() @@ -433,7 +379,7 @@ indicates that that part of the testspec was not present. """ - testcase = funcname = None + testcase = funcname = tcasename = None testspec = testspec.strip() parts = testspec.split(':') if len(parts) > 1 and parts[1].startswith('\\'): # windows abs path @@ -451,6 +397,7 @@ objname, _, funcname = rest.partition('.') obj = getattr(mod, objname) if isclass(obj) and issubclass(obj, TestCase): + tcasename = objname testcase = obj if funcname: meth = getattr(obj, funcname) @@ -462,14 +409,15 @@ raise TypeError("'%s' is not a TestCase or a function." % objname) - return (mod, testcase, funcname) + return (mod, tcasename, funcname) + def _try_call(func): """Calls the given method, captures stdout and stderr, and returns the status (OK, SKIP, FAIL). """ status = 'OK' - if PY3 and getattr(func, '__unittest_expecting_failure__', False): + if getattr(func, '__unittest_expecting_failure__', False): expected = True else: expected = False @@ -482,8 +430,6 @@ status = 'OK' expected = True except Exception as err: - if PY2 and isinstance(err, _ExpectedFailure): - expected = True status = 'FAIL' sys.stderr.write(traceback.format_exc()) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testflo-1.3.4/testflo/util.py new/testflo-1.4.1/testflo/util.py --- old/testflo-1.3.4/testflo/util.py 2018-11-16 19:02:04.000000000 +0100 +++ new/testflo-1.4.1/testflo/util.py 2020-02-28 14:43:11.000000000 +0100 @@ -7,14 +7,9 @@ import itertools import inspect import warnings +from importlib import import_module -from six import string_types, PY3 -from six.moves.configparser import ConfigParser - -try: - from multiprocessing import cpu_count -except ImportError: - pass +from configparser import ConfigParser from fnmatch import fnmatch from os.path import join, dirname, basename, isfile, abspath, split, splitext @@ -25,6 +20,7 @@ _store = {} + def _get_parser(): """Returns a parser to handle command line args.""" @@ -41,14 +37,8 @@ help='Specifies a time limit in seconds for tests to be saved to ' 'the quicktests.in file.') - try: - cpus = cpu_count() - except: - warnings.warn('CPU count could not be determined. Defaulting to 1') - cpus = 1 - parser.add_argument('-n', '--numprocs', type=int, action='store', - dest='num_procs', metavar='NUM_PROCS', default=cpus, + dest='num_procs', metavar='NUM_PROCS', help='Number of processes to run. By default, this will ' 'use the number of CPUs available. To force serial' ' execution, specify a value of 1.') @@ -69,7 +59,7 @@ parser.add_argument('-f', '--fail', action='store_true', dest='save_fails', help="Save failed tests to failtests.in file.") parser.add_argument('--full_path', action='store_true', dest='full_path', - help="Display full test specs instead of shortened names.") + help="Display full test specs instead of shortened names.") parser.add_argument('-i', '--isolated', action='store_true', dest='isolated', help="Run each test in a separate subprocess.") parser.add_argument('--nompi', action='store_true', dest='nompi', @@ -105,6 +95,9 @@ parser.add_argument('--noreport', action='store_true', dest='noreport', help="Don't create a test results file.") + parser.add_argument('--show_skipped', action='store_true', dest='show_skipped', + help="Display a list of any skipped tests in the summary.") + parser.add_argument('tests', metavar='test', nargs='*', help='A test method, test case, module, or directory to run.') @@ -113,14 +106,13 @@ help='Pattern to use for test discovery. Multiple patterns are allowed.', default=[]) - parser.add_argument('--timeout', action='store', dest='timeout', - default=-1.0, type=float, + parser.add_argument('--timeout', action='store', dest='timeout', type=float, help='Timeout in seconds. Test will be terminated if it takes longer than timeout. Only' ' works for tests running in a subprocess (MPI and isolated).') return parser -def _get_testflo_subproc_args(): +def _options2args(): """Gets the testflo args that should be used in subprocesses.""" cmdset = set([ @@ -148,6 +140,7 @@ return keep + def _file_gen(dname, fmatch=bool, dmatch=None): """A generator returning files under the given directory, with optional file and directory filtering. @@ -204,32 +197,32 @@ subject to directory filtering. """ - startdirs = [start] if isinstance(start, string_types) else start + startdirs = [start] if isinstance(start, str) else start if len(startdirs) == 0: return iter([]) if match is None: matcher = bool - elif isinstance(match, string_types): + elif isinstance(match, str): matcher = lambda name: fnmatch(name, match) else: matcher = match if dirmatch is None: dmatcher = bool - elif isinstance(dirmatch, string_types): + elif isinstance(dirmatch, str): dmatcher = lambda name: fnmatch(name, dirmatch) else: dmatcher = dirmatch - if isinstance(exclude, string_types): + if isinstance(exclude, str): fmatch = lambda name: matcher(name) and not fnmatch(name, exclude) elif exclude is not None: fmatch = lambda name: matcher(name) and not exclude(name) else: fmatch = matcher - if isinstance(direxclude, string_types): + if isinstance(direxclude, str): if dmatcher is bool: dmatch = lambda name: not fnmatch(name, direxclude) else: @@ -246,7 +239,7 @@ return iters[0] -def get_module_path(fpath): +def fpath2modpath(fpath): """Given a module filename, return its full Python name including enclosing packages. (based on existence of ``__init__.py`` files) """ @@ -298,7 +291,7 @@ """ if fname.endswith('.py'): - modpath = get_module_path(fname) + modpath = fpath2modpath(fname) if not modpath: raise RuntimeError("can't find module %s" % fname) else: @@ -311,8 +304,7 @@ start_coverage() try: - __import__(modpath) - mod = sys.modules[modpath] + mod = import_module(modpath) except ImportError: # this might be a module that's not in the same # environment as testflo, so try temporarily prepending @@ -322,12 +314,11 @@ sys.path.extend(parent_dirs(fname)) sys.path.append(os.getcwd()) try: - __import__(modpath) - mod = sys.modules[modpath] + mod = import_module(modpath) # don't keep this module around in sys.modules, but # keep a reference to it, else multiprocessing on Windows # will have problems - _store[modpath] = sys.modules[modpath] + _store[modpath] = mod del sys.modules[modpath] finally: sys.path = oldpath @@ -386,6 +377,7 @@ except: return 0. + def elapsed_str(elapsed): """return a string of the form hh:mm:sec""" hrs = int(elapsed/3600) @@ -394,9 +386,8 @@ elapsed -= (mins * 60) return "%02d:%02d:%.2f" % (hrs, mins, elapsed) + # in python3, inspect.ismethod doesn't work as you might expect, so... -if PY3: - def ismethod(obj): - return inspect.isfunction(obj) or inspect.ismethod(obj) -else: - ismethod = inspect.ismethod +def ismethod(obj): + return inspect.isfunction(obj) or inspect.ismethod(obj) + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testflo-1.3.4/testflo.egg-info/PKG-INFO new/testflo-1.4.1/testflo.egg-info/PKG-INFO --- old/testflo-1.3.4/testflo.egg-info/PKG-INFO 1970-01-01 01:00:00.000000000 +0100 +++ new/testflo-1.4.1/testflo.egg-info/PKG-INFO 2020-02-28 19:44:32.000000000 +0100 @@ -0,0 +1,78 @@ +Metadata-Version: 1.1 +Name: testflo +Version: 1.4.1 +Summary: A simple flow-based testing framework +Home-page: UNKNOWN +Author: UNKNOWN +Author-email: UNKNOWN +License: Apache 2.0 +Description: + usage: testflo [options] + + positional arguments: + test A test method, test case, module, or directory to run. + + optional arguments: + -h, --help show this help message and exit + -c FILE, --config FILE + Path of config file where preferences are specified. + -t FILE, --testfile FILE + Path to a file containing one testspec per line. + --maxtime TIME_LIMIT Specifies a time limit in seconds for tests to be + saved to the quicktests.in file. + -n NUM_PROCS, --numprocs NUM_PROCS + Number of processes to run. By default, this will use + the number of CPUs available. To force serial + execution, specify a value of 1. + -o FILE, --outfile FILE + Name of test report file. Default is + testflo_report.out. + -v, --verbose Include testspec and elapsed time in screen output. + Also shows all stderr output, even if test doesn't + fail + --compact Limit output to a single character for each test. + --dryrun Don't actually run tests, but print which tests would + have been run. + --pre_announce Announce the name of each test before it runs. This + can help track down a hanging test. This automatically + sets -n 1. + -f, --fail Save failed tests to failtests.in file. + --full_path Display full test specs instead of shortened names. + -i, --isolated Run each test in a separate subprocess. + --nompi Force all tests to run without MPI. This can be useful + for debugging. + -x, --stop Stop after the first test failure, or as soon as + possible when running concurrent tests. + -s, --nocapture Standard output (stdout) will not be captured and will + be written to the screen immediately. + --coverage Perform coverage analysis and display results on + stdout + --coverage-html Perform coverage analysis and display results in + browser + --coverpkg PKG Add the given package to the coverage list. You can + use this option multiple times to cover multiple + packages. + --cover-omit FILE Add a file name pattern to remove it from coverage. + -b, --benchmark Specifies that benchmarks are to be run rather than + tests, so only files starting with "benchmark\_" will + be executed. + -d FILE, --datafile FILE + Name of benchmark data file. Default is + benchmark_data.csv. + --noreport Don't create a test results file. + -m GLOB, --match GLOB, --testmatch GLOB + Pattern to use for test discovery. Multiple patterns + are allowed. + --timeout TIMEOUT Timeout in seconds. Test will be terminated if it + takes longer than timeout. Only works for tests + running in a subprocess (MPI and isolated). + +Platform: UNKNOWN +Classifier: Development Status :: 4 - Beta +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Natural Language :: English +Classifier: Operating System :: MacOS :: MacOS X +Classifier: Operating System :: POSIX :: Linux +Classifier: Operating System :: Microsoft :: Windows +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: Implementation :: CPython diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testflo-1.3.4/testflo.egg-info/SOURCES.txt new/testflo-1.4.1/testflo.egg-info/SOURCES.txt --- old/testflo-1.3.4/testflo.egg-info/SOURCES.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/testflo-1.4.1/testflo.egg-info/SOURCES.txt 2020-02-28 19:44:32.000000000 +0100 @@ -0,0 +1,29 @@ +DESIGN.txt +LICENSE.txt +MANIFEST.in +README.md +RELEASE_NOTES.txt +setup.cfg +setup.py +testflo/__init__.py +testflo/benchmark.py +testflo/cover.py +testflo/devnull.py +testflo/discover.py +testflo/filters.py +testflo/isolatedrun.py +testflo/main.py +testflo/mpirun.py +testflo/options.py +testflo/printer.py +testflo/qman.py +testflo/runner.py +testflo/summary.py +testflo/test.py +testflo/util.py +testflo.egg-info/PKG-INFO +testflo.egg-info/SOURCES.txt +testflo.egg-info/dependency_links.txt +testflo.egg-info/entry_points.txt +testflo.egg-info/requires.txt +testflo.egg-info/top_level.txt \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testflo-1.3.4/testflo.egg-info/dependency_links.txt new/testflo-1.4.1/testflo.egg-info/dependency_links.txt --- old/testflo-1.3.4/testflo.egg-info/dependency_links.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/testflo-1.4.1/testflo.egg-info/dependency_links.txt 2020-02-28 19:44:32.000000000 +0100 @@ -0,0 +1 @@ + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testflo-1.3.4/testflo.egg-info/entry_points.txt new/testflo-1.4.1/testflo.egg-info/entry_points.txt --- old/testflo-1.3.4/testflo.egg-info/entry_points.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/testflo-1.4.1/testflo.egg-info/entry_points.txt 2020-02-28 19:44:32.000000000 +0100 @@ -0,0 +1,4 @@ + + [console_scripts] + testflo=testflo.main:main + \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testflo-1.3.4/testflo.egg-info/requires.txt new/testflo-1.4.1/testflo.egg-info/requires.txt --- old/testflo-1.3.4/testflo.egg-info/requires.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/testflo-1.4.1/testflo.egg-info/requires.txt 2020-02-28 19:44:32.000000000 +0100 @@ -0,0 +1 @@ +coverage<5.0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testflo-1.3.4/testflo.egg-info/top_level.txt new/testflo-1.4.1/testflo.egg-info/top_level.txt --- old/testflo-1.3.4/testflo.egg-info/top_level.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/testflo-1.4.1/testflo.egg-info/top_level.txt 2020-02-28 19:44:32.000000000 +0100 @@ -0,0 +1 @@ +testflo