Retire Packaging Deb project repos

This commit is part of a series to retire the Packaging Deb
project. Step 2 is to remove all content from the project
repos, replacing it with a README notification where to find
ongoing work, and how to recover the repo if needed at some
future point (as in
https://docs.openstack.org/infra/manual/drivers.html#retiring-a-project).

Change-Id: Ia5a15a7e460cb5c23a7f74818b0f916da6ecaa85
This commit is contained in:
Tony Breeds 2017-09-12 16:08:56 -06:00
parent 459665e8bf
commit f3df9c92d3
54 changed files with 14 additions and 3910 deletions

View File

@ -1,7 +0,0 @@
[run]
branch = True
source = os_testr
omit = os_testr/tests/*,os_testr/openstack/*
[report]
ignore_errors = True

54
.gitignore vendored
View File

@ -1,54 +0,0 @@
*.py[cod]
# C extensions
*.so
# Packages
*.egg*
dist
build
eggs
parts
bin
var
sdist
develop-eggs
.installed.cfg
lib
lib64
# Installer logs
pip-log.txt
# Unit test / coverage reports
cover/
.coverage*
!.coveragerc
.tox
nosetests.xml
.testrepository
.venv
# Translations
*.mo
# Mr Developer
.mr.developer.cfg
.project
.pydevproject
# Complexity
output/*.html
output/*/index.html
# Sphinx
doc/build
# pbr generates these
AUTHORS
ChangeLog
# Editors
*~
.*.swp
.*sw?

View File

@ -1,4 +0,0 @@
[gerrit]
host=review.openstack.org
port=29418
project=openstack/os-testr.git

View File

@ -1,3 +0,0 @@
# Format is:
# <preferred e-mail> <other e-mail 1>
# <preferred e-mail> <other e-mail 2>

View File

@ -1,7 +0,0 @@
[DEFAULT]
test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \
OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \
OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \
${PYTHON:-python} -m subunit.run discover -t ./ . $LISTOPT $IDOPTION
test_id_option=--load-list $IDFILE
test_list_option=--list

View File

@ -1,16 +0,0 @@
If you would like to contribute to the development of OpenStack,
you must follow the steps in this page:
http://docs.openstack.org/infra/manual/developers.html
Once those steps have been completed, changes to OpenStack
should be submitted for review via the Gerrit tool, following
the workflow documented at:
http://docs.openstack.org/infra/manual/developers.html#development-workflow
Pull requests submitted through GitHub will be ignored.
Bugs should be filed on Launchpad, not GitHub:
https://bugs.launchpad.net/os-testr

View File

@ -1,4 +0,0 @@
os-testr Style Commandments
===============================================
Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/

176
LICENSE
View File

@ -1,176 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.

View File

@ -1,6 +0,0 @@
include AUTHORS
include ChangeLog
exclude .gitignore
exclude .gitreview
global-exclude *.pyc

14
README Normal file
View File

@ -0,0 +1,14 @@
This project is no longer maintained.
The contents of this repository are still available in the Git
source code management system. To see the contents of this
repository before it reached its end of life, please check out the
previous commit with "git checkout HEAD^1".
For ongoing work on maintaining OpenStack packages in the Debian
distribution, please see the Debian OpenStack packaging team at
https://wiki.debian.org/OpenStack/.
For any further questions, please email
openstack-dev@lists.openstack.org or join #openstack-dev on
Freenode.

View File

@ -1,28 +0,0 @@
========
os-testr
========
.. image:: https://img.shields.io/pypi/v/os-testr.svg
:target: https://pypi.python.org/pypi/os-testr/
:alt: Latest Version
.. image:: https://img.shields.io/pypi/dm/os-testr.svg
:target: https://pypi.python.org/pypi/os-testr/
:alt: Downloads
A testr wrapper to provide functionality for OpenStack projects.
* Free software: Apache license
* Documentation: http://docs.openstack.org/os-testr/
* Source: http://git.openstack.org/cgit/openstack/os-testr
* Bugs: http://bugs.launchpad.net/os-testr
Features
--------
* ``ostestr``: a testr wrapper that uses subunit-trace for output and builds
some helpful extra functionality around testr
* ``subunit-trace``: an output filter for a subunit stream which provides
useful information about the run
* ``subunit2html``: generates a test results html page from a subunit stream
* ``generate-subunit``: generate a subunit stream for a single test

View File

@ -1,2 +0,0 @@
[python: **.py]

View File

@ -1,94 +0,0 @@
# -*- coding: utf-8 -*-
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import sys
sys.path.insert(0, os.path.abspath('../..'))
# -- General configuration ----------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = [
'sphinx.ext.autodoc',
# 'sphinx.ext.intersphinx',
'openstackdocstheme'
]
# openstackdocstheme options
repository_name = 'openstack/os-testr'
bug_project = 'os-testr'
bug_tag = ''
# Must set this variable to include year, month, day, hours, and minutes.
html_last_updated_fmt = '%Y-%m-%d %H:%M'
# autodoc generation is a bit aggressive and a nuisance when doing heavy
# text edit cycles.
# execute "export SPHINX_DEBUG=1" in your terminal to disable
# The suffix of source filenames.
source_suffix = '.rst'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'os-testr'
copyright = u'2015, Matthew Treinish'
# If true, '()' will be appended to :func: etc. cross-reference text.
add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
add_module_names = True
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# -- Options for HTML output --------------------------------------------------
# The theme to use for HTML and HTML Help pages. Major themes that come with
# Sphinx are currently 'default' and 'sphinxdoc'.
# html_theme_path = ["."]
html_theme = 'openstackdocs'
# html_static_path = ['static']
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
html_use_smartypants = False
# Output file base name for HTML help builder.
htmlhelp_basename = '%sdoc' % project
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass
# [howto/manual]).
latex_documents = [
('index',
'%s.tex' % project,
u'%s Documentation' % project,
u'Matthew Treinish', 'manual'),
]
man_pages = [('ostestr', 'ostestr', 'tooling to run OpenStack tests',
['Matthew Treinish'], 1),
('subunit_trace', 'subunit-trace', 'pretty output filter for '
'subunit streams', ['Matthew Treinish'], 1),
('subunit2html', 'subunit2html', 'generate a html results page '
'from a subunit stream', ['Matthew Treinish'], 1)]
# Example configuration for intersphinx: refer to the Python standard library.
# intersphinx_mapping = {'http://docs.python.org/': None}

View File

@ -1,4 +0,0 @@
============
Contributing
============
.. include:: ../../../CONTRIBUTING.rst

View File

@ -1,35 +0,0 @@
====================================
Welcome to os-testr's documentation!
====================================
.. image:: https://img.shields.io/pypi/v/os-testr.svg
:target: https://pypi.python.org/pypi/os-testr/
:alt: Latest Version
.. image:: https://img.shields.io/pypi/dm/os-testr.svg
:target: https://pypi.python.org/pypi/os-testr/
:alt: Downloads
A testr wrapper to provide functionality for OpenStack projects.
* Free software: Apache license
* Documentation: http://docs.openstack.org/os-testr/
* Source: http://git.openstack.org/cgit/openstack/os-testr
* Bugs: http://bugs.launchpad.net/os-testr
Contents:
.. toctree::
:maxdepth: 2
install/index
contributor/index
user/index
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

View File

@ -1,12 +0,0 @@
============
Installation
============
At the command line::
$ pip install os-testr
Or, if you have virtualenvwrapper installed::
$ mkvirtualenv os-testr
$ pip install os-testr

View File

@ -1,51 +0,0 @@
.. generate_subunit:
generate-subunit
================
generate-subunit is a simple tool to, as its name implies, generate a subunit
stream. It will generate a stream with a single test result to STDOUT. The
subunit protocol lets you concatenate multiple streams together so if you want
to generate a stream with multiple just append the output of multiple executions
of generate-subunit.
Summary
-------
generate-subunit timestamp secs [status] [test_id]
Usage
-----
generate-subunit has 2 mandatory arguments. These are needed to specify when
the "test" started running and how long it took. The first argument is a POSIX
timestamp (which can returned by the date util using ``date +%s``) for when it
started running. The second argument is the number of seconds it took for the
execution to finish. For example::
$ generate-subunit $(date +%s) 42
will generate a stream with the test_id 'devstack' successfully running for 42
secs starting when the command was executed. This leads into the 2 optional
arguments. The first optional argument is for specifying the status. This must
be the 3rd argument when calling generate-subunit. Valid status options can
be found in the `testtools documentation`_. If status is not specified it will
default to success. For example::
$ generate-subunit $(date +%s) 42 fail
will be the same as the previous example except that it marks the test as
failing.
.. _testtools documentation: http://testtools.readthedocs.io/en/latest/api.html#testtools.StreamResult.status
The other optional argument is the test_id (aka test name) and is used to
identify the "test" being run. For better or worse this defaults to *devstack*.
(which is an artifact of why this tool was originally created) Note, this must
be the 4th argument when calling generate-subunit. This means you also must
specify a status if you want to set your own test_id. For example::
$ generate-subunit %(date +%s) 42 fail my_little_test
will generate a subunit stream as before except instead the test will be named
my_little_test.

View File

@ -1 +0,0 @@
.. include:: ../../../ChangeLog

View File

@ -1,14 +0,0 @@
=====
Usage
=====
This section contains the documentation for each of tools packaged in os-testr
.. toctree::
:maxdepth: 2
ostestr
subunit_trace
subunit2html
generate_subunit
history

View File

