]> git.apps.os.sepia.ceph.com Git - teuthology.git/commitdiff
teuthology-describe-tests: change input format to yaml
authorJosh Durgin <jdurgin@redhat.com>
Tue, 1 Dec 2015 01:14:34 +0000 (17:14 -0800)
committerJosh Durgin <jdurgin@redhat.com>
Wed, 9 Dec 2015 21:04:54 +0000 (13:04 -0800)
Rather than using comments, read description metadata from a yaml
'description' section. Make it a list so that teuthology-suite
aggregates the descriptions in order, and allow multiple fields by
making the list contain a dict.

Signed-off-by: Josh Durgin <jdurgin@redhat.com>
scripts/describe_tests.py
teuthology/describe_tests.py
teuthology/exceptions.py
teuthology/test/test_describe_tests.py

index c6648abcc1326a73110aeda47725d5449cbb0325..b59ff6258a57e4260533a38eae9d0096078aa402 100644 (file)
@@ -8,21 +8,29 @@ usage:
     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]
 """
 
 
index 13f192eca648d25ab52e4cce4fb67643371b2eaf..340d2fce0c129de05cf61f438f3b1d3f92c28c46 100644 (file)
@@ -1,43 +1,54 @@
 # -*- 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))
@@ -51,15 +62,15 @@ def tree_with_info(cur_dir, filters, include_facet, prefix, rows,
         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
index 9b7c65991bbaee4092af16d6f879698568f1ed2e..4e4e135fd8daf4c6ba14aa7225ffe9a1ddd03853 100644 (file)
@@ -28,6 +28,10 @@ class ConfigError(RuntimeError):
     pass
 
 
+class ParseError(Exception):
+    pass
+
+
 class CommandFailedError(Exception):
 
     """
index 5a8ce32db8155b0b6f60c87955d8e2a064192cef..1fa44b8846ce2466dce37cc3baff8c1c52c9af44 100644 (file)
@@ -1,27 +1,33 @@
 # -*- 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:
@@ -36,8 +42,9 @@ tasks:
         - 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:
@@ -182,10 +189,28 @@ class TestDescribeTests(object):
 
 
 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]]'})