• 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', _TIMESTAMP_FORMAT)
43
44
45def make_log(**kwargs):
46    """Create a LogLine instance."""
47    # Construct a LogRecord
48    attributes = dict(name='testlogger',
49                      levelno=logging.INFO,
50                      levelname='INF',
51                      msg='[%s] %.3f %s',
52                      args=('MOD1', 3.14159, 'Real message here'),
53                      created=_TIMESTAMP_SAMPLE.timestamp(),
54                      filename='test.py',
55                      lineno=42,
56                      pathname='/home/user/test.py')
57    # Override any above attrs that are passed in.
58    attributes.update(kwargs)
59    # Create the record
60    record = logging.makeLogRecord(dict(attributes))
61    # Format
62    formatted_message = formatter.format(record)
63    log_line = LogLine(record=record,
64                       formatted_log=formatted_message,
65                       ansi_stripped_log='')
66    log_line.update_metadata()
67    return log_line
68
69
70class TestTableView(unittest.TestCase):
71    """Tests for rendering log lines into tables."""
72    def setUp(self):
73        # Show large diffs
74        self.maxDiff = None  # pylint: disable=invalid-name
75        self.prefs = ConsolePrefs(project_file=False,
76                                  project_user_file=False,
77                                  user_file=False)
78        self.prefs.reset_config()
79
80    @parameterized.expand([
81        (
82            'Correct column widths with all fields set',
83            [
84                make_log(
85                    args=('M1', 1.2345, 'Something happened'),
86                    extra_metadata_fields=dict(module='M1', anumber=12)),
87
88                make_log(
89                    args=('MD2', 567.5, 'Another cool event'),
90                    extra_metadata_fields=dict(module='MD2', anumber=123)),
91            ],
92            dict(module=len('MD2'), anumber=len('123')),
93        ),
94        (
95            'Missing metadata fields on some rows',
96            [
97                make_log(
98                    args=('M1', 54321.2, 'Something happened'),
99                    extra_metadata_fields=dict(module='M1', anumber=54321.2)),
100
101                make_log(
102                    args=('MOD2', 567.5, 'Another cool event'),
103                    extra_metadata_fields=dict(module='MOD2')),
104            ],
105            dict(module=len('MOD2'), anumber=len('54321.200')),
106        ),
107    ]) # yapf: disable
108    def test_column_widths(self, _name, logs, expected_widths) -> None:
109        """Test colum widths calculation."""
110        table = TableView(self.prefs)
111        for log in logs:
112            table.update_metadata_column_widths(log)
113            metadata_fields = {
114                k: v
115                for k, v in log.metadata.fields.items()
116                if k not in ['py_file', 'py_logger']
117            }
118            # update_metadata_column_widths shoulp populate self.metadata.fields
119            self.assertEqual(metadata_fields, log.record.extra_metadata_fields)
120        # Check expected column widths
121        results = {
122            k: v
123            for k, v in dict(table.column_widths).items()
124            if k not in ['time', 'level', 'py_file', 'py_logger']
125        }
126        self.assertCountEqual(expected_widths, results)
127
128    @parameterized.expand([
129        (
130            'Build header adding fields incrementally',
131            [
132                make_log(
133                    args=('MODULE2', 567.5, 'Another cool event'),
134                    extra_metadata_fields=dict(
135                        # timestamp missing
136                        module='MODULE2')),
137
138                make_log(
139                    args=('MODULE1', 54321.2, 'Something happened'),
140                    extra_metadata_fields=dict(
141                        # timestamp added in
142                        module='MODULE1', timestamp=54321.2)),
143            ],
144            [
145                [('bold', 'Time             '),
146                 _TABLE_PADDING_FRAGMENT,
147                 ('bold', 'Lev'),
148                 _TABLE_PADDING_FRAGMENT,
149                 ('bold', 'Module '),
150                 _TABLE_PADDING_FRAGMENT,
151                 ('bold', 'Message')],
152
153                [('bold', 'Time             '),
154                 _TABLE_PADDING_FRAGMENT,
155                 ('bold', 'Lev'),
156                 _TABLE_PADDING_FRAGMENT,
157                 ('bold', 'Module '),
158                 _TABLE_PADDING_FRAGMENT,
159                 # timestamp added in
160                 ('bold', 'Timestamp'),
161                 _TABLE_PADDING_FRAGMENT,
162                 ('bold', 'Message')],
163            ],
164        ),
165    ]) # yapf: disable
166    def test_formatted_header(self, _name, logs, expected_headers) -> None:
167        """Test colum widths calculation."""
168        table = TableView(self.prefs)
169
170        for log, header in zip(logs, expected_headers):
171            table.update_metadata_column_widths(log)
172            self.assertEqual(table.formatted_header(), header)
173
174    @parameterized.expand([
175        (
176            'Build rows adding fields incrementally',
177            [
178                make_log(
179                    args=('MODULE2', 567.5, 'Another cool event'),
180                    extra_metadata_fields=dict(
181                        # timestamp missing
182                        module='MODULE2',
183                        msg='Another cool event')),
184
185                make_log(
186                    args=('MODULE2', 567.5, 'Another cool event'),
187                    extra_metadata_fields=dict(
188                        # timestamp and msg missing
189                        module='MODULE2')),
190
191                make_log(
192                    args=('MODULE1', 54321.2, 'Something happened'),
193                    extra_metadata_fields=dict(
194                        # timestamp added in
195                        module='MODULE1', timestamp=54321.2,
196                        msg='Something happened')),
197            ],
198            [
199                [
200                    ('class:log-time', _TIMESTAMP_SAMPLE_STRING),
201                    _TABLE_PADDING_FRAGMENT,
202                    ('class:log-level-20', 'INF'),
203                    _TABLE_PADDING_FRAGMENT,
204                    ('class:log-table-column-0', 'MODULE2'),
205                    _TABLE_PADDING_FRAGMENT,
206                    ('', 'Another cool event'),
207                    ('', '\n')
208                ],
209
210                [
211                    ('class:log-time', _TIMESTAMP_SAMPLE_STRING),
212                    _TABLE_PADDING_FRAGMENT,
213                    ('class:log-level-20', 'INF'),
214                    _TABLE_PADDING_FRAGMENT,
215                    ('class:log-table-column-0', 'MODULE2'),
216                    _TABLE_PADDING_FRAGMENT,
217                    ('', '[MODULE2] 567.500 Another cool event'),
218                    ('', '\n')
219                ],
220
221                [
222                    ('class:log-time', _TIMESTAMP_SAMPLE_STRING),
223                    _TABLE_PADDING_FRAGMENT,
224                    ('class:log-level-20', 'INF'),
225                    _TABLE_PADDING_FRAGMENT,
226                    ('class:log-table-column-0', 'MODULE1'),
227                    _TABLE_PADDING_FRAGMENT,
228                    ('class:log-table-column-1', '54321.200'),
229                    _TABLE_PADDING_FRAGMENT,
230                    ('', 'Something happened'),
231                    ('', '\n')
232                ],
233            ],
234        ),
235    ]) # yapf: disable
236    def test_formatted_rows(self, _name, logs, expected_log_format) -> None:
237        """Test colum widths calculation."""
238        table = TableView(self.prefs)
239        # Check each row meets expected formats incrementally.
240        for log, formatted_log in zip(logs, expected_log_format):
241            table.update_metadata_column_widths(log)
242            self.assertEqual(formatted_log, table.formatted_row(log))
243
244
245if __name__ == '__main__':
246    unittest.main()
247