@ -1,256 +0,0 @@
.. _ostestr:
ostestr
=======
The ostestr command provides a wrapper around the testr command included in
the testrepository package. It's designed to build on the functionality
included in testr and workaround several UI bugs in the short term. By default
it also has output that is much more useful for OpenStack's test suites which
are lengthy in both runtime and number of tests. Please note that the CLI
semantics are still a work in progress as the project is quite young, so
default behavior might change in future version.
Summary
-------
ostestr [-b|--blacklist-file <blacklist_file>] [-r|--regex REGEX]
[-w|--whitelist-file <whitelist_file>]
[-p|--pretty] [--no-pretty] [-s|--subunit] [-l|--list]
[-n|--no-discover <test_id>] [--slowest] [--no-slowest]
[--pdb <test_id>] [--parallel] [--serial]
[-c|--concurrency <workers>] [--until-failure] [--print-exclude]
Options
-------
--blacklist-file BLACKLIST_FILE, -b BLACKLIST_FILE
Path to a blacklist file, this file contains a
separate regex exclude on each newline
--whitelist-file WHITELIST_FILE, -w WHITELIST_FILE
Path to a whitelist file, this file contains a
separate regex on each newline
--regex REGEX, -r REGEX
A normal testr selection regex.
--black-regex BLACK_REGEX, -B BLACK_REGEX
Test rejection regex. If the test cases durring a
search opration matches, it will be removed from the
final test list.
--pretty, -p
Print pretty output from subunit-trace. This is
mutually exclusive with --subunit
--no-pretty
Disable the pretty output with subunit-trace
--subunit, -s
output the raw subunit v2 from the test run this is
mutually exclusive with --pretty
--list, -l
List all the tests which will be run.
--no-discover TEST_ID, -n TEST_ID
Takes in a single test to bypasses test discover and
just execute the test specified
--slowest
After the test run print the slowest tests
--no-slowest
After the test run don't print the slowest tests
--pdb TEST_ID
Run a single test that has pdb traces added
--parallel
Run tests in parallel (this is the default)
--serial
Run tests serially
--concurrency WORKERS, -c WORKERS
The number of workers to use when running in parallel.
By default this is the number of cpus
--until-failure
Run the tests in a loop until a failure is
encountered. Running with subunit or prettyoutput
enable will force the loop to run testsserially
--print-exclude
If an exclude file is used this option will prints the
comment from the same line and all skipped tests
before the test run
Running Tests
-------------
os-testr is primarily for running tests at it's basic level you just invoke
ostestr to run a test suite for a project. (assuming it's setup to run tests
using testr already) For example::
$ ostestr
This will run tests in parallel (with the number of workers matching the number
of CPUs) and with subunit-trace output. If you need to run tests in serial you
can use the serial option::
$ ostestr --serial
Or if you need to adjust the concurrency but still run in parallel you can use
-c/--concurrency::
$ ostestr --concurrency 2
If you only want to run an individual test module or more specific (a single
class, or test) and parallel execution doesn't matter, you can use the
-n/--no-discover to skip test discovery and just directly calls subunit.run on
the tests under the covers. Bypassing discovery is desirable when running a
small subset of tests in a larger test suite because the discovery time can
often far exceed the total run time of the tests.
For example::
$ ostestr --no-discover test.test_thing.TestThing.test_thing_method
Additionally, if you need to run a single test module, class, or single test
with pdb enabled you can use --pdb to directly call testtools.run under the
covers which works with pdb. For example::
$ ostestr --pdb tests.test_thing.TestThing.test_thing_method
Test Selection
--------------
ostestr intially designed to build on top of the test selection in testr.
testr only exposed a regex option to select tests. This functionality is
exposed via the --regex option. For example::
$ ostestr --regex 'magic\.regex'
This will do a straight passthrough of the provided regex to testr.
When ostestr is asked to do more complex test selection than a sinlge regex,
it will ask testr for a full list of tests than passing the filtered test list
back to testr.
ostestr allows you do to do simple test exclusion via apssing rejection/black regex::
$ ostestr --black-regex 'slow_tests|bad_tests'
ostestr also allow you to combine these argumants::
$ ostestr --regex ui\.interface --black-regex 'slow_tests|bad_tests'
Here first we selected all tests which matches to 'ui\.interface',
than we are dropping all test which matches
'slow_tests|bad_tests' from the final list.
ostestr also allows you to specify a blacklist file to define a set
of regexes to exclude. You can specify a blacklist file with the
--blacklist_file/-b option, for example::
$ ostestr --blacklist_file $path_to_file
The format for the file is line separated regex, with '#' used to signify the
start of a comment on a line. For example::
# Blacklist File
^regex1 # Excludes these tests
.*regex2 # exclude those tests
The regex used in the blacklist File or passed as argument, will be used
to drop tests from the initial selection list.
Will generate a list which will exclude both any tests
matching '^regex1' and '.*regex2'. If a blacklist file is used in conjunction
with the --regex option the regex specified with --regex will be used for the intial
test selection. Also it's worth noting that the
regex test selection options can not be used in conjunction with the
--no-discover or --pdb options described in the previous section. This is
because the regex selection requires using testr under the covers to actually
do the filtering, and those 2 options do not use testr.
The dual of the blacklist file is the whitelist file which altering the initial
test selection regex, by joining the white list elements by '|'.
You can specify the path to the file with --whitelist_file/-w, for example::
$ ostestr --whitelist_file $path_to_file
The format for the file is more or less identical to the blacklist file::
# Whitelist File
^regex1 # Include these tests
.*regex2 # include those tests
However, instead of excluding the matches it will include them.
It's also worth noting that you can use the test list option to dry run any
selection arguments you are using. You just need to use --list/-l with your
selection options to do this, for example::
$ ostestr --regex 'regex3.*' --blacklist_file blacklist.txt --list
This will list all the tests which will be run by ostestr using that combination
of arguments.
Please not that all of this selection functionality will be expanded on in the
future and a default grammar for selecting multiple tests will be chosen in a
future release. However as of right now all current arguments (which have
guarantees on always remaining in place) are still required to perform any
selection logic while this functionality is still under development.
Output Options
--------------
By default ostestr will use subunit-trace as the output filter on the test
run. It will also print the slowest tests from the run after the run is
concluded. You can disable the printing the slowest tests with the --no-slowest
flag, for example::
$ ostestr --no-slowest
If you'd like to disable the subunit-trace output you can do this using
--no-pretty::
$ ostestr --no-pretty
ostestr also provides the option to just output the raw subunit stream on
STDOUT with --subunit/-s. Note if you want to use this you also have to
specify --no-pretty as the subunit-trace output and the raw subunit output
are mutually exclusive. For example, to get raw subunit output the arguments
would be::
$ ostestr --no-pretty --subunit
An additional option on top of the blacklist file is --print-exclude option.
When this option is specified when using a blacklist file before the tests are
run ostestr will print all the tests it will be excluding from the blacklist
file. If a line in the blacklist file has a comment that will be printed before
listing the tests which will be excluded by that line's regex. If no comment is
present on a line the regex from that line will be used instead. For example,
if you were using the example blacklist file from the previous section the
output before the regular test run output would be::
$ ostestr -b blacklist-file blacklist.txt --print-exclude
Excludes these tests
regex1_match
regex1_exclude
exclude those tests
regex2_match
regex2_exclude
...
Notes for running with tox
--------------------------
If you use `tox`_ for running your tests and call ostestr as the test command
it's recommended that you set a posargs following ostestr on the commands
stanza. For example::
[testenv]
commands = ostestr {posargs}
.. _tox: https://tox.readthedocs.org/en/latest/
this will enable end users to pass args to configure the output, use the
selection logic, or any other options directly from the tox cli. This will let
tox take care of the venv management and the environment separation but enable
direct access to all of the ostestr options to easily customize your test run.
For example, assuming the above posargs usage you would be to do::
$ tox -epy34 -- --regex ^regex1
or to skip discovery::
$ tox -epy34 -- -n test.test_thing.TestThing.test_thing_method

View File

@ -1,33 +0,0 @@
.. _subunit2html:
subunit2html
============
subunit2html is a tool that takes in a subunit stream file and will output an
html page
Summary
-------
subunit2html subunit_stream [output]
Usage
-----
subunit2html takes in 1 mandatory argument. This is used to specify the location
of the subunit stream file. For example::
$ subunit2html subunit_stream
By default subunit2html will store the generated html results file at
results.html file in the current working directory.
An optional second argument can be provided to set the output path of the html
results file that is generated. If it is provided this will be the output path
for saving the generated file, otherwise results.html in the current working
directory will be used. For example::
$ subunit2html subunit_stream test_results.html
will write the generated html results file to test_results.html in the current
working directory

View File

@ -1,112 +0,0 @@
.. _subunit_trace:
subunit-trace
=============
subunit-trace is an output filter for subunit streams. It is often used in
conjunction with test runners that emit subunit to enable a consistent and
useful realtime output from a test run.
Summary
-------
subunit-trace [--fails|-f] [--failonly] [--perc-diff|-d] [--no-summary]
[--diff-threshold|-t <threshold>] [--color]
Options
-------
--no-failure-debug, -n
Disable printing failure debug information in realtime
--fails, -f
Print failure debug information after the stream is
processed
--failonly
Don't print success items
--perc-diff, -d
Print percent change in run time on each test
--diff-threshold THRESHOLD, -t THRESHOLD
Threshold to use for displaying percent change from the
avg run time. If one is not specified the percent
change will always be displayed.
--no-summary
Don't print the summary of the test run after completes
--color
Print result with colors
Usage
-----
subunit-trace will take a subunit stream in via STDIN. This is the only input
into the tool. It will then print on STDOUT the formatted test result output
for the test run information contained in the stream.
A subunit v2 stream must be passed into subunit-trace. If only a subunit v1
stream is available you must use the subunit-1to2 utility to convert it before
passing the stream into subunit-trace. For example this can be done by chaining
pipes::
$ cat subunit_v1 | subunit-1to2 | subunit-trace
Adjusting per test output
^^^^^^^^^^^^^^^^^^^^^^^^^
subunit-trace provides several options to customize it's output. This allows
users to customize the output from subunit-trace to suit their needs. The output
from subunit-trace basically comes in 2 parts, the per test output, and the
summary at the end. By default subunit-trace will print failure messages during
the per test output, meaning when a test fails it will also print the message
and any traceback and other attachments at that time. However this can be
disabled by using --no-failure-debug, -n. For example::
$ testr run --subunit | subunit-trace --no-failure-debug
Here is also the option to print all failures together at the end of the test
run before the summary view. This is done using the --fails/-f option. For
example::
$ testr run --subunit | subunit-trace --fails
Often the --fails and --no-failure-debug options are used in conjunction to
only print failures at the end of a test run. This is useful for large test
suites where an error message might be lost in the noise. To do this ::
$ testr run --subunit | subunit-trace --fails --no-failure-debug
By default subunit-trace will print a line for each test after it completes with
the test status. However, if you only want to see the run time output for
failures and not any other test status you can use the --failonly option. For
example::
$ testr run --subunit | subunit-trace --failonly
The last output option provided by subunit-trace is to disable the summary view
of the test run which is normally displayed at the end of a run. You can do
this using the --no-summary option. For example::
$ testr run --subunit | subunit-trace --no-summary
Show per test run time percent change
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
subunit-trace provides an option to display the percent change in run time
from the previous run. To do this subunit-trace leverages the testr internals
a bit. It uses the times.dbm database which, the file repository type in
testrepository will create, to get the previous run time for a test. If testr
hasn't ever been used before or for whatever reason subunit-trace is unable to
find the times.dbm file from testr no percentages will be displayed even if it's
enabled. Additionally, if a test is run which does not have an entry in the
times.dbm file will not have a percentage printed for it.
To enable this feature you use --perc-diff/-d, for example::
$ testr run --subunit | subunit-trace --perc-diff
There is also the option to set a threshold value for this option. If used it
acts as an absolute value and only percentage changes that exceed it will be
printed. Use the --diff-threshold/-t option to set a threshold, for example::
$ testr run --subunit | subunit-trace --perc-diff --threshold 45
This will only display percent differences when the change in run time is either
>=45% faster or <=45% slower.

View File

@ -1,19 +0,0 @@
# -*- coding: utf-8 -*-
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import pbr.version
__version__ = pbr.version.VersionInfo(
'os_testr').version_string()

View File

@ -1,58 +0,0 @@
#!/usr/bin/env python2
# Copyright 2015 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import datetime
import sys
import pbr.version
import subunit
from subunit import iso8601
__version__ = pbr.version.VersionInfo('os_testr').version_string()
def main():
if '--version' in sys.argv:
print(__version__)
exit(0)
start_time = datetime.datetime.fromtimestamp(float(sys.argv[1])).replace(
tzinfo=iso8601.UTC)
elapsed_time = datetime.timedelta(seconds=int(sys.argv[2]))
stop_time = start_time + elapsed_time
if len(sys.argv) > 3:
status = sys.argv[3]
else:
status = 'success'
if len(sys.argv) > 4:
test_id = sys.argv[4]
else:
test_id = 'devstack'
# Write the subunit test
output = subunit.v2.StreamResultToBytes(sys.stdout)
output.startTestRun()
output.status(timestamp=start_time, test_id=test_id)
# Write the end of the test
output.status(test_status=status, timestamp=stop_time, test_id=test_id)
output.stopTestRun()
if __name__ == '__main__':
main()

View File

