1*61c4878aSAndroid Build Coastguard Worker# Copyright 2021 The Pigweed Authors 2*61c4878aSAndroid Build Coastguard Worker# 3*61c4878aSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); you may not 4*61c4878aSAndroid Build Coastguard Worker# use this file except in compliance with the License. You may obtain a copy of 5*61c4878aSAndroid Build Coastguard Worker# the License at 6*61c4878aSAndroid Build Coastguard Worker# 7*61c4878aSAndroid Build Coastguard Worker# https://www.apache.org/licenses/LICENSE-2.0 8*61c4878aSAndroid Build Coastguard Worker# 9*61c4878aSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 10*61c4878aSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11*61c4878aSAndroid Build Coastguard Worker# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12*61c4878aSAndroid Build Coastguard Worker# License for the specific language governing permissions and limitations under 13*61c4878aSAndroid Build Coastguard Worker# the License. 14*61c4878aSAndroid Build Coastguard Worker"""Tests for pw_console.text_formatting""" 15*61c4878aSAndroid Build Coastguard Worker 16*61c4878aSAndroid Build Coastguard Workerfrom datetime import datetime 17*61c4878aSAndroid Build Coastguard Workerimport logging 18*61c4878aSAndroid Build Coastguard Workerimport unittest 19*61c4878aSAndroid Build Coastguard Worker 20*61c4878aSAndroid Build Coastguard Workerfrom parameterized import parameterized # type: ignore 21*61c4878aSAndroid Build Coastguard Worker 22*61c4878aSAndroid Build Coastguard Workerfrom pw_console.console_prefs import ConsolePrefs 23*61c4878aSAndroid Build Coastguard Workerfrom pw_console.log_line import LogLine 24*61c4878aSAndroid Build Coastguard Workerfrom pw_console.widgets.table import TableView 25*61c4878aSAndroid Build Coastguard Worker 26*61c4878aSAndroid Build Coastguard Worker_TIMESTAMP_FORMAT = '%Y%m%d %H:%M:%S' 27*61c4878aSAndroid Build Coastguard Worker_TIMESTAMP_SAMPLE = datetime(2021, 6, 30, 16, 10, 37, 818901) 28*61c4878aSAndroid Build Coastguard Worker_TIMESTAMP_SAMPLE_STRING = _TIMESTAMP_SAMPLE.strftime(_TIMESTAMP_FORMAT) 29*61c4878aSAndroid Build Coastguard Worker 30*61c4878aSAndroid Build Coastguard Worker_TABLE_PADDING = ' ' 31*61c4878aSAndroid Build Coastguard Worker_TABLE_PADDING_FRAGMENT = ('', _TABLE_PADDING) 32*61c4878aSAndroid Build Coastguard Worker 33*61c4878aSAndroid Build Coastguard Workerformatter = logging.Formatter( 34*61c4878aSAndroid Build Coastguard Worker '\x1b[30m\x1b[47m' 35*61c4878aSAndroid Build Coastguard Worker '%(asctime)s' 36*61c4878aSAndroid Build Coastguard Worker '\x1b[0m' 37*61c4878aSAndroid Build Coastguard Worker ' ' 38*61c4878aSAndroid Build Coastguard Worker '\x1b[33m\x1b[1m' 39*61c4878aSAndroid Build Coastguard Worker '%(levelname)s' 40*61c4878aSAndroid Build Coastguard Worker '\x1b[0m' 41*61c4878aSAndroid Build Coastguard Worker ' ' 42*61c4878aSAndroid Build Coastguard Worker '%(message)s', 43*61c4878aSAndroid Build Coastguard Worker _TIMESTAMP_FORMAT, 44*61c4878aSAndroid Build Coastguard Worker) 45*61c4878aSAndroid Build Coastguard Worker 46*61c4878aSAndroid Build Coastguard Worker 47*61c4878aSAndroid Build Coastguard Workerdef make_log(**kwargs): 48*61c4878aSAndroid Build Coastguard Worker """Create a LogLine instance.""" 49*61c4878aSAndroid Build Coastguard Worker # Construct a LogRecord 50*61c4878aSAndroid Build Coastguard Worker attributes = dict( 51*61c4878aSAndroid Build Coastguard Worker name='testlogger', 52*61c4878aSAndroid Build Coastguard Worker levelno=logging.INFO, 53*61c4878aSAndroid Build Coastguard Worker levelname='INF', 54*61c4878aSAndroid Build Coastguard Worker msg='[%s] %.3f %s', 55*61c4878aSAndroid Build Coastguard Worker args=('MOD1', 3.14159, 'Real message here'), 56*61c4878aSAndroid Build Coastguard Worker created=_TIMESTAMP_SAMPLE.timestamp(), 57*61c4878aSAndroid Build Coastguard Worker filename='test.py', 58*61c4878aSAndroid Build Coastguard Worker lineno=42, 59*61c4878aSAndroid Build Coastguard Worker pathname='/home/user/test.py', 60*61c4878aSAndroid Build Coastguard Worker ) 61*61c4878aSAndroid Build Coastguard Worker # Override any above attrs that are passed in. 62*61c4878aSAndroid Build Coastguard Worker attributes.update(kwargs) 63*61c4878aSAndroid Build Coastguard Worker # Create the record 64*61c4878aSAndroid Build Coastguard Worker record = logging.makeLogRecord(dict(attributes)) 65*61c4878aSAndroid Build Coastguard Worker # Format 66*61c4878aSAndroid Build Coastguard Worker formatted_message = formatter.format(record) 67*61c4878aSAndroid Build Coastguard Worker log_line = LogLine( 68*61c4878aSAndroid Build Coastguard Worker record=record, formatted_log=formatted_message, ansi_stripped_log='' 69*61c4878aSAndroid Build Coastguard Worker ) 70*61c4878aSAndroid Build Coastguard Worker log_line.update_metadata() 71*61c4878aSAndroid Build Coastguard Worker return log_line 72*61c4878aSAndroid Build Coastguard Worker 73*61c4878aSAndroid Build Coastguard Worker 74*61c4878aSAndroid Build Coastguard Workerclass TestTableView(unittest.TestCase): 75*61c4878aSAndroid Build Coastguard Worker """Tests for rendering log lines into tables.""" 76*61c4878aSAndroid Build Coastguard Worker 77*61c4878aSAndroid Build Coastguard Worker maxDiff = None 78*61c4878aSAndroid Build Coastguard Worker 79*61c4878aSAndroid Build Coastguard Worker def setUp(self): 80*61c4878aSAndroid Build Coastguard Worker # Show large diffs 81*61c4878aSAndroid Build Coastguard Worker self.prefs = ConsolePrefs( 82*61c4878aSAndroid Build Coastguard Worker project_file=False, project_user_file=False, user_file=False 83*61c4878aSAndroid Build Coastguard Worker ) 84*61c4878aSAndroid Build Coastguard Worker self.prefs.reset_config() 85*61c4878aSAndroid Build Coastguard Worker 86*61c4878aSAndroid Build Coastguard Worker @parameterized.expand( 87*61c4878aSAndroid Build Coastguard Worker [ 88*61c4878aSAndroid Build Coastguard Worker ( 89*61c4878aSAndroid Build Coastguard Worker 'Correct column widths with all fields set', 90*61c4878aSAndroid Build Coastguard Worker [ 91*61c4878aSAndroid Build Coastguard Worker make_log( 92*61c4878aSAndroid Build Coastguard Worker args=('M1', 1.2345, 'Something happened'), 93*61c4878aSAndroid Build Coastguard Worker extra_metadata_fields=dict(module='M1', anumber=12), 94*61c4878aSAndroid Build Coastguard Worker ), 95*61c4878aSAndroid Build Coastguard Worker make_log( 96*61c4878aSAndroid Build Coastguard Worker args=('MD2', 567.5, 'Another cool event'), 97*61c4878aSAndroid Build Coastguard Worker extra_metadata_fields=dict(module='MD2', anumber=123), 98*61c4878aSAndroid Build Coastguard Worker ), 99*61c4878aSAndroid Build Coastguard Worker ], 100*61c4878aSAndroid Build Coastguard Worker dict(module=len('MD2'), anumber=len('123')), 101*61c4878aSAndroid Build Coastguard Worker ), 102*61c4878aSAndroid Build Coastguard Worker ( 103*61c4878aSAndroid Build Coastguard Worker 'Missing metadata fields on some rows', 104*61c4878aSAndroid Build Coastguard Worker [ 105*61c4878aSAndroid Build Coastguard Worker make_log( 106*61c4878aSAndroid Build Coastguard Worker args=('M1', 54321.2, 'Something happened'), 107*61c4878aSAndroid Build Coastguard Worker extra_metadata_fields=dict( 108*61c4878aSAndroid Build Coastguard Worker module='M1', anumber=54321.2 109*61c4878aSAndroid Build Coastguard Worker ), 110*61c4878aSAndroid Build Coastguard Worker ), 111*61c4878aSAndroid Build Coastguard Worker make_log( 112*61c4878aSAndroid Build Coastguard Worker args=('MOD2', 567.5, 'Another cool event'), 113*61c4878aSAndroid Build Coastguard Worker extra_metadata_fields=dict(module='MOD2'), 114*61c4878aSAndroid Build Coastguard Worker ), 115*61c4878aSAndroid Build Coastguard Worker ], 116*61c4878aSAndroid Build Coastguard Worker dict(module=len('MOD2'), anumber=len('54321.200')), 117*61c4878aSAndroid Build Coastguard Worker ), 118*61c4878aSAndroid Build Coastguard Worker ] 119*61c4878aSAndroid Build Coastguard Worker ) 120*61c4878aSAndroid Build Coastguard Worker def test_column_widths(self, _name, logs, expected_widths) -> None: 121*61c4878aSAndroid Build Coastguard Worker """Test colum widths calculation.""" 122*61c4878aSAndroid Build Coastguard Worker table = TableView(self.prefs) 123*61c4878aSAndroid Build Coastguard Worker for log in logs: 124*61c4878aSAndroid Build Coastguard Worker table.update_metadata_column_widths(log) 125*61c4878aSAndroid Build Coastguard Worker metadata_fields = { 126*61c4878aSAndroid Build Coastguard Worker k: v 127*61c4878aSAndroid Build Coastguard Worker for k, v in log.metadata.fields.items() 128*61c4878aSAndroid Build Coastguard Worker if k not in ['py_file', 'py_logger'] 129*61c4878aSAndroid Build Coastguard Worker } 130*61c4878aSAndroid Build Coastguard Worker # update_metadata_column_widths shoulp populate self.metadata.fields 131*61c4878aSAndroid Build Coastguard Worker self.assertEqual(metadata_fields, log.record.extra_metadata_fields) 132*61c4878aSAndroid Build Coastguard Worker # Check expected column widths 133*61c4878aSAndroid Build Coastguard Worker results = { 134*61c4878aSAndroid Build Coastguard Worker k: v 135*61c4878aSAndroid Build Coastguard Worker for k, v in dict(table.column_widths).items() 136*61c4878aSAndroid Build Coastguard Worker if k not in ['time', 'level', 'py_file', 'py_logger'] 137*61c4878aSAndroid Build Coastguard Worker } 138*61c4878aSAndroid Build Coastguard Worker self.assertCountEqual(expected_widths, results) 139*61c4878aSAndroid Build Coastguard Worker 140*61c4878aSAndroid Build Coastguard Worker @parameterized.expand( 141*61c4878aSAndroid Build Coastguard Worker [ 142*61c4878aSAndroid Build Coastguard Worker ( 143*61c4878aSAndroid Build Coastguard Worker 'Build header adding fields incrementally', 144*61c4878aSAndroid Build Coastguard Worker [ 145*61c4878aSAndroid Build Coastguard Worker make_log( 146*61c4878aSAndroid Build Coastguard Worker args=('MODULE2', 567.5, 'Another cool event'), 147*61c4878aSAndroid Build Coastguard Worker extra_metadata_fields=dict( 148*61c4878aSAndroid Build Coastguard Worker # timestamp missing 149*61c4878aSAndroid Build Coastguard Worker module='MODULE2' 150*61c4878aSAndroid Build Coastguard Worker ), 151*61c4878aSAndroid Build Coastguard Worker ), 152*61c4878aSAndroid Build Coastguard Worker make_log( 153*61c4878aSAndroid Build Coastguard Worker args=('MODULE1', 54321.2, 'Something happened'), 154*61c4878aSAndroid Build Coastguard Worker extra_metadata_fields=dict( 155*61c4878aSAndroid Build Coastguard Worker # timestamp added in 156*61c4878aSAndroid Build Coastguard Worker module='MODULE1', 157*61c4878aSAndroid Build Coastguard Worker timestamp=54321.2, 158*61c4878aSAndroid Build Coastguard Worker ), 159*61c4878aSAndroid Build Coastguard Worker ), 160*61c4878aSAndroid Build Coastguard Worker ], 161*61c4878aSAndroid Build Coastguard Worker [ 162*61c4878aSAndroid Build Coastguard Worker [ 163*61c4878aSAndroid Build Coastguard Worker ('bold', 'Time '), 164*61c4878aSAndroid Build Coastguard Worker _TABLE_PADDING_FRAGMENT, 165*61c4878aSAndroid Build Coastguard Worker ('bold', 'Lev'), 166*61c4878aSAndroid Build Coastguard Worker _TABLE_PADDING_FRAGMENT, 167*61c4878aSAndroid Build Coastguard Worker ('bold', 'Module '), 168*61c4878aSAndroid Build Coastguard Worker _TABLE_PADDING_FRAGMENT, 169*61c4878aSAndroid Build Coastguard Worker ('bold', 'Message'), 170*61c4878aSAndroid Build Coastguard Worker ], 171*61c4878aSAndroid Build Coastguard Worker [ 172*61c4878aSAndroid Build Coastguard Worker ('bold', 'Time '), 173*61c4878aSAndroid Build Coastguard Worker _TABLE_PADDING_FRAGMENT, 174*61c4878aSAndroid Build Coastguard Worker ('bold', 'Lev'), 175*61c4878aSAndroid Build Coastguard Worker _TABLE_PADDING_FRAGMENT, 176*61c4878aSAndroid Build Coastguard Worker ('bold', 'Module '), 177*61c4878aSAndroid Build Coastguard Worker _TABLE_PADDING_FRAGMENT, 178*61c4878aSAndroid Build Coastguard Worker # timestamp added in 179*61c4878aSAndroid Build Coastguard Worker ('bold', 'Timestamp'), 180*61c4878aSAndroid Build Coastguard Worker _TABLE_PADDING_FRAGMENT, 181*61c4878aSAndroid Build Coastguard Worker ('bold', 'Message'), 182*61c4878aSAndroid Build Coastguard Worker ], 183*61c4878aSAndroid Build Coastguard Worker ], 184*61c4878aSAndroid Build Coastguard Worker ), 185*61c4878aSAndroid Build Coastguard Worker ] 186*61c4878aSAndroid Build Coastguard Worker ) 187*61c4878aSAndroid Build Coastguard Worker def test_formatted_header(self, _name, logs, expected_headers) -> None: 188*61c4878aSAndroid Build Coastguard Worker """Test colum widths calculation.""" 189*61c4878aSAndroid Build Coastguard Worker table = TableView(self.prefs) 190*61c4878aSAndroid Build Coastguard Worker 191*61c4878aSAndroid Build Coastguard Worker for log, header in zip(logs, expected_headers): 192*61c4878aSAndroid Build Coastguard Worker table.update_metadata_column_widths(log) 193*61c4878aSAndroid Build Coastguard Worker self.assertEqual(table.formatted_header(), header) 194*61c4878aSAndroid Build Coastguard Worker 195*61c4878aSAndroid Build Coastguard Worker @parameterized.expand( 196*61c4878aSAndroid Build Coastguard Worker [ 197*61c4878aSAndroid Build Coastguard Worker ( 198*61c4878aSAndroid Build Coastguard Worker 'Build rows adding fields incrementally', 199*61c4878aSAndroid Build Coastguard Worker [ 200*61c4878aSAndroid Build Coastguard Worker make_log( 201*61c4878aSAndroid Build Coastguard Worker args=('MODULE2', 567.5, 'Another cool event'), 202*61c4878aSAndroid Build Coastguard Worker extra_metadata_fields=dict( 203*61c4878aSAndroid Build Coastguard Worker # timestamp missing 204*61c4878aSAndroid Build Coastguard Worker module='MODULE2', 205*61c4878aSAndroid Build Coastguard Worker msg='Another cool event', 206*61c4878aSAndroid Build Coastguard Worker ), 207*61c4878aSAndroid Build Coastguard Worker ), 208*61c4878aSAndroid Build Coastguard Worker make_log( 209*61c4878aSAndroid Build Coastguard Worker args=('MODULE2', 567.5, 'Another cool event'), 210*61c4878aSAndroid Build Coastguard Worker extra_metadata_fields=dict( 211*61c4878aSAndroid Build Coastguard Worker # timestamp and msg missing 212*61c4878aSAndroid Build Coastguard Worker module='MODULE2' 213*61c4878aSAndroid Build Coastguard Worker ), 214*61c4878aSAndroid Build Coastguard Worker ), 215*61c4878aSAndroid Build Coastguard Worker make_log( 216*61c4878aSAndroid Build Coastguard Worker args=('MODULE1', 54321.2, 'Something happened'), 217*61c4878aSAndroid Build Coastguard Worker extra_metadata_fields=dict( 218*61c4878aSAndroid Build Coastguard Worker # timestamp added in 219*61c4878aSAndroid Build Coastguard Worker module='MODULE1', 220*61c4878aSAndroid Build Coastguard Worker timestamp=54321.2, 221*61c4878aSAndroid Build Coastguard Worker msg='Something happened', 222*61c4878aSAndroid Build Coastguard Worker ), 223*61c4878aSAndroid Build Coastguard Worker ), 224*61c4878aSAndroid Build Coastguard Worker ], 225*61c4878aSAndroid Build Coastguard Worker [ 226*61c4878aSAndroid Build Coastguard Worker [ 227*61c4878aSAndroid Build Coastguard Worker ('class:log-time', _TIMESTAMP_SAMPLE_STRING), 228*61c4878aSAndroid Build Coastguard Worker _TABLE_PADDING_FRAGMENT, 229*61c4878aSAndroid Build Coastguard Worker ('class:log-level-20', 'INF'), 230*61c4878aSAndroid Build Coastguard Worker _TABLE_PADDING_FRAGMENT, 231*61c4878aSAndroid Build Coastguard Worker ('class:log-table-column-0', 'MODULE2'), 232*61c4878aSAndroid Build Coastguard Worker _TABLE_PADDING_FRAGMENT, 233*61c4878aSAndroid Build Coastguard Worker ('', 'Another cool event'), 234*61c4878aSAndroid Build Coastguard Worker ('', '\n'), 235*61c4878aSAndroid Build Coastguard Worker ], 236*61c4878aSAndroid Build Coastguard Worker [ 237*61c4878aSAndroid Build Coastguard Worker ('class:log-time', _TIMESTAMP_SAMPLE_STRING), 238*61c4878aSAndroid Build Coastguard Worker _TABLE_PADDING_FRAGMENT, 239*61c4878aSAndroid Build Coastguard Worker ('class:log-level-20', 'INF'), 240*61c4878aSAndroid Build Coastguard Worker _TABLE_PADDING_FRAGMENT, 241*61c4878aSAndroid Build Coastguard Worker ('class:log-table-column-0', 'MODULE2'), 242*61c4878aSAndroid Build Coastguard Worker _TABLE_PADDING_FRAGMENT, 243*61c4878aSAndroid Build Coastguard Worker ('', '[MODULE2] 567.500 Another cool event'), 244*61c4878aSAndroid Build Coastguard Worker ('', '\n'), 245*61c4878aSAndroid Build Coastguard Worker ], 246*61c4878aSAndroid Build Coastguard Worker [ 247*61c4878aSAndroid Build Coastguard Worker ('class:log-time', _TIMESTAMP_SAMPLE_STRING), 248*61c4878aSAndroid Build Coastguard Worker _TABLE_PADDING_FRAGMENT, 249*61c4878aSAndroid Build Coastguard Worker ('class:log-level-20', 'INF'), 250*61c4878aSAndroid Build Coastguard Worker _TABLE_PADDING_FRAGMENT, 251*61c4878aSAndroid Build Coastguard Worker ('class:log-table-column-0', 'MODULE1'), 252*61c4878aSAndroid Build Coastguard Worker _TABLE_PADDING_FRAGMENT, 253*61c4878aSAndroid Build Coastguard Worker ('class:log-table-column-1', '54321.200'), 254*61c4878aSAndroid Build Coastguard Worker _TABLE_PADDING_FRAGMENT, 255*61c4878aSAndroid Build Coastguard Worker ('', 'Something happened'), 256*61c4878aSAndroid Build Coastguard Worker ('', '\n'), 257*61c4878aSAndroid Build Coastguard Worker ], 258*61c4878aSAndroid Build Coastguard Worker ], 259*61c4878aSAndroid Build Coastguard Worker ), 260*61c4878aSAndroid Build Coastguard Worker ] 261*61c4878aSAndroid Build Coastguard Worker ) 262*61c4878aSAndroid Build Coastguard Worker def test_formatted_rows(self, _name, logs, expected_log_format) -> None: 263*61c4878aSAndroid Build Coastguard Worker """Test colum widths calculation.""" 264*61c4878aSAndroid Build Coastguard Worker table = TableView(self.prefs) 265*61c4878aSAndroid Build Coastguard Worker # Check each row meets expected formats incrementally. 266*61c4878aSAndroid Build Coastguard Worker for log, formatted_log in zip(logs, expected_log_format): 267*61c4878aSAndroid Build Coastguard Worker table.update_metadata_column_widths(log) 268*61c4878aSAndroid Build Coastguard Worker self.assertEqual(formatted_log, table.formatted_row(log)) 269*61c4878aSAndroid Build Coastguard Worker 270*61c4878aSAndroid Build Coastguard Worker 271*61c4878aSAndroid Build Coastguard Workerif __name__ == '__main__': 272*61c4878aSAndroid Build Coastguard Worker unittest.main() 273