From 464a9e0adc1f6ebed14b95e83a81b1e343f5465d Mon Sep 17 00:00:00 2001 From: "mccasland, trevor (tm2086)" Date: Mon, 12 Mar 2018 16:47:30 -0500 Subject: [PATCH] Add attachments flag to get_test_runs_by_status By adding an 'include_attachments' flag to the get_test_runs_by_status method we can join the Attachments table with the same structure as the RunMetadata table allowing us to optionally include attachments or metadata with test_runs filtered by a given status. Notes on testing: * As far as I could tell, logstash.o.o doesn't store attachments[1] so to test this patch you will have to setup/use another subunit2sql db with attachments stored in it * you can find the openstack-health patch to render the attachments in the Needed-By change of the footer of this commit message. Notes on performance: * on average loading attachments is about 13% slower when calling this api with python[2]. The query times in the paste were produced by calling the api method get_test_runs_by_status_for_run_ids 100x in a for loop with 49 test_runs being returned from the call, which is the number of failures in the last 10 runs. [1] http://paste.openstack.org/show/719192/ [2] http://paste.openstack.org/show/738662/ Needed-By: I3ce2fc50ada9462de3df29b5a8b76b12a548fd12 Change-Id: I31468c825cf259b8df62134e578f31d96af6ac75 --- ...-include-attachments-60969df79bf23b13.yaml | 6 ++ subunit2sql/db/api.py | 52 +++++---- subunit2sql/tests/db/test_api.py | 102 ++++++++++++++++++ 3 files changed, 140 insertions(+), 20 deletions(-) create mode 100644 releasenotes/notes/add-include-attachments-60969df79bf23b13.yaml diff --git a/releasenotes/notes/add-include-attachments-60969df79bf23b13.yaml b/releasenotes/notes/add-include-attachments-60969df79bf23b13.yaml new file mode 100644 index 0000000..011d973 --- /dev/null +++ b/releasenotes/notes/add-include-attachments-60969df79bf23b13.yaml @@ -0,0 +1,6 @@ +--- +features: + - An 'include_attachments' boolean option has been added to the db api + method, get_test_runs_by_status_for_run_ids(). When enabled, a list + of attachments with labels in each of the test runs dictionaries will + be returned. diff --git a/subunit2sql/db/api.py b/subunit2sql/db/api.py index dcaf291..091b421 100644 --- a/subunit2sql/db/api.py +++ b/subunit2sql/db/api.py @@ -1852,7 +1852,8 @@ def get_runs_by_status_grouped_by_run_metadata(key, start_date=None, def get_test_runs_by_status_for_run_ids(status, run_ids, key=None, - session=None, include_run_id=False): + session=None, include_run_id=False, + include_attachments=False): """Get a list of test run dicts by status for all the specified runs :param str status: The test status to filter the returned test runs on @@ -1864,6 +1865,8 @@ def get_test_runs_by_status_for_run_ids(status, run_ids, key=None, will be acquired for the duration of this operation :param bool include_run_id: boolean flag to enable including the run uuid in the test run dicts returned + :param bool include_attachments: boolean flag to enable including a list + of attachments with labels in the test run dicts returned :return test_runs: A list of dicts for the test_runs and associated data :rtype: list @@ -1874,34 +1877,43 @@ def get_test_runs_by_status_for_run_ids(status, run_ids, key=None, models.Test, models.TestRun.test_id == models.Test.id).join( models.Run, models.TestRun.run_id == models.Run.id).filter( models.Run.uuid.in_(run_ids)) - + query_values = [models.Test.test_id, models.Run.artifacts, + models.TestRun.start_time, + models.TestRun.start_time_microsecond, + models.TestRun.stop_time, + models.TestRun.stop_time_microsecond, + models.Run.uuid] + if include_attachments: + query = query.join( + models.Attachments, + models.TestRun.id == models.Attachments.test_run_id) + query_values.extend([models.Attachments.label, + models.Attachments.attachment]) if key: query = query.join( models.RunMetadata, models.TestRun.run_id == models.RunMetadata.run_id).filter( models.RunMetadata.key == key) - results = query.values(models.Test.test_id, models.Run.artifacts, - models.TestRun.start_time, - models.TestRun.start_time_microsecond, - models.TestRun.stop_time, - models.TestRun.stop_time_microsecond, - models.RunMetadata.value, - models.Run.uuid) - else: - results = query.values(models.Test.test_id, models.Run.artifacts, - models.TestRun.start_time, - models.TestRun.start_time_microsecond, - models.TestRun.stop_time, - models.TestRun.stop_time_microsecond, - models.Run.uuid) - test_runs = [] + query_values.append(models.RunMetadata.value) + + results = query.values(*query_values) + test_runs = {} for result in results: test_run = { 'test_id': result.test_id, 'link': result.artifacts, 'start_time': result.start_time, - 'stop_time': result.stop_time, + 'stop_time': result.stop_time } + test_run_key = result.uuid + result.test_id + if include_attachments: + attachment_dict = {'label': result.label, + 'attachment': result.attachment} + if test_run_key in test_runs: + test_run = test_runs[test_run_key] + test_run['attachments'].append(attachment_dict) + continue + test_run['attachments'] = [attachment_dict] if include_run_id: test_run['uuid'] = result.uuid if result.start_time_microsecond is not None: @@ -1912,8 +1924,8 @@ def get_test_runs_by_status_for_run_ids(status, run_ids, key=None, microsecond=result.stop_time_microsecond) if hasattr(result, "value"): test_run[key] = result.value - test_runs.append(test_run) - return test_runs + test_runs[test_run_key] = test_run + return list(test_runs.values()) def get_all_run_metadata_keys(session=None): diff --git a/subunit2sql/tests/db/test_api.py b/subunit2sql/tests/db/test_api.py index e7cef78..f6cc839 100644 --- a/subunit2sql/tests/db/test_api.py +++ b/subunit2sql/tests/db/test_api.py @@ -779,6 +779,108 @@ class TestDatabaseAPI(base.TestCase): 'stop_time': stop_timestamp, }, result[0]) + def test_get_test_runs_by_status_for_run_ids_with_attachments(self): + attach_dict = {'attach_label': b'attach', + 'attach_label_a': b'attach_a'} + run_b = api.create_run(artifacts='fake_url') + run_a = api.create_run() + run_c = api.create_run() + test_a = api.create_test('fake_test') + start_timestamp = datetime.datetime(1914, 6, 28, 10, 45, 0) + stop_timestamp = datetime.datetime(1914, 6, 28, 10, 50, 0) + api.create_test_run(test_a.id, run_a.id, 'success', + datetime.datetime.utcnow()) + test_run_ab = api.create_test_run(test_a.id, run_b.id, 'fail', + start_timestamp, stop_timestamp) + api.create_test_run(test_a.id, run_c.id, 'success', + datetime.datetime.utcnow()) + api.add_test_run_attachments(attach_dict, test_run_ab.id) + result = api.get_test_runs_by_status_for_run_ids( + 'fail', + [run_a.uuid, run_b.uuid, run_c.uuid], + include_attachments=True) + self.assertEqual(1, len(result)) + expected_attachments = [{'label': 'attach_label', + 'attachment': b'attach'}, + {'label': 'attach_label_a', + 'attachment': b'attach_a'}] + self.assertItemsEqual(expected_attachments, result[0]['attachments']) + + def test_get_many_test_runs_by_status_for_run_ids_with_attachments(self): + attach_dict_test_b_run_a = {'attach_label': b'attach1', + 'run_a': b'test_b'} + attach_dict_test_a_run_b = {'attach_label': b'attach2', + 'run_b': b'test_a'} + attach_dict_test_a_run_c = {'attach_label': b'attach3', + 'run_c': b'test_a'} + attach_dict_test_b_run_c = {'attach_label': b'attach4', + 'run_c': b'test_b'} + run_a = api.create_run(artifacts='fake_url') + run_b = api.create_run(artifacts='fake_url') + run_c = api.create_run(artifacts='fake_url') + test_a = api.create_test('fake_test_a') + test_b = api.create_test('fake_test_b') + start_timestamp = datetime.datetime(1914, 6, 28, 11, 45, 1) + stop_timestamp = datetime.datetime(1914, 6, 28, 11, 50, 1) + test_b_run_a = api.create_test_run(test_b.id, run_a.id, 'fail', + start_timestamp, stop_timestamp) + test_a_run_b = api.create_test_run(test_a.id, run_b.id, 'fail', + start_timestamp, stop_timestamp) + test_a_run_c = api.create_test_run(test_a.id, run_c.id, 'fail', + start_timestamp, stop_timestamp) + test_b_run_c = api.create_test_run(test_b.id, run_c.id, 'fail', + start_timestamp, stop_timestamp) + api.add_test_run_attachments(attach_dict_test_b_run_a, test_b_run_a.id) + api.add_test_run_attachments(attach_dict_test_a_run_b, test_a_run_b.id) + api.add_test_run_attachments(attach_dict_test_a_run_c, test_a_run_c.id) + api.add_test_run_attachments(attach_dict_test_b_run_c, test_b_run_c.id) + results = api.get_test_runs_by_status_for_run_ids( + 'fail', + [run_a.uuid, run_b.uuid, run_c.uuid], + include_attachments=True, + include_run_id=True) + self.assertEqual(4, len(results)) + expected_attachments_test_b_run_a = [{'label': u'attach_label', + 'attachment': b'attach1'}, + {'label': u'run_a', + 'attachment': b'test_b'}] + expected_attachments_test_a_run_b = [{'label': u'attach_label', + 'attachment': b'attach2'}, + {'label': u'run_b', + 'attachment': b'test_a'}] + expected_attachments_test_a_run_c = [{'label': u'attach_label', + 'attachment': b'attach3'}, + {'label': u'run_c', + 'attachment': b'test_a'}] + expected_attachments_test_b_run_c = [{'label': u'attach_label', + 'attachment': b'attach4'}, + {'label': u'run_c', + 'attachment': b'test_b'}] + test_b_run_a_called = False + test_a_run_b_called = False + test_a_run_c_called = False + test_b_run_c_called = False + for result in results: + uuid = result['uuid'] + test_id = result['test_id'] + if test_id == test_b.test_id and uuid == run_a.uuid: + expected_attachments = expected_attachments_test_b_run_a + test_b_run_a_called = True + elif test_id == test_a.test_id and uuid == run_b.uuid: + expected_attachments = expected_attachments_test_a_run_b + test_a_run_b_called = True + elif test_id == test_a.test_id and uuid == run_c.uuid: + expected_attachments = expected_attachments_test_a_run_c + test_a_run_c_called = True + elif test_id == test_b.test_id and uuid == run_c.uuid: + expected_attachments = expected_attachments_test_b_run_c + test_b_run_c_called = True + self.assertItemsEqual(expected_attachments, result['attachments']) + self.assertTrue(test_b_run_a_called) + self.assertTrue(test_a_run_b_called) + self.assertTrue(test_a_run_c_called) + self.assertTrue(test_b_run_c_called) + def test_get_test_runs_by_status_for_run_ids_with_meta(self): run_b = api.create_run(artifacts='fake_url') run_a = api.create_run()