@ -1,315 +0,0 @@
#!/usr/bin/env python2
# Copyright 2015 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import argparse
import atexit
import copy
import io
import os
import subprocess
import sys
import tempfile
import pbr.version
from subunit import run as subunit_run
from testtools import run as testtools_run
from os_testr import regex_builder as rb
from os_testr import testlist_builder as tlb
__version__ = pbr.version.VersionInfo('os_testr').version_string()
def get_parser(args):
parser = argparse.ArgumentParser(
description='Tool to run openstack tests')
parser.add_argument('--version', action='version',
version='%s' % __version__)
parser.add_argument('--blacklist-file', '-b', '--blacklist_file',
help='Path to a blacklist file, this file '
'contains a separate regex exclude on each '
'newline')
parser.add_argument('--whitelist-file', '-w', '--whitelist_file',
help='Path to a whitelist file, this file '
'contains a separate regex on each newline.')
group = parser.add_mutually_exclusive_group()
group.add_argument('--regex', '-r',
help='A normal testr selection regex.')
group.add_argument('--path', metavar='FILE_OR_DIRECTORY',
help='A file name or directory of tests to run.')
group.add_argument('--no-discover', '-n', metavar='TEST_ID',
help="Takes in a single test to bypasses test "
"discover and just execute the test specified. "
"A file name may be used in place of a test "
"name.")
parser.add_argument('--black-regex', '-B',
help='Test rejection regex. If a test cases name '
'matches on re.search() operation , '
'it will be removed from the final test list. '
'Effectively the black-regex is added to '
' black regex list, but you do need to edit a file. '
'The black filtering happens after the initial '
' white selection, which by default is everything.')
pretty = parser.add_mutually_exclusive_group()
pretty.add_argument('--pretty', '-p', dest='pretty', action='store_true',
help='Print pretty output from subunit-trace. This is '
'mutually exclusive with --subunit')
pretty.add_argument('--no-pretty', dest='pretty', action='store_false',
help='Disable the pretty output with subunit-trace')
parser.add_argument('--subunit', '-s', action='store_true',
help='output the raw subunit v2 from the test run '
'this is mutually exclusive with --pretty')
parser.add_argument('--list', '-l', action='store_true',
help='List all the tests which will be run.')
parser.add_argument('--color', action='store_true',
help='Use color in the pretty output')
slowest = parser.add_mutually_exclusive_group()
slowest.add_argument('--slowest', dest='slowest', action='store_true',
help="after the test run print the slowest tests")
slowest.add_argument('--no-slowest', dest='slowest', action='store_false',
help="after the test run don't print the slowest "
"tests")
parser.add_argument('--pdb', metavar='TEST_ID',
help='Run a single test that has pdb traces added')
parallel = parser.add_mutually_exclusive_group()
parallel.add_argument('--parallel', dest='parallel', action='store_true',
help='Run tests in parallel (this is the default)')
parallel.add_argument('--serial', dest='parallel', action='store_false',
help='Run tests serially')
parser.add_argument('--concurrency', '-c', type=int, metavar='WORKERS',
help='The number of workers to use when running in '
'parallel. By default this is the number of cpus')
parser.add_argument('--until-failure', action='store_true',
help='Run the tests in a loop until a failure is '
'encountered. Running with subunit or pretty'
'output enable will force the loop to run tests'
'serially')
parser.add_argument('--print-exclude', action='store_true',
help='If an exclude file is used this option will '
'prints the comment from the same line and all '
'skipped tests before the test run')
parser.set_defaults(pretty=True, slowest=True, parallel=True)
return parser.parse_known_args(args)
def call_testr(regex, subunit, pretty, list_tests, slowest, parallel, concur,
until_failure, color, list_of_tests=None, others=None):
others = others or []
if parallel:
cmd = ['testr', 'run', '--parallel']
if concur:
cmd.append('--concurrency=%s' % concur)
else:
cmd = ['testr', 'run']
if list_tests:
cmd = ['testr', 'list-tests']
elif (subunit or pretty) and not until_failure:
cmd.append('--subunit')
elif not (subunit or pretty) and until_failure:
cmd.append('--until-failure')
if list_of_tests:
test_fd, test_file_name = tempfile.mkstemp()
atexit.register(os.remove, test_file_name)
test_file = os.fdopen(test_fd, 'w')
test_file.write('\n'.join(list_of_tests) + '\n')
test_file.close()
cmd.extend(('--load-list', test_file_name))
elif regex:
cmd.append(regex)
env = copy.deepcopy(os.environ)
if pretty:
subunit_trace_cmd = ['subunit-trace', '--no-failure-debug', '-f']
if color:
subunit_trace_cmd.append('--color')
# This workaround is necessary because of lp bug 1411804 it's super hacky
# and makes tons of unfounded assumptions, but it works for the most part
if (subunit or pretty) and until_failure:
test_list = rb._get_test_list(regex, env)
count = 0
failed = False
if not test_list:
print("No tests to run")
return 1
# If pretty or subunit output is desired manually loop forever over
# test individually and generate the desired output in a linear series
# this avoids 1411804 while retaining most of the desired behavior
while True:
for test in test_list:
if pretty:
cmd = ['python', '-m', 'subunit.run', test]
ps = subprocess.Popen(cmd, env=env, stdout=subprocess.PIPE)
subunit_trace_cmd.append('--no-summary')
proc = subprocess.Popen(subunit_trace_cmd,
env=env,
stdin=ps.stdout)
ps.stdout.close()
proc.communicate()
if proc.returncode > 0:
failed = True
break
else:
try:
subunit_run.main([sys.argv[0], test], sys.stdout)
except SystemExit as e:
if e > 0:
print("Ran %s tests without failure" % count)
return 1
else:
raise
count = count + 1
if failed:
print("Ran %s tests without failure" % count)
return 0
# If not until-failure special case call testr like normal
elif pretty and not list_tests:
cmd.extend(others)
ps = subprocess.Popen(cmd, env=env, stdout=subprocess.PIPE)
proc = subprocess.Popen(subunit_trace_cmd,
env=env, stdin=ps.stdout)
ps.stdout.close()
else:
cmd.extend(others)
proc = subprocess.Popen(cmd, env=env)
proc.communicate()
return_code = proc.returncode
if slowest and not list_tests:
print("\nSlowest Tests:\n")
slow_proc = subprocess.Popen(['testr', 'slowest'], env=env)
slow_proc.communicate()
return return_code
def call_subunit_run(test_id, pretty, subunit):
env = copy.deepcopy(os.environ)
cmd_save_results = ['testr', 'load', '--subunit']
if pretty:
# Use subunit run module
cmd = ['python', '-m', 'subunit.run', test_id]
ps = subprocess.Popen(cmd, env=env, stdout=subprocess.PIPE)
# Save subunit results via testr
pfile = subprocess.Popen(cmd_save_results, env=env,
stdin=ps.stdout, stdout=subprocess.PIPE)
ps.stdout.close()
# Transform output via subunit-trace
proc = subprocess.Popen(['subunit-trace', '--no-failure-debug', '-f'],
env=env, stdin=pfile.stdout)
pfile.stdout.close()
proc.communicate()
return proc.returncode
elif subunit:
sstdout = io.BytesIO()
subunit_run.main([sys.argv[0], test_id], sstdout)
pfile = subprocess.Popen(cmd_save_results, env=env,
stdin=subprocess.PIPE)
pfile.communicate(input=sstdout.getvalue())
else:
testtools_run.main([sys.argv[0], test_id], sys.stdout)
def _ensure_testr():
if not os.path.isdir('.testrepository'):
subprocess.call(['testr', 'init'])
def _select_and_call_runner(opts, exclude_regex, others):
ec = 1
_ensure_testr()
if not opts.no_discover and not opts.pdb:
ec = call_testr(exclude_regex, opts.subunit, opts.pretty, opts.list,
opts.slowest, opts.parallel, opts.concurrency,
opts.until_failure, opts.color, None, others)
else:
if others:
print('Unexpected arguments: ' + ' '.join(others))
return 2
test_to_run = opts.no_discover or opts.pdb
if test_to_run.find('/') != -1:
test_to_run = rb.path_to_regex(test_to_run)
ec = call_subunit_run(test_to_run, opts.pretty, opts.subunit)
return ec
def _call_testr_with_list(opts, test_list, others):
ec = 1
_ensure_testr()
if opts.list:
print("\n".join(test_list))
return 0
ec = call_testr(None, opts.subunit, opts.pretty, opts.list,
opts.slowest, opts.parallel, opts.concurrency,
opts.until_failure, opts.color, test_list, others)
return ec
def ostestr(args):
opts, others = get_parser(args)
if opts.pretty and opts.subunit:
msg = ('Subunit output and pretty output cannot be specified at the '
'same time')
print(msg)
return 2
if opts.list and opts.no_discover:
msg = ('you can not list tests when you are bypassing discovery to '
'run a single test')
print(msg)
return 3
if not opts.parallel and opts.concurrency:
msg = "You can't specify a concurrency to use when running serially"
print(msg)
return 4
if (opts.pdb or opts.no_discover) and opts.until_failure:
msg = "You can not use until_failure mode with pdb or no-discover"
print(msg)
return 5
if ((opts.pdb or opts.no_discover) and
(opts.blacklist_file or opts.whitelist_file)):
msg = "You can not use blacklist or whitelist with pdb or no-discover"
print(msg)
return 6
if ((opts.pdb or opts.no_discover) and (opts.black_regex)):
msg = "You can not use black-regex with pdb or no-discover"
print(msg)
return 7
if opts.path:
regex = rb.path_to_regex(opts.path)
else:
regex = opts.regex
if opts.blacklist_file or opts.whitelist_file or opts.black_regex:
list_of_tests = tlb.construct_list(opts.blacklist_file,
opts.whitelist_file,
regex,
opts.black_regex,
opts.print_exclude)
return (_call_testr_with_list(opts, list_of_tests, others))
else:
return (_select_and_call_runner(opts, regex, others))
def main():
exit(ostestr(sys.argv[1:]))
if __name__ == '__main__':
main()

View File

@ -1,103 +0,0 @@
# Copyright 2016 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import copy
import os
import subprocess
def _get_test_list(regex, env=None):
env = env or copy.deepcopy(os.environ)
testr_args = ['testr', 'list-tests']
if regex:
testr_args.append(regex)
proc = subprocess.Popen(testr_args, env=env,
stdout=subprocess.PIPE, universal_newlines=True)
out = proc.communicate()[0]
raw_test_list = out.split('\n')
bad = False
test_list = []
exclude_list = ['OS_', 'CAPTURE', 'TEST_TIMEOUT', 'PYTHON',
'subunit.run discover']
for line in raw_test_list:
for exclude in exclude_list:
if exclude in line or not line:
bad = True
break
if not bad:
test_list.append(line)
bad = False
return test_list
def print_skips(regex, message):
test_list = _get_test_list(regex)
if test_list:
if message:
print(message)
else:
print('Skipped because of regex %s:' % regex)
for test in test_list:
print(test)
# Extra whitespace to separate
print('\n')
def path_to_regex(path):
root, _ = os.path.splitext(path)
return root.replace('/', '.')
def get_regex_from_whitelist_file(file_path):
lines = []
for line in open(file_path).read().splitlines():
split_line = line.strip().split('#')
# Before the # is the regex
line_regex = split_line[0].strip()
if line_regex:
lines.append(line_regex)
return '|'.join(lines)
def construct_regex(blacklist_file, whitelist_file, regex, print_exclude):
"""Deprecated, please use testlist_builder.construct_list instead."""
if not blacklist_file:
exclude_regex = ''
else:
black_file = open(blacklist_file, 'r')
exclude_regex = ''
for line in black_file:
raw_line = line.strip()
split_line = raw_line.split('#')
# Before the # is the regex
line_regex = split_line[0].strip()
if len(split_line) > 1:
# After the # is a comment
comment = split_line[1].strip()
else:
comment = ''
if line_regex:
if print_exclude:
print_skips(line_regex, comment)
if exclude_regex:
exclude_regex = '|'.join([line_regex, exclude_regex])
else:
exclude_regex = line_regex
if exclude_regex:
exclude_regex = "^((?!" + exclude_regex + ").)*$"
if regex:
exclude_regex += regex
if whitelist_file:
exclude_regex += '%s' % get_regex_from_whitelist_file(whitelist_file)
return exclude_regex

View File

