Skip to content

Commit bf02f35

Browse files
committed
test: test dictLoggerConfig - working logging reset and windows
Finally figured out why things weren't being restored. Bug in the code. Created a class fixture that stores and restores the logging config. Also using os.path.join and other machinations to make the tests run under windows and linux correctly.
1 parent 24b82e4 commit bf02f35

File tree

1 file changed

+101
-86
lines changed

1 file changed

+101
-86
lines changed

test/test_config.py

Lines changed: 101 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -415,8 +415,71 @@ def testOctalNumberOption(self):
415415
print(type(config._get_option('UMASK')))
416416

417417

418+
@pytest.mark.usefixtures("save_restore_logging")
418419
class TrackerConfig(unittest.TestCase):
419420

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+
420483
backend = 'anydbm'
421484

422485
def setUp(self):
@@ -1139,22 +1202,8 @@ def testDictLoggerConfigViaJson(self):
11391202
}
11401203
""")
11411204

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")
11581207

11591208
# happy path
11601209
with open(log_config_filename, "w") as log_config_file:
@@ -1176,10 +1225,11 @@ def testDictLoggerConfigViaJson(self):
11761225
self.assertEqual(
11771226
cm.exception.args[0],
11781227
('Error parsing json logging dict '
1179-
'(_test_instance/_test_log_config.json) near \n\n '
1228+
'(%s) near \n\n '
11801229
'"version": 1, # only supported version\n\nExpecting '
11811230
'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)
11831233
)
11841234

11851235
# broken trailing , on last dict element
@@ -1198,9 +1248,10 @@ def testDictLoggerConfigViaJson(self):
11981248
self.assertEqual(
11991249
cm.exception.args[0],
12001250
('Error parsing json logging dict '
1201-
'(_test_instance/_test_log_config.json) near \n\n'
1202-
' }\n\nExpecting 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)
12041255
)
12051256

12061257
# 3.13+ diags FIXME
@@ -1211,22 +1262,12 @@ def testDictLoggerConfigViaJson(self):
12111262
self.assertEqual(
12121263
cm.exception.args[0],
12131264
('Error parsing json logging dict '
1214-
'(_test_instance/_test_log_config.json) near \n\n'
1215-
' }\n\nExpecting 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)
12171269
)
12181270
'''
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-
12301271
# happy path for init_logging()
12311272

12321273
# verify preconditions
@@ -1269,9 +1310,10 @@ def testDictLoggerConfigViaJson(self):
12691310
cm.exception.args[0].replace(
12701311
"object\n", "object, got 'int'\n"),
12711312
('Error loading logging dict from '
1272-
'_test_instance/_test_log_config.json.\n'
1313+
'%s.\n'
12731314
"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)
12751317
)
12761318

12771319
# broken invalid level MANGO
@@ -1288,16 +1330,18 @@ def testDictLoggerConfigViaJson(self):
12881330
self.assertEqual(
12891331
cm.exception.args[0],
12901332
("Error loading logging dict from "
1291-
"_test_instance/_test_log_config.json.\nValueError: "
1333+
"%s.\nValueError: "
12921334
"Unable to configure logger 'roundup.hyperdb'\nUnknown level: "
1293-
"'MANGO'\n")
1335+
"'MANGO'\n" % log_config_filename)
12941336

12951337
)
12961338

12971339
# broken invalid output directory
12981340
test_config = config1.replace(
12991341
' "_test_instance/access.log"',
13001342
' "not_a_test_instance/access.log"')
1343+
access_filename = os.path.join("not_a_test_instance", "access.log")
1344+
13011345
with open(log_config_filename, "w") as log_config_file:
13021346
log_config_file.write(test_config)
13031347

@@ -1307,51 +1351,22 @@ def testDictLoggerConfigViaJson(self):
13071351
config = self.db.config.init_logging()
13081352

13091353
# 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)
13571372

0 commit comments

Comments
 (0)