Merge "Don't require tox_envlist"
This commit is contained in:
commit
a7deb384f9
@ -13,6 +13,9 @@ Runs tox for a project
|
||||
``ALL`` runs all test environments while an empty string runs
|
||||
all test environments configured with ``envlist`` in tox.
|
||||
|
||||
Internally this will always be expanded into a comma separated
|
||||
list of test environments to run.
|
||||
|
||||
.. zuul:rolevar:: tox_executable
|
||||
:default: tox
|
||||
|
||||
|
@ -32,9 +32,14 @@ requirements:
|
||||
options:
|
||||
tox_envlist:
|
||||
description:
|
||||
- The tox environment to operate in.
|
||||
- The tox test environments to act on.
|
||||
required: true
|
||||
type: str
|
||||
tox_show_config:
|
||||
description:
|
||||
- Path to a file containing the output from C(tox --showconfig).
|
||||
required: true
|
||||
type: path
|
||||
project_dir:
|
||||
description:
|
||||
- The directory in which the project we care about is in.
|
||||
@ -159,19 +164,126 @@ def _get_package_root(name, sibling_packages):
|
||||
return pkg_root
|
||||
|
||||
|
||||
def find_installed_siblings(tox_python, package_name, sibling_python_packages):
|
||||
installed_sibling_packages = []
|
||||
for dep_name in get_installed_packages(tox_python):
|
||||
log.append(
|
||||
"Found {name} python package installed".format(
|
||||
name=dep_name))
|
||||
if (dep_name == package_name or
|
||||
prAPI.to_filename(dep_name) == package_name):
|
||||
# We don't need to re-process ourself.
|
||||
# We've filtered ourselves from the source dir list,
|
||||
# but let's be sure nothing is weird.
|
||||
log.append(
|
||||
"Skipping {name} because it's us".format(
|
||||
name=dep_name))
|
||||
continue
|
||||
if dep_name in sibling_python_packages:
|
||||
log.append(
|
||||
"Package {name} on system in {root}".format(
|
||||
name=dep_name,
|
||||
root=sibling_python_packages[dep_name]))
|
||||
installed_sibling_packages.append(dep_name)
|
||||
elif prAPI.to_filename(dep_name) in sibling_python_packages:
|
||||
real_name = prAPI.to_filename(dep_name)
|
||||
log.append(
|
||||
"Package {name} ({pkg_name}) on system in {root}".format(
|
||||
name=dep_name,
|
||||
pkg_name=real_name,
|
||||
root=sibling_python_packages[real_name]))
|
||||
# need to use dep_name here for later constraint file rewrite
|
||||
installed_sibling_packages.append(dep_name)
|
||||
return installed_sibling_packages
|
||||
|
||||
|
||||
def install_siblings(envdir, projects, package_name, constraints):
|
||||
changed = False
|
||||
tox_python = '{envdir}/bin/python'.format(envdir=envdir)
|
||||
|
||||
sibling_python_packages = get_sibling_python_packages(
|
||||
projects, tox_python)
|
||||
for name, root in sibling_python_packages.items():
|
||||
log.append("Sibling {name} at {root}".format(name=name,
|
||||
root=root))
|
||||
|
||||
installed_sibling_packages = find_installed_siblings(
|
||||
tox_python,
|
||||
package_name,
|
||||
sibling_python_packages)
|
||||
|
||||
if constraints:
|
||||
constraints_file = write_new_constraints_file(
|
||||
constraints, installed_sibling_packages)
|
||||
|
||||
for sibling_package in installed_sibling_packages:
|
||||
changed = True
|
||||
log.append("Uninstalling {name}".format(name=sibling_package))
|
||||
uninstall_output = subprocess.check_output(
|
||||
[tox_python, '-m',
|
||||
'pip', 'uninstall', '-y', sibling_package],
|
||||
stderr=subprocess.STDOUT)
|
||||
log.extend(uninstall_output.decode('utf-8').split('\n'))
|
||||
|
||||
args = [tox_python, '-m', 'pip', 'install']
|
||||
if constraints:
|
||||
args.extend(['-c', constraints_file])
|
||||
|
||||
pkg_root = _get_package_root(sibling_package,
|
||||
sibling_python_packages)
|
||||
log.append(
|
||||
"Installing {name} from {root} for deps".format(
|
||||
name=sibling_package,
|
||||
root=pkg_root))
|
||||
args.append(pkg_root)
|
||||
|
||||
install_output = subprocess.check_output(args)
|
||||
log.extend(install_output.decode('utf-8').split('\n'))
|
||||
|
||||
for sibling_package in installed_sibling_packages:
|
||||
changed = True
|
||||
pkg_root = _get_package_root(sibling_package,
|
||||
sibling_python_packages)
|
||||
log.append(
|
||||
"Installing {name} from {root}".format(
|
||||
name=sibling_package,
|
||||
root=pkg_root))
|
||||
|
||||
install_output = subprocess.check_output(
|
||||
[tox_python, '-m', 'pip', 'install', '--no-deps',
|
||||
pkg_root])
|
||||
log.extend(install_output.decode('utf-8').split('\n'))
|
||||
return changed
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
tox_envlist=dict(required=True, type='str'),
|
||||
tox_show_config=dict(required=True, type='path'),
|
||||
tox_constraints_file=dict(type='str'),
|
||||
project_dir=dict(required=True, type='str'),
|
||||
projects=dict(required=True, type='list'),
|
||||
)
|
||||
)
|
||||
envlist = module.params['tox_envlist']
|
||||
constraints = module.params.get('tox_constraints_file')
|
||||
project_dir = module.params['project_dir']
|
||||
projects = module.params['projects']
|
||||
tox_envlist = module.params.get('tox_envlist', '')
|
||||
tox_show_config = module.params.get('tox_show_config')
|
||||
|
||||
tox_config = configparser.ConfigParser()
|
||||
tox_config.read(tox_show_config)
|
||||
|
||||
envlist = {testenv.strip() for testenv
|
||||
in tox_envlist.split(',')}
|
||||
|
||||
if not envlist:
|
||||
module.exit_json(
|
||||
changed=False,
|
||||
msg='No envlist to run, no action needed.')
|
||||
|
||||
log.append('Using envlist: {}'.format(envlist))
|
||||
|
||||
if not os.path.exists(os.path.join(project_dir, 'setup.cfg')):
|
||||
module.exit_json(changed=False, msg="No setup.cfg, no action needed")
|
||||
@ -187,110 +299,34 @@ def main():
|
||||
module.exit_json(
|
||||
changed=False, msg="No name in setup.cfg, skipping siblings")
|
||||
|
||||
envdir = '{project_dir}/.tox/{envlist}'.format(
|
||||
project_dir=project_dir, envlist=envlist)
|
||||
if not os.path.exists(envdir):
|
||||
module.exit_json(
|
||||
changed=False, msg="envdir does not exist, skipping siblings")
|
||||
|
||||
tox_python = '{envdir}/bin/python'.format(envdir=envdir)
|
||||
# Write a log file into the .tox dir so that it'll get picked up
|
||||
# Name it with envlist as a prefix so that fetch-tox-output will properly
|
||||
# get it in a multi-env scenario
|
||||
log_dir = '{envdir}/log'.format(envdir=envdir)
|
||||
log_file = '{log_dir}/{envlist}-siblings.txt'.format(
|
||||
log_dir=log_dir, envlist=envlist)
|
||||
|
||||
log.append(
|
||||
"Processing siblings for {name} from {project_dir}".format(
|
||||
name=package_name,
|
||||
project_dir=project_dir))
|
||||
|
||||
changed = False
|
||||
|
||||
try:
|
||||
sibling_python_packages = get_sibling_python_packages(
|
||||
projects, tox_python)
|
||||
for name, root in sibling_python_packages.items():
|
||||
log.append("Sibling {name} at {root}".format(name=name, root=root))
|
||||
found_sibling_packages = []
|
||||
for dep_name in get_installed_packages(tox_python):
|
||||
log.append(
|
||||
"Found {name} python package installed".format(
|
||||
name=dep_name))
|
||||
if (dep_name == package_name or
|
||||
prAPI.to_filename(dep_name) == package_name):
|
||||
# We don't need to re-process ourself. We've filtered ourselves
|
||||
# from the source dir list, but let's be sure nothing is weird.
|
||||
log.append(
|
||||
"Skipping {name} because it's us".format(
|
||||
name=dep_name))
|
||||
continue
|
||||
if dep_name in sibling_python_packages:
|
||||
log.append(
|
||||
"Package {name} on system in {root}".format(
|
||||
name=dep_name,
|
||||
root=sibling_python_packages[dep_name]))
|
||||
changed = True
|
||||
found_sibling_packages.append(dep_name)
|
||||
elif prAPI.to_filename(dep_name) in sibling_python_packages:
|
||||
real_name = prAPI.to_filename(dep_name)
|
||||
log.append(
|
||||
"Package {name} ({pkg_name}) on system in {root}".format(
|
||||
name=dep_name,
|
||||
pkg_name=real_name,
|
||||
root=sibling_python_packages[real_name]))
|
||||
changed = True
|
||||
# need to use dep_name here for later constraint file rewrite
|
||||
found_sibling_packages.append(dep_name)
|
||||
|
||||
if constraints:
|
||||
constraints_file = write_new_constraints_file(
|
||||
constraints, found_sibling_packages)
|
||||
|
||||
for sibling_package in found_sibling_packages:
|
||||
log.append("Uninstalling {name}".format(name=sibling_package))
|
||||
uninstall_output = subprocess.check_output(
|
||||
[tox_python, '-m',
|
||||
'pip', 'uninstall', '-y', sibling_package],
|
||||
stderr=subprocess.STDOUT)
|
||||
log.extend(uninstall_output.decode('utf-8').split('\n'))
|
||||
|
||||
args = [tox_python, '-m', 'pip', 'install']
|
||||
if constraints:
|
||||
args.extend(['-c', constraints_file])
|
||||
|
||||
pkg_root = _get_package_root(sibling_package,
|
||||
sibling_python_packages)
|
||||
log.append(
|
||||
"Installing {name} from {root} for deps".format(
|
||||
name=sibling_package,
|
||||
root=pkg_root))
|
||||
args.append(pkg_root)
|
||||
|
||||
install_output = subprocess.check_output(args)
|
||||
log.extend(install_output.decode('utf-8').split('\n'))
|
||||
|
||||
for sibling_package in found_sibling_packages:
|
||||
pkg_root = _get_package_root(sibling_package,
|
||||
sibling_python_packages)
|
||||
log.append(
|
||||
"Installing {name} from {root}".format(
|
||||
name=sibling_package,
|
||||
root=pkg_root))
|
||||
|
||||
install_output = subprocess.check_output(
|
||||
[tox_python, '-m', 'pip', 'install', '--no-deps',
|
||||
pkg_root])
|
||||
log.extend(install_output.decode('utf-8').split('\n'))
|
||||
except Exception as e:
|
||||
tb = traceback.format_exc()
|
||||
log.append(str(e))
|
||||
log.append(tb)
|
||||
module.fail_json(msg=str(e), log="\n".join(log))
|
||||
finally:
|
||||
log_text = "\n".join(log)
|
||||
module.append_to_file(log_file, log_text)
|
||||
for testenv in envlist:
|
||||
testenv_config = tox_config['testenv:{}'.format(testenv)]
|
||||
envdir = testenv_config['envdir']
|
||||
envlogdir = testenv_config['envlogdir']
|
||||
try:
|
||||
# Write a log file into the .tox dir so that it'll get picked up
|
||||
# Name it with testenv as a prefix so that fetch-tox-output
|
||||
# will properly get it in a multi-env scenario
|
||||
log_file = '{envlogdir}/{testenv}-siblings.txt'.format(
|
||||
envlogdir=envlogdir, testenv=testenv)
|
||||
changed = changed or install_siblings(envdir,
|
||||
projects,
|
||||
package_name,
|
||||
constraints)
|
||||
except Exception as e:
|
||||
tb = traceback.format_exc()
|
||||
log.append(str(e))
|
||||
log.append(tb)
|
||||
module.fail_json(msg=str(e), log="\n".join(log))
|
||||
finally:
|
||||
log_text = "\n".join(log)
|
||||
module.append_to_file(log_file, log_text)
|
||||
module.exit_json(changed=changed, msg=log_text)
|
||||
|
||||
|
||||
|
@ -1,8 +1,3 @@
|
||||
- name: Require tox_envlist variable
|
||||
fail:
|
||||
msg: tox_envlist is required for this role
|
||||
when: tox_envlist is not defined
|
||||
|
||||
- name: Check to see if the constraints file exists
|
||||
stat:
|
||||
path: "{{ tox_constraints_file }}"
|
||||
@ -25,6 +20,49 @@
|
||||
UPPER_CONSTRAINTS_FILE: "{{ tox_constraints_file }}"
|
||||
when: tox_constraints_file is defined
|
||||
|
||||
# Tox siblings cannot take 'ALL' and tox_parse_output expects
|
||||
# an envlist to be supplied so always set _tox_envlist equal to
|
||||
# the list of testenvs we're going to run
|
||||
- name: Set _tox_envlist from supplied tox_envlist
|
||||
set_fact:
|
||||
_tox_envlist: "{{ tox_envlist }}"
|
||||
when:
|
||||
- tox_envlist is defined and tox_envlist
|
||||
- tox_envlist != "ALL"
|
||||
|
||||
- name: Get tox default envlist
|
||||
command: "{{ tox_executable }} -l"
|
||||
args:
|
||||
chdir: "{{ zuul_work_dir }}"
|
||||
register: _tox_default_envlist
|
||||
when: tox_envlist is not defined or not tox_envlist
|
||||
|
||||
- name: Set tox envlist fact
|
||||
set_fact:
|
||||
_tox_envlist: "{{ _tox_default_envlist.stdout_lines | join(',') }}"
|
||||
when:
|
||||
- _tox_default_envlist is defined
|
||||
- _tox_default_envlist.stdout_lines is defined
|
||||
|
||||
- name: Get all tox testenvs
|
||||
command: "{{ tox_executable }} -a"
|
||||
args:
|
||||
chdir: "{{ zuul_work_dir }}"
|
||||
register: _tox_all_testenvs
|
||||
when: tox_envlist is defined and tox_envlist == 'ALL'
|
||||
|
||||
- name: Set tox envlist fact
|
||||
set_fact:
|
||||
_tox_envlist: "{{ _tox_all_testenvs.stdout_lines | join(',') }}"
|
||||
when:
|
||||
- _tox_all_testenvs is defined
|
||||
- _tox_all_testenvs.stdout_lines is defined
|
||||
|
||||
- name: Fail if tox_envlist is empty
|
||||
fail:
|
||||
msg: "No envlist configured in zuul or tox.ini"
|
||||
when: _tox_envlist is not defined
|
||||
|
||||
- name: Install tox siblings
|
||||
include: siblings.yaml
|
||||
when: tox_install_siblings
|
||||
@ -33,9 +71,7 @@
|
||||
debug:
|
||||
msg: >-
|
||||
{{ tox_executable }}
|
||||
{% if tox_envlist is defined and tox_envlist %}
|
||||
-e{{ tox_envlist }}
|
||||
{% endif %}
|
||||
-e{{ _tox_envlist }}
|
||||
{{ tox_extra_args }}
|
||||
|
||||
- block:
|
||||
@ -45,9 +81,7 @@
|
||||
environment: "{{ tox_environment|combine(tox_constraints_env|default({})) }}"
|
||||
command: >-
|
||||
{{ tox_executable }}
|
||||
{% if tox_envlist is defined and tox_envlist %}
|
||||
-e{{ tox_envlist }}
|
||||
{% endif %}
|
||||
-e{{ _tox_envlist }}
|
||||
{{ tox_extra_args }}
|
||||
register: tox_output
|
||||
|
||||
@ -57,7 +91,7 @@
|
||||
- name: Look for output
|
||||
tox_parse_output:
|
||||
tox_output: '{{ tox_output.stdout }}'
|
||||
tox_envlist: '{{ tox_envlist }}'
|
||||
tox_envlist: '{{ _tox_envlist }}'
|
||||
workdir: '{{ zuul_work_dir }}'
|
||||
when: tox_inline_comments
|
||||
register: file_comments
|
||||
|
@ -1,14 +1,35 @@
|
||||
# Install sibling with tox so we can replace them later
|
||||
- name: Run tox without tests
|
||||
command: "{{ tox_executable }} --notest -e{{ tox_envlist }}"
|
||||
command: >-
|
||||
{{ tox_executable }}
|
||||
--notest
|
||||
-e{{ _tox_envlist }}
|
||||
args:
|
||||
chdir: "{{ zuul_work_dir }}"
|
||||
environment: "{{ tox_environment|combine(tox_constraints_env|default({})) }}"
|
||||
|
||||
# TODO(mordred) handle tox_envlist being a list
|
||||
# This is needed since python < 3.2 can't parse ini files from strings
|
||||
- name: Create a tempfile to save tox showconfig
|
||||
tempfile:
|
||||
register: _tox_show_config_tempfile
|
||||
|
||||
# py27, py35..py38 etc are generated on the fly if not
|
||||
# explicitly added to tox.ini, so force tox to generate
|
||||
# the config for the testenvs we're using.
|
||||
- name: Get tox envlist config
|
||||
shell: "{{ tox_executable }} --showconfig -e {{ _tox_envlist }} > {{ _tox_show_config_tempfile.path }}"
|
||||
args:
|
||||
chdir: "{{ zuul_work_dir }}"
|
||||
|
||||
- name: Install any sibling python packages
|
||||
tox_install_sibling_packages:
|
||||
tox_envlist: "{{ tox_envlist }}"
|
||||
tox_envlist: "{{ _tox_envlist }}"
|
||||
tox_show_config: "{{ _tox_show_config_tempfile.path }}"
|
||||
tox_constraints_file: "{{ tox_constraints_file | default(omit) }}"
|
||||
project_dir: "{{ zuul_work_dir }}"
|
||||
projects: "{{ zuul.projects.values() | selectattr('required') | list }}"
|
||||
|
||||
- name: Remove tempfile
|
||||
file:
|
||||
state: absent
|
||||
path: "{{ _tox_show_config_tempfile.path }}"
|
||||
|
12
test-playbooks/python/tox.ini
Normal file
12
test-playbooks/python/tox.ini
Normal file
@ -0,0 +1,12 @@
|
||||
[tox]
|
||||
envlist = linters
|
||||
skipsdist = true
|
||||
|
||||
[testenv]
|
||||
whitelist_externals = sh
|
||||
|
||||
[testenv:linters]
|
||||
commands = sh -c "echo linters >> {posargs}"
|
||||
|
||||
[testenv:non-default]
|
||||
commands = sh -c "echo non-default >> {posargs}"
|
78
test-playbooks/python/tox.yaml
Normal file
78
test-playbooks/python/tox.yaml
Normal file
@ -0,0 +1,78 @@
|
||||
- hosts: all
|
||||
tasks:
|
||||
- name: Run bindep
|
||||
include_role:
|
||||
name: bindep
|
||||
- name: Run tox with constraints
|
||||
include_role:
|
||||
name: tox
|
||||
vars:
|
||||
tox_envlist: docs
|
||||
tox_constraints_file: '{{ zuul.project.src_dir }}/zuul-tests.d/test-constraints.txt'
|
||||
|
||||
- name: Run tox with multiple testenvs
|
||||
include_role:
|
||||
name: tox
|
||||
vars:
|
||||
tox_envlist: docs,linters
|
||||
tox_environment:
|
||||
ANSIBLE_ROLES_PATH: "{{ ansible_user_dir }}/{{ zuul.project.src_dir }}/roles"
|
||||
ANSIBLE_LIBRARY: "{{ ansible_user_dir }}/{{ zuul.project.src_dir }}/tests/fake-ansible"
|
||||
|
||||
- name: Create tempfile to verify testenvs ran
|
||||
tempfile:
|
||||
register: default_tempfile
|
||||
|
||||
- block:
|
||||
- name: Run tox with empty envlist
|
||||
include_role:
|
||||
name: tox
|
||||
vars:
|
||||
zuul_work_dir: "{{ zuul.project.src_dir }}/test-playbooks/python/"
|
||||
tox_extra_args: "{{ default_tempfile.path }}"
|
||||
tox_install_siblings: false
|
||||
tox_envlist: ''
|
||||
tox_environment:
|
||||
ANSIBLE_ROLES_PATH: "{{ ansible_user_dir }}/{{ zuul.project.src_dir }}/roles"
|
||||
ANSIBLE_LIBRARY: "{{ ansible_user_dir }}/{{ zuul.project.src_dir }}/tests/fake-ansible"
|
||||
|
||||
- name: Make sure magic lines are present
|
||||
lineinfile:
|
||||
path: "{{ default_tempfile.path }}"
|
||||
line: linters
|
||||
check_mode: true
|
||||
register: default_status
|
||||
failed_when: default_status is changed
|
||||
always:
|
||||
- name: Remove tempfile
|
||||
file:
|
||||
state: absent
|
||||
path: "{{ default_tempfile.path }}"
|
||||
|
||||
- block:
|
||||
- name: Create tempfile to verify testenvs ran
|
||||
tempfile:
|
||||
register: ALL_tempfile
|
||||
|
||||
- name: Run tox with ALL
|
||||
include_role:
|
||||
name: tox
|
||||
vars:
|
||||
zuul_work_dir: "{{ zuul.project.src_dir }}/test-playbooks/python/"
|
||||
tox_install_siblings: false
|
||||
tox_extra_args: "{{ ALL_tempfile.path }}"
|
||||
tox_envlist: 'ALL'
|
||||
tox_environment:
|
||||
ANSIBLE_ROLES_PATH: "{{ ansible_user_dir }}/{{ zuul.project.src_dir }}/roles"
|
||||
ANSIBLE_LIBRARY: "{{ ansible_user_dir }}/{{ zuul.project.src_dir }}/tests/fake-ansible"
|
||||
always:
|
||||
- name: Make sure magic lines are present
|
||||
loop:
|
||||
- linters
|
||||
- non-default
|
||||
lineinfile:
|
||||
path: "{{ ALL_tempfile.path }}"
|
||||
line: "{{ item }}"
|
||||
check_mode: true
|
||||
register: ALL_status
|
||||
failed_when: ALL_status is changed
|
@ -1,14 +0,0 @@
|
||||
- hosts: all
|
||||
tasks:
|
||||
- name: Run bindep
|
||||
include_role:
|
||||
name: bindep
|
||||
- name: Run tox with constraints
|
||||
include_role:
|
||||
name: tox
|
||||
vars:
|
||||
tox_envlist: docs
|
||||
tox_constraints_file: '{{ zuul.project.src_dir }}/zuul-tests.d/test-constraints.txt'
|
||||
tox_environment:
|
||||
ANSIBLE_ROLES_PATH: "{{ ansible_user_dir }}/{{ zuul.project.src_dir }}/roles"
|
||||
ANSIBLE_LIBRARY: "{{ ansible_user_dir }}/{{ zuul.project.src_dir }}/tests/fake-ansible"
|
@ -400,13 +400,14 @@
|
||||
label: centos-8-plain
|
||||
|
||||
- job:
|
||||
name: zuul-jobs-test-tox-siblings
|
||||
name: zuul-jobs-test-tox
|
||||
description: Test the tox role's sibling functionality
|
||||
files:
|
||||
- roles/tox/.*
|
||||
- tox.ini
|
||||
- test-playbooks/tox-siblings.yaml
|
||||
run: test-playbooks/tox-siblings.yaml
|
||||
- test-playbooks/python/tox.yaml
|
||||
- test-playbooks/python/tox.ini
|
||||
run: test-playbooks/python/tox.yaml
|
||||
required-projects:
|
||||
- zuul/zuul
|
||||
- zuul/nodepool
|
||||
@ -540,7 +541,7 @@
|
||||
- zuul-jobs-test-fetch-sphinx-tarball-ubuntu-bionic-plain
|
||||
- zuul-jobs-test-fetch-sphinx-tarball-ubuntu-xenial-plain
|
||||
- zuul-jobs-test-fetch-sphinx-tarball-centos-8-plain
|
||||
- zuul-jobs-test-tox-siblings
|
||||
- zuul-jobs-test-tox
|
||||
- zuul-jobs-test-fetch-tox-output
|
||||
- zuul-jobs-test-fetch-tox-output-synchronize
|
||||
- zuul-jobs-test-fetch-subunit-output
|
||||
@ -585,7 +586,7 @@
|
||||
- zuul-jobs-test-fetch-sphinx-tarball-ubuntu-bionic-plain
|
||||
- zuul-jobs-test-fetch-sphinx-tarball-ubuntu-xenial-plain
|
||||
- zuul-jobs-test-fetch-sphinx-tarball-centos-8-plain
|
||||
- zuul-jobs-test-tox-siblings
|
||||
- zuul-jobs-test-tox
|
||||
- zuul-jobs-test-fetch-tox-output
|
||||
- zuul-jobs-test-fetch-tox-output-synchronize
|
||||
- zuul-jobs-test-fetch-subunit-output
|
||||
|
Loading…
Reference in New Issue
Block a user