@ -1,765 +0,0 @@
#!/usr/bin/python
#
# Copyright 2012-2013 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Utility to convert a subunit stream to an html results file.
Code is adapted from the pyunit Html test runner at
http://tungwaiyip.info/software/HTMLTestRunner.html
Takes two arguments. First argument is path to subunit log file, second
argument is path of desired output file. Second argument is optional,
defaults to 'results.html'.
Original HTMLTestRunner License:
------------------------------------------------------------------------
Copyright (c) 2004-2007, Wai Yip Tung
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name Wai Yip Tung nor the names of its contributors may be
used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
import collections
import datetime
import io
import sys
import traceback
from xml.sax import saxutils
import pbr.version
import subunit
import testtools
__version__ = pbr.version.VersionInfo('os_testr').version_string()
class TemplateData(object):
"""Define a HTML template for report customerization and generation.
Overall structure of an HTML report
HTML
+------------------------+
|<html> |
| <head> |
| |
| STYLESHEET |
| +----------------+ |
| | | |
| +----------------+ |
| |
| </head> |
| |
| <body> |
| |
| HEADING |
| +----------------+ |
| | | |
| +----------------+ |
| |
| REPORT |
| +----------------+ |
| | | |
| +----------------+ |
| |
| ENDING |
| +----------------+ |
| | | |
| +----------------+ |
| |
| </body> |
|</html> |
+------------------------+
"""
STATUS = {
0: 'pass',
1: 'fail',
2: 'error',
3: 'skip',
}
DEFAULT_TITLE = 'Unit Test Report'
DEFAULT_DESCRIPTION = ''
# ------------------------------------------------------------------------
# HTML Template
HTML_TMPL = r"""<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>%(title)s</title>
<meta name="generator" content="%(generator)s"/>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
%(stylesheet)s
</head>
<body>
<script language="javascript" type="text/javascript"><!--
output_list = Array();
/* level - 0:Summary; 1:Failed; 2:All */
function showCase(level) {
trs = document.getElementsByTagName("tr");
for (var i = 0; i < trs.length; i++) {
tr = trs[i];
id = tr.id;
if (id.substr(0,2) == 'ft') {
if (level < 1) {
tr.className = 'hiddenRow';
}
else {
tr.className = '';
}
}
if (id.substr(0,2) == 'pt') {
if (level > 1) {
tr.className = '';
}
else {
tr.className = 'hiddenRow';
}
}
}
}
function showClassDetail(cid, count) {
var id_list = Array(count);
var toHide = 1;
for (var i = 0; i < count; i++) {
tid0 = 't' + cid.substr(1) + '.' + (i+1);
tid = 'f' + tid0;
tr = document.getElementById(tid);
if (!tr) {
tid = 'p' + tid0;
tr = document.getElementById(tid);
}
id_list[i] = tid;
if (tr.className) {
toHide = 0;
}
}
for (var i = 0; i < count; i++) {
tid = id_list[i];
if (toHide) {
document.getElementById('div_'+tid).style.display = 'none'
document.getElementById(tid).className = 'hiddenRow';
}
else {
document.getElementById(tid).className = '';
}
}
}
function showTestDetail(div_id){
var details_div = document.getElementById(div_id)
var displayState = details_div.style.display
// alert(displayState)
if (displayState != 'block' ) {
displayState = 'block'
details_div.style.display = 'block'
}
else {
details_div.style.display = 'none'
}
}
function html_escape(s) {
s = s.replace(/&/g,'&amp;');
s = s.replace(/</g,'&lt;');
s = s.replace(/>/g,'&gt;');
return s;
}
/* obsoleted by detail in <div>
function showOutput(id, name) {
var w = window.open("", //url
name,
"resizable,scrollbars,status,width=800,height=450");
d = w.document;
d.write("<pre>");
d.write(html_escape(output_list[id]));
d.write("\n");
d.write("<a href='javascript:window.close()'>close</a>\n");
d.write("</pre>\n");
d.close();
}
*/
--></script>
%(heading)s
%(report)s
%(ending)s
</body>
</html>
"""
# variables: (title, generator, stylesheet, heading, report, ending)
# ------------------------------------------------------------------------
# Stylesheet
#
# alternatively use a <link> for external style sheet, e.g.
# <link rel="stylesheet" href="$url" type="text/css">
STYLESHEET_TMPL = """
<style type="text/css" media="screen">
body { font-family: verdana, arial, helvetica, sans-serif;
font-size: 80%; }
table { font-size: 100%; width: 100%;}
pre { font-size: 80%; }
/* -- heading -------------------------------------------------------------- */
h1 {
font-size: 16pt;
color: gray;
}
.heading {
margin-top: 0ex;
margin-bottom: 1ex;
}
.heading .attribute {
margin-top: 1ex;
margin-bottom: 0;
}
.heading .description {
margin-top: 4ex;
margin-bottom: 6ex;
}
/* -- css div popup -------------------------------------------------------- */
a.popup_link {
}
a.popup_link:hover {
color: red;
}
.popup_window {
display: none;
overflow-x: scroll;
/*border: solid #627173 1px; */
padding: 10px;
background-color: #E6E6D6;
font-family: "Ubuntu Mono", "Lucida Console", "Courier New", monospace;
text-align: left;
font-size: 8pt;
}
}
/* -- report --------------------------------------------------------------- */
#show_detail_line {
margin-top: 3ex;
margin-bottom: 1ex;
}
#result_table {
width: 100%;
border-collapse: collapse;
border: 1px solid #777;
}
#header_row {
font-weight: bold;
color: white;
background-color: #777;
}
#result_table td {
border: 1px solid #777;
padding: 2px;
}
#total_row { font-weight: bold; }
.passClass { background-color: #6c6; }
.failClass { background-color: #c60; }
.errorClass { background-color: #c00; }
.passCase { color: #6c6; }
.failCase { color: #c60; font-weight: bold; }
.errorCase { color: #c00; font-weight: bold; }
.hiddenRow { display: none; }
.testcase { margin-left: 2em; }
td.testname {width: 40%}
td.small {width: 40px}
/* -- ending --------------------------------------------------------------- */
#ending {
}
</style>
"""
# ------------------------------------------------------------------------
# Heading
#
HEADING_TMPL = """<div class='heading'>
<h1>%(title)s</h1>
%(parameters)s
<p class='description'>%(description)s</p>
</div>
""" # variables: (title, parameters, description)
HEADING_ATTRIBUTE_TMPL = """
<p class='attribute'><strong>%(name)s:</strong> %(value)s</p>
""" # variables: (name, value)
# ------------------------------------------------------------------------
# Report
#
REPORT_TMPL = """
<p id='show_detail_line'>Show
<a href='javascript:showCase(0)'>Summary</a>
<a href='javascript:showCase(1)'>Failed</a>
<a href='javascript:showCase(2)'>All</a>
</p>
<table id='result_table'>
<colgroup>
<col align='left' />
<col align='right' />
<col align='right' />
<col align='right' />
<col align='right' />
<col align='right' />
<col align='right' />
<col align='right' />
</colgroup>
<tr id='header_row'>
<td>Test Group/Test case</td>
<td>Count</td>
<td>Pass</td>
<td>Fail</td>
<td>Error</td>
<td>Skip</td>
<td>View</td>
<td> </td>
</tr>
%(test_list)s
<tr id='total_row'>
<td>Total</td>
<td>%(count)s</td>
<td>%(Pass)s</td>
<td>%(fail)s</td>
<td>%(error)s</td>
<td>%(skip)s</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
</table>
""" # variables: (test_list, count, Pass, fail, error)
REPORT_CLASS_TMPL = r"""
<tr class='%(style)s'>
<td class="testname">%(desc)s</td>
<td class="small">%(count)s</td>
<td class="small">%(Pass)s</td>
<td class="small">%(fail)s</td>
<td class="small">%(error)s</td>
<td class="small">%(skip)s</td>
<td class="small"><a href="javascript:showClassDetail('%(cid)s',%(count)s)"
>Detail</a></td>
<td> </td>
</tr>
""" # variables: (style, desc, count, Pass, fail, error, cid)
REPORT_TEST_WITH_OUTPUT_TMPL = r"""
<tr id='%(tid)s' class='%(Class)s'>
<td class='%(style)s'><div class='testcase'>%(desc)s</div></td>
<td colspan='7' align='left'>
<!--css div popup start-->
<a class="popup_link" onfocus='this.blur();'
href="javascript:showTestDetail('div_%(tid)s')" >
%(status)s</a>
<div id='div_%(tid)s' class="popup_window">
<div style='text-align: right; color:red;cursor:pointer'>
<a onfocus='this.blur();'
onclick="document.getElementById('div_%(tid)s').style.display = 'none' " >
[x]</a>
</div>
<pre>
%(script)s
</pre>
</div>
<!--css div popup end-->
</td>
</tr>
""" # variables: (tid, Class, style, desc, status)
REPORT_TEST_NO_OUTPUT_TMPL = r"""
<tr id='%(tid)s' class='%(Class)s'>
<td class='%(style)s'><div class='testcase'>%(desc)s</div></td>
<td colspan='6' align='center'>%(status)s</td>
</tr>
""" # variables: (tid, Class, style, desc, status)
REPORT_TEST_OUTPUT_TMPL = r"""
%(id)s: %(output)s
""" # variables: (id, output)
# ------------------------------------------------------------------------
# ENDING
#
ENDING_TMPL = """<div id='ending'>&nbsp;</div>"""
# -------------------- The end of the Template class -------------------
class ClassInfoWrapper(object):
def __init__(self, name, mod):
self.name = name
self.mod = mod
def __repr__(self):
return "%s" % (self.name)
class HtmlOutput(testtools.TestResult):
"""Output test results in html."""
def __init__(self, html_file='result.html'):
super(HtmlOutput, self).__init__()
self.success_count = 0
self.failure_count = 0
self.error_count = 0
self.skip_count = 0
self.result = []
self.html_file = html_file
def addSuccess(self, test):
self.success_count += 1
output = test.shortDescription()
if output is None:
output = test.id()
self.result.append((0, test, output, ''))
def addSkip(self, test, err):
output = test.shortDescription()
if output is None:
output = test.id()
self.skip_count += 1
self.result.append((3, test, output, ''))
def addError(self, test, err):
output = test.shortDescription()
if output is None:
output = test.id()
# Skipped tests are handled by SkipTest Exceptions.
# if err[0] == SkipTest:
# self.skip_count += 1
# self.result.append((3, test, output, ''))
else:
self.error_count += 1
_exc_str = self.formatErr(err)
self.result.append((2, test, output, _exc_str))
def addFailure(self, test, err):
print(test)
self.failure_count += 1
_exc_str = self.formatErr(err)
output = test.shortDescription()
if output is None:
output = test.id()
self.result.append((1, test, output, _exc_str))
def formatErr(self, err):
exctype, value, tb = err
return ''.join(traceback.format_exception(exctype, value, tb))
def stopTestRun(self):
super(HtmlOutput, self).stopTestRun()
self.stopTime = datetime.datetime.now()
report_attrs = self._getReportAttributes()
generator = 'subunit2html %s' % __version__
heading = self._generate_heading(report_attrs)
report = self._generate_report()
ending = self._generate_ending()
output = TemplateData.HTML_TMPL % dict(
title=saxutils.escape(TemplateData.DEFAULT_TITLE),
generator=generator,
stylesheet=TemplateData.STYLESHEET_TMPL,
heading=heading,
report=report,
ending=ending,
)
if self.html_file:
with open(self.html_file, 'wb') as html_file:
html_file.write(output.encode('utf8'))
def _getReportAttributes(self):
"""Return report attributes as a list of (name, value)."""
status = []
if self.success_count:
status.append('Pass %s' % self.success_count)
if self.failure_count:
status.append('Failure %s' % self.failure_count)
if self.error_count:
status.append('Error %s' % self.error_count)
if self.skip_count:
status.append('Skip %s' % self.skip_count)
if status:
status = ' '.join(status)
else:
status = 'none'
return [
('Status', status),
]
def _generate_heading(self, report_attrs):
a_lines = []
for name, value in report_attrs:
line = TemplateData.HEADING_ATTRIBUTE_TMPL % dict(
name=saxutils.escape(name),
value=saxutils.escape(value),
)
a_lines.append(line)
heading = TemplateData.HEADING_TMPL % dict(
title=saxutils.escape(TemplateData.DEFAULT_TITLE),
parameters=''.join(a_lines),
description=saxutils.escape(TemplateData.DEFAULT_DESCRIPTION),
)
return heading
def _generate_report(self):
rows = []
sortedResult = self._sortResult(self.result)
for cid, (cls, cls_results) in enumerate(sortedResult):
# subtotal for a class
np = nf = ne = ns = 0
for n, t, o, e in cls_results:
if n == 0:
np += 1
elif n == 1:
nf += 1
elif n == 2:
ne += 1
else:
ns += 1
# format class description
if cls.mod == "__main__":
name = cls.name
else:
name = "%s" % (cls.name)
doc = cls.__doc__ and cls.__doc__.split("\n")[0] or ""
desc = doc and '%s: %s' % (name, doc) or name
row = TemplateData.REPORT_CLASS_TMPL % dict(
style=(ne > 0 and 'errorClass' or nf > 0
and 'failClass' or 'passClass'),
desc = desc,
count = np + nf + ne + ns,
Pass = np,
fail = nf,
error = ne,
skip = ns,
cid = 'c%s' % (cid + 1),
)
rows.append(row)
for tid, (n, t, o, e) in enumerate(cls_results):
self._generate_report_test(rows, cid, tid, n, t, o, e)
report = TemplateData.REPORT_TMPL % dict(
test_list=''.join(rows),
count=str(self.success_count + self.failure_count +
self.error_count + self.skip_count),
Pass=str(self.success_count),
fail=str(self.failure_count),
error=str(self.error_count),
skip=str(self.skip_count),
)
return report
def _sortResult(self, result_list):
# unittest does not seems to run in any particular order.
# Here at least we want to group them together by class.
rmap = {}
classes = []
# Differentiate between classes that have test failures so we can sort
# them at the top of the html page for easier troubleshooting
clsmap_has_failure = collections.defaultdict(bool)
def track_has_failure(name, n):
if n == 1 or n == 2:
clsmap_has_failure[name] = True
for n, t, o, e in result_list:
if hasattr(t, '_tests'):
for inner_test in t._tests:
name = self._add_cls(rmap, classes, inner_test,
(n, inner_test, o, e))
track_has_failure(name, n)
else:
name = self._add_cls(rmap, classes, t, (n, t, o, e))
track_has_failure(name, n)
failclasses = []
passclasses = []
for cls in classes:
append_to = (failclasses if clsmap_has_failure[str(cls)]
else passclasses)
append_to.append(cls)
classort = lambda s: str(s)
sortedfailclasses = sorted(failclasses, key=classort)
sortedpassclasses = sorted(passclasses, key=classort)
sortedclasses = sortedfailclasses + sortedpassclasses
r = [(cls, rmap[str(cls)]) for cls in sortedclasses]
return r
def _add_cls(self, rmap, classes, test, data_tuple):
if hasattr(test, 'test'):
test = test.test
if test.__class__ == subunit.RemotedTestCase:
cl = test._RemotedTestCase__description.rsplit('.', 1)[0]
else:
cl = test.id().rsplit('.', 1)[0]
mod = cl.rsplit('.', 1)[0]
cls = ClassInfoWrapper(cl, mod)
if not str(cls) in rmap:
rmap[str(cls)] = []
classes.append(cls)
rmap[str(cls)].append(data_tuple)
return str(cls)
def _generate_report_test(self, rows, cid, tid, n, t, o, e):
# e.g. 'pt1.1', 'ft1.1', etc
# ptx.x for passed/skipped tests and ftx.x for failed/errored tests.
has_output = bool(o or e)
tid = ((n == 0 or n == 3) and
'p' or 'f') + 't%s.%s' % (cid + 1, tid + 1)
name = t.id().split('.')[-1]
# if shortDescription is not the function name, use it
if t.shortDescription().find(name) == -1:
doc = t.shortDescription()
else:
doc = None
desc = doc and ('%s: %s' % (name, doc)) or name
tmpl = (has_output and TemplateData.REPORT_TEST_WITH_OUTPUT_TMPL
or TemplateData.REPORT_TEST_NO_OUTPUT_TMPL)
script = TemplateData.REPORT_TEST_OUTPUT_TMPL % dict(
id=tid,
output=saxutils.escape(o + e),
)
row = tmpl % dict(
tid=tid,
Class=((n == 0 or n == 3) and 'hiddenRow' or 'none'),
style=(n == 2 and 'errorCase' or
(n == 1 and 'failCase' or 'none')),
desc=desc,
script=script,
status=TemplateData.STATUS[n],
)
rows.append(row)
if not has_output:
return
def _generate_ending(self):
return TemplateData.ENDING_TMPL
def startTestRun(self):
super(HtmlOutput, self).startTestRun()
class FileAccumulator(testtools.StreamResult):
def __init__(self):
super(FileAccumulator, self).__init__()
self.route_codes = collections.defaultdict(io.BytesIO)
def status(self, **kwargs):
if kwargs.get('file_name') != 'stdout':
return
file_bytes = kwargs.get('file_bytes')
if not file_bytes:
return
route_code = kwargs.get('route_code')
stream = self.route_codes[route_code]
stream.write(file_bytes)
def main():
if '--version' in sys.argv:
print(__version__)
exit(0)
if len(sys.argv) < 2:
print("Need at least one argument: path to subunit log.")
exit(1)
subunit_file = sys.argv[1]
if len(sys.argv) > 2:
html_file = sys.argv[2]
else:
html_file = 'results.html'
html_result = HtmlOutput(html_file)
stream = open(subunit_file, 'rb')
# Feed the subunit stream through both a V1 and V2 parser.
# Depends on having the v2 capable libraries installed.
# First V2.
# Non-v2 content and captured non-test output will be presented as file
# segments called stdout.
suite = subunit.ByteStreamToStreamResult(stream, non_subunit_name='stdout')
# The HTML output code is in legacy mode.
result = testtools.StreamToExtendedDecorator(html_result)
# Divert non-test output
accumulator = FileAccumulator()
result = testtools.StreamResultRouter(result)
result.add_rule(accumulator, 'test_id', test_id=None)
result.startTestRun()
suite.run(result)
# Now reprocess any found stdout content as V1 subunit
for bytes_io in accumulator.route_codes.values():
bytes_io.seek(0)
suite = subunit.ProtocolTestCase(bytes_io)
suite.run(html_result)
result.stopTestRun()
if __name__ == '__main__':
main()

