• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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    def setUp(self):
78        # Show large diffs
79        self.maxDiff = None  # pylint: disable=invalid-name
80        self.prefs = ConsolePrefs(
81            project_file=False, project_user_file=False, user_file=False
82        )
83        self.prefs.reset_config()
84
85    @parameterized.expand(
86        [
87            (
88                'Correct column widths with all fields set',
89                [
90                    make_log(
91                        args=('M1', 1.2345, 'Something happened'),
92                        extra_metadata_fields=dict(module='M1', anumber=12),
93                    ),
94                    make_log(
95                        args=('MD2', 567.5, 'Another cool event'),
96                        extra_metadata_fields=dict(module='MD2', anumber=123),
97                    ),
98                ],
99                dict(module=len('MD2'), anumber=len('123')),
100            ),
101            (
102                'Missing metadata fields on some rows',
103                [
104                    make_log(
105                        args=('M1', 54321.2, 'Something happened'),
106                        extra_metadata_fields=dict(
107                            module='M1', anumber=54321.2
108                        ),
109                    ),
110                    make_log(
111                        args=('MOD2', 567.5, 'Another cool event'),
112                        extra_metadata_fields=dict(module='MOD2'),
113                    ),
114                ],
115                dict(module=len('MOD2'), anumber=len('54321.200')),
116            ),
117        ]
118    )
119    def test_column_widths(self, _name, logs, expected_widths) -> None:
120        """Test colum widths calculation."""
121        table = TableView(self.prefs)
122        for log in logs:
123            table.update_metadata_column_widths(log)
124            metadata_fields = {
125                k: v
126                for k, v in log.metadata.fields.items()
127                if k not in ['py_file', 'py_logger']
128            }
129            # update_metadata_column_widths shoulp populate self.metadata.fields
130            self.assertEqual(metadata_fields, log.record.extra_metadata_fields)
131        # Check expected column widths
132        results = {
133            k: v
134            for k, v in dict(table.column_widths).items()
135            if k not in ['time', 'level', 'py_file', 'py_logger']
136        }
137        self.assertCountEqual(expected_widths, results)
138
139    @parameterized.expand(
140        [
141            (
142                'Build header adding fields incrementally',
143                [
144                    make_log(
145                        args=('MODULE2', 567.5, 'Another cool event'),
146                        extra_metadata_fields=dict(
147                            # timestamp missing
148                            module='MODULE2'
149                        ),
150                    ),
151                    make_log(
152                        args=('MODULE1', 54321.2, 'Something happened'),
153                        extra_metadata_fields=dict(
154                            # timestamp added in
155                            module='MODULE1',
156                            timestamp=54321.2,
157                        ),
158                    ),
159                ],
160                [
161                    [
162                        ('bold', 'Time             '),
163                        _TABLE_PADDING_FRAGMENT,
164                        ('bold', 'Lev'),
165                        _TABLE_PADDING_FRAGMENT,
166                        ('bold', 'Module '),
167                        _TABLE_PADDING_FRAGMENT,
168                        ('bold', 'Message'),
169                    ],
170                    [
171                        ('bold', 'Time             '),
172                        _TABLE_PADDING_FRAGMENT,
173                        ('bold', 'Lev'),
174                        _TABLE_PADDING_FRAGMENT,
175                        ('bold', 'Module '),
176                        _TABLE_PADDING_FRAGMENT,
177                        # timestamp added in
178                        ('bold', 'Timestamp'),
179                        _TABLE_PADDING_FRAGMENT,
180                        ('bold', 'Message'),
181                    ],
182                ],
183            ),
184        ]
185    )
186    def test_formatted_header(self, _name, logs, expected_headers) -> None:
187        """Test colum widths calculation."""
188        table = TableView(self.prefs)
189
190        for log, header in zip(logs, expected_headers):
191            table.update_metadata_column_widths(log)
192            self.assertEqual(table.formatted_header(), header)
193
194    @parameterized.expand(
195        [
196            (
197                'Build rows adding fields incrementally',
198                [
199                    make_log(
200                        args=('MODULE2', 567.5, 'Another cool event'),
201                        extra_metadata_fields=dict(
202                            # timestamp missing
203                            module='MODULE2',
204                            msg='Another cool event',
205                        ),
206                    ),
207                    make_log(
208                        args=('MODULE2', 567.5, 'Another cool event'),
209                        extra_metadata_fields=dict(
210                            # timestamp and msg missing
211                            module='MODULE2'
212                        ),
213                    ),
214                    make_log(
215                        args=('MODULE1', 54321.2, 'Something happened'),
216                        extra_metadata_fields=dict(
217                            # timestamp added in
218                            module='MODULE1',
219                            timestamp=54321.2,
220                            msg='Something happened',
221                        ),
222                    ),
223                ],
224                [
225                    [
226                        ('class:log-time', _TIMESTAMP_SAMPLE_STRING),
227                        _TABLE_PADDING_FRAGMENT,
228                        ('class:log-level-20', 'INF'),
229                        _TABLE_PADDING_FRAGMENT,
230                        ('class:log-table-column-0', 'MODULE2'),
231                        _TABLE_PADDING_FRAGMENT,
232                        ('', 'Another cool event'),
233                        ('', '\n'),
234                    ],
235                    [
236                        ('class:log-time', _TIMESTAMP_SAMPLE_STRING),
237                        _TABLE_PADDING_FRAGMENT,
238                        ('class:log-level-20', 'INF'),
239                        _TABLE_PADDING_FRAGMENT,
240                        ('class:log-table-column-0', 'MODULE2'),
241                        _TABLE_PADDING_FRAGMENT,
242                        ('', '[MODULE2] 567.500 Another cool event'),
243                        ('', '\n'),
244                    ],
245                    [
246                        ('class:log-time', _TIMESTAMP_SAMPLE_STRING),
247                        _TABLE_PADDING_FRAGMENT,
248                        ('class:log-level-20', 'INF'),
249                        _TABLE_PADDING_FRAGMENT,
250                        ('class:log-table-column-0', 'MODULE1'),
251                        _TABLE_PADDING_FRAGMENT,
252                        ('class:log-table-column-1', '54321.200'),
253                        _TABLE_PADDING_FRAGMENT,
254                        ('', 'Something happened'),
255                        ('', '\n'),
256                    ],
257                ],
258            ),
259        ]
260    )
261    def test_formatted_rows(self, _name, logs, expected_log_format) -> None:
262        """Test colum widths calculation."""
263        table = TableView(self.prefs)
264        # Check each row meets expected formats incrementally.
265        for log, formatted_log in zip(logs, expected_log_format):
266            table.update_metadata_column_widths(log)
267            self.assertEqual(formatted_log, table.formatted_row(log))
268
269
270if __name__ == '__main__':
271    unittest.main()
272