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