diff --git a/manilaclient/tests/functional/utils.py b/manilaclient/tests/functional/utils.py new file mode 100644 index 0000000..a94c72e --- /dev/null +++ b/manilaclient/tests/functional/utils.py @@ -0,0 +1,87 @@ +# Copyright 2015 Mirantis Inc. +# 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. + +import six +from tempest_lib.cli import output_parser + + +def multi_line_row_table(output_lines, group_by_column_index=0): + parsed_table = output_parser.table(output_lines) + + rows = parsed_table['values'] + row_index = 0 + + def get_column_index(column_name, headers, default): + return next( + (i for i, h in enumerate(headers) if h.lower() == column_name), + default + ) + + if group_by_column_index is None: + group_by_column_index = get_column_index( + 'id', parsed_table['headers'], 0) + + def is_embedded_table(parsed_rows): + def is_table_border(t): + return six.text_type(t).startswith('+') + + return (isinstance(parsed_rows, list) + and len(parsed_rows) > 3 + and is_table_border(parsed_rows[0]) + and is_table_border(parsed_rows[-1])) + + def merge_cells(master_cell, value_cell): + if value_cell: + if not isinstance(master_cell, list): + master_cell = [master_cell] + master_cell.append(value_cell) + + if is_embedded_table(master_cell): + return multi_line_row_table('\n'.join(master_cell), None) + + return master_cell + + def is_empty_row(row): + empty_cells = 0 + for cell in row: + if cell == '': + empty_cells += 1 + return len(row) == empty_cells + + while row_index < len(rows): + row = rows[row_index] + line_with_value = row_index > 0 and row[group_by_column_index] == '' + + if line_with_value and not is_empty_row(row): + rows[row_index - 1] = list(map(merge_cells, + rows[row_index - 1], + rows.pop(row_index))) + else: + row_index += 1 + + return parsed_table + + +def listing(output_lines): + """Return list of dicts with basic item info parsed from cli output.""" + + items = [] + table_ = multi_line_row_table(output_lines) + for row in table_['values']: + item = {} + for col_idx, col_key in enumerate(table_['headers']): + item[col_key] = row[col_idx] + items.append(item) + return items diff --git a/manilaclient/tests/unit/test_functional_utils.py b/manilaclient/tests/unit/test_functional_utils.py new file mode 100644 index 0000000..c8d7943 --- /dev/null +++ b/manilaclient/tests/unit/test_functional_utils.py @@ -0,0 +1,225 @@ +# Copyright 2015 Mirantis Inc. +# 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. + +import ddt + +from manilaclient.tests.functional import utils as func_utils +from manilaclient.tests.unit import utils + + +@ddt.ddt +class ShellTest(utils.TestCase): + + OUTPUT_LINES_SIMPLE = """ ++----+------+---------+ +| ID | Name | Status | ++----+------+---------+ +| 11 | foo | BUILD | +| 21 | bar | ERROR | ++----+------+---------+ +""" + OUTPUT_LINES_ONE_MULTI_ROW = """ ++----+------+---------+ +| ID | Name | Status | ++----+------+---------+ +| 11 | foo | BUILD | +| 21 | bar | ERROR | +| | | ERROR2 | +| 31 | bee | None | ++----+------+---------+ +""" + + OUTPUT_LINES_COMPLICATED_MULTI_ROW = """ ++----+------+---------+ +| ID | Name | Status | ++----+------+---------+ +| 11 | foo | BUILD | +| 21 | bar | ERROR | +| | | ERROR2 | +| | | ERROR3 | +| 31 | bee | None | +| | bee2 | | +| | bee3 | | +| 41 | rand | None | +| | rend | None2 | +| | | | ++----+------+---------+ +""" + + OUTPUT_LINES_COMPLICATED_MULTI_ROW_WITH_SHIFTED_ID = """ ++----+----+------+---------+ +| ** | ID | Name | Status | ++----+----+------+---------+ +| ** | 11 | foo | BUILD | +| | 21 | bar | ERROR | +| | | | ERROR2 | +| | | | ERROR3 | +| | | | | +| ** | 31 | bee | None | +| | | bee2 | | +| | | | | ++----+----+------+---------+ +""" + + OUTPUT_LINES_NESTED_TABLE = """ ++----+----+------+--------------+ +| ** | ID | Name | Status | ++----+----+------+--------------+ +| ** | 11 | foo | +----+----+ | +| | | | | aa | bb | | +| | | | +----+----+ | +| | | | +----+----+ | +| | 21 | bar | ERROR | +| | | | ERROR2 | +| | | | ERROR3 | ++----+----+------+--------------+ +""" + OUTPUT_LINES_NESTED_TABLE_MULTI_LINE = """ ++----+----+------+--------------+ +| ** | ID | Name | Status | ++----+----+------+--------------+ +| ** | 11 | foo | +----+----+ | +| | | | | id | bb | | +| | | | +----+----+ | +| | | | | 01 | a1 | | +| | | | | | a2 | | +| | | | +----+----+ | +| | 21 | bar | ERROR | +| | | | ERROR2 | +| | | | ERROR3 | ++----+----+------+--------------+ +""" + OUTPUT_LINES_DETAILS = """ ++----------+--------+ +| Property | Value | ++----------+--------+ +| foo | BUILD | +| bar | ERROR | +| | ERROR2 | +| | ERROR3 | +| bee | None | ++----------+--------+ +""" + + @ddt.data({'input': OUTPUT_LINES_SIMPLE, + 'valid_values': [ + ['11', 'foo', 'BUILD'], + ['21', 'bar', 'ERROR'] + ]}, + {'input': OUTPUT_LINES_ONE_MULTI_ROW, + 'valid_values': [ + ['11', 'foo', 'BUILD'], + ['21', 'bar', ['ERROR', 'ERROR2']], + ['31', 'bee', 'None'], + ]}, + {'input': OUTPUT_LINES_COMPLICATED_MULTI_ROW, + 'valid_values': [ + ['11', 'foo', 'BUILD'], + ['21', 'bar', ['ERROR', 'ERROR2', 'ERROR3']], + ['31', ['bee', 'bee2', 'bee3'], 'None'], + ['41', ['rand', 'rend'], ['None', 'None2']], + ['', '', ''] + ]}) + @ddt.unpack + def test_multi_line_row_table(self, input, valid_values): + + actual_result = func_utils.multi_line_row_table(input) + + self.assertEqual(['ID', 'Name', 'Status'], actual_result['headers']) + self.assertEqual(valid_values, actual_result['values']) + + def test_multi_line_row_table_shifted_id_column(self): + input = self.OUTPUT_LINES_COMPLICATED_MULTI_ROW_WITH_SHIFTED_ID + valid_values = [ + ['**', '11', 'foo', 'BUILD'], + ['', '21', 'bar', ['ERROR', 'ERROR2', 'ERROR3']], + ['', '', '', ''], + ['**', '31', ['bee', 'bee2'], 'None'], + ['', '', '', ''] + ] + + actual_result = func_utils.multi_line_row_table( + input, group_by_column_index=1) + + self.assertEqual(['**', 'ID', 'Name', 'Status'], + actual_result['headers']) + self.assertEqual(valid_values, actual_result['values']) + + @ddt.data({'input': OUTPUT_LINES_NESTED_TABLE, + 'valid_nested': { + 'headers': ['aa', 'bb'], + 'values': [] + }}, + {'input': OUTPUT_LINES_NESTED_TABLE_MULTI_LINE, + 'valid_nested': { + 'headers': ['id', 'bb'], + 'values': [['01', ['a1', 'a2']]] + }},) + @ddt.unpack + def test_nested_tables(self, input, valid_nested): + + actual_result = func_utils.multi_line_row_table( + input, group_by_column_index=1) + + self.assertEqual(['**', 'ID', 'Name', 'Status'], + actual_result['headers']) + + self.assertEqual(2, len(actual_result['values'])) + self.assertEqual(valid_nested, actual_result['values'][0][3]) + + @ddt.data({'input': OUTPUT_LINES_DETAILS, + 'valid_values': [ + ['foo', 'BUILD'], + ['bar', ['ERROR', 'ERROR2', 'ERROR3']], + ['bee', 'None'], + ]}) + @ddt.unpack + def test_details(self, input, valid_values): + actual_result = func_utils.multi_line_row_table(input) + + self.assertEqual(['Property', 'Value'], actual_result['headers']) + self.assertEqual(valid_values, actual_result['values']) + + @ddt.data({'input_data': OUTPUT_LINES_DETAILS, + 'output_data': [ + {'Property': 'foo', 'Value': 'BUILD'}, + {'Property': 'bar', 'Value': ['ERROR', 'ERROR2', 'ERROR3']}, + {'Property': 'bee', 'Value': 'None'}]}, + {'input_data': OUTPUT_LINES_SIMPLE, + 'output_data': [ + {'ID': '11', 'Name': 'foo', 'Status': 'BUILD'}, + {'ID': '21', 'Name': 'bar', 'Status': 'ERROR'}, + ]}, + {'input_data': OUTPUT_LINES_ONE_MULTI_ROW, + 'output_data': [ + {'ID': '11', 'Name': 'foo', 'Status': 'BUILD'}, + {'ID': '21', 'Name': 'bar', 'Status': ['ERROR', 'ERROR2']}, + {'ID': '31', 'Name': 'bee', 'Status': 'None'}, + ]}, + {'input_data': OUTPUT_LINES_COMPLICATED_MULTI_ROW, + 'output_data': [ + {'ID': '11', 'Name': 'foo', 'Status': 'BUILD'}, + {'ID': '21', 'Name': 'bar', + 'Status': ['ERROR', 'ERROR2', 'ERROR3']}, + {'ID': '31', 'Name': ['bee', 'bee2', 'bee3'], + 'Status': 'None'}, + {'ID': '41', 'Name': ['rand', 'rend'], + 'Status': ['None', 'None2']}, + {'ID': '', 'Name': '', 'Status': ''}, + ]}) + @ddt.unpack + def test_listing(self, input_data, output_data): + actual_result = func_utils.listing(input_data) + self.assertEqual(output_data, actual_result)