1#!/usr/bin/env python 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); you may 4# not use this file except in compliance with the License. You may obtain 5# a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12# License for the specific language governing permissions and limitations 13# under the License. 14 15import argparse 16import os 17import textwrap 18 19from six import StringIO 20from unittest import mock 21 22from cliff.formatters import table 23from cliff.tests import base 24from cliff.tests import test_columns 25 26 27class args(object): 28 def __init__(self, max_width=0, print_empty=False, fit_width=False): 29 self.fit_width = fit_width 30 if max_width > 0: 31 self.max_width = max_width 32 else: 33 # Envvar is only taken into account iff CLI parameter not given 34 self.max_width = int(os.environ.get('CLIFF_MAX_TERM_WIDTH', 0)) 35 self.print_empty = print_empty 36 37 38def _table_tester_helper(tags, data, extra_args=None): 39 """Get table output as a string, formatted according to 40 CLI arguments, environment variables and terminal size 41 42 tags - tuple of strings for data tags (column headers or fields) 43 data - tuple of strings for single data row 44 - list of tuples of strings for multiple rows of data 45 extra_args - an instance of class args 46 - a list of strings for CLI arguments 47 """ 48 sf = table.TableFormatter() 49 50 if extra_args is None: 51 # Default to no CLI arguments 52 parsed_args = args() 53 elif type(extra_args) == args: 54 # Use the given CLI arguments 55 parsed_args = extra_args 56 else: 57 # Parse arguments as if passed on the command-line 58 parser = argparse.ArgumentParser(description='Testing...') 59 sf.add_argument_group(parser) 60 parsed_args = parser.parse_args(extra_args) 61 62 output = StringIO() 63 emitter = sf.emit_list if type(data) is list else sf.emit_one 64 emitter(tags, data, output, parsed_args) 65 return output.getvalue() 66 67 68class TestTableFormatter(base.TestBase): 69 70 @mock.patch('cliff.utils.terminal_width') 71 def test(self, tw): 72 tw.return_value = 80 73 c = ('a', 'b', 'c', 'd') 74 d = ('A', 'B', 'C', 'test\rcarriage\r\nreturn') 75 expected = textwrap.dedent('''\ 76 +-------+---------------+ 77 | Field | Value | 78 +-------+---------------+ 79 | a | A | 80 | b | B | 81 | c | C | 82 | d | test carriage | 83 | | return | 84 +-------+---------------+ 85 ''') 86 self.assertEqual(expected, _table_tester_helper(c, d)) 87 88 89class TestTerminalWidth(base.TestBase): 90 91 # Multi-line output when width is restricted to 42 columns 92 expected_ml_val = textwrap.dedent('''\ 93 +-------+--------------------------------+ 94 | Field | Value | 95 +-------+--------------------------------+ 96 | a | A | 97 | b | B | 98 | c | C | 99 | d | dddddddddddddddddddddddddddddd | 100 | | dddddddddddddddddddddddddddddd | 101 | | ddddddddddddddddd | 102 +-------+--------------------------------+ 103 ''') 104 105 # Multi-line output when width is restricted to 80 columns 106 expected_ml_80_val = textwrap.dedent('''\ 107 +-------+----------------------------------------------------------------------+ 108 | Field | Value | 109 +-------+----------------------------------------------------------------------+ 110 | a | A | 111 | b | B | 112 | c | C | 113 | d | dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd | 114 | | ddddddddd | 115 +-------+----------------------------------------------------------------------+ 116 ''') # noqa 117 118 # Single-line output, for when no line length restriction apply 119 expected_sl_val = textwrap.dedent('''\ 120 +-------+-------------------------------------------------------------------------------+ 121 | Field | Value | 122 +-------+-------------------------------------------------------------------------------+ 123 | a | A | 124 | b | B | 125 | c | C | 126 | d | ddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd | 127 +-------+-------------------------------------------------------------------------------+ 128 ''') # noqa 129 130 @mock.patch('cliff.utils.terminal_width') 131 def test_table_formatter_no_cli_param(self, tw): 132 tw.return_value = 80 133 c = ('a', 'b', 'c', 'd') 134 d = ('A', 'B', 'C', 'd' * 77) 135 self.assertEqual( 136 self.expected_ml_80_val, 137 _table_tester_helper(c, d, extra_args=args(fit_width=True)), 138 ) 139 140 @mock.patch('cliff.utils.terminal_width') 141 def test_table_formatter_cli_param(self, tw): 142 tw.return_value = 80 143 c = ('a', 'b', 'c', 'd') 144 d = ('A', 'B', 'C', 'd' * 77) 145 self.assertEqual( 146 self.expected_ml_val, 147 _table_tester_helper(c, d, extra_args=['--max-width', '42']), 148 ) 149 150 @mock.patch('cliff.utils.terminal_width') 151 def test_table_formatter_no_cli_param_unlimited_tw(self, tw): 152 tw.return_value = 0 153 c = ('a', 'b', 'c', 'd') 154 d = ('A', 'B', 'C', 'd' * 77) 155 # output should not be wrapped to multiple lines 156 self.assertEqual( 157 self.expected_sl_val, 158 _table_tester_helper(c, d, extra_args=args()), 159 ) 160 161 @mock.patch('cliff.utils.terminal_width') 162 def test_table_formatter_cli_param_unlimited_tw(self, tw): 163 tw.return_value = 0 164 c = ('a', 'b', 'c', 'd') 165 d = ('A', 'B', 'C', 'd' * 77) 166 self.assertEqual( 167 self.expected_ml_val, 168 _table_tester_helper(c, d, extra_args=['--max-width', '42']), 169 ) 170 171 @mock.patch('cliff.utils.terminal_width') 172 @mock.patch.dict(os.environ, {'CLIFF_MAX_TERM_WIDTH': '666'}) 173 def test_table_formatter_cli_param_envvar_big(self, tw): 174 tw.return_value = 80 175 c = ('a', 'b', 'c', 'd') 176 d = ('A', 'B', 'C', 'd' * 77) 177 self.assertEqual( 178 self.expected_ml_val, 179 _table_tester_helper(c, d, extra_args=['--max-width', '42']), 180 ) 181 182 @mock.patch('cliff.utils.terminal_width') 183 @mock.patch.dict(os.environ, {'CLIFF_MAX_TERM_WIDTH': '23'}) 184 def test_table_formatter_cli_param_envvar_tiny(self, tw): 185 tw.return_value = 80 186 c = ('a', 'b', 'c', 'd') 187 d = ('A', 'B', 'C', 'd' * 77) 188 self.assertEqual( 189 self.expected_ml_val, 190 _table_tester_helper(c, d, extra_args=['--max-width', '42']), 191 ) 192 193 194class TestMaxWidth(base.TestBase): 195 196 expected_80 = textwrap.dedent('''\ 197 +--------------------------+---------------------------------------------+ 198 | Field | Value | 199 +--------------------------+---------------------------------------------+ 200 | field_name | the value | 201 | a_really_long_field_name | a value significantly longer than the field | 202 +--------------------------+---------------------------------------------+ 203 ''') 204 205 @mock.patch('cliff.utils.terminal_width') 206 def test_80(self, tw): 207 tw.return_value = 80 208 c = ('field_name', 'a_really_long_field_name') 209 d = ('the value', 'a value significantly longer than the field') 210 self.assertEqual(self.expected_80, _table_tester_helper(c, d)) 211 212 @mock.patch('cliff.utils.terminal_width') 213 def test_70(self, tw): 214 # resize value column 215 tw.return_value = 70 216 c = ('field_name', 'a_really_long_field_name') 217 d = ('the value', 'a value significantly longer than the field') 218 expected = textwrap.dedent('''\ 219 +--------------------------+-----------------------------------------+ 220 | Field | Value | 221 +--------------------------+-----------------------------------------+ 222 | field_name | the value | 223 | a_really_long_field_name | a value significantly longer than the | 224 | | field | 225 +--------------------------+-----------------------------------------+ 226 ''') 227 self.assertEqual( 228 expected, 229 _table_tester_helper(c, d, extra_args=['--fit-width']), 230 ) 231 232 @mock.patch('cliff.utils.terminal_width') 233 def test_50(self, tw): 234 # resize both columns 235 tw.return_value = 50 236 c = ('field_name', 'a_really_long_field_name') 237 d = ('the value', 'a value significantly longer than the field') 238 expected = textwrap.dedent('''\ 239 +-----------------------+------------------------+ 240 | Field | Value | 241 +-----------------------+------------------------+ 242 | field_name | the value | 243 | a_really_long_field_n | a value significantly | 244 | ame | longer than the field | 245 +-----------------------+------------------------+ 246 ''') 247 self.assertEqual( 248 expected, 249 _table_tester_helper(c, d, extra_args=['--fit-width']), 250 ) 251 252 @mock.patch('cliff.utils.terminal_width') 253 def test_10(self, tw): 254 # resize all columns limited by min_width=16 255 tw.return_value = 10 256 c = ('field_name', 'a_really_long_field_name') 257 d = ('the value', 'a value significantly longer than the field') 258 expected = textwrap.dedent('''\ 259 +------------------+------------------+ 260 | Field | Value | 261 +------------------+------------------+ 262 | field_name | the value | 263 | a_really_long_fi | a value | 264 | eld_name | significantly | 265 | | longer than the | 266 | | field | 267 +------------------+------------------+ 268 ''') 269 self.assertEqual( 270 expected, 271 _table_tester_helper(c, d, extra_args=['--fit-width']), 272 ) 273 274 275class TestListFormatter(base.TestBase): 276 277 _col_names = ('one', 'two', 'three') 278 _col_data = [( 279 'one one one one one', 280 'two two two two', 281 'three three')] 282 283 _expected_mv = { 284 80: textwrap.dedent('''\ 285 +---------------------+-----------------+-------------+ 286 | one | two | three | 287 +---------------------+-----------------+-------------+ 288 | one one one one one | two two two two | three three | 289 +---------------------+-----------------+-------------+ 290 '''), 291 292 50: textwrap.dedent('''\ 293 +----------------+-----------------+-------------+ 294 | one | two | three | 295 +----------------+-----------------+-------------+ 296 | one one one | two two two two | three three | 297 | one one | | | 298 +----------------+-----------------+-------------+ 299 '''), 300 301 47: textwrap.dedent('''\ 302 +---------------+---------------+-------------+ 303 | one | two | three | 304 +---------------+---------------+-------------+ 305 | one one one | two two two | three three | 306 | one one | two | | 307 +---------------+---------------+-------------+ 308 '''), 309 310 45: textwrap.dedent('''\ 311 +--------------+--------------+-------------+ 312 | one | two | three | 313 +--------------+--------------+-------------+ 314 | one one one | two two two | three three | 315 | one one | two | | 316 +--------------+--------------+-------------+ 317 '''), 318 319 40: textwrap.dedent('''\ 320 +------------+------------+------------+ 321 | one | two | three | 322 +------------+------------+------------+ 323 | one one | two two | three | 324 | one one | two two | three | 325 | one | | | 326 +------------+------------+------------+ 327 '''), 328 329 10: textwrap.dedent('''\ 330 +----------+----------+----------+ 331 | one | two | three | 332 +----------+----------+----------+ 333 | one one | two two | three | 334 | one one | two two | three | 335 | one | | | 336 +----------+----------+----------+ 337 '''), 338 } 339 340 @mock.patch('cliff.utils.terminal_width') 341 def test_table_list_formatter(self, tw): 342 tw.return_value = 80 343 c = ('a', 'b', 'c') 344 d1 = ('A', 'B', 'C') 345 d2 = ('D', 'E', 'test\rcarriage\r\nreturn') 346 data = [d1, d2] 347 expected = textwrap.dedent('''\ 348 +---+---+---------------+ 349 | a | b | c | 350 +---+---+---------------+ 351 | A | B | C | 352 | D | E | test carriage | 353 | | | return | 354 +---+---+---------------+ 355 ''') 356 self.assertEqual(expected, _table_tester_helper(c, data)) 357 358 @mock.patch('cliff.utils.terminal_width') 359 def test_table_formatter_formattable_column(self, tw): 360 tw.return_value = 0 361 c = ('a', 'b', 'c', 'd') 362 d = ('A', 'B', 'C', test_columns.FauxColumn(['the', 'value'])) 363 expected = textwrap.dedent('''\ 364 +-------+---------------------------------------------+ 365 | Field | Value | 366 +-------+---------------------------------------------+ 367 | a | A | 368 | b | B | 369 | c | C | 370 | d | I made this string myself: ['the', 'value'] | 371 +-------+---------------------------------------------+ 372 ''') 373 self.assertEqual(expected, _table_tester_helper(c, d)) 374 375 @mock.patch('cliff.utils.terminal_width') 376 def test_formattable_column(self, tw): 377 tw.return_value = 80 378 c = ('a', 'b', 'c') 379 d1 = ('A', 'B', test_columns.FauxColumn(['the', 'value'])) 380 data = [d1] 381 expected = textwrap.dedent('''\ 382 +---+---+---------------------------------------------+ 383 | a | b | c | 384 +---+---+---------------------------------------------+ 385 | A | B | I made this string myself: ['the', 'value'] | 386 +---+---+---------------------------------------------+ 387 ''') 388 self.assertEqual(expected, _table_tester_helper(c, data)) 389 390 @mock.patch('cliff.utils.terminal_width') 391 def test_max_width_80(self, tw): 392 # no resize 393 width = tw.return_value = 80 394 self.assertEqual( 395 self._expected_mv[width], 396 _table_tester_helper(self._col_names, self._col_data), 397 ) 398 399 @mock.patch('cliff.utils.terminal_width') 400 def test_max_width_50(self, tw): 401 # resize 1 column 402 width = tw.return_value = 50 403 actual = _table_tester_helper(self._col_names, self._col_data, 404 extra_args=['--fit-width']) 405 self.assertEqual(self._expected_mv[width], actual) 406 self.assertEqual(width, len(actual.splitlines()[0])) 407 408 @mock.patch('cliff.utils.terminal_width') 409 def test_max_width_45(self, tw): 410 # resize 2 columns 411 width = tw.return_value = 45 412 actual = _table_tester_helper(self._col_names, self._col_data, 413 extra_args=['--fit-width']) 414 self.assertEqual(self._expected_mv[width], actual) 415 self.assertEqual(width, len(actual.splitlines()[0])) 416 417 @mock.patch('cliff.utils.terminal_width') 418 def test_max_width_40(self, tw): 419 # resize all columns 420 width = tw.return_value = 40 421 actual = _table_tester_helper(self._col_names, self._col_data, 422 extra_args=['--fit-width']) 423 self.assertEqual(self._expected_mv[width], actual) 424 self.assertEqual(width, len(actual.splitlines()[0])) 425 426 @mock.patch('cliff.utils.terminal_width') 427 def test_max_width_10(self, tw): 428 # resize all columns limited by min_width=8 429 width = tw.return_value = 10 430 actual = _table_tester_helper(self._col_names, self._col_data, 431 extra_args=['--fit-width']) 432 self.assertEqual(self._expected_mv[width], actual) 433 # 3 columns each 8 wide, plus table spacing and borders 434 expected_width = 11 * 3 + 1 435 self.assertEqual(expected_width, len(actual.splitlines()[0])) 436 437 # Force a wide terminal by overriding its width with envvar 438 @mock.patch('cliff.utils.terminal_width') 439 @mock.patch.dict(os.environ, {'CLIFF_MAX_TERM_WIDTH': '666'}) 440 def test_max_width_and_envvar_max(self, tw): 441 # no resize 442 tw.return_value = 80 443 self.assertEqual( 444 self._expected_mv[80], 445 _table_tester_helper(self._col_names, self._col_data), 446 ) 447 448 # resize 1 column 449 tw.return_value = 50 450 self.assertEqual( 451 self._expected_mv[80], 452 _table_tester_helper(self._col_names, self._col_data), 453 ) 454 455 # resize 2 columns 456 tw.return_value = 45 457 self.assertEqual( 458 self._expected_mv[80], 459 _table_tester_helper(self._col_names, self._col_data), 460 ) 461 462 # resize all columns 463 tw.return_value = 40 464 self.assertEqual( 465 self._expected_mv[80], 466 _table_tester_helper(self._col_names, self._col_data), 467 ) 468 469 # resize all columns limited by min_width=8 470 tw.return_value = 10 471 self.assertEqual( 472 self._expected_mv[80], 473 _table_tester_helper(self._col_names, self._col_data), 474 ) 475 476 # Force a narrow terminal by overriding its width with envvar 477 @mock.patch('cliff.utils.terminal_width') 478 @mock.patch.dict(os.environ, {'CLIFF_MAX_TERM_WIDTH': '47'}) 479 def test_max_width_and_envvar_mid(self, tw): 480 # no resize 481 tw.return_value = 80 482 self.assertEqual( 483 self._expected_mv[47], 484 _table_tester_helper(self._col_names, self._col_data), 485 ) 486 487 # resize 1 column 488 tw.return_value = 50 489 actual = _table_tester_helper(self._col_names, self._col_data) 490 self.assertEqual(self._expected_mv[47], actual) 491 self.assertEqual(47, len(actual.splitlines()[0])) 492 493 # resize 2 columns 494 tw.return_value = 45 495 actual = _table_tester_helper(self._col_names, self._col_data) 496 self.assertEqual(self._expected_mv[47], actual) 497 self.assertEqual(47, len(actual.splitlines()[0])) 498 499 # resize all columns 500 tw.return_value = 40 501 actual = _table_tester_helper(self._col_names, self._col_data) 502 self.assertEqual(self._expected_mv[47], actual) 503 self.assertEqual(47, len(actual.splitlines()[0])) 504 505 # resize all columns limited by min_width=8 506 tw.return_value = 10 507 actual = _table_tester_helper(self._col_names, self._col_data) 508 self.assertEqual(self._expected_mv[47], actual) 509 self.assertEqual(47, len(actual.splitlines()[0])) 510 511 @mock.patch.dict(os.environ, {'CLIFF_MAX_TERM_WIDTH': '80'}) 512 def test_env_maxwidth_noresize(self): 513 # no resize 514 self.assertEqual( 515 self._expected_mv[80], 516 _table_tester_helper(self._col_names, self._col_data), 517 ) 518 519 @mock.patch.dict(os.environ, {'CLIFF_MAX_TERM_WIDTH': '50'}) 520 def test_env_maxwidth_resize_one(self): 521 # resize 1 column 522 actual = _table_tester_helper(self._col_names, self._col_data) 523 self.assertEqual(self._expected_mv[50], actual) 524 self.assertEqual(50, len(actual.splitlines()[0])) 525 526 @mock.patch.dict(os.environ, {'CLIFF_MAX_TERM_WIDTH': '45'}) 527 def test_env_maxwidth_resize_two(self): 528 # resize 2 columns 529 actual = _table_tester_helper(self._col_names, self._col_data) 530 self.assertEqual(self._expected_mv[45], actual) 531 self.assertEqual(45, len(actual.splitlines()[0])) 532 533 @mock.patch.dict(os.environ, {'CLIFF_MAX_TERM_WIDTH': '40'}) 534 def test_env_maxwidth_resize_all(self): 535 # resize all columns 536 actual = _table_tester_helper(self._col_names, self._col_data) 537 self.assertEqual(self._expected_mv[40], actual) 538 self.assertEqual(40, len(actual.splitlines()[0])) 539 540 @mock.patch.dict(os.environ, {'CLIFF_MAX_TERM_WIDTH': '8'}) 541 def test_env_maxwidth_resize_all_tiny(self): 542 # resize all columns limited by min_width=8 543 actual = _table_tester_helper(self._col_names, self._col_data) 544 self.assertEqual(self._expected_mv[10], actual) 545 # 3 columns each 8 wide, plus table spacing and borders 546 expected_width = 11 * 3 + 1 547 self.assertEqual(expected_width, len(actual.splitlines()[0])) 548 549 @mock.patch.dict(os.environ, {'CLIFF_MAX_TERM_WIDTH': '42'}) 550 def test_env_maxwidth_args_big(self): 551 self.assertEqual( 552 self._expected_mv[80], 553 _table_tester_helper(self._col_names, self._col_data, 554 extra_args=args(666)), 555 ) 556 557 @mock.patch.dict(os.environ, {'CLIFF_MAX_TERM_WIDTH': '42'}) 558 def test_env_maxwidth_args_tiny(self): 559 self.assertEqual( 560 self._expected_mv[40], 561 _table_tester_helper(self._col_names, self._col_data, 562 extra_args=args(40)), 563 ) 564 565 @mock.patch('cliff.utils.terminal_width') 566 def test_empty(self, tw): 567 tw.return_value = 80 568 c = ('a', 'b', 'c') 569 data = [] 570 expected = '\n' 571 self.assertEqual(expected, _table_tester_helper(c, data)) 572 573 @mock.patch('cliff.utils.terminal_width') 574 def test_empty_table(self, tw): 575 tw.return_value = 80 576 c = ('a', 'b', 'c') 577 data = [] 578 expected = textwrap.dedent('''\ 579 +---+---+---+ 580 | a | b | c | 581 +---+---+---+ 582 +---+---+---+ 583 ''') 584 self.assertEqual( 585 expected, 586 _table_tester_helper(c, data, 587 extra_args=['--print-empty']), 588 ) 589 590 591class TestFieldWidths(base.TestBase): 592 593 def test(self): 594 tf = table.TableFormatter 595 self.assertEqual( 596 { 597 'a': 1, 598 'b': 2, 599 'c': 3, 600 'd': 10 601 }, 602 tf._field_widths( 603 ('a', 'b', 'c', 'd'), 604 '+---+----+-----+------------+'), 605 ) 606 607 def test_zero(self): 608 tf = table.TableFormatter 609 self.assertEqual( 610 { 611 'a': 0, 612 'b': 0, 613 'c': 0 614 }, 615 tf._field_widths( 616 ('a', 'b', 'c'), 617 '+--+-++'), 618 ) 619 620 def test_info(self): 621 tf = table.TableFormatter 622 self.assertEqual((49, 4), (tf._width_info(80, 10))) 623 self.assertEqual((76, 76), (tf._width_info(80, 1))) 624 self.assertEqual((79, 0), (tf._width_info(80, 0))) 625 self.assertEqual((0, 0), (tf._width_info(0, 80))) 626