1# -*- coding: utf-8 -*- 2""" 3Utilities for truncating assertion output. 4 5Current default behaviour is to truncate assertion explanations at 6~8 terminal lines, unless running in "-vv" mode or running on CI. 7""" 8from __future__ import absolute_import 9from __future__ import division 10from __future__ import print_function 11 12import os 13 14import six 15 16DEFAULT_MAX_LINES = 8 17DEFAULT_MAX_CHARS = 8 * 80 18USAGE_MSG = "use '-vv' to show" 19 20 21def truncate_if_required(explanation, item, max_length=None): 22 """ 23 Truncate this assertion explanation if the given test item is eligible. 24 """ 25 if _should_truncate_item(item): 26 return _truncate_explanation(explanation) 27 return explanation 28 29 30def _should_truncate_item(item): 31 """ 32 Whether or not this test item is eligible for truncation. 33 """ 34 verbose = item.config.option.verbose 35 return verbose < 2 and not _running_on_ci() 36 37 38def _running_on_ci(): 39 """Check if we're currently running on a CI system.""" 40 env_vars = ["CI", "BUILD_NUMBER"] 41 return any(var in os.environ for var in env_vars) 42 43 44def _truncate_explanation(input_lines, max_lines=None, max_chars=None): 45 """ 46 Truncate given list of strings that makes up the assertion explanation. 47 48 Truncates to either 8 lines, or 640 characters - whichever the input reaches 49 first. The remaining lines will be replaced by a usage message. 50 """ 51 52 if max_lines is None: 53 max_lines = DEFAULT_MAX_LINES 54 if max_chars is None: 55 max_chars = DEFAULT_MAX_CHARS 56 57 # Check if truncation required 58 input_char_count = len("".join(input_lines)) 59 if len(input_lines) <= max_lines and input_char_count <= max_chars: 60 return input_lines 61 62 # Truncate first to max_lines, and then truncate to max_chars if max_chars 63 # is exceeded. 64 truncated_explanation = input_lines[:max_lines] 65 truncated_explanation = _truncate_by_char_count(truncated_explanation, max_chars) 66 67 # Add ellipsis to final line 68 truncated_explanation[-1] = truncated_explanation[-1] + "..." 69 70 # Append useful message to explanation 71 truncated_line_count = len(input_lines) - len(truncated_explanation) 72 truncated_line_count += 1 # Account for the part-truncated final line 73 msg = "...Full output truncated" 74 if truncated_line_count == 1: 75 msg += " ({} line hidden)".format(truncated_line_count) 76 else: 77 msg += " ({} lines hidden)".format(truncated_line_count) 78 msg += ", {}".format(USAGE_MSG) 79 truncated_explanation.extend([six.text_type(""), six.text_type(msg)]) 80 return truncated_explanation 81 82 83def _truncate_by_char_count(input_lines, max_chars): 84 # Check if truncation required 85 if len("".join(input_lines)) <= max_chars: 86 return input_lines 87 88 # Find point at which input length exceeds total allowed length 89 iterated_char_count = 0 90 for iterated_index, input_line in enumerate(input_lines): 91 if iterated_char_count + len(input_line) > max_chars: 92 break 93 iterated_char_count += len(input_line) 94 95 # Create truncated explanation with modified final line 96 truncated_result = input_lines[:iterated_index] 97 final_line = input_lines[iterated_index] 98 if final_line: 99 final_line_truncate_point = max_chars - iterated_char_count 100 final_line = final_line[:final_line_truncate_point] 101 truncated_result.append(final_line) 102 return truncated_result 103