From 6aa733e0f348bc9c3362d0a5c5b881cc77472968 Mon Sep 17 00:00:00 2001 From: Doug Schveninger Date: Sat, 18 Jul 2020 11:03:21 -0500 Subject: [PATCH] Improve unit tests for subunit_describe_calls in order to enhance the cmd to support kubernetes url based routing over port based routing. Once this is merged I will submit another patch set to subunit_describe_calls to support -u --urls (Optional) The path to a JSON file describing the urls being used by different services. Note Can not be used with -p. Added test cases for cliff subprocess and found a bug with the -v. The standard -v --verbose of cliff conflict with this command so I changed the -v --verbose to -a --all for print header with printing to stdout I am also getting ready to add test cases for cliff subprocess support for different options. Bug #1890060 Correct os join in test cases to avoid a conflict with https://review.opendev.org/#/c/683026 Closes-bug: #1890060 Change-Id: I9459db0dbeda721187ea5f4802c7453c2092dac3 --- .../subunt-describe-call-verbose-arg-fix.yaml | 10 + tempest/cmd/subunit_describe_calls.py | 28 +- .../calls.subunit | Bin .../calls_subunit_expected.json | 87 +++ .../tests/cmd/test_subunit_describe_calls.py | 604 ++++++++++++------ 5 files changed, 522 insertions(+), 207 deletions(-) create mode 100644 releasenotes/notes/25/subunt-describe-call-verbose-arg-fix.yaml rename tempest/tests/cmd/{sample_streams => subunit_describe_calls_data}/calls.subunit (100%) create mode 100644 tempest/tests/cmd/subunit_describe_calls_data/calls_subunit_expected.json diff --git a/releasenotes/notes/25/subunt-describe-call-verbose-arg-fix.yaml b/releasenotes/notes/25/subunt-describe-call-verbose-arg-fix.yaml new file mode 100644 index 0000000000..d2a644e89a --- /dev/null +++ b/releasenotes/notes/25/subunt-describe-call-verbose-arg-fix.yaml @@ -0,0 +1,10 @@ +--- +fixes: + - | + Fixed bug #1890060. tempest subunit_describe_calls --verbose not working with Cliff CLI. + The subunit_describe_calls --verbose argument was a boolean and worked in the non Cliff CLI + which is now deprecated, but does not work with cliff since --verbase is a standard cliff + argument which is an int. Since the tool is in lib directory we cannot change the interface, + so we add a new argument -a --all-stdout that will allow cliff CLI to support the + feature in subunnit_describe_calls to print request and response headers and bodies + to stdout. \ No newline at end of file diff --git a/tempest/cmd/subunit_describe_calls.py b/tempest/cmd/subunit_describe_calls.py index e0295381e5..172fbaa809 100644 --- a/tempest/cmd/subunit_describe_calls.py +++ b/tempest/cmd/subunit_describe_calls.py @@ -30,6 +30,8 @@ Runtime Arguments * ``--ports, -p``: (Optional) The path to a JSON file describing the ports being used by different services * ``--verbose, -v``: (Optional) Print Request and Response Headers and Body + data to stdout in the non cliff deprecated CLI +* ``--all-stdout, -a``: (Optional) Print Request and Response Headers and Body data to stdout @@ -278,7 +280,7 @@ def parse(stream, non_subunit_name, ports): return url_parser -def output(url_parser, output_file, verbose): +def output(url_parser, output_file, all_stdout): if output_file is not None: with open(output_file, "w") as outfile: outfile.write(json.dumps(url_parser.test_logs)) @@ -294,7 +296,7 @@ def output(url_parser, output_file, verbose): sys.stdout.write('\t- {0} {1} request for {2} to {3}\n'.format( item.get('status_code'), item.get('verb'), item.get('service'), item.get('url'))) - if verbose: + if all_stdout: sys.stdout.write('\t\t- request headers: {0}\n'.format( item.get('request_headers'))) sys.stdout.write('\t\t- request body: {0}\n'.format( @@ -313,7 +315,7 @@ def entry_point(cl_args=None): "please use: 'tempest subunit-describe-calls'") cl_args = ArgumentParser().parse_args() parser = parse(cl_args.subunit, cl_args.non_subunit_name, cl_args.ports) - output(parser, cl_args.output_file, cl_args.verbose) + output(parser, cl_args.output_file, cl_args.all_stdout) def _parser_add_args(parser): @@ -339,9 +341,23 @@ def _parser_add_args(parser): help="A JSON file describing the ports for each service." ) - parser.add_argument( - "-v", "--verbose", action='store_true', default=False, - help="Add Request and Response header and body data to stdout." + group = parser.add_mutually_exclusive_group() + # the -v and --verbose command are for the old subunit-describe-calls + # main() CLI interface. It does not work with the new + # tempest subunit-describe-callss CLI. So when the main CLI approach is + # deleted this argument is not needed. + group.add_argument( + "-v", "--verbose", action='store_true', dest='all_stdout', + help='Add Request and Response header and body data to stdout print.' + ' NOTE: This argument deprecated and does not work with' + ' tempest subunit-describe-calls CLI.' + ' Use new option: "-a", "--all-stdout"' + ) + group.add_argument( + "-a", "--all-stdout", action='store_true', + help="Add Request and Response header and body data to stdout print." + " Note: this argument work with the subunit-describe-calls and" + " tempest subunit-describe-calls CLI commands." ) diff --git a/tempest/tests/cmd/sample_streams/calls.subunit b/tempest/tests/cmd/subunit_describe_calls_data/calls.subunit similarity index 100% rename from tempest/tests/cmd/sample_streams/calls.subunit rename to tempest/tests/cmd/subunit_describe_calls_data/calls.subunit diff --git a/tempest/tests/cmd/subunit_describe_calls_data/calls_subunit_expected.json b/tempest/tests/cmd/subunit_describe_calls_data/calls_subunit_expected.json new file mode 100644 index 0000000000..53976eecde --- /dev/null +++ b/tempest/tests/cmd/subunit_describe_calls_data/calls_subunit_expected.json @@ -0,0 +1,87 @@ +{"bar":[ + { + "name":"AgentsAdminTestJSON:setUp", + "request_body":"{\"agent\": {\"url\": \"xxx://xxxx/xxx/xxx\", \"hypervisor\": \"common\", \"md5hash\": \"add6bb58e139be103324d04d82d8f545\", \"version\": \"7.0\", \"architecture\": \"tempest-x86_64-424013832\", \"os\": \"linux\"}}", + "request_headers":"{'Content-Type': 'application/json', 'Accept': 'application/json', 'X-Auth-Token': ''}", + "response_body":"{\"agent\": {\"url\": \"xxx://xxxx/xxx/xxx\", \"hypervisor\": \"common\", \"md5hash\": \"add6bb58e139be103324d04d82d8f545\", \"version\": \"7.0\", \"architecture\": \"tempest-x86_64-424013832\", \"os\": \"linux\", \"agent_id\": 1}}", + "response_headers":"{'status': '200', 'content-length': '203', 'x-compute-request-id': 'req-25ddaae2-0ef1-40d1-8228-59bd64a7e75b', 'vary': 'X-OpenStack-Nova-API-Version', 'connection': 'close', 'x-openstack-nova-api-version': '2.1', 'date': 'Tue, 02 Feb 2016 03:27:00 GMT', 'content-type': 'application/json'}", + "service":"Nova", + "status_code":"200", + "url":"v2.1//os-agents", + "verb":"POST" +}, + { + "name":"AgentsAdminTestJSON:test_create_agent", + "request_body":"{\"agent\": {\"url\": \"xxx://xxxx/xxx/xxx\", \"hypervisor\": \"kvm\", \"md5hash\": \"add6bb58e139be103324d04d82d8f545\", \"version\": \"7.0\", \"architecture\": \"tempest-x86-252246646\", \"os\": \"win\"}}", + "request_headers":"{'Content-Type': 'application/json', 'Accept': 'application/json', 'X-Auth-Token': ''}", + "response_body":"{\"agent\": {\"url\": \"xxx://xxxx/xxx/xxx\", \"hypervisor\": \"kvm\", \"md5hash\": \"add6bb58e139be103324d04d82d8f545\", \"version\": \"7.0\", \"architecture\": \"tempest-x86-252246646\", \"os\": \"win\", \"agent_id\": 2}}", + "response_headers":"{'status': '200', 'content-length': '195', 'x-compute-request-id': 'req-b4136f06-c015-4e7e-995f-c43831e3ecce', 'vary': 'X-OpenStack-Nova-API-Version', 'connection': 'close', 'x-openstack-nova-api-version': '2.1', 'date': 'Tue, 02 Feb 2016 03:27:00 GMT', 'content-type': 'application/json'}", + "service":"Nova", + "status_code":"200", + "url":"v2.1//os-agents", + "verb":"POST" +}, + { + "name":"AgentsAdminTestJSON:tearDown", + "request_body":"None", + "request_headers":"{'Content-Type': 'application/json', 'Accept': 'application/json', 'X-Auth-Token': ''}", + "response_body":"", + "response_headers":"{'status': '200', 'content-length': '0', 'x-compute-request-id': 'req-ee905fd6-a5b5-4da4-8c37-5363cb25bd9d', 'vary': 'X-OpenStack-Nova-API-Version', 'connection': 'close', 'x-openstack-nova-api-version': '2.1', 'date': 'Tue, 02 Feb 2016 03:27:00 GMT', 'content-type': 'application/json'}", + "service":"Nova", + "status_code":"200", + "url":"v2.1//os-agents/1", + "verb":"DELETE" +}, + { + "name":"AgentsAdminTestJSON:_run_cleanups", + "request_body":"None", + "request_headers":"{'Content-Type': 'application/json', 'Accept': 'application/json', 'X-Auth-Token': ''}", + "response_headers":"{'status': '200', 'content-length': '0', 'x-compute-request-id': 'req-e912cac0-63e0-4679-a68a-b6d18ddca074', 'vary': 'X-OpenStack-Nova-API-Version', 'connection': 'close', 'x-openstack-nova-api-version': '2.1', 'date': 'Tue, 02 Feb 2016 03:27:00 GMT', 'content-type': 'application/json'}", + "service":"Nova", + "status_code":"200", + "url":"v2.1//os-agents/2", + "verb":"DELETE" +}], "foo":[ + { + "name":"AgentsAdminTestJSON:setUp", + "request_body":"{\"agent\": {\"url\": \"xxx://xxxx/xxx/xxx\", \"hypervisor\": \"common\", \"md5hash\": \"add6bb58e139be103324d04d82d8f545\", \"version\": \"7.0\", \"architecture\": \"tempest-x86_64-948635295\", \"os\": \"linux\"}}", + "request_headers":"{'Content-Type': 'application/json', 'Accept': 'application/json', 'X-Auth-Token': ''}", + "response_body":"{\"agent\": {\"url\": \"xxx://xxxx/xxx/xxx\", \"hypervisor\": \"common\", \"md5hash\": \"add6bb58e139be103324d04d82d8f545\", \"version\": \"7.0\", \"architecture\": \"tempest-x86_64-948635295\", \"os\": \"linux\", \"agent_id\": 3}}", + "response_headers":"{'status': '200', 'content-length': '203', 'x-compute-request-id': 'req-ccd2116d-04b1-4ffe-ae32-fb623f68bf1c', 'vary': 'X-OpenStack-Nova-API-Version', 'connection': 'close', 'x-openstack-nova-api-version': '2.1', 'date': 'Tue, 02 Feb 2016 03:27:01 GMT', 'content-type': 'application/json'}", + "service":"Nova", + "status_code":"200", + "url":"v2.1//os-agents", + "verb":"POST" +}, + { + "name":"AgentsAdminTestJSON:test_delete_agent", + "request_body":"None", + "request_headers":"{'Content-Type': 'application/json', 'Accept': 'application/json', 'X-Auth-Token': ''}", + "response_body":"", + "response_headers":"{'status': '200', 'content-length': '0', 'x-compute-request-id': 'req-6e7fa28f-ae61-4388-9a78-947c58bc0588', 'vary': 'X-OpenStack-Nova-API-Version', 'connection': 'close', 'x-openstack-nova-api-version': '2.1', 'date': 'Tue, 02 Feb 2016 03:27:01 GMT', 'content-type': 'application/json'}", + "service":"Nova", + "status_code":"200", + "url":"v2.1//os-agents/3", + "verb":"DELETE" +}, + { + "name":"AgentsAdminTestJSON:test_delete_agent", + "request_body":"None", + "request_headers":"{'Content-Type': 'application/json', 'Accept': 'application/json', 'X-Auth-Token': ''}", + "response_body":"{\"agents\": []}", + "response_headers":"{'status': '200', 'content-length': '14', 'content-location': 'http://23.253.76.97:8774/v2.1/cf6b1933fe5b476fbbabb876f6d1b924/os-agents', 'x-compute-request-id': 'req-e41aa9b4-41a6-4138-ae04-220b768eb644', 'vary': 'X-OpenStack-Nova-API-Version', 'connection': 'close', 'x-openstack-nova-api-version': '2.1', 'date': 'Tue, 02 Feb 2016 03:27:01 GMT', 'content-type': 'application/json'}", + "service":"Nova", + "status_code":"200", + "url":"v2.1//os-agents", + "verb":"GET" +}, + { + "name":"AgentsAdminTestJSON:tearDown", + "request_body":"None", + "request_headers":"{'Content-Type': 'application/json', 'Accept': 'application/json', 'X-Auth-Token': ''}", + "response_headers":"{'status': '404', 'content-length': '82', 'x-compute-request-id': 'req-e297aeea-91cf-4f26-b49c-8f46b1b7a926', 'vary': 'X-OpenStack-Nova-API-Version', 'connection': 'close', 'x-openstack-nova-api-version': '2.1', 'date': 'Tue, 02 Feb 2016 03:27:02 GMT', 'content-type': 'application/json; charset=UTF-8'}", + "service":"Nova", + "status_code":"404", + "url":"v2.1//os-agents/3", + "verb":"DELETE" +}]} \ No newline at end of file diff --git a/tempest/tests/cmd/test_subunit_describe_calls.py b/tempest/tests/cmd/test_subunit_describe_calls.py index cb34ba6943..4fed84a794 100644 --- a/tempest/tests/cmd/test_subunit_describe_calls.py +++ b/tempest/tests/cmd/test_subunit_describe_calls.py @@ -14,220 +14,422 @@ # License for the specific language governing permissions and limitations # under the License. +import argparse +from io import StringIO import os +import shutil import subprocess +import sys import tempfile +from unittest import mock +from unittest.mock import patch + +from oslo_serialization import jsonutils as json from tempest.cmd import subunit_describe_calls from tempest.tests import base -class TestSubunitDescribeCalls(base.TestCase): - def test_return_code(self): - subunit_file = os.path.join( - os.path.dirname(os.path.abspath(__file__)), - 'sample_streams/calls.subunit') - p = subprocess.Popen([ - 'subunit-describe-calls', '-s', subunit_file, - '-o', tempfile.mkstemp()[1]], stdin=subprocess.PIPE) - p.communicate() - self.assertEqual(0, p.returncode) +class TestArgumentParser(base.TestCase): + def test_init(self): + test_object = subunit_describe_calls.ArgumentParser() + self.assertEqual("subunit-describe-calls", test_object.prog) + self.assertEqual(subunit_describe_calls.DESCRIPTION, + test_object.description) - def test_verbose(self): - subunit_file = os.path.join( - os.path.dirname(os.path.abspath(__file__)), - 'sample_streams/calls.subunit') - p = subprocess.Popen([ - 'subunit-describe-calls', '-s', subunit_file, - '-v'], stdin=subprocess.PIPE, stdout=subprocess.PIPE) - stdout = p.communicate() - self.assertEqual(0, p.returncode) - self.assertIn(b'- request headers:', stdout[0]) - self.assertIn(b'- request body:', stdout[0]) - self.assertIn(b'- response headers:', stdout[0]) - self.assertIn(b'- response body:', stdout[0]) - def test_return_code_no_output(self): - subunit_file = os.path.join( +class TestUrlParser(base.TestCase): + services_custom_ports = { + "18776": "Block Storage", + "18774": "Nova", + "18773": "Nova-API", + "18386": "Sahara", + "35358": "Keystone", + "19292": "Glance", + "19696": "Neutron", + "16000": "Swift", + "18004": "Heat", + "18777": "Ceilometer", + "10080": "Horizon", + "18080": "Swift", + "1873": "rsync", + "13260": "iSCSI", + "13306": "MySQL", + "15672": "AMQP", + "18082": "murano"} + + def setUp(self): + super(TestUrlParser, self).setUp() + self.test_object = subunit_describe_calls.UrlParser() + + def test_get_service_default_ports(self): + base_url = "http://site.something.com:" + for port in self.test_object.services: + url = base_url + port + "/v2/action" + service = self.test_object.services[port] + self.assertEqual(service, self.test_object.get_service(url)) + + def test_get_service_custom_ports(self): + self.test_object = subunit_describe_calls.\ + UrlParser(services=self.services_custom_ports) + base_url = "http://site.something.com:" + for port in self.services_custom_ports: + url = base_url + port + "/v2/action" + service = self.services_custom_ports[port] + self.assertEqual(service, self.test_object.get_service(url)) + + def test_get_service_port_not_found(self): + url = "https://site.somewhere.com:1234/v2/action" + self.assertEqual("Unknown", self.test_object.get_service(url)) + self.assertEqual("Unknown", self.test_object.get_service("")) + + def test_parse_details_none(self): + self.assertIsNone(self.test_object.parse_details(None)) + + def test_url_path_ports(self): + uuid_sample1 = "3715e0bb-b1b3-4291-aa13-2c86c3b9ec93" + uuid_sample2 = "2715e0bb-b1b4-4291-aa13-2c86c3b9ec88" + + # test http url + host = "http://host.company.com" + url = host + ":8776/v3/" + uuid_sample1 + "/types/" + \ + uuid_sample2 + "/extra_specs" + self.assertEqual("v3//types//extra_specs", + self.test_object.url_path(url)) + url = host + ":8774/v2.1/servers/" + uuid_sample1 + self.assertEqual("v2.1/servers/", + self.test_object.url_path(url)) + # test https url + host = "https://host.company.com" + url = host + ":8776/v3/" + uuid_sample1 + "/types/" + \ + uuid_sample2 + "/extra_specs" + self.assertEqual("v3//types//extra_specs", + self.test_object.url_path(url)) + url = host + ":8774/v2.1/servers/" + uuid_sample1 + self.assertEqual("v2.1/servers/", + self.test_object.url_path(url)) + + def test_url_path_no_match(self): + host_port = 'https://host.company.com:1234/' + url = 'v2/action/no/special/data' + self.assertEqual(url, self.test_object.url_path(host_port + url)) + url = 'data' + self.assertEqual(url, self.test_object.url_path(url)) + + +class TestCliBase(base.TestCase): + """Base class for share code on all CLI sub-process testing""" + + def setUp(self): + super(TestCliBase, self).setUp() + self._subunit_file = os.path.join( os.path.dirname(os.path.abspath(__file__)), - 'sample_streams/calls.subunit') + 'subunit_describe_calls_data', 'calls.subunit') + + def _bytes_to_string(self, data): + if isinstance(data, (bytes, bytearray)): + data = str(data, 'utf-8') + return data + + def _assert_cli_message(self, data): + data = self._bytes_to_string(data) + self.assertIn("Running subunit_describe_calls ...", data) + + def _assert_deprecated_warning(self, stdout): + self.assertIn( + b"Use of: 'subunit-describe-calls' is deprecated, " + b"please use: 'tempest subunit-describe-calls'", stdout) + + def _assert_expect_json(self, json_data): + expected_file_name = os.path.join( + os.path.dirname(os.path.abspath(__file__)), + 'subunit_describe_calls_data', 'calls_subunit_expected.json') + with open(expected_file_name, "rb") as read_file: + expected_result = json.load(read_file) + self.assertDictEqual(expected_result, json_data) + + def _assert_headers_and_bodies(self, data): + data = self._bytes_to_string(data) + self.assertIn('- request headers:', data) + self.assertIn('- request body:', data) + self.assertIn('- response headers:', data) + self.assertIn('- response body:', data) + + def _assert_methods_details(self, data): + data = self._bytes_to_string(data) + self.assertIn('foo', data) + self.assertIn('- 200 POST request for Nova to v2.1//', + data) + self.assertIn('- 200 DELETE request for Nova to v2.1//', + data) + self.assertIn('- 200 GET request for Nova to v2.1//', + data) + self.assertIn('- 404 DELETE request for Nova to v2.1//', + data) + + def _assert_mutual_exclusive_message(self, stderr): + self.assertIn(b"usage: subunit-describe-calls " + b"[-h] [-s []]", stderr) + self.assertIn(b"[-n ] [-o ]", + stderr) + self.assertIn(b"[-p ] [-v | -a]", stderr) + self.assertIn( + b"subunit-describe-calls: error: argument -v/--verbose: " + b"not allowed with argument -a/--all-stdout", stderr) + + def _assert_no_headers_and_bodies(self, data): + data = self._bytes_to_string(data) + self.assertNotIn('- request headers:', data) + self.assertNotIn('- request body:', data) + self.assertNotIn('- response headers:', data) + self.assertNotIn('- response body:', data) + + +class TestMainCli(TestCliBase): + """Test cases that use subunit_describe_calls module main interface + + via subprocess calls to make sure the total user experience + is well defined and tested. This interface is deprecated. + Note: these test do not affect code coverage percentages. + """ + + def test_main_output_file(self): + temp_file = tempfile.mkstemp()[1] p = subprocess.Popen([ - 'subunit-describe-calls', '-s', subunit_file], - stdin=subprocess.PIPE, stdout=subprocess.PIPE) - stdout = p.communicate() + 'subunit-describe-calls', '-s', self._subunit_file, + '-o', temp_file], stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdout, stderr = p.communicate() self.assertEqual(0, p.returncode) - self.assertIn(b'foo', stdout[0]) - self.assertIn(b'- 200 POST request for Nova to v2.1//', - stdout[0]) - self.assertIn(b'- 200 DELETE request for Nova to v2.1//', - stdout[0]) - self.assertIn(b'- 200 GET request for Nova to v2.1//', - stdout[0]) - self.assertIn(b'- 404 DELETE request for Nova to v2.1//', - stdout[0]) - self.assertNotIn(b'- request headers:', stdout[0]) - self.assertNotIn(b'- request body:', stdout[0]) - self.assertNotIn(b'- response headers:', stdout[0]) - self.assertNotIn(b'- response body:', stdout[0]) + self._assert_cli_message(stdout) + self._assert_deprecated_warning(stdout) + with open(temp_file, 'r') as file: + data = json.loads(file.read()) + self._assert_expect_json(data) + + def test_main_verbose(self): + p = subprocess.Popen([ + 'subunit-describe-calls', '-s', self._subunit_file, + '-v'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout, stderr = p.communicate() + self.assertEqual(0, p.returncode) + self._assert_cli_message(stdout) + self._assert_deprecated_warning(stdout) + self._assert_methods_details(stdout) + self._assert_headers_and_bodies(stdout) + + def test_main_all_stdout(self): + p = subprocess.Popen([ + 'subunit-describe-calls', '-s', self._subunit_file, + '--all-stdout'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout, stderr = p.communicate() + self.assertEqual(0, p.returncode) + self._assert_cli_message(stdout) + self._assert_deprecated_warning(stdout) + self._assert_methods_details(stdout) + self._assert_headers_and_bodies(stdout) + + def test_main(self): + p = subprocess.Popen([ + 'subunit-describe-calls', '-s', self._subunit_file], + stdin=subprocess.PIPE, stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout, stderr = p.communicate() + self.assertEqual(0, p.returncode) + self._assert_cli_message(stdout) + self._assert_deprecated_warning(stdout) + self._assert_methods_details(stdout) + self._assert_no_headers_and_bodies(stdout) + + def test_main_verbose_and_all_stdout(self): + p = subprocess.Popen([ + 'subunit-describe-calls', '-s', self._subunit_file, + '-a', '-v'], + stdin=subprocess.PIPE, stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout, stderr = p.communicate() + self.assertEqual(2, p.returncode) + self._assert_cli_message(stdout) + self._assert_deprecated_warning(stdout) + self._assert_mutual_exclusive_message(stderr) + + +class TestCli(TestCliBase): + """Test cases that use tempest subunit_describe_calls cliff interface + + via subprocess calls to make sure the total user experience + is well defined and tested. + Note: these test do not affect code coverage percentages. + """ + + def _assert_cliff_verbose(self, stdout): + self.assertIn(b'tempest initialize_app', stdout) + self.assertIn(b'prepare_to_run_command TempestSubunitDescribeCalls', + stdout) + self.assertIn(b'tempest clean_up TempestSubunitDescribeCalls', + stdout) + + def test_run_all_stdout(self): + p = subprocess.Popen(['tempest', 'subunit-describe-calls', + '-s', self._subunit_file, '-a'], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout, stderr = p.communicate() + self.assertEqual(0, p.returncode) + self._assert_cli_message(stdout) + self._assert_methods_details(stdout) + self._assert_headers_and_bodies(stdout) + + def test_run_verbose(self): + p = subprocess.Popen(['tempest', 'subunit-describe-calls', + '-s', self._subunit_file, '-v'], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout, stderr = p.communicate() + self.assertEqual(0, p.returncode) + self._assert_cli_message(stdout) + self._assert_methods_details(stdout) + self._assert_no_headers_and_bodies(stdout) + self._assert_cliff_verbose(stderr) + + def test_run_min(self): + p = subprocess.Popen(['tempest', 'subunit-describe-calls', + '-s', self._subunit_file], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout, stderr = p.communicate() + self.assertEqual(0, p.returncode) + self._assert_cli_message(stdout) + self._assert_methods_details(stdout) + self._assert_no_headers_and_bodies(stdout) + + def test_run_verbose_all_stdout(self): + """Test Cliff -v argument + + Since Cliff framework has a argument at the + abstract command level the -v or --verbose for + this command is not processed as a boolean. + So the use of verbose only exists for the + deprecated main CLI interface. When the + main is deleted this test would not be needed. + """ + p = subprocess.Popen(['tempest', 'subunit-describe-calls', + '-s', self._subunit_file, '-a', '-v'], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout, stderr = p.communicate() + self.assertEqual(0, p.returncode) + self._assert_cli_message(stdout) + self._assert_cliff_verbose(stderr) + self._assert_methods_details(stdout) + + +class TestSubunitDescribeCalls(TestCliBase): + """Test cases use the subunit_describe_calls module interface + + and effect code coverage reporting + """ + + def setUp(self): + super(TestSubunitDescribeCalls, self).setUp() + self.test_object = subunit_describe_calls.TempestSubunitDescribeCalls( + app=mock.Mock(), + app_args=mock.Mock(spec=argparse.Namespace)) def test_parse(self): - subunit_file = os.path.join( - os.path.dirname(os.path.abspath(__file__)), - 'sample_streams/calls.subunit') - parser = subunit_describe_calls.parse( - open(subunit_file), "pythonlogging", None) - expected_result = { - 'bar': [{ - 'name': 'AgentsAdminTestJSON:setUp', - 'request_body': '{"agent": {"url": "xxx://xxxx/xxx/xxx", ' - '"hypervisor": "common", "md5hash": ' - '"add6bb58e139be103324d04d82d8f545", "version": "7.0", ' - '"architecture": "tempest-x86_64-424013832", "os": "linux"}}', - 'request_headers': "{'Content-Type': 'application/json', " - "'Accept': 'application/json', 'X-Auth-Token': ''}", - 'response_body': '{"agent": {"url": "xxx://xxxx/xxx/xxx", ' - '"hypervisor": "common", "md5hash": ' - '"add6bb58e139be103324d04d82d8f545", "version": "7.0", ' - '"architecture": "tempest-x86_64-424013832", "os": "linux", ' - '"agent_id": 1}}', - 'response_headers': "{'status': '200', 'content-length': " - "'203', 'x-compute-request-id': " - "'req-25ddaae2-0ef1-40d1-8228-59bd64a7e75b', 'vary': " - "'X-OpenStack-Nova-API-Version', 'connection': 'close', " - "'x-openstack-nova-api-version': '2.1', 'date': " - "'Tue, 02 Feb 2016 03:27:00 GMT', 'content-type': " - "'application/json'}", - 'service': 'Nova', - 'status_code': '200', - 'url': 'v2.1//os-agents', - 'verb': 'POST'}, { - 'name': 'AgentsAdminTestJSON:test_create_agent', - 'request_body': '{"agent": {"url": "xxx://xxxx/xxx/xxx", ' - '"hypervisor": "kvm", "md5hash": ' - '"add6bb58e139be103324d04d82d8f545", "version": "7.0", ' - '"architecture": "tempest-x86-252246646", "os": "win"}}', - 'request_headers': "{'Content-Type': 'application/json', " - "'Accept': 'application/json', 'X-Auth-Token': ''}", - 'response_body': '{"agent": {"url": "xxx://xxxx/xxx/xxx", ' - '"hypervisor": "kvm", "md5hash": ' - '"add6bb58e139be103324d04d82d8f545", "version": "7.0", ' - '"architecture": "tempest-x86-252246646", "os": "win", ' - '"agent_id": 2}}', - 'response_headers': "{'status': '200', 'content-length': " - "'195', 'x-compute-request-id': " - "'req-b4136f06-c015-4e7e-995f-c43831e3ecce', 'vary': " - "'X-OpenStack-Nova-API-Version', 'connection': 'close', " - "'x-openstack-nova-api-version': '2.1', 'date': " - "'Tue, 02 Feb 2016 03:27:00 GMT', 'content-type': " - "'application/json'}", - 'service': 'Nova', - 'status_code': '200', - 'url': 'v2.1//os-agents', - 'verb': 'POST'}, { - 'name': 'AgentsAdminTestJSON:tearDown', - 'request_body': 'None', - 'request_headers': "{'Content-Type': 'application/json', " - "'Accept': 'application/json', 'X-Auth-Token': ''}", - 'response_body': '', - 'response_headers': "{'status': '200', 'content-length': " - "'0', 'x-compute-request-id': " - "'req-ee905fd6-a5b5-4da4-8c37-5363cb25bd9d', 'vary': " - "'X-OpenStack-Nova-API-Version', 'connection': 'close', " - "'x-openstack-nova-api-version': '2.1', 'date': " - "'Tue, 02 Feb 2016 03:27:00 GMT', 'content-type': " - "'application/json'}", - 'service': 'Nova', - 'status_code': '200', - 'url': 'v2.1//os-agents/1', - 'verb': 'DELETE'}, { - 'name': 'AgentsAdminTestJSON:_run_cleanups', - 'request_body': 'None', - 'request_headers': "{'Content-Type': 'application/json', " - "'Accept': 'application/json', 'X-Auth-Token': ''}", - 'response_headers': "{'status': '200', 'content-length': " - "'0', 'x-compute-request-id': " - "'req-e912cac0-63e0-4679-a68a-b6d18ddca074', 'vary': " - "'X-OpenStack-Nova-API-Version', 'connection': 'close', " - "'x-openstack-nova-api-version': '2.1', 'date': " - "'Tue, 02 Feb 2016 03:27:00 GMT', 'content-type': " - "'application/json'}", - 'service': 'Nova', - 'status_code': '200', - 'url': 'v2.1//os-agents/2', - 'verb': 'DELETE'}], - 'foo': [{ - 'name': 'AgentsAdminTestJSON:setUp', - 'request_body': '{"agent": {"url": "xxx://xxxx/xxx/xxx", ' - '"hypervisor": "common", "md5hash": ' - '"add6bb58e139be103324d04d82d8f545", "version": "7.0", ' - '"architecture": "tempest-x86_64-948635295", "os": "linux"}}', - 'request_headers': "{'Content-Type': 'application/json', " - "'Accept': 'application/json', 'X-Auth-Token': ''}", - 'response_body': '{"agent": {"url": "xxx://xxxx/xxx/xxx", ' - '"hypervisor": "common", "md5hash": ' - '"add6bb58e139be103324d04d82d8f545", "version": "7.0", ' - '"architecture": "tempest-x86_64-948635295", "os": "linux", ' - '"agent_id": 3}}', - 'response_headers': "{'status': '200', 'content-length': " - "'203', 'x-compute-request-id': " - "'req-ccd2116d-04b1-4ffe-ae32-fb623f68bf1c', 'vary': " - "'X-OpenStack-Nova-API-Version', 'connection': 'close', " - "'x-openstack-nova-api-version': '2.1', 'date': " - "'Tue, 02 Feb 2016 03:27:01 GMT', 'content-type': " - "'application/json'}", - 'service': 'Nova', - 'status_code': '200', - 'url': 'v2.1//os-agents', - 'verb': 'POST'}, { - 'name': 'AgentsAdminTestJSON:test_delete_agent', - 'request_body': 'None', - 'request_headers': "{'Content-Type': 'application/json', " - "'Accept': 'application/json', 'X-Auth-Token': ''}", - 'response_body': '', - 'response_headers': "{'status': '200', 'content-length': " - "'0', 'x-compute-request-id': " - "'req-6e7fa28f-ae61-4388-9a78-947c58bc0588', 'vary': " - "'X-OpenStack-Nova-API-Version', 'connection': 'close', " - "'x-openstack-nova-api-version': '2.1', 'date': " - "'Tue, 02 Feb 2016 03:27:01 GMT', 'content-type': " - "'application/json'}", - 'service': 'Nova', - 'status_code': '200', - 'url': 'v2.1//os-agents/3', - 'verb': 'DELETE'}, { - 'name': 'AgentsAdminTestJSON:test_delete_agent', - 'request_body': 'None', - 'request_headers': "{'Content-Type': 'application/json', " - "'Accept': 'application/json', 'X-Auth-Token': ''}", - 'response_body': '{"agents": []}', - 'response_headers': "{'status': '200', 'content-length': " - "'14', 'content-location': " - "'http://23.253.76.97:8774/v2.1/" - "cf6b1933fe5b476fbbabb876f6d1b924/os-agents', " - "'x-compute-request-id': " - "'req-e41aa9b4-41a6-4138-ae04-220b768eb644', 'vary': " - "'X-OpenStack-Nova-API-Version', 'connection': 'close', " - "'x-openstack-nova-api-version': '2.1', 'date': " - "'Tue, 02 Feb 2016 03:27:01 GMT', 'content-type': " - "'application/json'}", - 'service': 'Nova', - 'status_code': '200', - 'url': 'v2.1//os-agents', - 'verb': 'GET'}, { - 'name': 'AgentsAdminTestJSON:tearDown', - 'request_body': 'None', - 'request_headers': "{'Content-Type': 'application/json', " - "'Accept': 'application/json', 'X-Auth-Token': ''}", - 'response_headers': "{'status': '404', 'content-length': " - "'82', 'x-compute-request-id': " - "'req-e297aeea-91cf-4f26-b49c-8f46b1b7a926', 'vary': " - "'X-OpenStack-Nova-API-Version', 'connection': 'close', " - "'x-openstack-nova-api-version': '2.1', 'date': " - "'Tue, 02 Feb 2016 03:27:02 GMT', 'content-type': " - "'application/json; charset=UTF-8'}", - 'service': 'Nova', - 'status_code': '404', - 'url': 'v2.1//os-agents/3', - 'verb': 'DELETE'}]} + with open(self._subunit_file, 'r') as read_file: + parser = subunit_describe_calls.parse( + read_file, "pythonlogging", None) + self._assert_expect_json(parser.test_logs) - self.assertEqual(expected_result, parser.test_logs) + def test_get_description(self): + self.assertEqual(subunit_describe_calls.DESCRIPTION, + self.test_object.get_description()) + + def test_get_parser_default_min(self): + parser = self.test_object.get_parser('NAME') + parsed_args = parser.parse_args([]) + self.assertIsNone(parsed_args.output_file) + self.assertIsNone(parsed_args.ports) + self.assertFalse(parsed_args.all_stdout) + self.assertEqual(parsed_args.subunit, sys.stdin) + + def test_get_parser_default_max(self): + temp_dir = tempfile.mkdtemp(prefix="parser") + self.addCleanup(shutil.rmtree, temp_dir, ignore_errors=True) + outfile_name = os.path.join(temp_dir, 'output.json') + open(outfile_name, 'a').close() + portfile_name = os.path.join(temp_dir, 'ports.json') + open(portfile_name, 'a').close() + + parser = self.test_object.get_parser('NAME') + parsed_args = parser.parse_args(["-a", "-o " + outfile_name, + "-p " + portfile_name]) + + self.assertIsNotNone(parsed_args.output_file) + self.assertIsNotNone(parsed_args.ports) + self.assertTrue(parsed_args.all_stdout) + self.assertEqual(parsed_args.subunit, sys.stdin) + + def test_take_action_min(self): + parser = self.test_object.get_parser('NAME') + parsed_args = parser.parse_args(["-s" + self._subunit_file],) + with patch('sys.stdout', new=StringIO()) as mock_stdout: + self.test_object.take_action(parsed_args) + + stdout_data = mock_stdout.getvalue() + self._assert_methods_details(stdout_data) + self._assert_no_headers_and_bodies(stdout_data) + + def test_take_action_all_stdout(self): + parser = self.test_object.get_parser('NAME') + parsed_args = parser.parse_args(["-as" + self._subunit_file],) + with patch('sys.stdout', new=StringIO()) as mock_stdout: + self.test_object.take_action(parsed_args) + + stdout_data = mock_stdout.getvalue() + self._assert_methods_details(stdout_data) + self._assert_headers_and_bodies(stdout_data) + + def test_take_action_outfile_files(self): + temp_file = tempfile.mkstemp()[1] + parser = self.test_object.get_parser('NAME') + parsed_args = parser.parse_args( + ["-as" + self._subunit_file, '-o', temp_file], ) + with patch('sys.stdout', new=StringIO()) as mock_stdout: + self.test_object.take_action(parsed_args) + stdout_data = mock_stdout.getvalue() + self._assert_cli_message(stdout_data) + with open(temp_file, 'r') as file: + data = json.loads(file.read()) + self._assert_expect_json(data) + + def test_take_action_no_items(self): + temp_file = tempfile.mkstemp()[1] + parser = self.test_object.get_parser('NAME') + parsed_args = parser.parse_args( + ["-as" + temp_file], ) + with patch('sys.stdout', new=StringIO()) as mock_stdout: + self.test_object.take_action(parsed_args) + stdout_data = mock_stdout.getvalue() + self._assert_cli_message(stdout_data) + + def test_take_action_exception(self): + parser = self.test_object.get_parser('NAME') + parsed_args = parser.parse_args(["-s" + self._subunit_file],) + with patch('sys.stderr', new=StringIO()) as mock_stderr: + with patch('tempest.cmd.subunit_describe_calls.entry_point') \ + as mock_method: + mock_method.side_effect = OSError() + self.assertRaises(OSError, self.test_object.take_action, + parsed_args) + stderr_data = mock_stderr.getvalue() + + self.assertIn("Traceback (most recent call last):", stderr_data) + self.assertIn("entry_point(parsed_args)", stderr_data)