1import sys 2 3 4def create_display(opts, tests, total_tests, workers): 5 if opts.quiet: 6 return NopDisplay() 7 8 num_tests = len(tests) 9 of_total = (" of %d" % total_tests) if (num_tests != total_tests) else "" 10 header = "-- Testing: %d%s tests, %d workers --" % (num_tests, of_total, workers) 11 12 progress_bar = None 13 if opts.succinct and opts.useProgressBar: 14 import lit.ProgressBar 15 16 try: 17 tc = lit.ProgressBar.TerminalController() 18 progress_bar = lit.ProgressBar.ProgressBar(tc, header) 19 header = None 20 except ValueError: 21 progress_bar = lit.ProgressBar.SimpleProgressBar("Testing: ") 22 23 return Display(opts, tests, header, progress_bar) 24 25 26class ProgressPredictor(object): 27 def __init__(self, tests): 28 self.completed = 0 29 self.time_elapsed = 0.0 30 self.predictable_tests_remaining = 0 31 self.predictable_time_remaining = 0.0 32 self.unpredictable_tests_remaining = 0 33 34 for test in tests: 35 if test.previous_elapsed: 36 self.predictable_tests_remaining += 1 37 self.predictable_time_remaining += test.previous_elapsed 38 else: 39 self.unpredictable_tests_remaining += 1 40 41 def update(self, test): 42 self.completed += 1 43 self.time_elapsed += test.result.elapsed 44 45 if test.previous_elapsed: 46 self.predictable_tests_remaining -= 1 47 self.predictable_time_remaining -= test.previous_elapsed 48 else: 49 self.unpredictable_tests_remaining -= 1 50 51 # NOTE: median would be more precise, but might be too slow. 52 average_test_time = (self.time_elapsed + self.predictable_time_remaining) / ( 53 self.completed + self.predictable_tests_remaining 54 ) 55 unpredictable_time_remaining = ( 56 average_test_time * self.unpredictable_tests_remaining 57 ) 58 total_time_remaining = ( 59 self.predictable_time_remaining + unpredictable_time_remaining 60 ) 61 total_time = self.time_elapsed + total_time_remaining 62 63 if total_time > 0: 64 return self.time_elapsed / total_time 65 return 0 66 67 68class NopDisplay(object): 69 def print_header(self): 70 pass 71 72 def update(self, test): 73 pass 74 75 def clear(self, interrupted): 76 pass 77 78 79class Display(object): 80 def __init__(self, opts, tests, header, progress_bar): 81 self.opts = opts 82 self.num_tests = len(tests) 83 self.header = header 84 self.progress_predictor = ProgressPredictor(tests) if progress_bar else None 85 self.progress_bar = progress_bar 86 self.completed = 0 87 88 def print_header(self): 89 if self.header: 90 print(self.header) 91 if self.progress_bar: 92 self.progress_bar.update(0.0, "") 93 94 def update(self, test): 95 self.completed += 1 96 97 show_result = ( 98 test.isFailure() 99 or self.opts.showAllOutput 100 or (not self.opts.quiet and not self.opts.succinct) 101 ) 102 if show_result: 103 if self.progress_bar: 104 self.progress_bar.clear(interrupted=False) 105 self.print_result(test) 106 107 if self.progress_bar: 108 if test.isFailure(): 109 self.progress_bar.barColor = "RED" 110 percent = self.progress_predictor.update(test) 111 self.progress_bar.update(percent, test.getFullName()) 112 113 def clear(self, interrupted): 114 if self.progress_bar: 115 self.progress_bar.clear(interrupted) 116 117 def print_result(self, test): 118 # Show the test result line. 119 test_name = test.getFullName() 120 print( 121 "%s: %s (%d of %d)" 122 % (test.result.code.name, test_name, self.completed, self.num_tests) 123 ) 124 125 # Show the test failure output, if requested. 126 if (test.isFailure() and self.opts.showOutput) or self.opts.showAllOutput: 127 if test.isFailure(): 128 print( 129 "%s TEST '%s' FAILED %s" % ("*" * 20, test.getFullName(), "*" * 20) 130 ) 131 out = test.result.output 132 # Encode/decode so that, when using Python 3.6.5 in Windows 10, 133 # print(out) doesn't raise UnicodeEncodeError if out contains 134 # special characters. However, Python 2 might try to decode 135 # as part of the encode call if out is already encoded, so skip 136 # encoding if it raises UnicodeDecodeError. 137 if sys.stdout.encoding: 138 try: 139 out = out.encode(encoding=sys.stdout.encoding, errors="replace") 140 except UnicodeDecodeError: 141 pass 142 # Python 2 can raise UnicodeDecodeError here too in cases 143 # where the stdout encoding is ASCII. Ignore decode errors 144 # in this case. 145 out = out.decode(encoding=sys.stdout.encoding, errors="ignore") 146 print(out) 147 print("*" * 20) 148 149 # Report test metrics, if present. 150 if test.result.metrics: 151 print("%s TEST '%s' RESULTS %s" % ("*" * 10, test.getFullName(), "*" * 10)) 152 items = sorted(test.result.metrics.items()) 153 for metric_name, value in items: 154 print("%s: %s " % (metric_name, value.format())) 155 print("*" * 10) 156 157 # Report micro-tests, if present 158 if test.result.microResults: 159 items = sorted(test.result.microResults.items()) 160 for micro_test_name, micro_test in items: 161 print("%s MICRO-TEST: %s" % ("*" * 3, micro_test_name)) 162 163 if micro_test.metrics: 164 sorted_metrics = sorted(micro_test.metrics.items()) 165 for metric_name, value in sorted_metrics: 166 print(" %s: %s " % (metric_name, value.format())) 167 168 # Ensure the output is flushed. 169 sys.stdout.flush() 170