xref: /aosp_15_r20/external/pigweed/pw_console/py/table_test.py (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1*61c4878aSAndroid Build Coastguard Worker# Copyright 2021 The Pigweed Authors
2*61c4878aSAndroid Build Coastguard Worker#
3*61c4878aSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); you may not
4*61c4878aSAndroid Build Coastguard Worker# use this file except in compliance with the License. You may obtain a copy of
5*61c4878aSAndroid Build Coastguard Worker# the License at
6*61c4878aSAndroid Build Coastguard Worker#
7*61c4878aSAndroid Build Coastguard Worker#     https://www.apache.org/licenses/LICENSE-2.0
8*61c4878aSAndroid Build Coastguard Worker#
9*61c4878aSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
10*61c4878aSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11*61c4878aSAndroid Build Coastguard Worker# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12*61c4878aSAndroid Build Coastguard Worker# License for the specific language governing permissions and limitations under
13*61c4878aSAndroid Build Coastguard Worker# the License.
14*61c4878aSAndroid Build Coastguard Worker"""Tests for pw_console.text_formatting"""
15*61c4878aSAndroid Build Coastguard Worker
16*61c4878aSAndroid Build Coastguard Workerfrom datetime import datetime
17*61c4878aSAndroid Build Coastguard Workerimport logging
18*61c4878aSAndroid Build Coastguard Workerimport unittest
19*61c4878aSAndroid Build Coastguard Worker
20*61c4878aSAndroid Build Coastguard Workerfrom parameterized import parameterized  # type: ignore
21*61c4878aSAndroid Build Coastguard Worker
22*61c4878aSAndroid Build Coastguard Workerfrom pw_console.console_prefs import ConsolePrefs
23*61c4878aSAndroid Build Coastguard Workerfrom pw_console.log_line import LogLine
24*61c4878aSAndroid Build Coastguard Workerfrom pw_console.widgets.table import TableView
25*61c4878aSAndroid Build Coastguard Worker
26*61c4878aSAndroid Build Coastguard Worker_TIMESTAMP_FORMAT = '%Y%m%d %H:%M:%S'
27*61c4878aSAndroid Build Coastguard Worker_TIMESTAMP_SAMPLE = datetime(2021, 6, 30, 16, 10, 37, 818901)
28*61c4878aSAndroid Build Coastguard Worker_TIMESTAMP_SAMPLE_STRING = _TIMESTAMP_SAMPLE.strftime(_TIMESTAMP_FORMAT)
29*61c4878aSAndroid Build Coastguard Worker
30*61c4878aSAndroid Build Coastguard Worker_TABLE_PADDING = '  '
31*61c4878aSAndroid Build Coastguard Worker_TABLE_PADDING_FRAGMENT = ('', _TABLE_PADDING)
32*61c4878aSAndroid Build Coastguard Worker
33*61c4878aSAndroid Build Coastguard Workerformatter = logging.Formatter(
34*61c4878aSAndroid Build Coastguard Worker    '\x1b[30m\x1b[47m'
35*61c4878aSAndroid Build Coastguard Worker    '%(asctime)s'
36*61c4878aSAndroid Build Coastguard Worker    '\x1b[0m'
37*61c4878aSAndroid Build Coastguard Worker    ' '
38*61c4878aSAndroid Build Coastguard Worker    '\x1b[33m\x1b[1m'
39*61c4878aSAndroid Build Coastguard Worker    '%(levelname)s'
40*61c4878aSAndroid Build Coastguard Worker    '\x1b[0m'
41*61c4878aSAndroid Build Coastguard Worker    ' '
42*61c4878aSAndroid Build Coastguard Worker    '%(message)s',
43*61c4878aSAndroid Build Coastguard Worker    _TIMESTAMP_FORMAT,
44*61c4878aSAndroid Build Coastguard Worker)
45*61c4878aSAndroid Build Coastguard Worker
46*61c4878aSAndroid Build Coastguard Worker
47*61c4878aSAndroid Build Coastguard Workerdef make_log(**kwargs):
48*61c4878aSAndroid Build Coastguard Worker    """Create a LogLine instance."""
49*61c4878aSAndroid Build Coastguard Worker    # Construct a LogRecord
50*61c4878aSAndroid Build Coastguard Worker    attributes = dict(
51*61c4878aSAndroid Build Coastguard Worker        name='testlogger',
52*61c4878aSAndroid Build Coastguard Worker        levelno=logging.INFO,
53*61c4878aSAndroid Build Coastguard Worker        levelname='INF',
54*61c4878aSAndroid Build Coastguard Worker        msg='[%s] %.3f %s',
55*61c4878aSAndroid Build Coastguard Worker        args=('MOD1', 3.14159, 'Real message here'),
56*61c4878aSAndroid Build Coastguard Worker        created=_TIMESTAMP_SAMPLE.timestamp(),
57*61c4878aSAndroid Build Coastguard Worker        filename='test.py',
58*61c4878aSAndroid Build Coastguard Worker        lineno=42,
59*61c4878aSAndroid Build Coastguard Worker        pathname='/home/user/test.py',
60*61c4878aSAndroid Build Coastguard Worker    )
61*61c4878aSAndroid Build Coastguard Worker    # Override any above attrs that are passed in.
62*61c4878aSAndroid Build Coastguard Worker    attributes.update(kwargs)
63*61c4878aSAndroid Build Coastguard Worker    # Create the record
64*61c4878aSAndroid Build Coastguard Worker    record = logging.makeLogRecord(dict(attributes))
65*61c4878aSAndroid Build Coastguard Worker    # Format
66*61c4878aSAndroid Build Coastguard Worker    formatted_message = formatter.format(record)
67*61c4878aSAndroid Build Coastguard Worker    log_line = LogLine(
68*61c4878aSAndroid Build Coastguard Worker        record=record, formatted_log=formatted_message, ansi_stripped_log=''
69*61c4878aSAndroid Build Coastguard Worker    )
70*61c4878aSAndroid Build Coastguard Worker    log_line.update_metadata()
71*61c4878aSAndroid Build Coastguard Worker    return log_line
72*61c4878aSAndroid Build Coastguard Worker
73*61c4878aSAndroid Build Coastguard Worker
74*61c4878aSAndroid Build Coastguard Workerclass TestTableView(unittest.TestCase):
75*61c4878aSAndroid Build Coastguard Worker    """Tests for rendering log lines into tables."""
76*61c4878aSAndroid Build Coastguard Worker
77*61c4878aSAndroid Build Coastguard Worker    maxDiff = None
78*61c4878aSAndroid Build Coastguard Worker
79*61c4878aSAndroid Build Coastguard Worker    def setUp(self):
80*61c4878aSAndroid Build Coastguard Worker        # Show large diffs
81*61c4878aSAndroid Build Coastguard Worker        self.prefs = ConsolePrefs(
82*61c4878aSAndroid Build Coastguard Worker            project_file=False, project_user_file=False, user_file=False
83*61c4878aSAndroid Build Coastguard Worker        )
84*61c4878aSAndroid Build Coastguard Worker        self.prefs.reset_config()
85*61c4878aSAndroid Build Coastguard Worker
86*61c4878aSAndroid Build Coastguard Worker    @parameterized.expand(
87*61c4878aSAndroid Build Coastguard Worker        [
88*61c4878aSAndroid Build Coastguard Worker            (
89*61c4878aSAndroid Build Coastguard Worker                'Correct column widths with all fields set',
90*61c4878aSAndroid Build Coastguard Worker                [
91*61c4878aSAndroid Build Coastguard Worker                    make_log(
92*61c4878aSAndroid Build Coastguard Worker                        args=('M1', 1.2345, 'Something happened'),
93*61c4878aSAndroid Build Coastguard Worker                        extra_metadata_fields=dict(module='M1', anumber=12),
94*61c4878aSAndroid Build Coastguard Worker                    ),
95*61c4878aSAndroid Build Coastguard Worker                    make_log(
96*61c4878aSAndroid Build Coastguard Worker                        args=('MD2', 567.5, 'Another cool event'),
97*61c4878aSAndroid Build Coastguard Worker                        extra_metadata_fields=dict(module='MD2', anumber=123),
98*61c4878aSAndroid Build Coastguard Worker                    ),
99*61c4878aSAndroid Build Coastguard Worker                ],
100*61c4878aSAndroid Build Coastguard Worker                dict(module=len('MD2'), anumber=len('123')),
101*61c4878aSAndroid Build Coastguard Worker            ),
102*61c4878aSAndroid Build Coastguard Worker            (
103*61c4878aSAndroid Build Coastguard Worker                'Missing metadata fields on some rows',
104*61c4878aSAndroid Build Coastguard Worker                [
105*61c4878aSAndroid Build Coastguard Worker                    make_log(
106*61c4878aSAndroid Build Coastguard Worker                        args=('M1', 54321.2, 'Something happened'),
107*61c4878aSAndroid Build Coastguard Worker                        extra_metadata_fields=dict(
108*61c4878aSAndroid Build Coastguard Worker                            module='M1', anumber=54321.2
109*61c4878aSAndroid Build Coastguard Worker                        ),
110*61c4878aSAndroid Build Coastguard Worker                    ),
111*61c4878aSAndroid Build Coastguard Worker                    make_log(
112*61c4878aSAndroid Build Coastguard Worker                        args=('MOD2', 567.5, 'Another cool event'),
113*61c4878aSAndroid Build Coastguard Worker                        extra_metadata_fields=dict(module='MOD2'),
114*61c4878aSAndroid Build Coastguard Worker                    ),
115*61c4878aSAndroid Build Coastguard Worker                ],
116*61c4878aSAndroid Build Coastguard Worker                dict(module=len('MOD2'), anumber=len('54321.200')),
117*61c4878aSAndroid Build Coastguard Worker            ),
118*61c4878aSAndroid Build Coastguard Worker        ]
119*61c4878aSAndroid Build Coastguard Worker    )
120*61c4878aSAndroid Build Coastguard Worker    def test_column_widths(self, _name, logs, expected_widths) -> None:
121*61c4878aSAndroid Build Coastguard Worker        """Test colum widths calculation."""
122*61c4878aSAndroid Build Coastguard Worker        table = TableView(self.prefs)
123*61c4878aSAndroid Build Coastguard Worker        for log in logs:
124*61c4878aSAndroid Build Coastguard Worker            table.update_metadata_column_widths(log)
125*61c4878aSAndroid Build Coastguard Worker            metadata_fields = {
126*61c4878aSAndroid Build Coastguard Worker                k: v
127*61c4878aSAndroid Build Coastguard Worker                for k, v in log.metadata.fields.items()
128*61c4878aSAndroid Build Coastguard Worker                if k not in ['py_file', 'py_logger']
129*61c4878aSAndroid Build Coastguard Worker            }
130*61c4878aSAndroid Build Coastguard Worker            # update_metadata_column_widths shoulp populate self.metadata.fields
131*61c4878aSAndroid Build Coastguard Worker            self.assertEqual(metadata_fields, log.record.extra_metadata_fields)
132*61c4878aSAndroid Build Coastguard Worker        # Check expected column widths
133*61c4878aSAndroid Build Coastguard Worker        results = {
134*61c4878aSAndroid Build Coastguard Worker            k: v
135*61c4878aSAndroid Build Coastguard Worker            for k, v in dict(table.column_widths).items()
136*61c4878aSAndroid Build Coastguard Worker            if k not in ['time', 'level', 'py_file', 'py_logger']
137*61c4878aSAndroid Build Coastguard Worker        }
138*61c4878aSAndroid Build Coastguard Worker        self.assertCountEqual(expected_widths, results)
139*61c4878aSAndroid Build Coastguard Worker
140*61c4878aSAndroid Build Coastguard Worker    @parameterized.expand(
141*61c4878aSAndroid Build Coastguard Worker        [
142*61c4878aSAndroid Build Coastguard Worker            (
143*61c4878aSAndroid Build Coastguard Worker                'Build header adding fields incrementally',
144*61c4878aSAndroid Build Coastguard Worker                [
145*61c4878aSAndroid Build Coastguard Worker                    make_log(
146*61c4878aSAndroid Build Coastguard Worker                        args=('MODULE2', 567.5, 'Another cool event'),
147*61c4878aSAndroid Build Coastguard Worker                        extra_metadata_fields=dict(
148*61c4878aSAndroid Build Coastguard Worker                            # timestamp missing
149*61c4878aSAndroid Build Coastguard Worker                            module='MODULE2'
150*61c4878aSAndroid Build Coastguard Worker                        ),
151*61c4878aSAndroid Build Coastguard Worker                    ),
152*61c4878aSAndroid Build Coastguard Worker                    make_log(
153*61c4878aSAndroid Build Coastguard Worker                        args=('MODULE1', 54321.2, 'Something happened'),
154*61c4878aSAndroid Build Coastguard Worker                        extra_metadata_fields=dict(
155*61c4878aSAndroid Build Coastguard Worker                            # timestamp added in
156*61c4878aSAndroid Build Coastguard Worker                            module='MODULE1',
157*61c4878aSAndroid Build Coastguard Worker                            timestamp=54321.2,
158*61c4878aSAndroid Build Coastguard Worker                        ),
159*61c4878aSAndroid Build Coastguard Worker                    ),
160*61c4878aSAndroid Build Coastguard Worker                ],
161*61c4878aSAndroid Build Coastguard Worker                [
162*61c4878aSAndroid Build Coastguard Worker                    [
163*61c4878aSAndroid Build Coastguard Worker                        ('bold', 'Time             '),
164*61c4878aSAndroid Build Coastguard Worker                        _TABLE_PADDING_FRAGMENT,
165*61c4878aSAndroid Build Coastguard Worker                        ('bold', 'Lev'),
166*61c4878aSAndroid Build Coastguard Worker                        _TABLE_PADDING_FRAGMENT,
167*61c4878aSAndroid Build Coastguard Worker                        ('bold', 'Module '),
168*61c4878aSAndroid Build Coastguard Worker                        _TABLE_PADDING_FRAGMENT,
169*61c4878aSAndroid Build Coastguard Worker                        ('bold', 'Message'),
170*61c4878aSAndroid Build Coastguard Worker                    ],
171*61c4878aSAndroid Build Coastguard Worker                    [
172*61c4878aSAndroid Build Coastguard Worker                        ('bold', 'Time             '),
173*61c4878aSAndroid Build Coastguard Worker                        _TABLE_PADDING_FRAGMENT,
174*61c4878aSAndroid Build Coastguard Worker                        ('bold', 'Lev'),
175*61c4878aSAndroid Build Coastguard Worker                        _TABLE_PADDING_FRAGMENT,
176*61c4878aSAndroid Build Coastguard Worker                        ('bold', 'Module '),
177*61c4878aSAndroid Build Coastguard Worker                        _TABLE_PADDING_FRAGMENT,
178*61c4878aSAndroid Build Coastguard Worker                        # timestamp added in
179*61c4878aSAndroid Build Coastguard Worker                        ('bold', 'Timestamp'),
180*61c4878aSAndroid Build Coastguard Worker                        _TABLE_PADDING_FRAGMENT,
181*61c4878aSAndroid Build Coastguard Worker                        ('bold', 'Message'),
182*61c4878aSAndroid Build Coastguard Worker                    ],
183*61c4878aSAndroid Build Coastguard Worker                ],
184*61c4878aSAndroid Build Coastguard Worker            ),
185*61c4878aSAndroid Build Coastguard Worker        ]
186*61c4878aSAndroid Build Coastguard Worker    )
187*61c4878aSAndroid Build Coastguard Worker    def test_formatted_header(self, _name, logs, expected_headers) -> None:
188*61c4878aSAndroid Build Coastguard Worker        """Test colum widths calculation."""
189*61c4878aSAndroid Build Coastguard Worker        table = TableView(self.prefs)
190*61c4878aSAndroid Build Coastguard Worker
191*61c4878aSAndroid Build Coastguard Worker        for log, header in zip(logs, expected_headers):
192*61c4878aSAndroid Build Coastguard Worker            table.update_metadata_column_widths(log)
193*61c4878aSAndroid Build Coastguard Worker            self.assertEqual(table.formatted_header(), header)
194*61c4878aSAndroid Build Coastguard Worker
195*61c4878aSAndroid Build Coastguard Worker    @parameterized.expand(
196*61c4878aSAndroid Build Coastguard Worker        [
197*61c4878aSAndroid Build Coastguard Worker            (
198*61c4878aSAndroid Build Coastguard Worker                'Build rows adding fields incrementally',
199*61c4878aSAndroid Build Coastguard Worker                [
200*61c4878aSAndroid Build Coastguard Worker                    make_log(
201*61c4878aSAndroid Build Coastguard Worker                        args=('MODULE2', 567.5, 'Another cool event'),
202*61c4878aSAndroid Build Coastguard Worker                        extra_metadata_fields=dict(
203*61c4878aSAndroid Build Coastguard Worker                            # timestamp missing
204*61c4878aSAndroid Build Coastguard Worker                            module='MODULE2',
205*61c4878aSAndroid Build Coastguard Worker                            msg='Another cool event',
206*61c4878aSAndroid Build Coastguard Worker                        ),
207*61c4878aSAndroid Build Coastguard Worker                    ),
208*61c4878aSAndroid Build Coastguard Worker                    make_log(
209*61c4878aSAndroid Build Coastguard Worker                        args=('MODULE2', 567.5, 'Another cool event'),
210*61c4878aSAndroid Build Coastguard Worker                        extra_metadata_fields=dict(
211*61c4878aSAndroid Build Coastguard Worker                            # timestamp and msg missing
212*61c4878aSAndroid Build Coastguard Worker                            module='MODULE2'
213*61c4878aSAndroid Build Coastguard Worker                        ),
214*61c4878aSAndroid Build Coastguard Worker                    ),
215*61c4878aSAndroid Build Coastguard Worker                    make_log(
216*61c4878aSAndroid Build Coastguard Worker                        args=('MODULE1', 54321.2, 'Something happened'),
217*61c4878aSAndroid Build Coastguard Worker                        extra_metadata_fields=dict(
218*61c4878aSAndroid Build Coastguard Worker                            # timestamp added in
219*61c4878aSAndroid Build Coastguard Worker                            module='MODULE1',
220*61c4878aSAndroid Build Coastguard Worker                            timestamp=54321.2,
221*61c4878aSAndroid Build Coastguard Worker                            msg='Something happened',
222*61c4878aSAndroid Build Coastguard Worker                        ),
223*61c4878aSAndroid Build Coastguard Worker                    ),
224*61c4878aSAndroid Build Coastguard Worker                ],
225*61c4878aSAndroid Build Coastguard Worker                [
226*61c4878aSAndroid Build Coastguard Worker                    [
227*61c4878aSAndroid Build Coastguard Worker                        ('class:log-time', _TIMESTAMP_SAMPLE_STRING),
228*61c4878aSAndroid Build Coastguard Worker                        _TABLE_PADDING_FRAGMENT,
229*61c4878aSAndroid Build Coastguard Worker                        ('class:log-level-20', 'INF'),
230*61c4878aSAndroid Build Coastguard Worker                        _TABLE_PADDING_FRAGMENT,
231*61c4878aSAndroid Build Coastguard Worker                        ('class:log-table-column-0', 'MODULE2'),
232*61c4878aSAndroid Build Coastguard Worker                        _TABLE_PADDING_FRAGMENT,
233*61c4878aSAndroid Build Coastguard Worker                        ('', 'Another cool event'),
234*61c4878aSAndroid Build Coastguard Worker                        ('', '\n'),
235*61c4878aSAndroid Build Coastguard Worker                    ],
236*61c4878aSAndroid Build Coastguard Worker                    [
237*61c4878aSAndroid Build Coastguard Worker                        ('class:log-time', _TIMESTAMP_SAMPLE_STRING),
238*61c4878aSAndroid Build Coastguard Worker                        _TABLE_PADDING_FRAGMENT,
239*61c4878aSAndroid Build Coastguard Worker                        ('class:log-level-20', 'INF'),
240*61c4878aSAndroid Build Coastguard Worker                        _TABLE_PADDING_FRAGMENT,
241*61c4878aSAndroid Build Coastguard Worker                        ('class:log-table-column-0', 'MODULE2'),
242*61c4878aSAndroid Build Coastguard Worker                        _TABLE_PADDING_FRAGMENT,
243*61c4878aSAndroid Build Coastguard Worker                        ('', '[MODULE2] 567.500 Another cool event'),
244*61c4878aSAndroid Build Coastguard Worker                        ('', '\n'),
245*61c4878aSAndroid Build Coastguard Worker                    ],
246*61c4878aSAndroid Build Coastguard Worker                    [
247*61c4878aSAndroid Build Coastguard Worker                        ('class:log-time', _TIMESTAMP_SAMPLE_STRING),
248*61c4878aSAndroid Build Coastguard Worker                        _TABLE_PADDING_FRAGMENT,
249*61c4878aSAndroid Build Coastguard Worker                        ('class:log-level-20', 'INF'),
250*61c4878aSAndroid Build Coastguard Worker                        _TABLE_PADDING_FRAGMENT,
251*61c4878aSAndroid Build Coastguard Worker                        ('class:log-table-column-0', 'MODULE1'),
252*61c4878aSAndroid Build Coastguard Worker                        _TABLE_PADDING_FRAGMENT,
253*61c4878aSAndroid Build Coastguard Worker                        ('class:log-table-column-1', '54321.200'),
254*61c4878aSAndroid Build Coastguard Worker                        _TABLE_PADDING_FRAGMENT,
255*61c4878aSAndroid Build Coastguard Worker                        ('', 'Something happened'),
256*61c4878aSAndroid Build Coastguard Worker                        ('', '\n'),
257*61c4878aSAndroid Build Coastguard Worker                    ],
258*61c4878aSAndroid Build Coastguard Worker                ],
259*61c4878aSAndroid Build Coastguard Worker            ),
260*61c4878aSAndroid Build Coastguard Worker        ]
261*61c4878aSAndroid Build Coastguard Worker    )
262*61c4878aSAndroid Build Coastguard Worker    def test_formatted_rows(self, _name, logs, expected_log_format) -> None:
263*61c4878aSAndroid Build Coastguard Worker        """Test colum widths calculation."""
264*61c4878aSAndroid Build Coastguard Worker        table = TableView(self.prefs)
265*61c4878aSAndroid Build Coastguard Worker        # Check each row meets expected formats incrementally.
266*61c4878aSAndroid Build Coastguard Worker        for log, formatted_log in zip(logs, expected_log_format):
267*61c4878aSAndroid Build Coastguard Worker            table.update_metadata_column_widths(log)
268*61c4878aSAndroid Build Coastguard Worker            self.assertEqual(formatted_log, table.formatted_row(log))
269*61c4878aSAndroid Build Coastguard Worker
270*61c4878aSAndroid Build Coastguard Worker
271*61c4878aSAndroid Build Coastguard Workerif __name__ == '__main__':
272*61c4878aSAndroid Build Coastguard Worker    unittest.main()
273