xref: /llvm-project/llvm/utils/lit/lit/display.py (revision b71edfaa4ec3c998aadb35255ce2f60bba2940b0)
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