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