teuthology-describe-tests -h
teuthology-describe-tests [options] [--] <suite_dir>
-Describe the contents of a qa suite by extracting comments
-starting with particular prefixes from files in the suite.
+Describe the contents of a qa suite by reading 'description' elements
+from yaml files in the suite.
-By default, the remainder of a line starting with '# desc:' will
-be included from each file in the specified suite directory.
+The 'description' element should contain a list with a dictionary
+of fields, e.g.:
+
+description:
+- field1: value1
+ field2: value2
+ field3: value3
+ desc: short human-friendly description
+
+Fields are user-defined, and are not required to be in all yaml files.
positional arguments:
- <suite_dir> qa suite path to traverse and describe
+ <suite_dir> path of qa suite
optional arguments:
- -h, --help Show this help message and exit
- -p <prefixes>, --prefix <prefixes> Comma-separated list of prefixes
- [default: desc]
- --show-facet [yes|no] List the facet of each file
- [default: yes]
+ -h, --help Show this help message and exit
+ -f <fields>, --fields <fields> Comma-separated list of fields to
+ include [default: desc]
+ --show-facet [yes|no] List the facet of each file
+ [default: yes]
"""
# -*- coding: utf-8 -*-
from prettytable import PrettyTable, FRAME, ALL
import os
+import yaml
+
+from teuthology.exceptions import ParseError
def main(args):
suite_dir = os.path.abspath(args["<suite_dir>"])
- filters = args["--prefix"].split(',')
+ fields = args["--fields"].split(',')
include_facet = args['--show-facet'] == 'yes'
- print(suite_dir)
- rows = tree_with_info(suite_dir, filters, include_facet, '', [])
+ try:
+ rows = tree_with_info(suite_dir, fields, include_facet, '', [])
+ except ParseError:
+ return 1
headers = ['path']
if include_facet:
headers.append('facet')
- table = PrettyTable(headers + filters)
+ table = PrettyTable(headers + fields)
table.align = 'l'
table.vrules = ALL
table.hrules = FRAME
for row in rows:
table.add_row(row)
+
+ print(suite_dir)
print(table)
-def extract_info(file_name, filters, _isdir=os.path.isdir, _open=open):
- result = {f: '' for f in filters}
- if _isdir(file_name):
- return result
+def extract_info(file_name, fields, _isdir=os.path.isdir, _open=open):
+ if _isdir(file_name) or not file_name.endswith('.yaml'):
+ return {f: '' for f in fields}
+
with _open(file_name, 'r') as f:
- for line in f:
- for filt in filters:
- prefix = '# ' + filt + ':'
- if line.startswith(prefix):
- if result[filt]:
- result[filt] += '\n'
- result[filt] += line[len(prefix):].rstrip('\n').strip()
- return result
+ parsed = yaml.load(f)
+
+ description = parsed.get('description', [{}])
+ if not (isinstance(description, list) and
+ len(description) == 1 and
+ isinstance(description[0], dict)):
+ print 'Error in description format in', file_name
+ print 'Description must be a list containing exactly one dict.'
+ print 'Description is:', description
+ raise ParseError()
+
+ return {field: description[0].get(field, '') for field in fields}
-def tree_with_info(cur_dir, filters, include_facet, prefix, rows,
+def tree_with_info(cur_dir, fields, include_facet, prefix, rows,
_listdir=os.listdir, _isdir=os.path.isdir,
_open=open):
files = sorted(_listdir(cur_dir))
else:
file_pad = '├── '
dir_pad = '│ '
- info = extract_info(path, filters, _isdir, _open)
+ info = extract_info(path, fields, _isdir, _open)
tree_node = prefix + file_pad + f
- meta = [info[f] for f in filters]
+ meta = [info[f] for f in fields]
row = [tree_node]
if include_facet:
row.append(facet)
rows.append(row + meta)
if _isdir(path):
- tree_with_info(path, filters, include_facet,
+ tree_with_info(path, fields, include_facet,
prefix + dir_pad, rows,
_listdir, _isdir, _open)
return rows
pass
+class ParseError(Exception):
+ pass
+
+
class CommandFailedError(Exception):
"""
# -*- coding: utf-8 -*-
+import pytest
+
from fake_fs import make_fake_fstools
from teuthology.describe_tests import tree_with_info, extract_info
+from teuthology.exceptions import ParseError
realistic_fs = {
'basic': {
'%': None,
'base': {
'install.yaml':
- """# desc: install ceph
+ """description:
+- desc: install ceph
install:
"""
},
'clusters': {
'fixed-1.yaml':
- """# desc: single node cluster
+ """description:
+- desc: single node cluster
roles:
- [osd.0, osd.1, osd.2, mon.a, mon.b, mon.c, client.0]
"""
},
'workloads': {
'rbd_api_tests_old_format.yaml':
- """# desc: c/c++ librbd api tests with format 1 images
-# rbd_features: none
+ """description:
+- desc: c/c++ librbd api tests with format 1 images
+ rbd_features: none
overrides:
ceph:
conf:
- rbd/test_librbd.sh
""",
'rbd_api_tests.yaml':
- """# desc: c/c++ librbd api tests with default settings
-# rbd_features: default
+ """description:
+- desc: c/c++ librbd api tests with default settings
+ rbd_features: default
tasks:
- workunit:
clients:
def test_extract_info_dir():
- simple_fs = {'a': {'b': '# foo:'}}
+ simple_fs = {'a': {'b.yaml': 'description: [{foo: c}]'}}
_, _, fake_isdir, fake_open = make_fake_fstools(simple_fs)
info = extract_info('a', [], fake_isdir, fake_open)
assert info == {}
info = extract_info('a', ['foo', 'bar'], fake_isdir, fake_open)
assert info == {'foo': '', 'bar': ''}
+
+ info = extract_info('a/b.yaml', ['foo', 'bar'], fake_isdir, fake_open)
+ assert info == {'foo': 'c', 'bar': ''}
+
+def check_parse_error(fs):
+ _, _, fake_isdir, fake_open = make_fake_fstools(fs)
+ with pytest.raises(ParseError):
+ a = extract_info('a.yaml', ['a'], fake_isdir, fake_open)
+ raise Exception(str(a))
+
+def test_extract_info_too_many_elements():
+ check_parse_error({'a.yaml': 'description: [{a: b}, {b: c}]'})
+
+def test_extract_info_not_a_list():
+ check_parse_error({'a.yaml': 'description: {a: b}'})
+
+def test_extract_info_not_a_dict():
+ check_parse_error({'a.yaml': 'description: [[a, b]]'})