Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-testflo for openSUSE:Factory checked in at 2026-04-18 21:35:14 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-testflo (Old) and /work/SRC/openSUSE:Factory/.python-testflo.new.11940 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-testflo" Sat Apr 18 21:35:14 2026 rev:12 rq:1347826 version:1.4.22 Changes: -------- --- /work/SRC/openSUSE:Factory/python-testflo/python-testflo.changes 2024-01-03 12:24:19.246014512 +0100 +++ /work/SRC/openSUSE:Factory/.python-testflo.new.11940/python-testflo.changes 2026-04-18 21:35:23.779170440 +0200 @@ -1,0 +2,22 @@ +Fri Apr 17 09:23:17 UTC 2026 - John Paul Adrian Glaubitz <[email protected]> + +- Update to 1.4.22 + * Previous release broke for projects that don't have a .coveragerc (#130) +- from version 1.4.21 + * Fixed a double initialization issue when coverage was active (#128) +- from version 1.4.20 + * Improved functionality of the -f option (#123) + * Added a workflow to publish to PyPi when a release is made on GitHub (#122) +- from version 1.4.19 + * Added current directory to sys.path during try_import to avoid any + relative import failures in test files (#118) +- from version 1.4.18 + * Removed setuptools as a dependency (#115) +- from version 1.4.17 + * Fixed a couple of bugs related to SubTests (#111) +- from version 1.4.16 + * Changed build system to hatchling; added a test workflow (#106) + * Fixed handling of --skip_dirs arg (#105) +- Update BuildRequires from pyproject.toml + +------------------------------------------------------------------- Old: ---- testflo-1.4.15.tar.gz New: ---- testflo-1.4.22.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-testflo.spec ++++++ --- /var/tmp/diff_new_pack.iwmxmJ/_old 2026-04-18 21:35:24.427196851 +0200 +++ /var/tmp/diff_new_pack.iwmxmJ/_new 2026-04-18 21:35:24.435197177 +0200 @@ -1,7 +1,7 @@ # # spec file for package python-testflo # -# Copyright (c) 2024 SUSE LLC +# Copyright (c) 2026 SUSE LLC and contributors # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -18,21 +18,21 @@ %{?sle15_python_module_pythons} Name: python-testflo -Version: 1.4.15 +Version: 1.4.22 Release: 0 Summary: A flow-based testing framework License: Apache-2.0 Group: Development/Languages/Python URL: https://github.com/OpenMDAO/testflo Source: https://files.pythonhosted.org/packages/source/t/testflo/testflo-%{version}.tar.gz +BuildRequires: %{python_module hatchling} BuildRequires: %{python_module pip} -BuildRequires: %{python_module setuptools} BuildRequires: %{python_module wheel} BuildRequires: fdupes BuildRequires: python-rpm-macros Requires: python-setuptools Requires(post): update-alternatives -Requires(preun):update-alternatives +Requires(preun): update-alternatives Recommends: python-coverage Recommends: python-mpi4py Recommends: python-psutil ++++++ testflo-1.4.15.tar.gz -> testflo-1.4.22.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testflo-1.4.15/.gitignore new/testflo-1.4.22/.gitignore --- old/testflo-1.4.15/.gitignore 1970-01-01 01:00:00.000000000 +0100 +++ new/testflo-1.4.22/.gitignore 2020-02-02 01:00:00.000000000 +0100 @@ -0,0 +1,2 @@ +*.pyc +*.egg-info diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testflo-1.4.15/DESIGN.txt new/testflo-1.4.22/DESIGN.txt --- old/testflo-1.4.15/DESIGN.txt 2021-12-12 23:38:56.000000000 +0100 +++ new/testflo-1.4.22/DESIGN.txt 1970-01-01 01:00:00.000000000 +0100 @@ -1,43 +0,0 @@ -testflo is a python testing framework that uses a pipeline of -iterators to process test specifications, run the tests, and process the -results. - -The testflo API consists of a single callable that takes -an input iterator of Test objects as an argument and returns an -output iterator of Test objects. The source of the pipeline is a plain -python iterator since it doesn't need an input iterator. By simply adding -members to the testflo pipeline, it's easy to add new features. - -The pipeline starts with an iterator of strings that I'll call -'general test specifiers'. These can have any of the following forms: - -<module or file path> -<module or file path>:<TestCase class name>.<method name> -<module or file path>:<function name> -<directory path> - -where <module or file path> is either the filesystem pathname of the -python file containing the test(s) or the python module path, e.g., -'foo.bar.baz'. - -The general test specifiers are iterated over by the TestDiscoverer, who -generates an output iterator of Test objects. There is a Test object for each -individual test. As of version 1.1, the objects in the TestDiscoverer's -output iterator can be either individual Test objects or lists of Test -objects. This change was necessary to support module level and TestCase -class level setup and teardown functions. The thought was that all tests -under either a module level setup/teardown or a TestCase class level -setup/teardown should be grouped and executed in the same process, so -when these functions are present, the Test objects are grouped into a list -and sent together to the ConcurrentTestRunner. After execution, the rest -of the pipeline sees only individual Test objects. - -The ConcurrentTestRunner -executes each test and passes an iterator of those to the ResultPrinter, -who then passes them on to the ResultSummary. - -The multiprocessing library is used in the ConcurrentTestRunner to support concurrent -execution of tests. It adds Test objects to a shared Queue that the -worker processes pull from. Then the workers place the finished Test objects in -a 'done' Queue that the ConcurrentTestRunner pulls from and passes downstream for -display, summary, or whatever. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testflo-1.4.15/MANIFEST.in new/testflo-1.4.22/MANIFEST.in --- old/testflo-1.4.15/MANIFEST.in 2021-12-12 23:38:56.000000000 +0100 +++ new/testflo-1.4.22/MANIFEST.in 1970-01-01 01:00:00.000000000 +0100 @@ -1,4 +0,0 @@ -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.4.15/PKG-INFO new/testflo-1.4.22/PKG-INFO --- old/testflo-1.4.15/PKG-INFO 2023-12-28 19:25:25.970558400 +0100 +++ new/testflo-1.4.22/PKG-INFO 2020-02-02 01:00:00.000000000 +0100 @@ -1,22 +1,22 @@ -Metadata-Version: 2.1 +Metadata-Version: 2.4 Name: testflo -Version: 1.4.15 +Version: 1.4.22 Summary: A simple flow-based testing framework -License: Apache 2.0 +License-Expression: Apache-2.0 +License-File: LICENSE.txt 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: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: Implementation :: CPython -Description-Content-Type: text/markdown -License-File: LICENSE.txt Requires-Dist: coverage>=6.0 +Description-Content-Type: text/markdown [![PyPI version][1]][2] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testflo-1.4.15/pyproject.toml new/testflo-1.4.22/pyproject.toml --- old/testflo-1.4.15/pyproject.toml 1970-01-01 01:00:00.000000000 +0100 +++ new/testflo-1.4.22/pyproject.toml 2020-02-02 01:00:00.000000000 +0100 @@ -0,0 +1,37 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "testflo" +dynamic = ["version"] +description = "A simple flow-based testing framework" +readme = "README.md" +license = "Apache-2.0" +classifiers = [ + "Development Status :: 4 - Beta", + "License :: OSI Approved :: Apache Software License", + "Natural Language :: English", + "Operating System :: MacOS :: MacOS X", + "Operating System :: Microsoft :: Windows", + "Operating System :: POSIX :: Linux", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: Implementation :: CPython", +] +dependencies = [ + "coverage>=6.0", +] + +[project.scripts] +testflo = "testflo.main:main" + +[tool.hatch.version] +path = "testflo/__init__.py" + +[tool.hatch.build.targets.sdist] +include = [ + "/testflo", +] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testflo-1.4.15/setup.cfg new/testflo-1.4.22/setup.cfg --- old/testflo-1.4.15/setup.cfg 2023-12-28 19:25:25.970558400 +0100 +++ new/testflo-1.4.22/setup.cfg 1970-01-01 01:00:00.000000000 +0100 @@ -1,7 +0,0 @@ -[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.4.15/setup.py new/testflo-1.4.22/setup.py --- old/testflo-1.4.15/setup.py 2023-10-26 20:14:41.000000000 +0200 +++ new/testflo-1.4.22/setup.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,42 +0,0 @@ -from setuptools import setup - -import re - -from pathlib import Path - -__version__ = re.findall( - r"""__version__ = ["']+([0-9\.\-dev]*)["']+""", - open('testflo/__init__.py').read(), -)[0] - -with open(Path(__file__).parent / "README.md", encoding="utf-8") as f: - long_description = f.read() - -setup(name='testflo', - version=__version__, - description="A simple flow-based testing framework", - long_description=long_description, - long_description_content_type='text/markdown', - 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 :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: Implementation :: CPython', - ], - license='Apache 2.0', - install_requires=[ - 'coverage>=6.0' - ], - packages=['testflo'], - entry_points=""" - [console_scripts] - testflo=testflo.main:main - """ - ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testflo-1.4.15/testflo/__init__.py new/testflo-1.4.22/testflo/__init__.py --- old/testflo-1.4.15/testflo/__init__.py 2023-12-28 19:24:57.000000000 +0100 +++ new/testflo-1.4.22/testflo/__init__.py 2020-02-02 01:00:00.000000000 +0100 @@ -1 +1 @@ -__version__ = '1.4.15' +__version__ = '1.4.22' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testflo-1.4.15/testflo/cover.py new/testflo-1.4.22/testflo/cover.py --- old/testflo-1.4.15/testflo/cover.py 2023-10-26 20:14:41.000000000 +0200 +++ new/testflo-1.4.22/testflo/cover.py 2020-02-02 01:00:00.000000000 +0100 @@ -3,146 +3,103 @@ """ import os import sys -import shutil import webbrowser try: import coverage - from coverage.config import HandyConfigParser + from coverage.collector import Collector except ImportError: coverage = None -else: - coverage.process_startup() -# use to hold a global coverage obj -_coverobj = None +def setup_coverage(options): + """ + Programmatically initializes coverage for the current process. + Ensures absolute paths for temp-dir safety and avoids double-init. + """ + if not (options.coverage or options.coveragehtml): + return None -def _to_ini(lst): - if lst: - return ','.join(lst) - return '' + # Prevent double-initialization + if Collector._collectors: + return None + + cover_dir = options.cover_dir or os.getcwd() + data_file = os.path.join(cover_dir, '.coverage') + cfg_file = os.path.join(cover_dir, '.coveragerc') + + kwargs = { + 'data_file': data_file, + 'data_suffix': True, + 'branch': options.cover_branch, + } + if os.path.isfile(cfg_file): + kwargs['config_file'] = cfg_file -def _write_temp_config(options, rcfile): - """ - Read any .coveragerc file if it exists, and override parts of it then generate our temp config. + cov = coverage.Coverage(**kwargs) - Parameters - ---------- - options : cmd line options - Options from the command line parser. - rcfile : str - The name of our temporary coverage config file. - """ - tmp_cfg = { - 'run': { - 'branch': False, - 'parallel': True, - 'concurrency': 'multiprocessing', - }, - 'report': { - 'ignore_errors': True, - 'skip_empty': True, - 'sort': '-cover', - }, - 'html': { - 'skip_empty': True, - } - } + cov.config.ignore_errors = True - if options.coverpkgs: - tmp_cfg['run']['source_pkgs'] = _to_ini(options.coverpkgs) + if sys.version_info >= (3, 13): + cov.set_option("run:core", "sysmon") + if options.coverpkgs: + cov.set_option("run:source", options.coverpkgs) if options.cover_omits: - tmp_cfg['run']['omit'] = _to_ini(options.cover_omits) - tmp_cfg['report']['omit'] = _to_ini(options.cover_omits) + omits = cov.get_option("run:omit") + if omits: + omits.extend(options.cover_omits) + else: + omits = options.cover_omits + cov.set_option("run:omit", omits) + + cov.set_option("run:disable_warnings", ["module-not-imported", "no-data-collected", + "couldnt-parse"]) + cov.set_option("report:ignore_errors", True) + cov.set_option("report:sort", "-cover") + cov.set_option("report:exclude_lines", [ + "pragma: no cover", + "if __name__ == .__main__.:", + "raise NotImplementedError", + "def __repr__", + ]) + + return cov - cfgparser = HandyConfigParser(our_file=True) - if os.path.isfile('.coveragerc'): - cfgparser.read(['.coveragerc']) +def finalize_coverage(options, cov): + """ + Combines all parallel coverage files and generates an HTML report. + """ + if cov is None: + return - cfgparser.read_dict(tmp_cfg) + cov.save() - with open(rcfile, 'w') as f: - cfgparser.write(f) + data_dir = options.cover_dir + # Combine all coverage files found in the data_dir + try: + cov.combine(data_paths=[data_dir]) + except coverage.exceptions.CoverageException as e: + print(f"Combining coverage files failed: {e}", file=sys.stderr) + return + + if options.coverage: + print("\n--- Coverage Summary ---") + cov.report(ignore_errors=True, skip_empty=True) + + if options.coveragehtml: + html_dir = os.path.join(data_dir, 'htmlcov') + cov.html_report(directory=html_dir, ignore_errors=True, skip_empty=True, + show_contexts=options.dyn_contexts) + index_file = os.path.join(html_dir, 'index.html') + if sys.platform == 'darwin': + os.system('open %s' % index_file) + else: + webbrowser.get().open(index_file) -def setup_coverage(options): - global _coverobj - if _coverobj is None and (options.coverage or options.coveragehtml): - if not coverage: - raise RuntimeError("coverage has not been installed.") - if not options.coverpkgs: - raise RuntimeError("No packages specified for coverage. " - "Use the --coverpkg option to add a package.") - oldcov = os.path.join(os.getcwd(), '.coverage') - if os.path.isfile(oldcov): - os.remove(oldcov) - covdir = os.path.join(os.getcwd(), '_covdir') - if os.path.isdir('_covdir'): - shutil.rmtree('_covdir') - os.mkdir('_covdir') - os.environ['COVERAGE_RUN'] = 'true' - os.environ['COVERAGE_RCFILE'] = rcfile = os.path.join(covdir, '_coveragerc_') - os.environ['COVERAGE_FILE'] = covfile = os.path.join(covdir, '.coverage') - os.environ['COVERAGE_PROCESS_START'] = rcfile - _write_temp_config(options, rcfile) - _coverobj = coverage.Coverage(data_file=covfile, data_suffix=True, config_file=rcfile) - return _coverobj - -def start_coverage(): - if _coverobj: - _coverobj.start() - -def stop_coverage(): - if _coverobj: - _coverobj.stop() - -def save_coverage(): - if _coverobj: - _coverobj.save() - -def finalize_coverage(options): - if _coverobj and options.coverpkgs: - rank = 0 - if not options.nompi: - try: - from mpi4py import MPI - rank = MPI.COMM_WORLD.rank - except ImportError: - pass - if rank == 0: - from testflo.util import find_files, find_module - excl = lambda n: (n.startswith('test_') and n.endswith('.py')) or \ - n.startswith('__init__.') - dirs = [] - for n in options.coverpkgs: - if os.path.isdir(n): - dirs.append(n) - else: - path = find_module(n) - if path is None: - raise RuntimeError("Can't find module %s" % n) - dirs.append(os.path.dirname(path)) - - morfs = list(find_files(dirs, match='*.py', exclude=excl)) - - _coverobj.combine() - _coverobj.save() - - if options.coverage: - _coverobj.report(morfs=morfs) - else: - dname = '_html' - _coverobj.html_report(morfs=morfs, directory=dname) - outfile = os.path.join(os.getcwd(), dname, 'index.html') - - if sys.platform == 'darwin': - os.system('open %s' % outfile) - else: - webbrowser.get().open(outfile) + print(f"\nHTML report generated at: {index_file}") - shutil.copy(_coverobj.get_data().data_filename(), - os.path.join(os.getcwd(), '.coverage')) + return cov \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testflo-1.4.15/testflo/deprecations.py new/testflo-1.4.22/testflo/deprecations.py --- old/testflo-1.4.15/testflo/deprecations.py 2023-10-23 21:57:00.000000000 +0200 +++ new/testflo-1.4.22/testflo/deprecations.py 2020-02-02 01:00:00.000000000 +0100 @@ -16,10 +16,11 @@ deprecations = {} - for test in input_iter: - for msg, locs in test.deprecations.items(): - deprecations[msg] = deprecations.get(msg, set()) | locs - yield test + for tests in input_iter: + for test in tests: + for msg, locs in test.deprecations.items(): + deprecations[msg] = deprecations.get(msg, set()) | locs + yield test report = self.generate_report(deprecations) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testflo-1.4.15/testflo/discover.py new/testflo-1.4.22/testflo/discover.py --- old/testflo-1.4.15/testflo/discover.py 2023-10-23 21:57:00.000000000 +0200 +++ new/testflo-1.4.22/testflo/discover.py 2020-02-02 01:00:00.000000000 +0100 @@ -61,7 +61,7 @@ # process so that we can execute the module or class level setup/teardown # only once while impacting all of the tests in that group. new_tcase_groups = [] - for tcase, tests in self._tcase_fixture_groups.items(): + for tests in self._tcase_fixture_groups.values(): tests = sorted(tests, key=lambda t: t.spec) # mark the first and last tests so that we know when to diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testflo-1.4.15/testflo/filters.py new/testflo-1.4.22/testflo/filters.py --- old/testflo-1.4.15/testflo/filters.py 2023-10-23 21:57:00.000000000 +0200 +++ new/testflo-1.4.22/testflo/filters.py 2020-02-02 01:00:00.000000000 +0100 @@ -1,5 +1,8 @@ from __future__ import print_function +import os + + class TimeFilter(object): """This iterator saves to the specified output file only those tests that complete successfully in max_time seconds or less. This output @@ -29,13 +32,17 @@ self.outfile = outfile def get_iter(self, input_iter): - fails = [] + try: + os.remove(self.outfile) + except OSError: + pass + for result in input_iter: if result.status == 'FAIL' and not result.expected_fail: - fails.append(result.spec) - yield result - - if fails: - with open(self.outfile, 'w') as f: - for spec in sorted(fails): + with open(self.outfile, 'a') as f: + if result.nprocs > 1: + spec = f"{result.spec} # mpi, nprocs={result.nprocs}" + else: + spec = result.spec print(spec, file=f) + yield result diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testflo-1.4.15/testflo/isolatedrun.py new/testflo-1.4.22/testflo/isolatedrun.py --- old/testflo-1.4.15/testflo/isolatedrun.py 2023-10-26 20:14:41.000000000 +0200 +++ new/testflo-1.4.22/testflo/isolatedrun.py 2020-02-02 01:00:00.000000000 +0100 @@ -5,12 +5,6 @@ """ if __name__ == '__main__': - try: - import coverage - except ImportError: - coverage = None - else: - coverage.process_startup() import sys import os @@ -19,27 +13,31 @@ from testflo.test import Test from testflo.qman import get_client_queue from testflo.options import get_options + from testflo.cover import setup_coverage queue = get_client_queue() os.environ['TESTFLO_QUEUE'] = '' options = get_options() + test = None - try: - try: - test = Test(sys.argv[1], options) - test.nocapture = True # so we don't lose stdout - test.run() - except: - print(traceback.format_exc()) - test.status = 'FAIL' - test.err_msg = traceback.format_exc() + if options.coverage or options.coveragehtml: + cov = setup_coverage(options) + else: + cov = None + try: + test = Test(sys.argv[1], options) + test.nocapture = True # so we don't lose stdout + test.run(cov=cov) except: - test.err_msg = traceback.format_exc() test.status = 'FAIL' + test.err_msg = traceback.format_exc() + finally: + sys.stdout.flush() + sys.stderr.flush() - sys.stdout.flush() - sys.stderr.flush() + queue.put(test) - queue.put(test) + if cov is not None: + cov.save() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testflo-1.4.15/testflo/main.py new/testflo-1.4.22/testflo/main.py --- old/testflo-1.4.15/testflo/main.py 2023-11-13 17:01:09.000000000 +0100 +++ new/testflo-1.4.22/testflo/main.py 2020-02-02 01:00:00.000000000 +0100 @@ -29,8 +29,6 @@ import time import warnings import multiprocessing -import atexit -import shutil from fnmatch import fnmatch, fnmatchcase @@ -43,12 +41,15 @@ from testflo.duration import DurationSummary from testflo.discover import TestDiscoverer from testflo.filters import TimeFilter, FailFilter +from testflo.cover import setup_coverage, finalize_coverage from testflo.util import read_config_file, read_test_file from testflo.options import get_options from testflo.qman import get_server_queue - -options = get_options() +try: + import coverage +except ImportError: + coverage = None def dryrun(input_iter): @@ -59,7 +60,11 @@ for test in tests: if test.status is None: test.status = 'OK' - print(test.spec) + if test.nprocs > 1: + spec = f"{test.spec} # mpi, nprocs={test.nprocs}" + else: + spec = test.spec + print(spec) yield test @@ -120,22 +125,12 @@ nprocs = options.num_procs - options.skip_dirs = [] - # read user prefs from ~/.testflo file. # create one if it doesn't exist homedir = os.path.expanduser('~') rcfile = os.path.join(homedir, '.testflo') - if not os.path.isfile(rcfile): - with open(rcfile, 'w') as f: - f.write("""[testflo] -skip_dirs=site-packages, - dist-packages, - build, - _build, - contrib -""") - read_config_file(rcfile, options) + if os.path.isfile(rcfile): + read_config_file(rcfile, options) if options.cfg: read_config_file(options.cfg, options) @@ -156,8 +151,14 @@ if not tests: tests = [os.getcwd()] + always_skip = {'site-packages', 'dist-packages', '__pycache__', 'build', '_build', + '.pixi'} + def dir_exclude(d): base = os.path.basename(d) + if base in always_skip: + return True + for skip in options.skip_dirs: if fnmatch(base, skip): return True @@ -167,21 +168,21 @@ os.environ['TESTFLO_RUNNING'] = '1' if options.coverage or options.coveragehtml: - os.environ['TESTFLO_MAIN_PID'] = str(os.getpid()) - # some coverage files aren't written until atexit of their processes, so put our finalize - # routine in atexit before their Coverage._atexit methods are registered so ours will be - # executed *after* theirs. - def _finalize(): - if os.getpid() == int(os.environ.get('TESTFLO_MAIN_PID', '0')): - from testflo.cover import finalize_coverage - finalize_coverage(options) - # clean up the temporary dir where we store the interim coverage files from all - # of the processes. - if os.path.isdir('_covdir'): - shutil.rmtree('_covdir') - atexit.register(_finalize) - from testflo.cover import setup_coverage - setup_coverage(options) + cov_dir = options.cover_dir or os.getcwd() + options.cover_dir = os.path.abspath(cov_dir) + if options.cover_omits is None: + options.cover_omits = [] + options.cover_omits.append('*/testflo/*') + + if not coverage: + raise RuntimeError("coverage has not been installed.") + if not options.coverpkgs: + raise RuntimeError("No packages specified for coverage. " + "Use the --coverpkg option to add a package.") + + cov = setup_coverage(options) + else: + cov = None if options.noreport: report_file = open(os.devnull, 'a') @@ -232,7 +233,7 @@ if options.pre_announce: options.num_procs = 1 - pipeline.append(ConcurrentTestRunner(options, queue).get_iter) + pipeline.append(ConcurrentTestRunner(options, queue, cov=cov).get_iter) if options.show_deprecations or options.deprecations_report: pipeline.append(DeprecationsReport(options).get_iter) @@ -271,6 +272,8 @@ if manager is not None: manager.shutdown() + finalize_coverage(options, cov) + return retval diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testflo-1.4.15/testflo/mpirun.py new/testflo-1.4.22/testflo/mpirun.py --- old/testflo-1.4.15/testflo/mpirun.py 2023-10-26 20:14:41.000000000 +0200 +++ new/testflo-1.4.22/testflo/mpirun.py 2020-02-02 01:00:00.000000000 +0100 @@ -6,12 +6,6 @@ """ if __name__ == '__main__': - try: - import coverage - except ImportError: - pass - else: - coverage.process_startup() import sys import os @@ -24,6 +18,7 @@ from testflo.test import Test from testflo.qman import get_client_queue from testflo.options import get_options + from testflo.cover import setup_coverage exitcode = 0 # use 0 for exit code of all ranks != 0 because otherwise, # MPI will terminate other processes @@ -32,13 +27,19 @@ os.environ['TESTFLO_QUEUE'] = '' options = get_options() + test = None + + if options.coverage or options.coveragehtml: + cov = setup_coverage(options) + else: + cov = None try: try: comm = MPI.COMM_WORLD test = Test(sys.argv[1], options) test.nocapture = True # so we don't lose stdout - test.run() + test.run(cov=cov) except: print(traceback.format_exc()) test.status = 'FAIL' @@ -71,3 +72,6 @@ if comm.rank == 0: queue.put(test) + + if cov is not None: + cov.save() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testflo-1.4.15/testflo/printer.py new/testflo-1.4.22/testflo/printer.py --- old/testflo-1.4.15/testflo/printer.py 2023-10-23 21:57:00.000000000 +0200 +++ new/testflo-1.4.22/testflo/printer.py 2020-02-02 01:00:00.000000000 +0100 @@ -43,7 +43,7 @@ if (self.verbose == 0 and (result.err_msg and show_msg)) or self.verbose > 0: if result.mpi and result.nprocs > 0: - run_type = '(mpi) ' + run_type = f'(mpi {result.nprocs}) ' elif result.isolated: run_type = '(isolated) ' else: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testflo-1.4.15/testflo/runner.py new/testflo-1.4.22/testflo/runner.py --- old/testflo-1.4.15/testflo/runner.py 2021-12-12 23:38:56.000000000 +0100 +++ new/testflo-1.4.22/testflo/runner.py 2020-02-02 01:00:00.000000000 +0100 @@ -8,43 +8,45 @@ from multiprocessing import Queue, Process -from testflo.cover import save_coverage -from testflo.test import Test -from testflo.options import get_options +from testflo.cover import setup_coverage -def worker(test_queue, done_queue, subproc_queue, worker_id): - """This is used by concurrent test processes. It takes a test +def worker(test_queue, done_queue, subproc_queue, worker_id, options): + """This is used by concurrent test processes. It takes a Test object off of the test_queue, runs it, then puts the Test object on the done_queue. """ + + cov = setup_coverage(options) + test_count = 0 - for tests in iter(test_queue.get, 'STOP'): + try: + for tests in iter(test_queue.get, 'STOP'): - done_tests = [] - for test in tests: - try: - test_count += 1 - done_tests.append(test.run(subproc_queue)) - except: - # we generally shouldn't get here, but just in case, - # handle it so that the main process doesn't hang at the - # end when it tries to join all of the concurrent processes. - done_tests.append(test) - - done_queue.put(done_tests) - - # don't save anything unless we actually ran a test - if test_count > 0: - save_coverage() + done_tests = [] + for test in tests: + try: + test_count += 1 + done_tests.append(test.run(subproc_queue, cov=cov)) + except: + # we generally shouldn't get here, but just in case, + # handle it so that the main process doesn't hang at the + # end when it tries to join all of the concurrent processes. + done_tests.append(test) + + done_queue.put(done_tests) + finally: + if cov: + cov.save() class TestRunner(object): - def __init__(self, options, subproc_queue): + def __init__(self, options, subproc_queue, cov): self.stop = options.stop self.pre_announce = options.pre_announce self._queue = subproc_queue + self.cov = cov def get_iter(self, input_iter): """Run tests serially.""" @@ -55,7 +57,7 @@ if self.pre_announce: print(" about to run %s " % test.short_name(), end='') sys.stdout.flush() - result = test.run(self._queue) + result = test.run(self._queue, cov=self.cov) yield result if self.stop: if (result.status == 'FAIL' and not result.expected_fail) or ( @@ -65,16 +67,14 @@ if stop: break - save_coverage() - class ConcurrentTestRunner(TestRunner): """TestRunner that uses the multiprocessing package to execute tests concurrently. """ - def __init__(self, options, subproc_queue): - super(ConcurrentTestRunner, self).__init__(options, subproc_queue) + def __init__(self, options, subproc_queue, cov): + super(ConcurrentTestRunner, self).__init__(options, subproc_queue, cov) self.num_procs = options.num_procs # only do concurrent stuff if num_procs > 1 @@ -84,7 +84,6 @@ # Create queues self.task_queue = Queue() self.done_queue = Queue() - self.procs = [] # Start worker processes @@ -93,7 +92,7 @@ self.procs.append(Process(target=worker, args=(self.task_queue, self.done_queue, subproc_queue, - worker_id))) + worker_id, options))) for proc in self.procs: proc.start() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testflo-1.4.15/testflo/test.py new/testflo-1.4.22/testflo/test.py --- old/testflo-1.4.15/testflo/test.py 2023-10-26 20:14:41.000000000 +0200 +++ new/testflo-1.4.22/testflo/test.py 2020-02-02 01:00:00.000000000 +0100 @@ -2,6 +2,7 @@ import os import sys +import shutil import time import warnings import traceback @@ -15,19 +16,16 @@ from unittest import TestCase, SkipTest from unittest.case import _UnexpectedSuccess -from testflo.cover import start_coverage, stop_coverage - from testflo.util import get_module, ismethod, get_memory_usage, \ - get_testpath, _options2args + get_testpath, _options2args, _testing_path from testflo.utresult import UnitTestResult from testflo.devnull import DevNull -from distutils import spawn mpirun_exe = None -if spawn.find_executable("mpirun") is not None: +if shutil.which("mpirun") is not None: mpirun_exe = "mpirun" -elif spawn.find_executable("mpiexec") is not None: +elif shutil.which("mpiexec") is not None: mpirun_exe = "mpiexec" @@ -46,17 +44,18 @@ self.size = 1 -# create a copy of sys.path with an extra entry at the beginning so that -# we can quickly replace the first entry with the curent test's dir rather -# than constantly copying the whole sys.path -_testing_path = ['.'] + sys.path - - @contextmanager -def testcontext(test): +def testcontext(test, cov): + if cov is not None: + cov.start() + if test.options.dyn_contexts: + cov.switch_context(test.spec) + global _testing_path old_sys_path = sys.path + os.environ['TESTFLO_SPEC'] = test.spec + _testing_path[0] = test.test_dir sys.path = _testing_path @@ -66,7 +65,11 @@ test.status = 'FAIL' test.err_msg = traceback.format_exc() finally: + if cov is not None: + cov.stop() sys.path = old_sys_path + if 'TESTFLO_SPEC' in os.environ: + del os.environ['TESTFLO_SPEC'] class Test(object): @@ -115,7 +118,7 @@ """Get the test's module, testcase (if any), function name, N_PROCS (for mpi tests) and ISOLATED and set our attributes. """ - with testcontext(self): + with testcontext(self, None): try: mod, self.tcasename, self.funcname = _parse_test_path(self.spec) self.modpath = mod.__name__ @@ -205,7 +208,7 @@ cmd = [mpirun_exe, '-n', str(self.nprocs), sys.executable, os.path.join(os.path.dirname(__file__), 'mpirun.py'), - self.spec] + _options2args() + self.spec] + _options2args(self.options) result = self._run_subproc(cmd, queue, os.environ) @@ -222,7 +225,7 @@ return result - def run(self, queue=None): + def run(self, queue=None, cov=None): """Runs the test, assuming status is not already known.""" if self.status is not None: # premature failure occurred (or dry run), just return @@ -240,7 +243,7 @@ elif self.options.isolated: return self._run_isolated(queue) - with testcontext(self): + with testcontext(self, cov): testpath, _ = get_testpath(self.spec) _, mod = get_module(testpath) @@ -287,8 +290,6 @@ sys.stdout = outstream sys.stderr = errstream - start_coverage() - self.start_time = time.perf_counter() catch_deps = self.options.show_deprecations or self.options.deprecations_report @@ -341,11 +342,13 @@ else: stream_val = '' if ut_subtests: + end_time = time.perf_counter() for sub, err in ut_subtests: subtest = SubTest(sub._subDescription(), self.spec, self.options) subtest.status = status subtest.err_msg = stream_val + err - subtest.end_time = time.perf_counter() + subtest.start_time = self.start_time + subtest.end_time = end_time subtest.memory_usage = get_memory_usage() subs.append(subtest) else: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testflo-1.4.15/testflo/tests/test_nested_fixtures.py new/testflo-1.4.22/testflo/tests/test_nested_fixtures.py --- old/testflo-1.4.15/testflo/tests/test_nested_fixtures.py 1970-01-01 01:00:00.000000000 +0100 +++ new/testflo-1.4.22/testflo/tests/test_nested_fixtures.py 2020-02-02 01:00:00.000000000 +0100 @@ -0,0 +1,67 @@ + +import os + +import unittest + +modpid = None + +def setUpModule(): + global modpid + modpid = os.getpid() + print("\ncalled setUpModule from pid %d\n" % modpid) + +def tearDownModule(): + global modpid + mypid = os.getpid() + assert mypid == modpid + print("\ncalled tearDownModule from pid %d\n" % mypid) + + +class TestfloTestCaseWFixture2(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.pid = os.getpid() + print("\nsetting up %s, pid=%d\n" % (cls.__name__, cls.pid)) + + @classmethod + def tearDownClass(cls): + assert os.getpid() == cls.pid + print("\ntearing down %s, pid=%d\n" % (cls.__name__, cls.pid)) + + def test_tcase_grouped_ok(self): + assert os.getpid() == self.pid + + def test_tcase_grouped_fail(self): + self.fail("failure 3") + + @unittest.expectedFailure + def test_tcase_grouped_expected_fail(self): + self.fail("I expected this") + + @unittest.expectedFailure + def test_tcase_grouped_unexpected_success(self): + pass + + @unittest.skip("skipping 2") + def test_tcase_grouped_skip(self): + self.fail("This test should have been skipped.") + + [email protected]("skipping a whole testcase...") +class SkippedTestCase2(unittest.TestCase): + def test_1(self): + self.fail("This test should have been skipped.") + + def test_2(self): + self.fail("This test should have been skipped.") + + def test_3(self): + self.fail("This test should have been skipped.") + + def test_4(self): + self.fail("This test should have been skipped.") + + +if __name__ == '__main__': + unittest.main() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testflo-1.4.15/testflo/tests/test_testflo.py new/testflo-1.4.22/testflo/tests/test_testflo.py --- old/testflo-1.4.15/testflo/tests/test_testflo.py 1970-01-01 01:00:00.000000000 +0100 +++ new/testflo-1.4.22/testflo/tests/test_testflo.py 2020-02-02 01:00:00.000000000 +0100 @@ -0,0 +1,104 @@ + +import os + +import unittest + +class TestfloTestCase(unittest.TestCase): + def test_ok(self): + pass + + def test_env_var(self): + testflo_running = os.getenv("TESTFLO_RUNNING", default=False) + self.assertNotEqual(testflo_running, False) + + def test_fail(self): + self.fail("This test should fail") + + @unittest.expectedFailure + def test_expected_fail_good(self): + self.fail("I expected this") + + @unittest.expectedFailure + def test_unexpected_success(self): + pass + + @unittest.skip("skipping 1") + def test_skip(self): + self.fail("This test should have been skipped.") + +class TestfloTestCaseWFixture(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.pid = os.getpid() + print("setting up %s, pid=%d" % (cls.__name__, cls.pid)) + + @classmethod + def tearDownClass(cls): + assert os.getpid() == cls.pid + print("tearing down %s, pid=%d" % (cls.__name__, cls.pid)) + + def test_tcase_grouped_ok(self): + assert os.getpid() == self.pid + + def test_tcase_grouped_fail(self): + self.fail("failure 2") + + @unittest.expectedFailure + def test_tcase_grouped_expected_fail(self): + self.fail("I expected this") + + @unittest.expectedFailure + def test_tcase_grouped_unexpected_success(self): + pass + + @unittest.skip("skipping 2") + def test_tcase_grouped_skip(self): + pass + + [email protected]("skipping a whole testcase...") +class SkippedTestCase(unittest.TestCase): + def test_1(self): + self.fail("This test should have been skipped.") + + def test_2(self): + self.fail("This test should have been skipped.") + + def test_3(self): + self.fail("This test should have been skipped.") + + def test_4(self): + self.fail("This test should have been skipped.") + + +class TestSubTests(unittest.TestCase): + def test_subtests(self): + # Base price by item + base_price = {"gum": 1.00, "milk": 2.50, "eggs": 2.75} + # Sales tax by state + sales_tax = {"Michigan": 0.06, "Ohio": 0.0575, "New Hampshire": 0.00} + + # Loop through each state and item and precompute expected price + precalculated_price = {} + for state in sales_tax: + precalculated_price[state] = {} + for item in base_price: + precalculated_price[state][item] = (1.0 + sales_tax[state]) * base_price[item] + + # Intentionally mess up michigan price for gum and ohio price for eggs + precalculated_price["Michigan"]["gum"] = 100.0 + precalculated_price["Ohio"]["eggs"] = -3.14159 + + # Run through nested subtests, by state and item, and double check that logged price matches expected + for state in sales_tax: + with self.subTest(state=state): + for item in base_price: + with self.subTest(item=item): + expected_price = (1.0 + sales_tax[state]) * base_price[item] + logged_price = precalculated_price[state][item] + assert logged_price == expected_price + + +class TestSubTestsMPI(TestSubTests): + N_PROCS = 2 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testflo-1.4.15/testflo/util.py new/testflo-1.4.22/testflo/util.py --- old/testflo-1.4.15/testflo/util.py 2023-10-26 20:14:41.000000000 +0200 +++ new/testflo-1.4.22/testflo/util.py 2020-02-02 01:00:00.000000000 +0100 @@ -17,7 +17,12 @@ from argparse import ArgumentParser, _AppendAction -from testflo.cover import start_coverage, stop_coverage + +# create a copy of sys.path with an extra entry at the beginning so that +# we can quickly replace the first entry with the curent test's dir rather +# than constantly copying the whole sys.path +_testing_path = ['.'] + sys.path + _store = {} @@ -89,7 +94,13 @@ parser.add_argument('--cover-omit', action='append', dest='cover_omits', metavar='FILE', help="Add a file name pattern to remove it from coverage.") - + parser.add_argument('--cover-branch', action='store_true', dest='cover_branch', + help="Enable branch coverage (more detailed but slower). Default is False.") + parser.add_argument('--cover-dir', action='store', dest='cover_dir', + metavar='DIR', help="Specify the coverage dir to use. Default is the " + "current working directory.") + parser.add_argument('--cover-contexts', action='store_true', dest='dyn_contexts', + help="Record which tests hit which lines (increases overhead)."), parser.add_argument('-b', '--benchmark', action='store_true', dest='benchmark', help='Specifies that benchmarks are to be run rather ' 'than tests, so only files starting with "benchmark_" ' @@ -147,31 +158,46 @@ return parser -def _options2args(): - """Gets the testflo args that should be used in subprocesses.""" +def _options2args(options): + """Gets the testflo args that should be passed to subprocesses.""" + + store_args = set([ + 'coverage_dir', + ]) - cmdset = set([ - '--nocapture', - '-s', - '--coverpkg', - '--coverage', - '--coverage-html', - '--cover-omit', + multi_args = set([ + 'coverpkg', + 'cover_omits', ]) + store_true_args = set([ + 'coverage', + 'coveragehtml', + 'nocapture', + 'cover_branch', + 'dyn_contexts', + ]) + + all_args = store_args | store_true_args | multi_args + + parser = _get_parser() + dest_to_flags = { + action.dest: (action.option_strings, action.default) for action in parser._actions + if action.dest in all_args + } + keep = [] - i = 0 - args = sys.argv[1:] - argslen = len(args) - while i < argslen: - arg = args[i] - if arg.split('=',1)[0] in cmdset: - keep.append(arg) - if ((arg.startswith('--coverpkg') or arg.startswith('--cover-omit')) - and '=' not in arg): - i += 1 - keep.append(args[i]) - i += 1 + for dest, (flags, default) in dest_to_flags.items(): + opt = getattr(options, dest) + if dest in store_args: + if opt != default: + keep.append(f"{flags[0]}={opt}") + elif dest in multi_args: + if opt: + for oparg in opt: + keep.append(f"{flags[0]}={oparg}") + elif dest in store_true_args and opt: + keep.append(flags[0]) return keep @@ -337,7 +363,11 @@ def try_import(fname, modpath): + global _testing_path try: + _testing_path[0] = os.path.dirname(fname) + old_sys_path = sys.path + sys.path = _testing_path mod = import_module(modpath) except ImportError: # this might be a module that's not in the same @@ -356,6 +386,8 @@ del sys.modules[modpath] finally: sys.path = oldpath + finally: + sys.path = old_sys_path return mod @@ -446,7 +478,9 @@ typ = lambda x: x if isinstance(action, _AppendAction): - setattr(options, name, [typ(s.strip()) for s in optstr.split(',') if s.strip()]) + oldval = getattr(options, name) + addval = [typ(s.strip()) for s in optstr.split(',') if s.strip()] + setattr(options, name, oldval+addval) else: setattr(options, name, typ(optstr)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testflo-1.4.15/testflo.egg-info/PKG-INFO new/testflo-1.4.22/testflo.egg-info/PKG-INFO --- old/testflo-1.4.15/testflo.egg-info/PKG-INFO 2023-12-28 19:25:25.000000000 +0100 +++ new/testflo-1.4.22/testflo.egg-info/PKG-INFO 1970-01-01 01:00:00.000000000 +0100 @@ -1,202 +0,0 @@ -Metadata-Version: 2.1 -Name: testflo -Version: 1.4.15 -Summary: A simple flow-based testing framework -License: Apache 2.0 -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 :: 3.6 -Classifier: Programming Language :: Python :: 3.7 -Classifier: Programming Language :: Python :: 3.8 -Classifier: Programming Language :: Python :: Implementation :: CPython -Description-Content-Type: text/markdown -License-File: LICENSE.txt -Requires-Dist: coverage>=6.0 - -[![PyPI version][1]][2] - -testflo -======= - -testflo is a python testing framework that uses a pipeline of -iterators to process test specifications, run the tests, and process the -results. - -Why write another testing framework? ------------------------------------- - -testflo was written to support testing of the OpenMDAO framework. -Some OpenMDAO features require execution under MPI while some others don't, -so we wanted a testing framework that could run all of our tests in the same -way and would allow us to build all of our tests using unittest.TestCase -objects that we were already familiar with. The MPI testing functionality -was originally implemented using the nose testing framework. It worked, but -was always buggy, and the size and complexity of the nose framework made it -difficult to know exactly what was going on. - -Enter testflo, an attempt to build a simpler testing framework that would have -the basic functionality of other test frameworks, with the additional -ability to run MPI unit tests that are very similar to regular unit tests. - - -Some testflo features ---------------------- - -* MPI unit testing -* *pre_announce* option to print test name before running in order to - quickly identify hanging MPI tests -* concurrent testing (on by default, use '-n 1' to turn it off) -* test coverage -* flexible execution - can be given a directory, a file, a module path, - *file:testcase.method*, *module:testcase.method*, or a file containing - a list of any of the above. Has options to generate test list files - containing all failed tests or all tests that execute within a certain - time limit. -* end of testing summary - - -Usage ------ - -For a full list of testflo options, execute the following: - -`testflo -h` - - -NOTE: Because testflo runs tests concurrently by default, your tests must be -written with concurrency in mind or they may fail. For example, if multiple -tests write output to a file with the same name, you have to make sure that those -tests are executed in different directories to prevent that file from being -corrupted. If your tests are not written to run concurrently, you can always -just run them with `testflo -n 1` and run them in serial instead. - -The following is an example of what an MPI unit test looks like. To tell -testflo that a TestCase is an MPI TestCase, you add a class attribute -called N_PROCS to it and set it to the number of MPI processes to use for the -test. That's all there is to it. Of course, depending on what sort of MPI code -you're testing, it's up to you to potentially test for different things on -different ranks. - - -```python - -class MyMPI_TestCase(TestCase): - - N_PROCS = 4 # this is how many MPI processes to use for this TestCase. - - def test_foo(self): - - # do your MPI testing here, e.g., - - if self.comm.rank == 0: - # some test only valid on rank 0... - - -``` - - -Here's an example of testflo output for openmdao.core: - - -``` - -openmdao$ testflo openmdao.core -............................................................................ -............................................................................ -............................................................................ -.............................. - -OK - -Passed: 258 -Failed: 0 -Skipped: 0 - - -Ran 258 tests using 8 processes -Wall clock time: 00:00:1.82 - -``` - -Running testflo in verbose mode on openmdao.core.test.test_problem is shown -below. The verbose output contains the full test name as well as the elapsed -time and memory usage. - - -``` - -openmdao$ testflo openmdao.core.test.test_problem -v -openmdao.core.test.test_problem:TestCheckSetup.test_pbo_messages ... OK (00:00:0.02, 69 MB) -openmdao.core.test.test_problem:TestProblem.test_check_promotes ... OK (00:00:0.01, 69 MB) -openmdao.core.test.test_problem:TestProblem.test_conflicting_connections ... OK (00:00:0.02, 69 MB) -openmdao.core.test.test_problem:TestProblem.test_conflicting_promoted_state_vars ... OK (00:00:0.00, 69 MB) -openmdao.core.test.test_problem:TestProblem.test_conflicting_promotions ... OK (00:00:0.01, 69 MB) -openmdao.core.test.test_problem:TestCheckSetup.test_out_of_order ... OK (00:00:0.02, 69 MB) -openmdao.core.test.test_problem:TestProblem.test_explicit_connection_errors ... OK (00:00:0.02, 69 MB) -openmdao.core.test.test_problem:TestProblem.test_find_subsystem ... OK (00:00:0.00, 69 MB) -openmdao.core.test.test_problem:TestCheckSetup.test_cycle ... OK (00:00:0.06, 69 MB) -openmdao.core.test.test_problem:TestProblem.test_input_input_explicit_conns_no_conn ... OK (00:00:0.01, 69 MB) -openmdao.core.test.test_problem:TestProblem.test_illegal_desvar ... OK (00:00:0.01, 69 MB) -openmdao.core.test.test_problem:TestProblem.test_input_input_explicit_conns_w_conn ... OK (00:00:0.02, 69 MB) -openmdao.core.test.test_problem:TestProblem.test_check_connections ... OK (00:00:0.06, 69 MB) -openmdao.core.test.test_problem:TestProblem.test_mode_auto ... OK (00:00:0.03, 69 MB) -openmdao.core.test.test_problem:TestProblem.test_check_parallel_derivs ... OK (00:00:0.01, 69 MB) -openmdao.core.test.test_problem:TestProblem.test_simplest_run ... OK (00:00:0.01, 69 MB) -openmdao.core.test.test_problem:TestProblem.test_basic_run ... OK (00:00:0.03, 69 MB) -openmdao.core.test.test_problem:TestProblem.test_change_solver_after_setup ... OK (00:00:0.04, 69 MB) -openmdao.core.test.test_problem:TestProblem.test_no_vecs ... OK (00:00:0.08, 69 MB) -openmdao.core.test.test_problem:TestProblem.test_src_idx_gt_src_size ... OK (00:00:0.01, 69 MB) -openmdao.core.test.test_problem:TestProblem.test_src_idx_neg ... OK (00:00:0.01, 69 MB) -openmdao.core.test.test_problem:TestProblem.test_simplest_run_w_promote ... OK (00:00:0.02, 69 MB) -openmdao.core.test.test_problem:TestProblem.test_unconnected_param_access ... OK (00:00:0.01, 69 MB) -openmdao.core.test.test_problem:TestProblem.test_variable_access_before_setup ... OK (00:00:0.00, 69 MB) -openmdao.core.test.test_problem:TestProblem.test_scalar_sizes ... OK (00:00:0.07, 69 MB) -openmdao.core.test.test_problem:TestProblem.test_byobj_run ... OK (00:00:0.01, 69 MB) -openmdao.core.test.test_problem:TestProblem.test_error_change_after_setup ... OK (00:00:0.31, 70 MB) -openmdao.core.test.test_problem:TestProblem.test_unconnected_param_access_with_promotes ... OK (00:00:0.04, 69 MB) -openmdao.core.test.test_problem:TestProblem.test_variable_access ... OK (00:00:0.06, 69 MB) -openmdao.core.test.test_problem:TestProblem.test_iprint ... OK (00:00:0.25, 73 MB) - - -OK - -Passed: 30 -Failed: 0 -Skipped: 0 - - -Ran 30 tests using 8 processes -Wall clock time: 00:00:1.17 - -``` - -Operating Systems and Python Versions -------------------------------------- - -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. It requires -python 3.5 or higher. - - -You can install testflo directly from github using the following command: - -`pip install git+https://github.com/OpenMDAO/testflo.git` - - -or install from PYPI using: - - -`pip install testflo` - - - -If you try it out and find any problems, submit them as issues on github at -https://github.com/OpenMDAO/testflo. - -[1]: https://badge.fury.io/py/testflo.svg "PyPI Version" -[2]: https://badge.fury.io/py/testflo "testflo @PyPI" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testflo-1.4.15/testflo.egg-info/SOURCES.txt new/testflo-1.4.22/testflo.egg-info/SOURCES.txt --- old/testflo-1.4.15/testflo.egg-info/SOURCES.txt 2023-12-28 19:25:25.000000000 +0100 +++ new/testflo-1.4.22/testflo.egg-info/SOURCES.txt 1970-01-01 01:00:00.000000000 +0100 @@ -1,32 +0,0 @@ -DESIGN.txt -LICENSE.txt -MANIFEST.in -README.md -setup.cfg -setup.py -testflo/__init__.py -testflo/__main__.py -testflo/benchmark.py -testflo/cover.py -testflo/deprecations.py -testflo/devnull.py -testflo/discover.py -testflo/duration.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/utresult.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.4.15/testflo.egg-info/dependency_links.txt new/testflo-1.4.22/testflo.egg-info/dependency_links.txt --- old/testflo-1.4.15/testflo.egg-info/dependency_links.txt 2023-12-28 19:25:25.000000000 +0100 +++ new/testflo-1.4.22/testflo.egg-info/dependency_links.txt 1970-01-01 01:00:00.000000000 +0100 @@ -1 +0,0 @@ - diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testflo-1.4.15/testflo.egg-info/entry_points.txt new/testflo-1.4.22/testflo.egg-info/entry_points.txt --- old/testflo-1.4.15/testflo.egg-info/entry_points.txt 2023-12-28 19:25:25.000000000 +0100 +++ new/testflo-1.4.22/testflo.egg-info/entry_points.txt 1970-01-01 01:00:00.000000000 +0100 @@ -1,2 +0,0 @@ -[console_scripts] -testflo = testflo.main:main diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testflo-1.4.15/testflo.egg-info/requires.txt new/testflo-1.4.22/testflo.egg-info/requires.txt --- old/testflo-1.4.15/testflo.egg-info/requires.txt 2023-12-28 19:25:25.000000000 +0100 +++ new/testflo-1.4.22/testflo.egg-info/requires.txt 1970-01-01 01:00:00.000000000 +0100 @@ -1 +0,0 @@ -coverage>=6.0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/testflo-1.4.15/testflo.egg-info/top_level.txt new/testflo-1.4.22/testflo.egg-info/top_level.txt --- old/testflo-1.4.15/testflo.egg-info/top_level.txt 2023-12-28 19:25:25.000000000 +0100 +++ new/testflo-1.4.22/testflo.egg-info/top_level.txt 1970-01-01 01:00:00.000000000 +0100 @@ -1 +0,0 @@ -testflo
