From: Josh Durgin Date: Wed, 2 Dec 2015 08:12:18 +0000 (-0800) Subject: describe-tests: add options to show the combinations of a suite X-Git-Tag: 1.1.0~727^2~8 X-Git-Url: http://git.apps.os.sepia.ceph.com/?a=commitdiff_plain;h=a2706b4dd10679ec3cc2795ecba7aa74c0b8800f;p=teuthology.git describe-tests: add options to show the combinations of a suite Match the behavior of teuthology-suite and generate the full product of all fragments by default. Include the same filtering and subset options as well. Just print an asii table for now. Signed-off-by: Josh Durgin --- diff --git a/scripts/describe_tests.py b/scripts/describe_tests.py index b59ff6258a..09924353f0 100644 --- a/scripts/describe_tests.py +++ b/scripts/describe_tests.py @@ -12,7 +12,7 @@ Describe the contents of a qa suite by reading 'description' elements from yaml files in the suite. The 'description' element should contain a list with a dictionary -of fields, e.g.: +of key/value pairs for entries, i.e.: description: - field1: value1 @@ -31,6 +31,27 @@ optional arguments: include [default: desc] --show-facet [yes|no] List the facet of each file [default: yes] + +options only for describing combinations represented by a suite: + -c, --combinations Describe test combinations rather than + individual yaml fragments + --filter Only list jobs whose filenames contain at + least one of the keywords in the comma + separated keyword string specified. + --filter-out Do not list jobs whose filenames contain + any of the keywords in the comma separated + keyword string specified. + -l , --limit List at most this many jobs + [default: 0] + --subset Instead of listing the entire + suite, break the set of jobs into + pieces (each of which + will contain each facet at least + once) and list piece . + Listing 0/, 1/, + 2/ ... -1/ + will list all jobs in the + suite (many more than once). """ diff --git a/teuthology/describe_tests.py b/teuthology/describe_tests.py index bae834f239..bdb8bf02c5 100644 --- a/teuthology/describe_tests.py +++ b/teuthology/describe_tests.py @@ -1,15 +1,101 @@ # -*- coding: utf-8 -*- + from prettytable import PrettyTable, FRAME, ALL import os import yaml from teuthology.exceptions import ParseError +from teuthology.suite import build_matrix, combine_path def main(args): suite_dir = os.path.abspath(args[""]) fields = args["--fields"].split(',') include_facet = args['--show-facet'] == 'yes' + if args['--combinations']: + limit = int(args['--limit']) + filter_in = None + if args['--filter']: + filter_in = [f.strip() for f in args['--filter'].split(',')] + filter_out = None + if args['--filter-out']: + filter_out = [f.strip() for f in args['--filter-out'].split(',')] + subset = None + if args['--subset']: + subset = map(int, args['--subset'].split('/')) + describe_combinations(suite_dir, fields, subset, + limit, filter_in, filter_out, + include_facet) + else: + describe_suite(suite_dir, fields, include_facet) + +def get_combinations(suite_dir, fields, subset, + limit, filter_in, filter_out, + include_facet, _isdir=os.path.isdir, _open=open, + _isfile=os.path.isfile, _listdir=os.listdir): + configs = [(combine_path(suite_dir, item[0]), item[1]) for item in + build_matrix(suite_dir, _isfile, _isdir, _listdir, subset)] + + num_listed = 0 + rows = [] + + facet_headers = set() + + for _, fragment_paths in configs: + if limit > 0 and num_listed >= limit: + break + if filter_in and not any([f in path for f in filter_in + for path in fragment_paths]): + continue + if filter_out and any([f in path for f in filter_out + for path in fragment_paths]): + continue + + fragment_fields = [extract_info(path, fields, _isdir, _open) + for path in fragment_paths] + + # merge fields from multiple fragments by joining their values with \n + metadata = {} + for fragment_meta in fragment_fields: + for field, value in fragment_meta.items(): + if value == '': + continue + if field in metadata: + metadata[field] += '\n' + str(value) + else: + metadata[field] = str(value) + + if include_facet: + # map final dir (facet) -> filename without the .yaml suffix + for path in fragment_paths: + facet = os.path.basename(os.path.dirname(path)) + metadata[facet] = os.path.basename(path)[:-5] + facet_headers.add(facet) + + rows.append(metadata) + num_listed += 1 + + headers = sorted(facet_headers) + fields + return headers, [[row.get(field, '') for field in headers] for row in rows] + +def describe_combinations(suite_dir, fields, subset, + limit, filter_in, filter_out, + include_facet): + headers, rows = get_combinations(suite_dir, fields, subset, + limit, filter_in, filter_out, + include_facet) + + table = PrettyTable(headers) + table.align = 'l' + table.vrules = ALL + table.hrules = ALL + + for row in rows: + table.add_row(row) + + print(table) + +def describe_suite(suite_dir, fields, include_facet): try: rows = tree_with_info(suite_dir, fields, include_facet, '', []) except ParseError: diff --git a/teuthology/test/test_describe_tests.py b/teuthology/test/test_describe_tests.py index 1780801743..fcd9adc224 100644 --- a/teuthology/test/test_describe_tests.py +++ b/teuthology/test/test_describe_tests.py @@ -2,7 +2,8 @@ import pytest from fake_fs import make_fake_fstools -from teuthology.describe_tests import tree_with_info, extract_info +from teuthology.describe_tests import (tree_with_info, extract_info, + get_combinations) from teuthology.exceptions import ParseError realistic_fs = { @@ -105,7 +106,7 @@ expected_rbd_features = [ class TestDescribeTests(object): def setup(self): - self.fake_listdir, _, self.fake_isdir, self.fake_open = \ + self.fake_listdir, self.fake_isfile, self.fake_isdir, self.fake_open = \ make_fake_fstools(realistic_fs) def test_no_filters(self): @@ -183,6 +184,38 @@ class TestDescribeTests(object): expected_rbd_features, expected_desc)) + def test_combinations_only_facets(self): + headers, rows = get_combinations('basic', [], None, 1, None, None, True, + self.fake_isdir, self.fake_open, + self.fake_isfile, self.fake_listdir) + assert headers == sorted(set(filter(bool, expected_facets))) + assert rows == [['install', 'fixed-1', 'rbd_api_tests']] + + def test_combinations_desc_features(self): + headers, rows = get_combinations('basic', ['desc', 'rbd_features'], + None, 1, None, None, False, + self.fake_isdir, self.fake_open, + self.fake_isfile, self.fake_listdir) + assert headers == ['desc', 'rbd_features'] + assert rows == [['install ceph\nsingle node cluster\nc/c++ librbd api tests with default settings', + 'default']] + + def test_combinations_filter_in(self): + headers, rows = get_combinations('basic', [], None, 0, ['old_format'], + None, True, + self.fake_isdir, self.fake_open, + self.fake_isfile, self.fake_listdir) + assert headers == sorted(set(filter(bool, expected_facets))) + assert rows == [['install', 'fixed-1', 'rbd_api_tests_old_format']] + + def test_combinations_filter_out(self): + headers, rows = get_combinations('basic', [], None, 0, None, + ['old_format'], True, + self.fake_isdir, self.fake_open, + self.fake_isfile, self.fake_listdir) + assert headers == sorted(set(filter(bool, expected_facets))) + assert rows == [['install', 'fixed-1', 'rbd_api_tests']] + def test_extract_info_dir(): simple_fs = {'a': {'b.yaml': 'description: [{foo: c}]'}}