truncate.py 3.3 KB

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