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