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