Merge "Seperate regex builder logic into a seperate module"
This commit is contained in:
commit
0cf7a786d3
@ -23,6 +23,8 @@ import pbr.version
|
|||||||
from subunit import run as subunit_run
|
from subunit import run as subunit_run
|
||||||
from testtools import run as testtools_run
|
from testtools import run as testtools_run
|
||||||
|
|
||||||
|
from os_testr import regex_builder as rb
|
||||||
|
|
||||||
|
|
||||||
__version__ = pbr.version.VersionInfo('os_testr').version_string()
|
__version__ = pbr.version.VersionInfo('os_testr').version_string()
|
||||||
|
|
||||||
@ -95,91 +97,6 @@ def get_parser(args):
|
|||||||
return parser.parse_known_args(args)
|
return parser.parse_known_args(args)
|
||||||
|
|
||||||
|
|
||||||
def _get_test_list(regex, env=None):
|
|
||||||
env = env or copy.deepcopy(os.environ)
|
|
||||||
proc = subprocess.Popen(['testr', 'list-tests', regex], env=env,
|
|
||||||
stdout=subprocess.PIPE)
|
|
||||||
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:
|
|
||||||
bad = True
|
|
||||||
break
|
|
||||||
elif 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):
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
def call_testr(regex, subunit, pretty, list_tests, slowest, parallel, concur,
|
def call_testr(regex, subunit, pretty, list_tests, slowest, parallel, concur,
|
||||||
until_failure, color, others=None):
|
until_failure, color, others=None):
|
||||||
others = others or []
|
others = others or []
|
||||||
@ -206,7 +123,7 @@ def call_testr(regex, subunit, pretty, list_tests, slowest, parallel, concur,
|
|||||||
# This workaround is necessary because of lp bug 1411804 it's super hacky
|
# 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
|
# and makes tons of unfounded assumptions, but it works for the most part
|
||||||
if (subunit or pretty) and until_failure:
|
if (subunit or pretty) and until_failure:
|
||||||
test_list = _get_test_list(regex, env)
|
test_list = rb._get_test_list(regex, env)
|
||||||
count = 0
|
count = 0
|
||||||
failed = False
|
failed = False
|
||||||
if not test_list:
|
if not test_list:
|
||||||
@ -292,7 +209,7 @@ def _select_and_call_runner(opts, exclude_regex, others):
|
|||||||
return 2
|
return 2
|
||||||
test_to_run = opts.no_discover or opts.pdb
|
test_to_run = opts.no_discover or opts.pdb
|
||||||
if test_to_run.find('/') != -1:
|
if test_to_run.find('/') != -1:
|
||||||
test_to_run = path_to_regex(test_to_run)
|
test_to_run = rb.path_to_regex(test_to_run)
|
||||||
ec = call_subunit_run(test_to_run, opts.pretty, opts.subunit)
|
ec = call_subunit_run(test_to_run, opts.pretty, opts.subunit)
|
||||||
return ec
|
return ec
|
||||||
|
|
||||||
@ -318,13 +235,13 @@ def main():
|
|||||||
print(msg)
|
print(msg)
|
||||||
exit(5)
|
exit(5)
|
||||||
if opts.path:
|
if opts.path:
|
||||||
regex = path_to_regex(opts.path)
|
regex = rb.path_to_regex(opts.path)
|
||||||
else:
|
else:
|
||||||
regex = opts.regex
|
regex = opts.regex
|
||||||
exclude_regex = construct_regex(opts.blacklist_file,
|
exclude_regex = rb.construct_regex(opts.blacklist_file,
|
||||||
opts.whitelist_file,
|
opts.whitelist_file,
|
||||||
regex,
|
regex,
|
||||||
opts.print_exclude)
|
opts.print_exclude)
|
||||||
exit(_select_and_call_runner(opts, exclude_regex, others))
|
exit(_select_and_call_runner(opts, exclude_regex, others))
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
102
os_testr/regex_builder.py
Normal file
102
os_testr/regex_builder.py
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
# 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)
|
||||||
|
proc = subprocess.Popen(['testr', 'list-tests', regex], env=env,
|
||||||
|
stdout=subprocess.PIPE)
|
||||||
|
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:
|
||||||
|
bad = True
|
||||||
|
break
|
||||||
|
elif 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):
|
||||||
|
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
|
117
os_testr/tests/test_ostestr.py
Normal file
117
os_testr/tests/test_ostestr.py
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
# -*- 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 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()
|
@ -12,17 +12,11 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
"""
|
|
||||||
test_os_testr
|
|
||||||
----------------------------------
|
|
||||||
|
|
||||||
Tests for `os_testr` module.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import mock
|
import mock
|
||||||
|
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from os_testr import os_testr
|
from os_testr import regex_builder as os_testr
|
||||||
from os_testr.tests import base
|
from os_testr.tests import base
|
||||||
|
|
||||||
|
|
||||||
@ -35,98 +29,6 @@ class TestPathToRegex(base.TestCase):
|
|||||||
self.assertEqual("openstack.tests.network.v2", result)
|
self.assertEqual("openstack.tests.network.v2", result)
|
||||||
|
|
||||||
|
|
||||||
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()
|
|
||||||
|
|
||||||
|
|
||||||
class TestConstructRegex(base.TestCase):
|
class TestConstructRegex(base.TestCase):
|
||||||
def test_regex_passthrough(self):
|
def test_regex_passthrough(self):
|
||||||
result = os_testr.construct_regex(None, None, 'fake_regex', False)
|
result = os_testr.construct_regex(None, None, 'fake_regex', False)
|
@ -25,7 +25,7 @@ packages =
|
|||||||
[entry_points]
|
[entry_points]
|
||||||
console_scripts =
|
console_scripts =
|
||||||
subunit-trace = os_testr.subunit_trace:main
|
subunit-trace = os_testr.subunit_trace:main
|
||||||
ostestr = os_testr.os_testr:main
|
ostestr = os_testr.ostestr:main
|
||||||
subunit2html = os_testr.subunit2html:main
|
subunit2html = os_testr.subunit2html:main
|
||||||
generate-subunit = os_testr.generate_subunit:main
|
generate-subunit = os_testr.generate_subunit:main
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user