View File

@ -1,403 +0,0 @@
#!/usr/bin/env python
# Copyright 2014 Hewlett-Packard Development Company, L.P.
# Copyright 2014 Samsung Electronics
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Trace a subunit stream in reasonable detail and high accuracy."""
from __future__ import absolute_import
import argparse
import datetime
import functools
import os
import re
import sys
import pbr.version
import subunit
import testtools
from os_testr.utils import colorizer
# NOTE(mtreinish) on python3 anydbm was renamed dbm and the python2 dbm module
# was renamed to dbm.ndbm, this block takes that into account
try:
import anydbm as dbm
except ImportError:
import dbm
DAY_SECONDS = 60 * 60 * 24
FAILS = []
RESULTS = {}
def total_seconds(timedelta):
# NOTE(mtreinish): This method is built-in to the timedelta class in
# python >= 2.7 it is here to enable it's use on older versions
return ((timedelta.days * DAY_SECONDS + timedelta.seconds) * 10 ** 6 +
timedelta.microseconds) / 10 ** 6
def cleanup_test_name(name, strip_tags=True, strip_scenarios=False):
"""Clean up the test name for display.
By default we strip out the tags in the test because they don't help us
in identifying the test that is run to it's result.
Make it possible to strip out the testscenarios information (not to
be confused with tempest scenarios) however that's often needed to
indentify generated negative tests.
"""
if strip_tags:
tags_start = name.find('[')
tags_end = name.find(']')
if tags_start > 0 and tags_end > tags_start:
newname = name[:tags_start]
newname += name[tags_end + 1:]
name = newname
if strip_scenarios:
tags_start = name.find('(')
tags_end = name.find(')')
if tags_start > 0 and tags_end > tags_start:
newname = name[:tags_start]
newname += name[tags_end + 1:]
name = newname
return name
def get_duration(timestamps):
start, end = timestamps
if not start or not end:
duration = ''
else:
delta = end - start
duration = '%d.%06ds' % (
delta.days * DAY_SECONDS + delta.seconds, delta.microseconds)
return duration
def find_worker(test):
"""Get the worker number.
If there are no workers because we aren't in a concurrent environment,
assume the worker number is 0.
"""
for tag in test['tags']:
if tag.startswith('worker-'):
return int(tag[7:])
return 0
# Print out stdout/stderr if it exists, always
def print_attachments(stream, test, all_channels=False):
"""Print out subunit attachments.
Print out subunit attachments that contain content. This
runs in 2 modes, one for successes where we print out just stdout
and stderr, and an override that dumps all the attachments.
"""
channels = ('stdout', 'stderr')
for name, detail in test['details'].items():
# NOTE(sdague): the subunit names are a little crazy, and actually
# are in the form pythonlogging:'' (with the colon and quotes)
name = name.split(':')[0]
if detail.content_type.type == 'test':
detail.content_type.type = 'text'
if (all_channels or name in channels) and detail.as_text():
title = "Captured %s:" % name
stream.write("\n%s\n%s\n" % (title, ('~' * len(title))))
# indent attachment lines 4 spaces to make them visually
# offset
for line in detail.as_text().split('\n'):
line = line.encode('utf8')
stream.write(" %s\n" % line)
def find_test_run_time_diff(test_id, run_time):
times_db_path = os.path.join(os.path.join(os.getcwd(), '.testrepository'),
'times.dbm')
if os.path.isfile(times_db_path):
try:
test_times = dbm.open(times_db_path)
except Exception:
return False
try:
avg_runtime = float(test_times.get(str(test_id), False))
except Exception:
try:
avg_runtime = float(test_times[str(test_id)])
except Exception:
avg_runtime = False
if avg_runtime and avg_runtime > 0:
run_time = float(run_time.rstrip('s'))
perc_diff = ((run_time - avg_runtime) / avg_runtime) * 100
return perc_diff
return False
def show_outcome(stream, test, print_failures=False, failonly=False,
enable_diff=False, threshold='0', abbreviate=False,
enable_color=False):
global RESULTS
status = test['status']
# TODO(sdague): ask lifeless why on this?
if status == 'exists':
return
worker = find_worker(test)
name = cleanup_test_name(test['id'])
duration = get_duration(test['timestamps'])
if worker not in RESULTS:
RESULTS[worker] = []
RESULTS[worker].append(test)
# don't count the end of the return code as a fail
if name == 'process-returncode':
return
for color in [colorizer.AnsiColorizer, colorizer.NullColorizer]:
if not enable_color:
color = colorizer.NullColorizer(stream)
break
if color.supported():
color = color(stream)
break
if status == 'fail' or status == 'uxsuccess':
FAILS.append(test)
if abbreviate:
color.write('F', 'red')
else:
stream.write('{%s} %s [%s] ... ' % (
worker, name, duration))
color.write('FAILED', 'red')
stream.write('\n')
if not print_failures:
print_attachments(stream, test, all_channels=True)
elif not failonly:
if status == 'success' or status == 'xfail':
if abbreviate:
color.write('.', 'green')
else:
out_string = '{%s} %s [%s' % (worker, name, duration)
perc_diff = find_test_run_time_diff(test['id'], duration)
if enable_diff:
if perc_diff and abs(perc_diff) >= abs(float(threshold)):
if perc_diff > 0:
out_string = out_string + ' +%.2f%%' % perc_diff
else:
out_string = out_string + ' %.2f%%' % perc_diff
stream.write(out_string + '] ... ')
color.write('ok', 'green')
stream.write('\n')
print_attachments(stream, test)
elif status == 'skip':
if abbreviate:
color.write('S', 'blue')
else:
reason = test['details'].get('reason', '')
if reason:
reason = ': ' + reason.as_text()
stream.write('{%s} %s ... ' % (
worker, name))
color.write('SKIPPED', 'blue')
stream.write('%s' % (reason))
stream.write('\n')
else:
if abbreviate:
stream.write('%s' % test['status'][0])
else:
stream.write('{%s} %s [%s] ... %s\n' % (
worker, name, duration, test['status']))
if not print_failures:
print_attachments(stream, test, all_channels=True)
stream.flush()
def print_fails(stream):
"""Print summary failure report.
Currently unused, however there remains debate on inline vs. at end
reporting, so leave the utility function for later use.
"""
if not FAILS:
return
stream.write("\n==============================\n")
stream.write("Failed %s tests - output below:" % len(FAILS))
stream.write("\n==============================\n")
for f in FAILS:
stream.write("\n%s\n" % f['id'])
stream.write("%s\n" % ('-' * len(f['id'])))
print_attachments(stream, f, all_channels=True)
stream.write('\n')
def count_tests(key, value):
count = 0
for k, v in RESULTS.items():
for item in v:
if key in item:
if re.search(value, item[key]):
count += 1
return count
def run_time():
runtime = 0.0
for k, v in RESULTS.items():
for test in v:
test_dur = get_duration(test['timestamps']).strip('s')
# NOTE(toabctl): get_duration() can return an empty string
# which leads to a ValueError when casting to float
if test_dur:
runtime += float(test_dur)
return runtime
def worker_stats(worker):
tests = RESULTS[worker]
num_tests = len(tests)
stop_time = tests[-1]['timestamps'][1]
start_time = tests[0]['timestamps'][0]
if not start_time or not stop_time:
delta = 'N/A'
else:
delta = stop_time - start_time
return num_tests, str(delta)
def print_summary(stream, elapsed_time):
stream.write("\n======\nTotals\n======\n")
stream.write("Ran: %s tests in %.4f sec.\n" % (
count_tests('status', '.*'), total_seconds(elapsed_time)))
stream.write(" - Passed: %s\n" % count_tests('status', '^success$'))
stream.write(" - Skipped: %s\n" % count_tests('status', '^skip$'))
stream.write(" - Expected Fail: %s\n" % count_tests('status', '^xfail$'))
stream.write(" - Unexpected Success: %s\n" % count_tests('status',
'^uxsuccess$'))
stream.write(" - Failed: %s\n" % count_tests('status', '^fail$'))
stream.write("Sum of execute time for each test: %.4f sec.\n" % run_time())
# we could have no results, especially as we filter out the process-codes
if RESULTS:
stream.write("\n==============\nWorker Balance\n==============\n")
for w in range(max(RESULTS.keys()) + 1):
if w not in RESULTS:
stream.write(
" - WARNING: missing Worker %s! "
"Race in testr accounting.\n" % w)
else:
num, time = worker_stats(w)
out_str = " - Worker %s (%s tests) => %s" % (w, num, time)
if time.isdigit():
out_str += 's'
out_str += '\n'
stream.write(out_str)
__version__ = pbr.version.VersionInfo('os_testr').version_string()
def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument('--version', action='version',
version='%s' % __version__)
parser.add_argument('--no-failure-debug', '-n', action='store_true',
dest='print_failures', help='Disable printing failure '
'debug information in realtime')
parser.add_argument('--fails', '-f', action='store_true',
dest='post_fails', help='Print failure debug '
'information after the stream is proccesed')
parser.add_argument('--failonly', action='store_true',
dest='failonly', help="Don't print success items",
default=(
os.environ.get('TRACE_FAILONLY', False)
is not False))
parser.add_argument('--abbreviate', '-a', action='store_true',
dest='abbreviate', help='Print one character status'
'for each test')
parser.add_argument('--perc-diff', '-d', action='store_true',
dest='enable_diff',
help="Print percent change in run time on each test ")
parser.add_argument('--diff-threshold', '-t', dest='threshold',
help="Threshold to use for displaying percent change "
"from the avg run time. If one is not specified "
"the percent change will always be displayed")
parser.add_argument('--no-summary', action='store_true',
help="Don't print the summary of the test run after "
" completes")
parser.add_argument('--color', action='store_true',
help="Print results with colors")
return parser.parse_args()
def trace(stdin, stdout, print_failures=False, failonly=False,
enable_diff=False, abbreviate=False, color=False, post_fails=False,
no_summary=False):
stream = subunit.ByteStreamToStreamResult(
stdin, non_subunit_name='stdout')
outcomes = testtools.StreamToDict(
functools.partial(show_outcome, stdout,
print_failures=print_failures,
failonly=failonly,
enable_diff=enable_diff,
abbreviate=abbreviate,
enable_color=color))
summary = testtools.StreamSummary()
result = testtools.CopyStreamResult([outcomes, summary])
result = testtools.StreamResultRouter(result)
cat = subunit.test_results.CatFiles(stdout)
result.add_rule(cat, 'test_id', test_id=None)
start_time = datetime.datetime.utcnow()
result.startTestRun()
try:
stream.run(result)
finally:
result.stopTestRun()
stop_time = datetime.datetime.utcnow()
elapsed_time = stop_time - start_time
if count_tests('status', '.*') == 0:
print("The test run didn't actually run any tests")
return 1
if post_fails:
print_fails(stdout)
if not no_summary:
print_summary(stdout, elapsed_time)
# NOTE(mtreinish): Ideally this should live in testtools streamSummary
# this is just in place until the behavior lands there (if it ever does)
if count_tests('status', '^success$') == 0:
print("\nNo tests were successful during the run")
return 1
return 0 if summary.wasSuccessful() else 1
def main():
args = parse_args()
exit(trace(sys.stdin, sys.stdout, args.print_failures, args.failonly,
args.enable_diff, args.abbreviate, args.color, args.post_fails,
args.no_summary))
if __name__ == '__main__':
main()

View File

@ -1,107 +0,0 @@
# Copyright 2016 RedHat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from os_testr import regex_builder
import re
def black_reader(blacklist_file):
black_file = open(blacklist_file, 'r')
regex_comment_lst = [] # tupple of (regex_compild, msg, skipped_lst)
for line in black_file:
raw_line = line.strip()
split_line = raw_line.split('#')
# Before the # is the regex
line_regex = split_line[0].strip()
if len(split_line) > 1:
# After the # is a comment
comment = ''.join(split_line[1:]).strip()
else:
comment = 'Skipped because of regex %s:' % line_regex
if not line_regex:
continue
regex_comment_lst.append((re.compile(line_regex), comment, []))
return regex_comment_lst
def print_skips(regex, message, test_list):
for test in test_list:
print(test)
# Extra whitespace to separate
print('\n')
def construct_list(blacklist_file, whitelist_file, regex, black_regex,
print_exclude):
"""Filters the discovered test cases
:retrun: iterable of strings. The strings are full
test cases names, including tags like.:
"project.api.TestClass.test_case[positive]"
"""
if not regex:
regex = '' # handle the other false things
if whitelist_file:
white_re = regex_builder.get_regex_from_whitelist_file(whitelist_file)
else:
white_re = ''
if not regex and white_re:
regex = white_re
elif regex and white_re:
regex = '|'.join((regex, white_re))
if blacklist_file:
black_data = black_reader(blacklist_file)
else:
black_data = None
if black_regex:
msg = "Skipped because of regex provided as a command line argument:"
record = (re.compile(black_regex), msg, [])
if black_data:
black_data.append(record)
else:
black_data = [record]
search_filter = re.compile(regex)
# NOTE(afazekas): we do not want to pass a giant re
# to an external application due to the arg length limitatios
list_of_test_cases = [test_case for test_case in
regex_builder._get_test_list('')
if search_filter.search(test_case)]
set_of_test_cases = set(list_of_test_cases)
if not black_data:
return set_of_test_cases
# NOTE(afazekas): We might use a faster logic when the
# print option is not requested
for (rex, msg, s_list) in black_data:
for test_case in list_of_test_cases:
if rex.search(test_case):
# NOTE(mtreinish): In the case of overlapping regex the test
# case might have already been removed from the set of tests
if test_case in set_of_test_cases:
set_of_test_cases.remove(test_case)
s_list.append(test_case)
if print_exclude:
for (rex, msg, s_list) in black_data:
if s_list:
print_skips(rex, msg, s_list)
return set_of_test_cases

View File

@ -1,23 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright 2010-2011 OpenStack Foundation
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslotest import base
class TestCase(base.BaseTestCase):
"""Test case base class for all unit tests."""

View File

@ -1,23 +0,0 @@
# Copyright 2013 IBM Corp.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import testtools
class FakeTestClass(testtools.TestCase):
def test_pass(self):
self.assertTrue(False)
def test_pass_list(self):
test_list = ['test', 'a', 'b']
self.assertIn('fail', test_list)

View File

@ -1,23 +0,0 @@
# Copyright 2013 IBM Corp.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import testtools
class FakeTestClass(testtools.TestCase):
def test_pass(self):
self.assertTrue(True)
def test_pass_list(self):
test_list = ['test', 'a', 'b']
self.assertIn('test', test_list)

View File

@ -1,20 +0,0 @@
[metadata]
name = tempest_unit_tests
version = 1
summary = Fake Project for testing wrapper scripts
author = OpenStack
author-email = openstack-dev@lists.openstack.org
home-page = http://www.openstack.org/
classifier =
Intended Audience :: Information Technology
Intended Audience :: System Administrators
Intended Audience :: Developers
License :: OSI Approved :: Apache Software License
Operating System :: POSIX :: Linux
Programming Language :: Python
Programming Language :: Python :: 2
Programming Language :: Python :: 2.7
[global]
setup-hooks =
pbr.hooks.setup_hook

View File

@ -1,5 +0,0 @@
[DEFAULT]
test_command=${PYTHON:-python} -m subunit.run discover -t ./ ./tests $LISTOPT $IDOPTION
test_id_option=--load-list $IDFILE
test_list_option=--list
group_regex=([^\.]*\.)*

View File

@ -1,187 +0,0 @@
# -*- coding: utf-8 -*-
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
test_os_testr
----------------------------------
Tests for `os_testr` module.
"""
import io
import mock
from os_testr import ostestr as os_testr
from os_testr.tests import base
class TestGetParser(base.TestCase):
def test_pretty(self):
namespace = os_testr.get_parser(['--pretty'])
self.assertEqual(True, namespace[0].pretty)
namespace = os_testr.get_parser(['--no-pretty'])
self.assertEqual(False, namespace[0].pretty)
self.assertRaises(SystemExit, os_testr.get_parser,
['--no-pretty', '--pretty'])
def test_slowest(self):
namespace = os_testr.get_parser(['--slowest'])
self.assertEqual(True, namespace[0].slowest)
namespace = os_testr.get_parser(['--no-slowest'])
self.assertEqual(False, namespace[0].slowest)
self.assertRaises(SystemExit, os_testr.get_parser,
['--no-slowest', '--slowest'])
def test_parallel(self):
namespace = os_testr.get_parser(['--parallel'])
self.assertEqual(True, namespace[0].parallel)
namespace = os_testr.get_parser(['--serial'])
self.assertEqual(False, namespace[0].parallel)
self.assertRaises(SystemExit, os_testr.get_parser,
['--parallel', '--serial'])
class TestCallers(base.TestCase):
def test_no_discover(self):
namespace = os_testr.get_parser(['-n', 'project.tests.foo'])
def _fake_exit(arg):
self.assertTrue(arg)
def _fake_run(*args, **kwargs):
return 'project.tests.foo' in args
with mock.patch.object(os_testr, 'exit', side_effect=_fake_exit), \
mock.patch.object(os_testr, 'get_parser', return_value=namespace), \
mock.patch.object(os_testr,
'call_subunit_run',
side_effect=_fake_run):
os_testr.main()
def test_no_discover_path(self):
namespace = os_testr.get_parser(['-n', 'project/tests/foo'])
def _fake_exit(arg):
self.assertTrue(arg)
def _fake_run(*args, **kwargs):
return 'project.tests.foo' in args
with mock.patch.object(os_testr, 'exit', side_effect=_fake_exit), \
mock.patch.object(os_testr, 'get_parser', return_value=namespace), \
mock.patch.object(os_testr,
'call_subunit_run',
side_effect=_fake_run):
os_testr.main()
def test_pdb(self):
namespace = os_testr.get_parser(['--pdb', 'project.tests.foo'])
def _fake_exit(arg):
self.assertTrue(arg)
def _fake_run(*args, **kwargs):
return 'project.tests.foo' in args
with mock.patch.object(os_testr, 'exit', side_effect=_fake_exit), \
mock.patch.object(os_testr, 'get_parser', return_value=namespace), \
mock.patch.object(os_testr,
'call_subunit_run',
side_effect=_fake_run):
os_testr.main()
def test_pdb_path(self):
namespace = os_testr.get_parser(['--pdb', 'project/tests/foo'])
def _fake_exit(arg):
self.assertTrue(arg)
def _fake_run(*args, **kwargs):
return 'project.tests.foo' in args
with mock.patch.object(os_testr, 'exit', side_effect=_fake_exit), \
mock.patch.object(os_testr, 'get_parser', return_value=namespace), \
mock.patch.object(os_testr,
'call_subunit_run',
side_effect=_fake_run):
os_testr.main()
def test_call_subunit_run_pretty(self):
'''Test call_subunit_run
Test ostestr call_subunit_run function when:
Pretty is True
'''
pretty = True
subunit = False
with mock.patch('subprocess.Popen', autospec=True) as mock_popen:
mock_popen.return_value.returncode = 0
mock_popen.return_value.stdout = io.BytesIO()
os_testr.call_subunit_run('project.tests.foo', pretty, subunit)
# Validate Popen was called three times
self.assertTrue(mock_popen.called, 'Popen was never called')
count = mock_popen.call_count
self.assertEqual(3, count, 'Popen was called %s'
' instead of 3 times' % count)
# Validate Popen called the right functions
called = mock_popen.call_args_list
msg = "Function %s not called"
function = ['python', '-m', 'subunit.run', 'project.tests.foo']
self.assertIn(function, called[0][0], msg % 'subunit.run')
function = ['testr', 'load', '--subunit']
self.assertIn(function, called[1][0], msg % 'testr load')
function = ['subunit-trace', '--no-failure-debug', '-f']
self.assertIn(function, called[2][0], msg % 'subunit-trace')
def test_call_subunit_run_sub(self):
'''Test call_subunit run
Test ostestr call_subunit_run function when:
Pretty is False and Subunit is True
'''
pretty = False
subunit = True
with mock.patch('subprocess.Popen', autospec=True) as mock_popen:
os_testr.call_subunit_run('project.tests.foo', pretty, subunit)
# Validate Popen was called once
self.assertTrue(mock_popen.called, 'Popen was never called')
count = mock_popen.call_count
self.assertEqual(1, count, 'Popen was called more than once')
# Validate Popen called the right function
called = mock_popen.call_args
function = ['testr', 'load', '--subunit']
self.assertIn(function, called[0], "testr load not called")
def test_call_subunit_run_testtools(self):
'''Test call_subunit_run
Test ostestr call_subunit_run function when:
Pretty is False and Subunit is False
'''
pretty = False
subunit = False
with mock.patch('testtools.run.main', autospec=True) as mock_run:
os_testr.call_subunit_run('project.tests.foo', pretty, subunit)
# Validate testtool.run was called once
self.assertTrue(mock_run.called, 'testtools.run was never called')
count = mock_run.call_count
self.assertEqual(1, count, 'testtools.run called more than once')

