comparison test/test_config.py @ 6638:e1588ae185dc issue2550923_computed_property

merge from default branch. Fix travis.ci so CI builds don't error out
author John Rouillard <rouilj@ieee.org>
date Thu, 21 Apr 2022 16:54:17 -0400
parents 0d99ae7c8de6
children ab2ed11c021e
comparison
equal deleted inserted replaced
6508:85db90cc1705 6638:e1588ae185dc
17 17
18 import unittest 18 import unittest
19 import logging 19 import logging
20 import fileinput 20 import fileinput
21 21
22 import os, shutil, errno 22 import os, shutil, errno, sys
23 23
24 import pytest 24 import pytest
25 from roundup import configuration 25 from roundup import configuration
26 26
27 try: 27 try:
35 # https://github.com/pytest-dev/pytest/issues/568 35 # https://github.com/pytest-dev/pytest/issues/568
36 from .pytest_patcher import mark_class 36 from .pytest_patcher import mark_class
37 skip_xapian = mark_class(pytest.mark.skip( 37 skip_xapian = mark_class(pytest.mark.skip(
38 "Skipping Xapian indexer tests: 'xapian' not installed")) 38 "Skipping Xapian indexer tests: 'xapian' not installed"))
39 include_no_xapian = lambda func, *args, **kwargs: func 39 include_no_xapian = lambda func, *args, **kwargs: func
40 40
41 _py3 = sys.version_info[0] > 2
42 if _py3:
43 skip_py2 = lambda func, *args, **kwargs: func
44 else:
45 # FIX: workaround for a bug in pytest.mark.skip():
46 # https://github.com/pytest-dev/pytest/issues/568
47 from .pytest_patcher import mark_class
48 skip_py2 = mark_class(pytest.mark.skip(
49 reason='Skipping test under python2.'))
50
41 51
42 config = configuration.CoreConfig() 52 config = configuration.CoreConfig()
43 config.DATABASE = "db" 53 config.DATABASE = "db"
44 config.RDBMS_NAME = "rounduptest" 54 config.RDBMS_NAME = "rounduptest"
45 config.RDBMS_HOST = "localhost" 55 config.RDBMS_HOST = "localhost"
337 try: 347 try:
338 shutil.rmtree(self.dirname) 348 shutil.rmtree(self.dirname)
339 except OSError as error: 349 except OSError as error:
340 if error.errno not in (errno.ENOENT, errno.ESRCH): raise 350 if error.errno not in (errno.ENOENT, errno.ESRCH): raise
341 351
342 def munge_configini(self, mods = None): 352 def munge_configini(self, mods = None, section=None):
343 """ modify config.ini to meet testing requirements 353 """ modify config.ini to meet testing requirements
344 354
345 mods is a list of tuples: 355 mods is a list of tuples:
346 [ ( "a = ", "b" ), ("c = ", None) ] 356 [ ( "a = ", "b" ), ("c = ", None), ("d = ", "b", "z = ") ]
347 Match line with first tuple element e.g. "a = ". Note specify 357 Match line with first tuple element e.g. "a = ". Note specify
348 trailing "=" and space to delimit keyword and properly format 358 trailing "=" and space to delimit keyword and properly format
349 replacement line. If first tuple element matches, the line is 359 replacement line. If there are two elements in the tuple,
360 and the first element matches, the line is
350 replaced with the concatenation of the first and second elements. 361 replaced with the concatenation of the first and second elements.
351 If second element is None ("" doesn't work), the line will be 362 If second element is None ("" doesn't work), the line will be
352 deleted. 363 deleted. If there are three elements in the tuple, the line
353 364 is replaced with the contcatentation of the third and second
354 Note the key/first element of tuple must be unique in config.ini. 365 elements (used to replace commented out parameters).
355 It is possible to have duplicates in different sections. This 366
356 method doesn't handle that. TBD option third element of tuple 367 Note if the key/first element of tuple is not unique in
357 defining section if needed. 368 config.ini, you must set the section to match the bracketed
369 section name.
358 """ 370 """
359 371
360 if mods is None: 372 if mods is None:
361 return 373 return
362 374
375 # if section is defined, the tests in the loop will turn
376 # it off on [main] if section != '[main]'.
377 in_section = True
378
363 for line in fileinput.input(os.path.join(self.dirname, "config.ini"), 379 for line in fileinput.input(os.path.join(self.dirname, "config.ini"),
364 inplace=True): 380 inplace=True):
365 for match, value in mods: 381 if section:
366 if line.startswith(match): 382 if line.startswith('['):
367 if value is not None: 383 in_section = False
368 print(match + value) 384
369 break 385 if line.startswith(section):
386 in_section = True
387
388 if in_section:
389 for rule in mods:
390 if len(rule) == 3:
391 match, value, repl = rule
392 else:
393 match, value = rule
394 repl = None
395
396 if line.startswith(match):
397 if value is not None:
398 if repl:
399 print(repl + value)
400 else:
401 print(match + value)
402 break
403 else:
404 print(line[:-1]) # remove trailing \n
370 else: 405 else:
371 print(line[:-1]) # remove trailing \n 406 print(line[:-1]) # remove trailing \n
372 407
373 def testNoDBInConfig(self): 408 def testNoDBInConfig(self):
374 """Arguably this should be tested in test_instance since it is 409 """Arguably this should be tested in test_instance since it is
384 with self.assertRaises(configuration.OptionUnsetError) as cm: 419 with self.assertRaises(configuration.OptionUnsetError) as cm:
385 instance.open(self.dirname) 420 instance.open(self.dirname)
386 421
387 self.assertEqual("RDBMS_BACKEND is not set" 422 self.assertEqual("RDBMS_BACKEND is not set"
388 " and has no default", cm.exception.__str__()) 423 " and has no default", cm.exception.__str__())
424
425 def testUnsetMailPassword_with_set_username(self):
426 """ Set [mail] username but don't set the
427 [mail] password. Should get an OptionValueError.
428 """
429 # SETUP: set mail username
430 self.munge_configini(mods=[ ("username = ", "foo"), ],
431 section="[mail]")
432
433 config = configuration.CoreConfig()
434
435 with self.assertRaises(configuration.OptionValueError) as cm:
436 config.load(self.dirname)
437
438 print(cm.exception)
439 # test repr. The type is right since it passed assertRaises.
440 self.assertIn("OptionValueError", repr(cm.exception))
441 # look for 'not defined'
442 self.assertEqual("not defined", cm.exception.args[1])
443
444
445 def testUnsetMailPassword_with_unset_username(self):
446 """ Set [mail] username but don't set the
447 [mail] password. Should get an OptionValueError.
448 """
449 config = configuration.CoreConfig()
450
451 config.load(self.dirname)
452
453 self.assertEqual(config['MAIL_USERNAME'], '')
454
455 with self.assertRaises(configuration.OptionUnsetError) as cm:
456 self.assertEqual(config['MAIL_PASSWORD'], 'NO DEFAULT')
457
458 def testSecretMandatory_missing_file(self):
459
460 # SETUP:
461 self.munge_configini(mods=[ ("secret_key = ", "file://secret_key"), ])
462
463 config = configuration.CoreConfig()
464
465 with self.assertRaises(configuration.OptionValueError) as cm:
466 config.load(self.dirname)
467
468 print(cm.exception)
469 self.assertEqual(cm.exception.args[0].setting, "secret_key")
470
471 def testSecretMandatory_load_from_file(self):
472
473 # SETUP:
474 self.munge_configini(mods=[ ("secret_key = ", "file://secret_key"), ])
475
476 secret = "ASDQWEZXCRFVBGTYHNMJU"
477 with open(self.dirname + "/secret_key", "w") as f:
478 f.write(secret + "\n")
479
480 config = configuration.CoreConfig()
481
482 config.load(self.dirname)
483
484 self.assertEqual(config['WEB_SECRET_KEY'], secret)
485
486
487 def testSecretMandatory_load_from_abs_file(self):
488
489 abs_file = "/tmp/secret_key.%s"%os.getpid()
490
491 # SETUP:
492 self.munge_configini(mods=[ ("secret_key = ", "file://%s"%abs_file), ])
493
494 secret = "ASDQWEZXCRFVBGTYHNMJU"
495 with open(abs_file, "w") as f:
496 f.write(secret + "\n")
497
498 config = configuration.CoreConfig()
499
500 config.load(self.dirname)
501
502 self.assertEqual(config['WEB_SECRET_KEY'], secret)
503
504 os.remove(abs_file)
505
506 def testSecretMandatory_empty_file(self):
507
508 self.munge_configini(mods=[ ("secret_key = ", "file:// secret_key"), ])
509
510 # file with no value just newline.
511 with open(self.dirname + "/secret_key", "w") as f:
512 f.write("\n")
513
514 config = configuration.CoreConfig()
515
516 with self.assertRaises(configuration.OptionValueError) as cm:
517 config.load(self.dirname)
518
519 print(cm.exception.args)
520 self.assertEqual(cm.exception.args[2],"Value must not be empty.")
521
522 def testNullableSecret_empty_file(self):
523
524 self.munge_configini(mods=[ ("password = ", "file://db_password"), ])
525
526 # file with no value just newline.
527 with open(self.dirname + "/db_password", "w") as f:
528 f.write("\n")
529
530 config = configuration.CoreConfig()
531
532 config.load(self.dirname)
533
534 v = config['RDBMS_PASSWORD']
535
536 self.assertEqual(v, None)
537
538 def testNullableSecret_with_file_value(self):
539
540 self.munge_configini(mods=[ ("password = ", "file://db_password"), ])
541
542 # file with no value just newline.
543 with open(self.dirname + "/db_password", "w") as f:
544 f.write("test\n")
545
546 config = configuration.CoreConfig()
547
548 config.load(self.dirname)
549
550 v = config['RDBMS_PASSWORD']
551
552 self.assertEqual(v, "test")
553
554 def testNullableSecret_with_value(self):
555
556 self.munge_configini(mods=[ ("password = ", "test"), ])
557
558 config = configuration.CoreConfig()
559
560 config.load(self.dirname)
561
562 v = config['RDBMS_PASSWORD']
563
564 self.assertEqual(v, "test")
565
566 def testSetMailPassword_with_set_username(self):
567 """ Set [mail] username and set the password.
568 Should have both values set.
569 """
570 # SETUP: set mail username
571 self.munge_configini(mods=[ ("username = ", "foo"),
572 ("#password = ", "passwordfoo",
573 "password = ") ],
574 section="[mail]")
575
576 config = configuration.CoreConfig()
577
578 config.load(self.dirname)
579
580 self.assertEqual(config['MAIL_USERNAME'], 'foo')
581 self.assertEqual(config['MAIL_PASSWORD'], 'passwordfoo')
582
583 def testSetMailPassword_from_file(self):
584 """ Set [mail] username and set the password.
585 Should have both values set.
586 """
587 # SETUP: set mail username
588 self.munge_configini(mods=[ ("username = ", "foo"),
589 ("#password = ", "file://password",
590 "password = ") ],
591 section="[mail]")
592 with open(self.dirname + "/password", "w") as f:
593 f.write("passwordfoo\n")
594
595 config = configuration.CoreConfig()
596
597 config.load(self.dirname)
598
599 self.assertEqual(config['MAIL_USERNAME'], 'foo')
600 self.assertEqual(config['MAIL_PASSWORD'], 'passwordfoo')
389 601
390 @skip_xapian 602 @skip_xapian
391 def testInvalidIndexerLanguage_w_empty(self): 603 def testInvalidIndexerLanguage_w_empty(self):
392 """ make sure we have a reasonable error message if 604 """ make sure we have a reasonable error message if
393 invalid indexer language is specified. This uses 605 invalid indexer language is specified. This uses
490 # don't test exception content. Done in 702 # don't test exception content. Done in
491 # testInvalidIndexerLanguage_w_empty 703 # testInvalidIndexerLanguage_w_empty
492 # if exception not generated assertRaises 704 # if exception not generated assertRaises
493 # will generate failure. 705 # will generate failure.
494 706
707 def testInvalidIndexerLanguage_w_native_fts(self):
708 """ Use explicit native-fts indexer. Verify exception is
709 generated.
710 """
711
712 self.munge_configini(mods=[ ("indexer = ", "native-fts"),
713 ("indexer_language = ", "NO_LANG") ])
714
715 with self.assertRaises(configuration.OptionValueError) as cm:
716 config.load(self.dirname)
717
718 # test repr. The type is right since it passed assertRaises.
719 self.assertIn("OptionValueError", repr(cm.exception))
720 # look for failing language
721 self.assertIn("NO_LANG", cm.exception.args[1])
722 # look for supported language
723 self.assertIn("basque", cm.exception.args[2])
724
495 def testLoadConfig(self): 725 def testLoadConfig(self):
496 """ run load to validate config """ 726 """ run load to validate config """
497 727
498 config = configuration.CoreConfig() 728 config = configuration.CoreConfig()
499 729
551 config_copy = config.copy() 781 config_copy = config.copy()
552 782
553 # this should work 783 # this should work
554 self.assertEqual(config_copy['HTML_VERSION'], 'xhtml') 784 self.assertEqual(config_copy['HTML_VERSION'], 'xhtml')
555 785
786 @skip_py2
787 def testConfigValueInterpolateError(self):
788 ''' error is not raised using ConfigParser under Python 2.
789 Unknown cause, so skip it if running python 2.
790 '''
791
792 self.munge_configini(mods=[ ("admin_email = ", "a bare % is invalid") ])
793
794 config = configuration.CoreConfig()
795
796 # load config generates:
797 '''
798 E roundup.configuration.ParsingOptionError: Error in _test_instance/config.ini with section [main] at option admin_email: '%' must be followed by '%' or '(', found: '% is invalid'
799 '''
800
801 with self.assertRaises(configuration.ParsingOptionError) as cm:
802 config.load(self.dirname)
803
804 print(cm.exception)
805 self.assertIn("'%' must be followed by '%' or '(', found: '% is invalid'", cm.exception.args[0])
806 self.assertIn("_test_instance/config.ini with section [main] at option admin_email", cm.exception.args[0])
807
808
809 from roundup.admin import AdminTool
810 from .test_admin import captured_output
811
812 admin=AdminTool()
813 with captured_output() as (out, err):
814 sys.argv=['main', '-i', self.dirname, 'get', 'tile', 'issue1']
815 ret = admin.main()
816
817 expected_err = "Error in _test_instance/config.ini with section [main] at option admin_email: '%' must be followed by '%' or '(', found: '% is invalid'"
818
819 self.assertEqual(ret, 1)
820 out = out.getvalue().strip()
821 self.assertEqual(out, expected_err)
822
556 def testInvalidIndexerValue(self): 823 def testInvalidIndexerValue(self):
557 """ Mistype native indexer. Verify exception is 824 """ Mistype native indexer. Verify exception is
558 generated. 825 generated.
559 """ 826 """
560 827
574 # verify that args show up in string representaton 841 # verify that args show up in string representaton
575 string_rep = cm.exception.__str__() 842 string_rep = cm.exception.__str__()
576 print(string_rep) 843 print(string_rep)
577 self.assertIn("nati", string_rep) 844 self.assertIn("nati", string_rep)
578 self.assertIn("'whoosh'", string_rep) 845 self.assertIn("'whoosh'", string_rep)
579
580

Roundup Issue Tracker: http://roundup-tracker.org/