diff --git a/README.rst b/README.rst index c2771e6..b8f3130 100644 --- a/README.rst +++ b/README.rst @@ -75,3 +75,17 @@ run(s) being added. The artifacts option should be used to pass in a url or path that points to any logs or other external test artifacts related to the run being added. The run_meta option takes in a dictionary which will be added to the database as key value pairs associated with the run being added. + +Creating a v2 Subunit Stream from the DB +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The sql2subunit utility is used for taking a run_id and creating a subunit +v2 stream from the data in the DB about that run. To create a new subunit +stream run:: + + sql2subunit $RUN_ID + +along with any options that you would normally use to either specify a config +file or the DB connection info. Running this command will print to stdout the +subunit v2 stream for the run specified by $RUN_ID, unless the --out_path +argument is specified to write it to a file instead. diff --git a/TODO.rst b/TODO.rst index 2615012..a18371c 100644 --- a/TODO.rst +++ b/TODO.rst @@ -6,6 +6,7 @@ Short Term * Add more unit tests * Migration tests * DB API unit tests + * Write subunit module * Flesh out query side of DB API to make it useful for building additional tooling. * Investigate dropping oslo.db from requirements to enable using other @@ -15,6 +16,5 @@ Short Term Longer Term ----------- - * Add a method of taking a test_run from the DB and create a subunit file * Add tooling to pull the data and visualize it in fun ways * Add some statistics functions on top of the DB api to perform analysis diff --git a/setup.cfg b/setup.cfg index 67288be..6b45b86 100644 --- a/setup.cfg +++ b/setup.cfg @@ -23,6 +23,7 @@ packages = [entry_points] console_scripts = subunit2sql = subunit2sql.shell:main + sql2subunit = subunit2sql.write_subunit:main [build_sphinx] source-dir = doc/source diff --git a/subunit2sql/db/api.py b/subunit2sql/db/api.py index 040d350..4e12ad5 100644 --- a/subunit2sql/db/api.py +++ b/subunit2sql/db/api.py @@ -153,6 +153,13 @@ def add_test_run_metadata(meta_dict, test_run_id, session=None): return metadata +def get_test_run_metadata(test_run_id, session=None): + session = session or get_session() + query = db_utils.model_query(models.TestRunMetadata, session).filter_by( + test_run_id=test_run_id) + return query.all() + + def get_all_tests(): query = db_utils.model_query(models.Test) return query.all() diff --git a/subunit2sql/shell.py b/subunit2sql/shell.py index ded1544..77d4ab7 100644 --- a/subunit2sql/shell.py +++ b/subunit2sql/shell.py @@ -23,19 +23,22 @@ from subunit2sql.db import api from subunit2sql import exceptions from subunit2sql import read_subunit as subunit -shell_opts = [ - cfg.StrOpt('state_path', default='$pybasedir', - help='Top level dir for maintaining subunit2sql state'), - cfg.MultiStrOpt('subunit_files', positional=True), - cfg.DictOpt('run_meta', short='r', default=None, - help='Dict of metadata about the run(s)'), - cfg.StrOpt('artifacts', short='a', default=None, - help='Location of run artifacts') -] - CONF = cfg.CONF -for opt in shell_opts: - CONF.register_cli_opt(opt) + + +def cli_opts(): + shell_opts = [ + cfg.StrOpt('state_path', default='$pybasedir', + help='Top level dir for maintaining subunit2sql state'), + cfg.MultiStrOpt('subunit_files', positional=True), + cfg.DictOpt('run_meta', short='r', default=None, + help='Dict of metadata about the run(s)'), + cfg.StrOpt('artifacts', short='a', default=None, + help='Location of run artifacts') + ] + + for opt in shell_opts: + CONF.register_cli_opt(opt) def state_path_def(*args): @@ -140,6 +143,7 @@ def process_results(results): def main(): + cli_opts() parse_args(sys.argv) if CONF.subunit_files: streams = [subunit.ReadSubunit(open(s, 'r')) for s in diff --git a/subunit2sql/write_subunit.py b/subunit2sql/write_subunit.py new file mode 100644 index 0000000..380d832 --- /dev/null +++ b/subunit2sql/write_subunit.py @@ -0,0 +1,99 @@ +# Copyright 2014 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 functools +import sys + +from oslo.config import cfg +import subunit +from subunit import iso8601 + +from subunit2sql.db import api +from subunit2sql import shell + +STATUS_CODES = frozenset([ + 'exists', + 'fail', + 'skip', + 'success', + 'uxsuccess', + 'xfail', +]) + +CONF = cfg.CONF + + +def cli_opts(): + shell_opts = [ + cfg.StrOpt('run_id', required=True, positional=True, + help='Run id to use for creating a subunit stream'), + cfg.StrOpt('out_path', short='o', default=None, + help='Path to write the subunit stream output, if none ' + 'is specified STDOUT will be used') + ] + for opt in shell_opts: + cfg.CONF.register_cli_opt(opt) + + +def convert_datetime(timestamp): + tz_timestamp = timestamp.replace(tzinfo=iso8601.UTC) + return tz_timestamp + + +def write_test(output, test_run, test, metadatas): + write_status = output.status + for meta in metadatas: + if meta.key == 'tags': + tags = meta.value + write_status = functools.partial(write_status, + test_tags=tags.split(',')) + start_time = convert_datetime(test_run.start_time) + write_status = functools.partial(write_status, + timestamp=start_time) + write_status = functools.partial(write_status, test_id=test.test_id) + if test_run.status in STATUS_CODES: + write_status = functools.partial(write_status, + test_status=test_run.status) + write_status = functools.partial(write_status, + timestamp=convert_datetime( + test_run.stop_time)) + write_status() + + +def sql2subunit(run_id, output=sys.stdout): + session = api.get_session() + test_runs = api.get_test_runs_by_run_id(run_id, session) + output = subunit.v2.StreamResultToBytes(output) + output.startTestRun() + for test in test_runs: + metadatas = api.get_test_run_metadata(test.id, session) + test_i = api.get_test_by_id(test.test_id) + write_test(output, test, test_i, metadatas) + output.stopTestRun() + session.close() + + +def main(): + cli_opts() + shell.parse_args(sys.argv) + if CONF.out_path: + fd = open(CONF.out_path, 'w') + else: + fd = sys.stdout + sql2subunit(CONF.run_id, fd) + if CONF.out_path: + fd.close() + +if __name__ == "__main__": + sys.exit(main())