View File

@ -1,189 +0,0 @@
# -*- coding: utf-8 -*-
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
import six
from os_testr import regex_builder as os_testr
from os_testr.tests import base
class TestPathToRegex(base.TestCase):
def test_file_name(self):
result = os_testr.path_to_regex("tests/network/v2/test_net.py")
self.assertEqual("tests.network.v2.test_net", result)
result = os_testr.path_to_regex("openstack/tests/network/v2")
self.assertEqual("openstack.tests.network.v2", result)
class TestConstructRegex(base.TestCase):
def test_regex_passthrough(self):
result = os_testr.construct_regex(None, None, 'fake_regex', False)
self.assertEqual(result, 'fake_regex')
def test_blacklist_regex_with_comments(self):
blacklist_file = six.StringIO()
for i in range(4):
blacklist_file.write('fake_regex_%s # A Comment\n' % i)
blacklist_file.seek(0)
with mock.patch('six.moves.builtins.open',
return_value=blacklist_file):
result = os_testr.construct_regex('fake_path', None, None, False)
self.assertEqual(
result,
"^((?!fake_regex_3|fake_regex_2|fake_regex_1|fake_regex_0).)*$")
def test_whitelist_regex_with_comments(self):
whitelist_file = six.StringIO()
for i in range(4):
whitelist_file.write('fake_regex_%s # A Comment\n' % i)
whitelist_file.seek(0)
with mock.patch('six.moves.builtins.open',
return_value=whitelist_file):
result = os_testr.construct_regex(None, 'fake_path', None, False)
self.assertEqual(
result,
"fake_regex_0|fake_regex_1|fake_regex_2|fake_regex_3")
def test_blacklist_regex_without_comments(self):
blacklist_file = six.StringIO()
for i in range(4):
blacklist_file.write('fake_regex_%s\n' % i)
blacklist_file.seek(0)
with mock.patch('six.moves.builtins.open',
return_value=blacklist_file):
result = os_testr.construct_regex('fake_path', None, None, False)
self.assertEqual(
result,
"^((?!fake_regex_3|fake_regex_2|fake_regex_1|fake_regex_0).)*$")
def test_blacklist_regex_with_comments_and_regex(self):
blacklist_file = six.StringIO()
for i in range(4):
blacklist_file.write('fake_regex_%s # Comments\n' % i)
blacklist_file.seek(0)
with mock.patch('six.moves.builtins.open',
return_value=blacklist_file):
result = os_testr.construct_regex('fake_path', None,
'fake_regex', False)
expected_regex = ("^((?!fake_regex_3|fake_regex_2|fake_regex_1|"
"fake_regex_0).)*$fake_regex")
self.assertEqual(result, expected_regex)
def test_blacklist_regex_without_comments_and_regex(self):
blacklist_file = six.StringIO()
for i in range(4):
blacklist_file.write('fake_regex_%s\n' % i)
blacklist_file.seek(0)
with mock.patch('six.moves.builtins.open',
return_value=blacklist_file):
result = os_testr.construct_regex('fake_path', None,
'fake_regex', False)
expected_regex = ("^((?!fake_regex_3|fake_regex_2|fake_regex_1|"
"fake_regex_0).)*$fake_regex")
self.assertEqual(result, expected_regex)
@mock.patch.object(os_testr, 'print_skips')
def test_blacklist_regex_with_comment_print_skips(self, print_mock):
blacklist_file = six.StringIO()
for i in range(4):
blacklist_file.write('fake_regex_%s # Comment\n' % i)
blacklist_file.seek(0)
with mock.patch('six.moves.builtins.open',
return_value=blacklist_file):
result = os_testr.construct_regex('fake_path', None,
None, True)
expected_regex = ("^((?!fake_regex_3|fake_regex_2|fake_regex_1|"
"fake_regex_0).)*$")
self.assertEqual(result, expected_regex)
calls = print_mock.mock_calls
self.assertEqual(len(calls), 4)
args = list(map(lambda x: x[1], calls))
self.assertIn(('fake_regex_0', 'Comment'), args)
self.assertIn(('fake_regex_1', 'Comment'), args)
self.assertIn(('fake_regex_2', 'Comment'), args)
self.assertIn(('fake_regex_3', 'Comment'), args)
@mock.patch.object(os_testr, 'print_skips')
def test_blacklist_regex_without_comment_print_skips(self, print_mock):
blacklist_file = six.StringIO()
for i in range(4):
blacklist_file.write('fake_regex_%s\n' % i)
blacklist_file.seek(0)
with mock.patch('six.moves.builtins.open',
return_value=blacklist_file):
result = os_testr.construct_regex('fake_path', None,
None, True)
expected_regex = ("^((?!fake_regex_3|fake_regex_2|"
"fake_regex_1|fake_regex_0).)*$")
self.assertEqual(result, expected_regex)
calls = print_mock.mock_calls
self.assertEqual(len(calls), 4)
args = list(map(lambda x: x[1], calls))
self.assertIn(('fake_regex_0', ''), args)
self.assertIn(('fake_regex_1', ''), args)
self.assertIn(('fake_regex_2', ''), args)
self.assertIn(('fake_regex_3', ''), args)
class TestWhitelistFile(base.TestCase):
def test_read_whitelist_file(self):
file_contents = """regex_a
regex_b"""
whitelist_file = six.StringIO()
whitelist_file.write(file_contents)
whitelist_file.seek(0)
with mock.patch('six.moves.builtins.open',
return_value=whitelist_file):
regex = os_testr.get_regex_from_whitelist_file('/path/to/not_used')
self.assertEqual('regex_a|regex_b', regex)
def test_whitelist_regex_without_comments_and_regex(self):
file_contents = """regex_a
regex_b"""
whitelist_file = six.StringIO()
whitelist_file.write(file_contents)
whitelist_file.seek(0)
with mock.patch('six.moves.builtins.open',
return_value=whitelist_file):
result = os_testr.construct_regex(None, 'fake_path',
None, False)
expected_regex = 'regex_a|regex_b'
self.assertEqual(result, expected_regex)
class TestGetTestList(base.TestCase):
def test__get_test_list(self):
test_list = os_testr._get_test_list('test__get_test_list')
self.assertIn('test__get_test_list', test_list[0])
def test__get_test_list_regex_is_empty(self):
test_list = os_testr._get_test_list('')
self.assertIn('', test_list[0])
def test__get_test_list_regex_is_none(self):
test_list = os_testr._get_test_list(None)
# NOTE(masayukig): We should get all of the tests. So we should have
# more than one test case.
self.assertGreater(len(test_list), 1)
self.assertIn('os_testr.tests.test_regex_builder.'
'TestGetTestList.test__get_test_list_regex_is_none',
test_list)

