Mercurial > p > roundup > code
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 |
