@@ -415,8 +415,71 @@ def testOctalNumberOption(self):
415
415
print (type (config ._get_option ('UMASK' )))
416
416
417
417
418
+ @pytest .mark .usefixtures ("save_restore_logging" )
418
419
class TrackerConfig (unittest .TestCase ):
419
420
421
+ @pytest .fixture (scope = "class" )
422
+ def save_restore_logging (self ):
423
+ """Save logger state and try to restore it after all tests in
424
+ this class have finished.
425
+
426
+ The primary test is testDictLoggerConfigViaJson which
427
+ can change the loggers and break tests that depend on caplog
428
+ """
429
+ # Save logger state for root and roundup top level logger
430
+ loggernames = ("" , "roundup" )
431
+
432
+ # The state attributes to save. Lists are shallow copied
433
+ state_to_save = ("filters" , "handlers" , "level" , "propagate" )
434
+
435
+ logger_state = {}
436
+ for name in loggernames :
437
+ logger_state [name ] = {}
438
+ roundup_logger = logging .getLogger (name )
439
+
440
+ for i in state_to_save :
441
+ attr = getattr (roundup_logger , i )
442
+ if isinstance (attr , list ):
443
+ logger_state [name ][i ] = attr .copy ()
444
+ else :
445
+ logger_state [name ][i ] = getattr (roundup_logger , i )
446
+
447
+ # run all class tests here
448
+ yield
449
+
450
+ # rip down all the loggers leaving the root logger reporting
451
+ # to stdout.
452
+ # otherwise logger config is leaking to other tests
453
+ roundup_loggers = [logging .getLogger (name ) for name in
454
+ logging .root .manager .loggerDict
455
+ if name .startswith ("roundup" )]
456
+
457
+ # cribbed from configuration.py:init_loggers
458
+ hdlr = logging .StreamHandler (sys .stdout )
459
+ formatter = logging .Formatter (
460
+ '%(asctime)s %(levelname)s %(message)s' )
461
+ hdlr .setFormatter (formatter )
462
+
463
+ for logger in roundup_loggers :
464
+ # no logging API to remove all existing handlers!?!
465
+ for h in logger .handlers :
466
+ h .close ()
467
+ logger .removeHandler (h )
468
+ logger .handlers = [hdlr ]
469
+ logger .setLevel ("WARNING" )
470
+ logger .propagate = True # important as caplog requires this
471
+
472
+ # Restore the info we stored before running tests
473
+ for name in loggernames :
474
+ local_logger = logging .getLogger (name )
475
+ for attr in logger_state [name ]:
476
+ setattr (local_logger , attr , logger_state [name ][attr ])
477
+
478
+ # reset logging as well
479
+ from importlib import reload
480
+ logging .shutdown ()
481
+ reload (logging )
482
+
420
483
backend = 'anydbm'
421
484
422
485
def setUp (self ):
@@ -1139,22 +1202,8 @@ def testDictLoggerConfigViaJson(self):
1139
1202
}
1140
1203
""" )
1141
1204
1142
- # save roundup logger state
1143
- loggernames = ("" , "roundup" )
1144
- logger_state = {}
1145
- for name in loggernames :
1146
- logger_state [name ] = {}
1147
-
1148
- roundup_logger = logging .getLogger ("roundup" )
1149
- for i in ("filters" , "handlers" , "level" , "propagate" ):
1150
- attr = getattr (roundup_logger , i )
1151
- if isinstance (attr , list ):
1152
- logger_state [name ][i ] = attr .copy ()
1153
- else :
1154
- logger_state [name ][i ] = getattr (roundup_logger , i )
1155
-
1156
- log_config_filename = self .instance .tracker_home \
1157
- + "/_test_log_config.json"
1205
+ log_config_filename = os .path .join (self .instance .tracker_home ,
1206
+ "_test_log_config.json" )
1158
1207
1159
1208
# happy path
1160
1209
with open (log_config_filename , "w" ) as log_config_file :
@@ -1176,10 +1225,11 @@ def testDictLoggerConfigViaJson(self):
1176
1225
self .assertEqual (
1177
1226
cm .exception .args [0 ],
1178
1227
('Error parsing json logging dict '
1179
- '(_test_instance/_test_log_config.json ) near \n \n '
1228
+ '(%s ) near \n \n '
1180
1229
'"version": 1, # only supported version\n \n Expecting '
1181
1230
'property name enclosed in double quotes: line 3 column 18.\n '
1182
- 'Maybe bad inline comment, 3 spaces needed before #.' )
1231
+ 'Maybe bad inline comment, 3 spaces needed before #.' %
1232
+ log_config_filename )
1183
1233
)
1184
1234
1185
1235
# broken trailing , on last dict element
@@ -1198,9 +1248,10 @@ def testDictLoggerConfigViaJson(self):
1198
1248
self .assertEqual (
1199
1249
cm .exception .args [0 ],
1200
1250
('Error parsing json logging dict '
1201
- '(_test_instance/_test_log_config.json) near \n \n '
1202
- ' }\n \n Expecting property name enclosed in double '
1203
- 'quotes: line 37 column 6.' )
1251
+ '(%s) near \n \n '
1252
+ ' }\n \n '
1253
+ 'Expecting property name enclosed in double '
1254
+ 'quotes: line 37 column 6.' % log_config_filename )
1204
1255
)
1205
1256
1206
1257
# 3.13+ diags FIXME
@@ -1211,22 +1262,12 @@ def testDictLoggerConfigViaJson(self):
1211
1262
self.assertEqual(
1212
1263
cm.exception.args[0],
1213
1264
('Error parsing json logging dict '
1214
- '(_test_instance/_test_log_config.json) near \n \n '
1215
- ' }\n \n Expecting property name enclosed in double '
1216
- 'quotes: line 37 column 6.')
1265
+ '(%s) near \n \n '
1266
+ ' "stream": "ext://sys.stdout"\n \n '
1267
+ 'Expecting property name enclosed in double '
1268
+ 'quotes: line 37 column 6.' % log_config_filename)
1217
1269
)
1218
1270
'''
1219
-
1220
- '''
1221
- # comment out as it breaks the logging config for caplog
1222
- # on test_rest.py:testBadFormAttributeErrorException
1223
- # for all rdbms backends.
1224
- # the log ERROR check never gets any info
1225
-
1226
- # commenting out root logger in config doesn't make it work.
1227
- # storing root logger and roundup logger state and restoring it
1228
- # still fails.
1229
-
1230
1271
# happy path for init_logging()
1231
1272
1232
1273
# verify preconditions
@@ -1269,9 +1310,10 @@ def testDictLoggerConfigViaJson(self):
1269
1310
cm .exception .args [0 ].replace (
1270
1311
"object\n " , "object, got 'int'\n " ),
1271
1312
('Error loading logging dict from '
1272
- '_test_instance/_test_log_config.json .\n '
1313
+ '%s .\n '
1273
1314
"ValueError: Unable to configure formatter 'http'\n "
1274
- "expected string or bytes-like object, got 'int'\n ")
1315
+ "expected string or bytes-like object, got 'int'\n " %
1316
+ log_config_filename )
1275
1317
)
1276
1318
1277
1319
# broken invalid level MANGO
@@ -1288,16 +1330,18 @@ def testDictLoggerConfigViaJson(self):
1288
1330
self .assertEqual (
1289
1331
cm .exception .args [0 ],
1290
1332
("Error loading logging dict from "
1291
- "_test_instance/_test_log_config.json .\n ValueError: "
1333
+ "%s .\n ValueError: "
1292
1334
"Unable to configure logger 'roundup.hyperdb'\n Unknown level: "
1293
- "'MANGO'\n ")
1335
+ "'MANGO'\n " % log_config_filename )
1294
1336
1295
1337
)
1296
1338
1297
1339
# broken invalid output directory
1298
1340
test_config = config1 .replace (
1299
1341
' "_test_instance/access.log"' ,
1300
1342
' "not_a_test_instance/access.log"' )
1343
+ access_filename = os .path .join ("not_a_test_instance" , "access.log" )
1344
+
1301
1345
with open (log_config_filename , "w" ) as log_config_file :
1302
1346
log_config_file .write (test_config )
1303
1347
@@ -1307,51 +1351,22 @@ def testDictLoggerConfigViaJson(self):
1307
1351
config = self .db .config .init_logging ()
1308
1352
1309
1353
# error includes full path which is different on different
1310
- # CI and dev platforms. So munge the path using re.sub.
1311
- self.assertEqual(
1312
- re.sub("directory: \' /.*not_a", 'directory: not_a' ,
1313
- cm.exception.args[0]),
1314
- ("Error loading logging dict from "
1315
- "_test_instance/_test_log_config.json.\n "
1316
- "ValueError: Unable to configure handler 'access'\n "
1317
- "[Errno 2] No such file or directory: "
1318
- "not_a_test_instance/access.log'\n "
1319
- )
1320
- )
1321
-
1322
- '''
1323
- # rip down all the loggers leaving the root logger reporting
1324
- # to stdout.
1325
- # otherwise logger config is leaking to other tests
1326
-
1327
- roundup_loggers = [logging .getLogger (name ) for name in
1328
- logging .root .manager .loggerDict
1329
- if name .startswith ("roundup" )]
1330
-
1331
- # cribbed from configuration.py:init_loggers
1332
- hdlr = logging .StreamHandler (sys .stdout )
1333
- formatter = logging .Formatter (
1334
- '%(asctime)s %(levelname)s %(message)s' )
1335
- hdlr .setFormatter (formatter )
1336
-
1337
- for logger in roundup_loggers :
1338
- # no logging API to remove all existing handlers!?!
1339
- for h in logger .handlers :
1340
- h .close ()
1341
- logger .removeHandler (h )
1342
- logger .handlers = [hdlr ]
1343
- logger .setLevel ("DEBUG" )
1344
- logger .propagate = True
1345
-
1346
- for name in loggernames :
1347
- local_logger = logging .getLogger (name )
1348
- for attr in logger_state [name ]:
1349
- # if I restore handlers state for root logger
1350
- # I break the test referenced above. -- WHY????
1351
- if attr == "handlers" and name == "" : continue
1352
- setattr (local_logger , attr , logger_state [name ][attr ])
1353
-
1354
- from importlib import reload
1355
- logging .shutdown ()
1356
- reload (logging )
1354
+ # CI and dev platforms. So munge the path using re.sub and
1355
+ # replace. Windows needs replace as the full path for windows
1356
+ # to the file has '\\\\' not '\\' when taken from __context__.
1357
+ # E.G.
1358
+ # ("Error loading logging dict from '
1359
+ # '_test_instance\\_test_log_config.json.\nValueError: '
1360
+ # "Unable to configure handler 'access'\n[Errno 2] No such file "
1361
+ # "or directory: "
1362
+ # "'C:\\\\tracker\\\\path\\\\not_a_test_instance\\\\access.log'\n")
1363
+ # sigh.....
1364
+ output = re .sub ("directory: \' .*not_a" , 'directory: not_a' ,
1365
+ cm .exception .args [0 ].replace (r'\\' ,'\\ ' ))
1366
+ target = ("Error loading logging dict from "
1367
+ "%s.\n "
1368
+ "ValueError: Unable to configure handler 'access'\n "
1369
+ "[Errno 2] No such file or directory: "
1370
+ "%s'\n " % (log_config_filename , access_filename ))
1371
+ self .assertEqual (output , target )
1357
1372
0 commit comments