View File

@ -1,104 +0,0 @@
# Copyright 2015 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
import shutil
import subprocess
import tempfile
import testtools
from os_testr.tests import base
from six import StringIO
DEVNULL = open(os.devnull, 'wb')
class TestReturnCodes(base.TestCase):
def setUp(self):
super(TestReturnCodes, self).setUp()
# Setup test dirs
self.directory = tempfile.mkdtemp(prefix='ostestr-unit')
self.addCleanup(shutil.rmtree, self.directory)
self.test_dir = os.path.join(self.directory, 'tests')
os.mkdir(self.test_dir)
# Setup Test files
self.testr_conf_file = os.path.join(self.directory, '.testr.conf')
self.setup_cfg_file = os.path.join(self.directory, 'setup.cfg')
self.passing_file = os.path.join(self.test_dir, 'test_passing.py')
self.failing_file = os.path.join(self.test_dir, 'test_failing.py')
self.init_file = os.path.join(self.test_dir, '__init__.py')
self.setup_py = os.path.join(self.directory, 'setup.py')
shutil.copy('os_testr/tests/files/testr-conf', self.testr_conf_file)
shutil.copy('os_testr/tests/files/passing-tests', self.passing_file)
shutil.copy('os_testr/tests/files/failing-tests', self.failing_file)
shutil.copy('setup.py', self.setup_py)
shutil.copy('os_testr/tests/files/setup.cfg', self.setup_cfg_file)
shutil.copy('os_testr/tests/files/__init__.py', self.init_file)
self.stdout = StringIO()
self.stderr = StringIO()
# Change directory, run wrapper and check result
self.addCleanup(os.chdir, os.path.abspath(os.curdir))
os.chdir(self.directory)
def assertRunExit(self, cmd, expected, subunit=False):
p = subprocess.Popen(
"%s" % cmd, shell=True,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = p.communicate()
if not subunit:
self.assertEqual(
p.returncode, expected,
"Stdout: %s; Stderr: %s" % (out, err))
else:
self.assertEqual(p.returncode, expected,
"Expected return code: %s doesn't match actual "
"return code of: %s" % (expected, p.returncode))
def test_default_passing(self):
self.assertRunExit('ostestr --regex passing', 0)
def test_default_fails(self):
self.assertRunExit('ostestr', 1)
def test_default_passing_no_slowest(self):
self.assertRunExit('ostestr --no-slowest --regex passing', 0)
def test_default_fails_no_slowest(self):
self.assertRunExit('ostestr --no-slowest', 1)
def test_default_serial_passing(self):
self.assertRunExit('ostestr --serial --regex passing', 0)
def test_default_serial_fails(self):
self.assertRunExit('ostestr --serial', 1)
def test_testr_subunit_passing(self):
self.assertRunExit('ostestr --no-pretty --subunit --regex passing', 0,
subunit=True)
@testtools.skip('Skipped because of testrepository lp bug #1411804')
def test_testr_subunit_fails(self):
self.assertRunExit('ostestr --no-pretty --subunit', 1, subunit=True)
def test_testr_no_pretty_passing(self):
self.assertRunExit('ostestr --no-pretty --regex passing', 0)
def test_testr_no_pretty_fails(self):
self.assertRunExit('ostestr --no-pretty', 1)
def test_list(self):
self.assertRunExit('ostestr --list', 0)

View File

@ -1,78 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import sys
from ddt import data
from ddt import ddt
from subunit import RemotedTestCase
from testtools import PlaceHolder
from os_testr import subunit2html
from os_testr.tests import base
@ddt
class TestSubunit2html(base.TestCase):
@data(RemotedTestCase, PlaceHolder)
def test_class_parsing(self, test_cls):
"""Tests that the class paths are parsed for v1 & v2 tests"""
test_ = test_cls("example.path.to.test.method")
obj_ = subunit2html.HtmlOutput()
cls_ = []
obj_._add_cls({}, cls_, test_, ())
self.assertEqual("example.path.to.test", cls_[0].name)
@data(RemotedTestCase, PlaceHolder)
def test_result_sorting(self, test_cls):
tests = []
for i in range(9):
tests.append(test_cls('example.path.to.test%d.method' % i))
# addFailure, addError, and addSkip need the real exc_info
try:
raise Exception('fake')
except Exception:
err = sys.exc_info()
obj = subunit2html.HtmlOutput()
obj.addSuccess(tests[3])
obj.addSuccess(tests[1])
# example.path.to.test2 has a failure
obj.addFailure(tests[2], err)
obj.addSkip(tests[0], err)
obj.addSuccess(tests[8])
# example.path.to.test5 has a failure (error)
obj.addError(tests[5], err)
# example.path.to.test4 has a failure
obj.addFailure(tests[4], err)
obj.addSuccess(tests[7])
# example.path.to.test6 has a success, a failure, and a success
obj.addSuccess(tests[6])
obj.addFailure(tests[6], err)
obj.addSuccess(tests[6])
sorted_result = obj._sortResult(obj.result)
# _sortResult returns a list of results of format:
# [(class, [test_result_tuple, ...]), ...]
# sorted by str(class)
#
# Classes with failures (2, 4, 5, and 6) should be sorted separately
# at the top. The rest of the classes should be in sorted order after.
expected_class_order = ['example.path.to.test2',
'example.path.to.test4',
'example.path.to.test5',
'example.path.to.test6',
'example.path.to.test0',
'example.path.to.test1',
'example.path.to.test3',
'example.path.to.test7',
'example.path.to.test8']
for i, r in enumerate(sorted_result):
self.assertEqual(expected_class_order[i], str(r[0]))

View File

@ -1,96 +0,0 @@
# Copyright 2015 SUSE Linux GmbH
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from datetime import datetime as dt
import io
import os
import subprocess
import sys
from ddt import data
from ddt import ddt
from ddt import unpack
from mock import patch
import six
from os_testr import subunit_trace
from os_testr.tests import base
@ddt
class TestSubunitTrace(base.TestCase):
@data(([dt(2015, 4, 17, 22, 23, 14, 111111),
dt(2015, 4, 17, 22, 23, 14, 111111)],
"0.000000s"),
([dt(2015, 4, 17, 22, 23, 14, 111111),
dt(2015, 4, 17, 22, 23, 15, 111111)],
"1.000000s"),
([dt(2015, 4, 17, 22, 23, 14, 111111),
None],
""))
@unpack
def test_get_durating(self, timestamps, expected_result):
self.assertEqual(subunit_trace.get_duration(timestamps),
expected_result)
@data(([dt(2015, 4, 17, 22, 23, 14, 111111),
dt(2015, 4, 17, 22, 23, 14, 111111)],
0.0),
([dt(2015, 4, 17, 22, 23, 14, 111111),
dt(2015, 4, 17, 22, 23, 15, 111111)],
1.0),
([dt(2015, 4, 17, 22, 23, 14, 111111),
None],
0.0))
@unpack
def test_run_time(self, timestamps, expected_result):
patched_res = {
0: [
{'timestamps': timestamps}
]
}
with patch.dict(subunit_trace.RESULTS, patched_res, clear=True):
self.assertEqual(subunit_trace.run_time(), expected_result)
def test_return_code_all_skips(self):
skips_stream = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
'sample_streams/all_skips.subunit')
p = subprocess.Popen(['subunit-trace'], stdin=subprocess.PIPE)
with open(skips_stream, 'rb') as stream:
p.communicate(stream.read())
self.assertEqual(1, p.returncode)
def test_return_code_normal_run(self):
regular_stream = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
'sample_streams/successful.subunit')
p = subprocess.Popen(['subunit-trace'], stdin=subprocess.PIPE)
with open(regular_stream, 'rb') as stream:
p.communicate(stream.read())
self.assertEqual(0, p.returncode)
def test_trace(self):
regular_stream = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
'sample_streams/successful.subunit')
bytes_ = io.BytesIO()
with open(regular_stream, 'rb') as stream:
bytes_.write(six.binary_type(stream.read()))
bytes_.seek(0)
stdin = io.TextIOWrapper(io.BufferedReader(bytes_))
returncode = subunit_trace.trace(stdin, sys.stdout)
self.assertEqual(0, returncode)

