From ecf608ed0d843e2d79c1369802571ffcaa872594 Mon Sep 17 00:00:00 2001 From: Masayuki Igawa Date: Tue, 25 Mar 2014 11:49:58 +0900 Subject: [PATCH 1/4] Add unit tests for cli.output_parser This commit adds unit tests for cli.output_parser. Partially implements bp unit-tests Change-Id: Ib5d2df61b791e9d76e0897609eb753e7440558ae --- __init__.py | 0 test_output_parser.py | 177 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 177 insertions(+) create mode 100644 __init__.py create mode 100644 test_output_parser.py diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test_output_parser.py b/test_output_parser.py new file mode 100644 index 0000000..7ad270c --- /dev/null +++ b/test_output_parser.py @@ -0,0 +1,177 @@ +# Copyright 2014 NEC Corporation. +# All Rights Reserved. +# +# 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. + + +from tempest.cli import output_parser +from tempest import exceptions +from tempest.tests import base + + +class TestOutputParser(base.TestCase): + OUTPUT_LINES = """ ++----+------+---------+ +| ID | Name | Status | ++----+------+---------+ +| 11 | foo | BUILD | +| 21 | bar | ERROR | +| 31 | bee | None | ++----+------+---------+ +""" + OUTPUT_LINES2 = """ ++----+-------+---------+ +| ID | Name2 | Status2 | ++----+-------+---------+ +| 41 | aaa | SSSSS | +| 51 | bbb | TTTTT | +| 61 | ccc | AAAAA | ++----+-------+---------+ +""" + + EXPECTED_TABLE = {'headers': ['ID', 'Name', 'Status'], + 'values': [['11', 'foo', 'BUILD'], + ['21', 'bar', 'ERROR'], + ['31', 'bee', 'None']]} + EXPECTED_TABLE2 = {'headers': ['ID', 'Name2', 'Status2'], + 'values': [['41', 'aaa', 'SSSSS'], + ['51', 'bbb', 'TTTTT'], + ['61', 'ccc', 'AAAAA']]} + + def test_table_with_normal_values(self): + actual = output_parser.table(self.OUTPUT_LINES) + self.assertIsInstance(actual, dict) + self.assertEqual(self.EXPECTED_TABLE, actual) + + def test_table_with_list(self): + output_lines = self.OUTPUT_LINES.split('\n') + actual = output_parser.table(output_lines) + self.assertIsInstance(actual, dict) + self.assertEqual(self.EXPECTED_TABLE, actual) + + def test_table_with_invalid_line(self): + output_lines = self.OUTPUT_LINES + "aaaa" + actual = output_parser.table(output_lines) + self.assertIsInstance(actual, dict) + self.assertEqual(self.EXPECTED_TABLE, actual) + + def test_tables_with_normal_values(self): + output_lines = 'test' + self.OUTPUT_LINES +\ + 'test2' + self.OUTPUT_LINES2 + expected = [{'headers': self.EXPECTED_TABLE['headers'], + 'label': 'test', + 'values': self.EXPECTED_TABLE['values']}, + {'headers': self.EXPECTED_TABLE2['headers'], + 'label': 'test2', + 'values': self.EXPECTED_TABLE2['values']}] + actual = output_parser.tables(output_lines) + self.assertIsInstance(actual, list) + self.assertEqual(expected, actual) + + def test_tables_with_invalid_values(self): + output_lines = 'test' + self.OUTPUT_LINES +\ + 'test2' + self.OUTPUT_LINES2 + '\n' + expected = [{'headers': self.EXPECTED_TABLE['headers'], + 'label': 'test', + 'values': self.EXPECTED_TABLE['values']}, + {'headers': self.EXPECTED_TABLE2['headers'], + 'label': 'test2', + 'values': self.EXPECTED_TABLE2['values']}] + actual = output_parser.tables(output_lines) + self.assertIsInstance(actual, list) + self.assertEqual(expected, actual) + + def test_tables_with_invalid_line(self): + output_lines = 'test' + self.OUTPUT_LINES +\ + 'test2' + self.OUTPUT_LINES2 +\ + '+----+-------+---------+' + expected = [{'headers': self.EXPECTED_TABLE['headers'], + 'label': 'test', + 'values': self.EXPECTED_TABLE['values']}, + {'headers': self.EXPECTED_TABLE2['headers'], + 'label': 'test2', + 'values': self.EXPECTED_TABLE2['values']}] + + actual = output_parser.tables(output_lines) + self.assertIsInstance(actual, list) + self.assertEqual(expected, actual) + + LISTING_OUTPUT = """ ++----+ +| ID | ++----+ +| 11 | +| 21 | +| 31 | ++----+ +""" + + def test_listing(self): + expected = [{'ID': '11'}, {'ID': '21'}, {'ID': '31'}] + actual = output_parser.listing(self.LISTING_OUTPUT) + self.assertIsInstance(actual, list) + self.assertEqual(expected, actual) + + def test_details_multiple_with_invalid_line(self): + self.assertRaises(exceptions.InvalidStructure, + output_parser.details_multiple, + self.OUTPUT_LINES) + + DETAILS_LINES1 = """First Table ++----------+--------+ +| Property | Value | ++----------+--------+ +| foo | BUILD | +| bar | ERROR | +| bee | None | ++----------+--------+ +""" + DETAILS_LINES2 = """Second Table ++----------+--------+ +| Property | Value | ++----------+--------+ +| aaa | VVVVV | +| bbb | WWWWW | +| ccc | XXXXX | ++----------+--------+ +""" + + def test_details_with_normal_line_label_false(self): + expected = {'foo': 'BUILD', 'bar': 'ERROR', 'bee': 'None'} + actual = output_parser.details(self.DETAILS_LINES1) + self.assertEqual(expected, actual) + + def test_details_with_normal_line_label_true(self): + expected = {'__label': 'First Table', + 'foo': 'BUILD', 'bar': 'ERROR', 'bee': 'None'} + actual = output_parser.details(self.DETAILS_LINES1, with_label=True) + self.assertEqual(expected, actual) + + def test_details_multiple_with_normal_line_label_false(self): + expected = [{'foo': 'BUILD', 'bar': 'ERROR', 'bee': 'None'}, + {'aaa': 'VVVVV', 'bbb': 'WWWWW', 'ccc': 'XXXXX'}] + actual = output_parser.details_multiple(self.DETAILS_LINES1 + + self.DETAILS_LINES2) + self.assertIsInstance(actual, list) + self.assertEqual(expected, actual) + + def test_details_multiple_with_normal_line_label_true(self): + expected = [{'__label': 'First Table', + 'foo': 'BUILD', 'bar': 'ERROR', 'bee': 'None'}, + {'__label': 'Second Table', + 'aaa': 'VVVVV', 'bbb': 'WWWWW', 'ccc': 'XXXXX'}] + actual = output_parser.details_multiple(self.DETAILS_LINES1 + + self.DETAILS_LINES2, + with_label=True) + self.assertIsInstance(actual, list) + self.assertEqual(expected, actual) From de34db3ee36b0c201bab702ef11b62b0413593fd Mon Sep 17 00:00:00 2001 From: Joe Gordon Date: Fri, 18 Jul 2014 11:34:39 +0200 Subject: [PATCH 2/4] Make sure cli CommandFailed prints out stdout and stderr Having a CommandFailed exception without stdout is very confusing, lets fix that. subprocess.CalledProcessError doesn't print out stderr or stdout so use our own error that does. Change-Id: I3bf75d6f526bdc58e64bbc7d83911e9d63e4b801 --- test_command_failed.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 test_command_failed.py diff --git a/test_command_failed.py b/test_command_failed.py new file mode 100644 index 0000000..c539ac6 --- /dev/null +++ b/test_command_failed.py @@ -0,0 +1,30 @@ +# 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. + +from tempest import cli +from tempest.tests import base + + +class TestOutputParser(base.TestCase): + + def test_command_failed_exception(self): + returncode = 1 + cmd = "foo" + stdout = "output" + stderr = "error" + try: + raise cli.CommandFailed(returncode, cmd, stdout, stderr) + except cli.CommandFailed as e: + self.assertIn(str(returncode), str(e)) + self.assertIn(cmd, str(e)) + self.assertIn(stdout, str(e)) + self.assertIn(stderr, str(e)) From 4587b9134491d7a40c0fb467ee8f7cc00c66b91c Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Fri, 25 Jul 2014 18:38:56 -0400 Subject: [PATCH 3/4] Move CommandFailed exception to tempest.exceptions When the CommandFailed exception was added it was only being used by the CLI tests. However, it is generally useful for anything that is using subprocess to make external calls. This patch moves it to tempest.exceptions to make using it simpler for non-cli tests to use the exception. Change-Id: Ibf5f1cbbb847d32976b54c4484acfc3c0e3b4f48 --- test_command_failed.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test_command_failed.py b/test_command_failed.py index c539ac6..36a4fc8 100644 --- a/test_command_failed.py +++ b/test_command_failed.py @@ -10,7 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. -from tempest import cli +from tempest import exceptions from tempest.tests import base @@ -22,8 +22,8 @@ class TestOutputParser(base.TestCase): stdout = "output" stderr = "error" try: - raise cli.CommandFailed(returncode, cmd, stdout, stderr) - except cli.CommandFailed as e: + raise exceptions.CommandFailed(returncode, cmd, stdout, stderr) + except exceptions.CommandFailed as e: self.assertIn(str(returncode), str(e)) self.assertIn(cmd, str(e)) self.assertIn(stdout, str(e)) From 36006e2703672f63493fffd5574f0d2a6b49cf7b Mon Sep 17 00:00:00 2001 From: Matt Riedemann Date: Wed, 30 Jul 2014 14:57:55 -0700 Subject: [PATCH 4/4] Add min_client_version decorator for CLI tests This adds the min_client_version decorator to be used in the CLI tests. The decorator is used skip CLI tests where the command being tested isn't available in the version of the client we're testing against. The cmd method is pulled out of the ClientTestBase class so it can be used by the decorator and is renamed so it does not conflict with the cmd argument passed to it. To start using the decorator and show how it works, two known CLI tests are decorated for minimum version checks on their clients. Part of blueprint minversion-check-for-cli-tests Change-Id: I8a0ee390048870d450772fa064d0dc2daa2b7036 --- test_cli.py | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 test_cli.py diff --git a/test_cli.py b/test_cli.py new file mode 100644 index 0000000..1fd5ccb --- /dev/null +++ b/test_cli.py @@ -0,0 +1,59 @@ +# Copyright 2014 IBM Corp. +# +# 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 mock +import testtools + +from tempest import cli +from tempest import exceptions +from tempest.tests import base + + +class TestMinClientVersion(base.TestCase): + """Tests for the min_client_version decorator. + """ + + def _test_min_version(self, required, installed, expect_skip): + + @cli.min_client_version(client='nova', version=required) + def fake(self, expect_skip): + if expect_skip: + # If we got here, the decorator didn't raise a skipException as + # expected so we need to fail. + self.fail('Should not have gotten past the decorator.') + + with mock.patch.object(cli, 'execute', + return_value=installed) as mock_cmd: + if expect_skip: + self.assertRaises(testtools.TestCase.skipException, fake, + self, expect_skip) + else: + fake(self, expect_skip) + mock_cmd.assert_called_once_with('nova', '', params='--version', + merge_stderr=True) + + def test_min_client_version(self): + # required, installed, expect_skip + cases = (('2.17.0', '2.17.0', False), + ('2.17.0', '2.18.0', False), + ('2.18.0', '2.17.0', True)) + + for case in cases: + self._test_min_version(*case) + + @mock.patch.object(cli, 'execute', return_value=' ') + def test_check_client_version_empty_output(self, mock_execute): + # Tests that an exception is raised if the command output is empty. + self.assertRaises(exceptions.TempestException, + cli.check_client_version, 'nova', '2.18.0')