View File

@ -1,138 +0,0 @@
# -*- coding: utf-8 -*-
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
import re
import six
from os_testr import testlist_builder as list_builder
from os_testr.tests import base
class TestBlackReader(base.TestCase):
def test_black_reader(self):
blacklist_file = six.StringIO()
for i in range(4):
blacklist_file.write('fake_regex_%s\n' % i)
blacklist_file.write('fake_regex_with_note_%s # note\n' % i)
blacklist_file.seek(0)
with mock.patch('six.moves.builtins.open',
return_value=blacklist_file):
result = list_builder.black_reader('fake_path')
self.assertEqual(2 * 4, len(result))
note_cnt = 0
# not assuming ordering, mainly just testing the type
for r in result:
self.assertEqual(r[2], [])
if r[1] == 'note':
note_cnt += 1
self.assertIn('search', dir(r[0])) # like a compiled regex
self.assertEqual(note_cnt, 4)
class TestConstructList(base.TestCase):
def test_simple_re(self):
test_lists = ['fake_test(scen)[tag,bar])', 'fake_test(scen)[egg,foo])']
with mock.patch('os_testr.regex_builder._get_test_list',
return_value=test_lists):
result = list_builder.construct_list(None,
None,
'foo',
None,
False)
self.assertEqual(list(result), ['fake_test(scen)[egg,foo])'])
def test_simple_black_re(self):
test_lists = ['fake_test(scen)[tag,bar])', 'fake_test(scen)[egg,foo])']
with mock.patch('os_testr.regex_builder._get_test_list',
return_value=test_lists):
result = list_builder.construct_list(None,
None,
None,
'foo',
False)
self.assertEqual(list(result), ['fake_test(scen)[tag,bar])'])
def test_blacklist(self):
black_list = [(re.compile('foo'), 'foo not liked', [])]
test_lists = ['fake_test(scen)[tag,bar])', 'fake_test(scen)[egg,foo])']
with mock.patch('os_testr.regex_builder._get_test_list',
return_value=test_lists):
with mock.patch('os_testr.testlist_builder.black_reader',
return_value=black_list):
result = list_builder.construct_list('file',
None,
'fake_test',
None,
False)
self.assertEqual(list(result), ['fake_test(scen)[tag,bar])'])
def test_whitelist(self):
white_list = 'fake_test1|fake_test2'
test_lists = ['fake_test1[tg]', 'fake_test2[tg]', 'fake_test3[tg]']
white_getter = 'os_testr.regex_builder.get_regex_from_whitelist_file'
with mock.patch('os_testr.regex_builder._get_test_list',
return_value=test_lists):
with mock.patch(white_getter,
return_value=white_list):
result = list_builder.construct_list(None,
'file',
None,
None,
False)
self.assertEqual(set(result),
set(('fake_test1[tg]', 'fake_test2[tg]')))
def test_whitelist_blacklist_re(self):
white_list = 'fake_test1|fake_test2'
test_lists = ['fake_test1[tg]', 'fake_test2[spam]',
'fake_test3[tg,foo]', 'fake_test4[spam]']
black_list = [(re.compile('spam'), 'spam not liked', [])]
white_getter = 'os_testr.regex_builder.get_regex_from_whitelist_file'
with mock.patch('os_testr.regex_builder._get_test_list',
return_value=test_lists):
with mock.patch(white_getter,
return_value=white_list):
with mock.patch('os_testr.testlist_builder.black_reader',
return_value=black_list):
result = list_builder.construct_list('black_file',
'white_file',
'foo',
None,
False)
self.assertEqual(set(result),
set(('fake_test1[tg]', 'fake_test3[tg,foo]')))
def test_overlapping_black_regex(self):
black_list = [(re.compile('compute.test_keypairs.KeypairsTestV210'),
'', []),
(re.compile('compute.test_keypairs.KeypairsTestV21'),
'', [])]
test_lists = [
'compute.test_keypairs.KeypairsTestV210.test_create_keypair',
'compute.test_keypairs.KeypairsTestV21.test_create_keypair',
'compute.test_fake.FakeTest.test_fake_test']
with mock.patch('os_testr.regex_builder._get_test_list',
return_value=test_lists):
with mock.patch('os_testr.testlist_builder.black_reader',
return_value=black_list):
result = list_builder.construct_list('file',
None,
'fake_test',
None,
False)
self.assertEqual(
list(result), ['compute.test_fake.FakeTest.test_fake_test'])

View File

@ -1,76 +0,0 @@
# Copyright 2016 Hewlett Packard Enterprise Development LP
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import six
import sys
from ddt import data
from ddt import ddt
from ddt import unpack
from os_testr.tests import base
from os_testr.utils import colorizer
@ddt
class TestNullColorizer(base.TestCase):
@data(None, "foo", sys.stdout, )
def test_supported_always_true(self, stream):
self.assertTrue(colorizer.NullColorizer.supported(stream))
@data(("foo", "red"), ("foo", "bar"))
@unpack
def test_write_string_ignore_color(self, text, color):
output = six.StringIO()
c = colorizer.NullColorizer(output)
c.write(text, color)
self.assertEqual(text, output.getvalue())
@data((None, "red"), (None, None))
@unpack
def test_write_none_exception(self, text, color):
c = colorizer.NullColorizer(sys.stdout)
self.assertRaises(TypeError, c.write, text, color)
@ddt
class TestAnsiColorizer(base.TestCase):
def test_supported_false(self):
# NOTE(masayukig): This returns False because our unittest env isn't
# interactive
self.assertFalse(colorizer.AnsiColorizer.supported(sys.stdout))
@data(None, "foo")
def test_supported_error(self, stream):
self.assertRaises(AttributeError,
colorizer.AnsiColorizer.supported, stream)
@data(("foo", "red", "31"), ("foo", "blue", "34"))
@unpack
def test_write_string_valid_color(self, text, color, color_code):
output = six.StringIO()
c = colorizer.AnsiColorizer(output)
c.write(text, color)
self.assertIn(text, output.getvalue())
self.assertIn(color_code, output.getvalue())
@data(("foo", None), ("foo", "invalid_color"))
@unpack
def test_write_string_invalid_color(self, text, color):
output = six.StringIO()
c = colorizer.AnsiColorizer(output)
self.assertRaises(KeyError, c.write, text, color)

View File

@ -1,98 +0,0 @@
# Copyright 2015 NEC Corporation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
# Colorizer Code is borrowed from Twisted:
# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import sys
class AnsiColorizer(object):
"""A colorizer is an object that loosely wraps around a stream
allowing callers to write text to the stream in a particular color.
Colorizer classes must implement C{supported()} and C{write(text, color)}.
"""
_colors = dict(black=30, red=31, green=32, yellow=33,
blue=34, magenta=35, cyan=36, white=37)
def __init__(self, stream):
self.stream = stream
@classmethod
def supported(cls, stream=sys.stdout):
"""Check the current platform supports coloring terminal output
A class method that returns True if the current platform supports
coloring terminal output using this method. Returns False otherwise.
"""
if not stream.isatty():
return False # auto color only on TTYs
try:
import curses
except ImportError:
return False
else:
try:
try:
return curses.tigetnum("colors") > 2
except curses.error:
curses.setupterm()
return curses.tigetnum("colors") > 2
except Exception:
# guess false in case of error
return False
def write(self, text, color):
"""Write the given text to the stream in the given color.
@param text: Text to be written to the stream.
@param color: A string label for a color. e.g. 'red', 'white'.
"""
color = self._colors[color]
self.stream.write('\x1b[%s;1m%s\x1b[0m' % (color, text))
class NullColorizer(object):
"""See _AnsiColorizer docstring."""
def __init__(self, stream):
self.stream = stream
@classmethod
def supported(cls, stream=sys.stdout):
return True
def write(self, text, color):
self.stream.write(text)

View File

@ -1,8 +0,0 @@
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
pbr!=2.1.0,>=2.0.0 # Apache-2.0
testrepository>=0.0.18 # Apache-2.0/BSD
python-subunit>=0.0.18 # Apache-2.0/BSD
testtools>=1.4.0 # MIT

View File

@ -1,40 +0,0 @@
[metadata]
name = os-testr
summary = A testr wrapper to provide functionality for OpenStack projects
description-file =
README.rst
author = OpenStack
author-email = openstack-dev@lists.openstack.org
home-page = http://docs.openstack.org/developer/os-testr/
classifier =
Environment :: OpenStack
Intended Audience :: Information Technology
Intended Audience :: System Administrators
License :: OSI Approved :: Apache Software License
Operating System :: POSIX :: Linux
Programming Language :: Python
Programming Language :: Python :: 2
Programming Language :: Python :: 2.7
Programming Language :: Python :: 3
Programming Language :: Python :: 3.4
Programming Language :: Python :: 3.5
[files]
packages =
os_testr
[entry_points]
console_scripts =
subunit-trace = os_testr.subunit_trace:main
ostestr = os_testr.ostestr:main
subunit2html = os_testr.subunit2html:main
generate-subunit = os_testr.generate_subunit:main
[build_sphinx]
source-dir = doc/source
build-dir = doc/build
all_files = 1
warning-is-error = 1
[upload_sphinx]
upload-dir = doc/build/html

View File

@ -1,29 +0,0 @@
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
import setuptools
# In python < 2.7.4, a lazy loading of package `pbr` will break
# setuptools if some other modules registered functions in `atexit`.
# solution from: http://bugs.python.org/issue15881#msg170215
try:
import multiprocessing # noqa
except ImportError:
pass
setuptools.setup(
setup_requires=['pbr>=2.0.0'],
pbr=True)

View File

@ -1,13 +0,0 @@
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0
coverage!=4.4,>=4.0 # Apache-2.0
sphinx>=1.6.2 # BSD
openstackdocstheme>=1.11.0 # Apache-2.0
oslotest>=1.10.0 # Apache-2.0
testscenarios>=0.4 # Apache-2.0/BSD
ddt>=1.0.1 # MIT
six>=1.9.0 # MIT

View File

@ -1,30 +0,0 @@
#!/usr/bin/env bash
# Client constraint file contains this client version pin that is in conflict
# with installing the client from source. We should remove the version pin in
# the constraints file before applying it for from-source installation.
CONSTRAINTS_FILE="$1"
shift 1
set -e
# NOTE(tonyb): Place this in the tox enviroment's log dir so it will get
# published to logs.openstack.org for easy debugging.
localfile="$VIRTUAL_ENV/log/upper-constraints.txt"
if [[ "$CONSTRAINTS_FILE" != http* ]]; then
CONSTRAINTS_FILE="file://$CONSTRAINTS_FILE"
fi
# NOTE(tonyb): need to add curl to bindep.txt if the project supports bindep
curl "$CONSTRAINTS_FILE" --insecure --progress-bar --output "$localfile"
pip install -c"$localfile" openstack-requirements
# This is the main purpose of the script: Allow local installation of
# the current repo. It is listed in constraints file and thus any
# install will be constrained and we need to unconstrain it.
edit-constraints "$localfile" -- "$CLIENT_NAME"
pip install -c"$localfile" -U "$@"
exit $?

41
tox.ini
View File

@ -1,41 +0,0 @@
[tox]
minversion = 2.0
envlist = py35,py34,py27,pypy,pep8
skipsdist = True
[testenv]
usedevelop = True
install_command = {toxinidir}/tools/tox_install.sh {env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages}
setenv =
VIRTUAL_ENV={envdir}
BRANCH_NAME=master
CLIENT_NAME=os-testr
whitelist_externals = find
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
commands =
find . -type f -name "*.pyc" -delete
ostestr {posargs}
[testenv:pep8]
commands = flake8
[testenv:venv]
commands = {posargs}
[testenv:cover]
commands = python setup.py test --coverage --coverage-package-name='os_testr' --testr-args='{posargs}'
[testenv:docs]
commands = python setup.py build_sphinx
[testenv:debug]
commands = oslo_debug_helper {posargs}
[flake8]
# E123, E125 skipped as they are invalid PEP-8.
show-source = True
ignore = E123,E125
builtins = _
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build