~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_config.py

  • Committer: Jelmer Vernooij
  • Date: 2012-01-05 16:03:11 UTC
  • mfrom: (6432 +trunk)
  • mto: This revision was merged to the branch mainline in revision 6433.
  • Revision ID: jelmer@samba.org-20120105160311-12o5p67kin1v3zps
Merge bzr.dev.

Show diffs side-by-side

added added

removed removed

Lines of Context:
144
144
config.test_stack_builder_registry.register('branch', build_branch_stack)
145
145
 
146
146
 
147
 
def build_remote_branch_stack(test):
 
147
def build_branch_only_stack(test):
148
148
    # There is only one permutation (but we won't be able to handle more with
149
149
    # this design anyway)
150
150
    (transport_class,
151
151
     server_class) = transport_remote.get_test_permutations()[0]
152
152
    build_backing_branch(test, 'branch', transport_class, server_class)
153
153
    b = branch.Branch.open(test.get_url('branch'))
154
 
    return config.RemoteBranchStack(b)
155
 
config.test_stack_builder_registry.register('remote_branch',
156
 
                                            build_remote_branch_stack)
 
154
    return config.BranchOnlyStack(b)
 
155
config.test_stack_builder_registry.register('branch_only',
 
156
                                            build_branch_only_stack)
157
157
 
158
158
def build_remote_control_stack(test):
159
159
    # There is only one permutation (but we won't be able to handle more with
537
537
 
538
538
    def test_log_format_default(self):
539
539
        my_config = config.Config()
540
 
        self.assertEqual('long', my_config.log_format())
 
540
        self.assertEqual('long',
 
541
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
542
                                              my_config.log_format))
541
543
 
542
544
    def test_acceptable_keys_default(self):
543
545
        my_config = config.Config()
1069
1071
si_g = 5g,
1070
1072
si_gb = 5gB,
1071
1073
""")
1072
 
        get_si = conf.get_user_option_as_int_from_SI
 
1074
        def get_si(s, default=None):
 
1075
            return self.applyDeprecated(
 
1076
                deprecated_in((2, 5, 0)),
 
1077
                conf.get_user_option_as_int_from_SI, s, default)
1073
1078
        self.assertEqual(100, get_si('plain'))
1074
1079
        self.assertEqual(5000, get_si('si_k'))
1075
1080
        self.assertEqual(5000, get_si('si_kb'))
1080
1085
        self.assertEqual(None, get_si('non-exist'))
1081
1086
        self.assertEqual(42, get_si('non-exist-with-default',  42))
1082
1087
 
 
1088
 
1083
1089
class TestSupressWarning(TestIniConfig):
1084
1090
 
1085
1091
    def make_warnings_config(self, s):
1317
1323
 
1318
1324
    def test_configured_logformat(self):
1319
1325
        my_config = self._get_sample_config()
1320
 
        self.assertEqual("short", my_config.log_format())
 
1326
        self.assertEqual("short",
 
1327
                         self.applyDeprecated(deprecated_in((2, 5, 0)),
 
1328
                                              my_config.log_format))
1321
1329
 
1322
1330
    def test_configured_acceptable_keys(self):
1323
1331
        my_config = self._get_sample_config()
2358
2366
class TestOptionConverterMixin(object):
2359
2367
 
2360
2368
    def assertConverted(self, expected, opt, value):
2361
 
        self.assertEquals(expected, opt.convert_from_unicode(value))
2362
 
 
2363
 
    def assertWarns(self, opt, value):
2364
 
        warnings = []
2365
 
        def warning(*args):
2366
 
            warnings.append(args[0] % args[1:])
2367
 
        self.overrideAttr(trace, 'warning', warning)
2368
 
        self.assertEquals(None, opt.convert_from_unicode(value))
2369
 
        self.assertLength(1, warnings)
2370
 
        self.assertEquals(
2371
 
            'Value "%s" is not valid for "%s"' % (value, opt.name),
2372
 
            warnings[0])
2373
 
 
2374
 
    def assertErrors(self, opt, value):
2375
 
        self.assertRaises(errors.ConfigOptionValueError,
2376
 
                          opt.convert_from_unicode, value)
2377
 
 
2378
 
    def assertConvertInvalid(self, opt, invalid_value):
2379
 
        opt.invalid = None
2380
 
        self.assertEquals(None, opt.convert_from_unicode(invalid_value))
2381
 
        opt.invalid = 'warning'
2382
 
        self.assertWarns(opt, invalid_value)
2383
 
        opt.invalid = 'error'
2384
 
        self.assertErrors(opt, invalid_value)
2385
 
 
2386
 
 
2387
 
class TestOptionWithBooleanConverter(tests.TestCase, TestOptionConverterMixin):
2388
 
 
2389
 
    def get_option(self):
2390
 
        return config.Option('foo', help='A boolean.',
2391
 
                             from_unicode=config.bool_from_store)
2392
 
 
2393
 
    def test_convert_invalid(self):
2394
 
        opt = self.get_option()
2395
 
        # A string that is not recognized as a boolean
2396
 
        self.assertConvertInvalid(opt, u'invalid-boolean')
2397
 
        # A list of strings is never recognized as a boolean
2398
 
        self.assertConvertInvalid(opt, [u'not', u'a', u'boolean'])
2399
 
 
2400
 
    def test_convert_valid(self):
2401
 
        opt = self.get_option()
2402
 
        self.assertConverted(True, opt, u'True')
2403
 
        self.assertConverted(True, opt, u'1')
2404
 
        self.assertConverted(False, opt, u'False')
2405
 
 
2406
 
 
2407
 
class TestOptionWithIntegerConverter(tests.TestCase, TestOptionConverterMixin):
2408
 
 
2409
 
    def get_option(self):
2410
 
        return config.Option('foo', help='An integer.',
2411
 
                             from_unicode=config.int_from_store)
2412
 
 
2413
 
    def test_convert_invalid(self):
2414
 
        opt = self.get_option()
2415
 
        # A string that is not recognized as an integer
2416
 
        self.assertConvertInvalid(opt, u'forty-two')
2417
 
        # A list of strings is never recognized as an integer
2418
 
        self.assertConvertInvalid(opt, [u'a', u'list'])
2419
 
 
2420
 
    def test_convert_valid(self):
2421
 
        opt = self.get_option()
2422
 
        self.assertConverted(16, opt, u'16')
2423
 
 
2424
 
 
2425
 
class TestOptionWithListConverter(tests.TestCase, TestOptionConverterMixin):
2426
 
 
2427
 
    def get_option(self):
2428
 
        return config.Option('foo', help='A list.',
2429
 
                             from_unicode=config.list_from_store)
2430
 
 
2431
 
    def test_convert_invalid(self):
2432
 
        # No string is invalid as all forms can be converted to a list
2433
 
        pass
2434
 
 
2435
 
    def test_convert_valid(self):
2436
 
        opt = self.get_option()
2437
 
        # An empty string is an empty list
2438
 
        self.assertConverted([], opt, '') # Using a bare str() just in case
2439
 
        self.assertConverted([], opt, u'')
2440
 
        # A boolean
2441
 
        self.assertConverted([u'True'], opt, u'True')
2442
 
        # An integer
2443
 
        self.assertConverted([u'42'], opt, u'42')
2444
 
        # A single string
2445
 
        self.assertConverted([u'bar'], opt, u'bar')
2446
 
        # A list remains a list (configObj will turn a string containing commas
2447
 
        # into a list, but that's not what we're testing here)
2448
 
        self.assertConverted([u'foo', u'1', u'True'],
2449
 
                             opt, [u'foo', u'1', u'True'])
2450
 
 
2451
 
 
2452
 
class TestOptionConverterMixin(object):
2453
 
 
2454
 
    def assertConverted(self, expected, opt, value):
2455
 
        self.assertEquals(expected, opt.convert_from_unicode(value))
2456
 
 
2457
 
    def assertWarns(self, opt, value):
2458
 
        warnings = []
2459
 
        def warning(*args):
2460
 
            warnings.append(args[0] % args[1:])
2461
 
        self.overrideAttr(trace, 'warning', warning)
2462
 
        self.assertEquals(None, opt.convert_from_unicode(value))
2463
 
        self.assertLength(1, warnings)
2464
 
        self.assertEquals(
2465
 
            'Value "%s" is not valid for "%s"' % (value, opt.name),
2466
 
            warnings[0])
2467
 
 
2468
 
    def assertErrors(self, opt, value):
2469
 
        self.assertRaises(errors.ConfigOptionValueError,
2470
 
                          opt.convert_from_unicode, value)
2471
 
 
2472
 
    def assertConvertInvalid(self, opt, invalid_value):
2473
 
        opt.invalid = None
2474
 
        self.assertEquals(None, opt.convert_from_unicode(invalid_value))
2475
 
        opt.invalid = 'warning'
2476
 
        self.assertWarns(opt, invalid_value)
2477
 
        opt.invalid = 'error'
2478
 
        self.assertErrors(opt, invalid_value)
2479
 
 
2480
 
 
2481
 
class TestOptionWithBooleanConverter(tests.TestCase, TestOptionConverterMixin):
2482
 
 
2483
 
    def get_option(self):
2484
 
        return config.Option('foo', help='A boolean.',
2485
 
                             from_unicode=config.bool_from_store)
2486
 
 
2487
 
    def test_convert_invalid(self):
2488
 
        opt = self.get_option()
2489
 
        # A string that is not recognized as a boolean
2490
 
        self.assertConvertInvalid(opt, u'invalid-boolean')
2491
 
        # A list of strings is never recognized as a boolean
2492
 
        self.assertConvertInvalid(opt, [u'not', u'a', u'boolean'])
2493
 
 
2494
 
    def test_convert_valid(self):
2495
 
        opt = self.get_option()
2496
 
        self.assertConverted(True, opt, u'True')
2497
 
        self.assertConverted(True, opt, u'1')
2498
 
        self.assertConverted(False, opt, u'False')
2499
 
 
2500
 
 
2501
 
class TestOptionWithIntegerConverter(tests.TestCase, TestOptionConverterMixin):
2502
 
 
2503
 
    def get_option(self):
2504
 
        return config.Option('foo', help='An integer.',
2505
 
                             from_unicode=config.int_from_store)
2506
 
 
2507
 
    def test_convert_invalid(self):
2508
 
        opt = self.get_option()
2509
 
        # A string that is not recognized as an integer
2510
 
        self.assertConvertInvalid(opt, u'forty-two')
2511
 
        # A list of strings is never recognized as an integer
2512
 
        self.assertConvertInvalid(opt, [u'a', u'list'])
2513
 
 
2514
 
    def test_convert_valid(self):
2515
 
        opt = self.get_option()
2516
 
        self.assertConverted(16, opt, u'16')
2517
 
 
2518
 
 
2519
 
class TestOptionWithListConverter(tests.TestCase, TestOptionConverterMixin):
2520
 
 
2521
 
    def get_option(self):
2522
 
        return config.Option('foo', help='A list.',
2523
 
                             from_unicode=config.list_from_store)
 
2369
        self.assertEquals(expected, opt.convert_from_unicode(None, value))
 
2370
 
 
2371
    def assertWarns(self, opt, value):
 
2372
        warnings = []
 
2373
        def warning(*args):
 
2374
            warnings.append(args[0] % args[1:])
 
2375
        self.overrideAttr(trace, 'warning', warning)
 
2376
        self.assertEquals(None, opt.convert_from_unicode(None, value))
 
2377
        self.assertLength(1, warnings)
 
2378
        self.assertEquals(
 
2379
            'Value "%s" is not valid for "%s"' % (value, opt.name),
 
2380
            warnings[0])
 
2381
 
 
2382
    def assertErrors(self, opt, value):
 
2383
        self.assertRaises(errors.ConfigOptionValueError,
 
2384
                          opt.convert_from_unicode, None, value)
 
2385
 
 
2386
    def assertConvertInvalid(self, opt, invalid_value):
 
2387
        opt.invalid = None
 
2388
        self.assertEquals(None, opt.convert_from_unicode(None, invalid_value))
 
2389
        opt.invalid = 'warning'
 
2390
        self.assertWarns(opt, invalid_value)
 
2391
        opt.invalid = 'error'
 
2392
        self.assertErrors(opt, invalid_value)
 
2393
 
 
2394
 
 
2395
class TestOptionWithBooleanConverter(tests.TestCase, TestOptionConverterMixin):
 
2396
 
 
2397
    def get_option(self):
 
2398
        return config.Option('foo', help='A boolean.',
 
2399
                             from_unicode=config.bool_from_store)
 
2400
 
 
2401
    def test_convert_invalid(self):
 
2402
        opt = self.get_option()
 
2403
        # A string that is not recognized as a boolean
 
2404
        self.assertConvertInvalid(opt, u'invalid-boolean')
 
2405
        # A list of strings is never recognized as a boolean
 
2406
        self.assertConvertInvalid(opt, [u'not', u'a', u'boolean'])
 
2407
 
 
2408
    def test_convert_valid(self):
 
2409
        opt = self.get_option()
 
2410
        self.assertConverted(True, opt, u'True')
 
2411
        self.assertConverted(True, opt, u'1')
 
2412
        self.assertConverted(False, opt, u'False')
 
2413
 
 
2414
 
 
2415
class TestOptionWithIntegerConverter(tests.TestCase, TestOptionConverterMixin):
 
2416
 
 
2417
    def get_option(self):
 
2418
        return config.Option('foo', help='An integer.',
 
2419
                             from_unicode=config.int_from_store)
 
2420
 
 
2421
    def test_convert_invalid(self):
 
2422
        opt = self.get_option()
 
2423
        # A string that is not recognized as an integer
 
2424
        self.assertConvertInvalid(opt, u'forty-two')
 
2425
        # A list of strings is never recognized as an integer
 
2426
        self.assertConvertInvalid(opt, [u'a', u'list'])
 
2427
 
 
2428
    def test_convert_valid(self):
 
2429
        opt = self.get_option()
 
2430
        self.assertConverted(16, opt, u'16')
 
2431
 
 
2432
 
 
2433
class TestOptionWithSIUnitConverter(tests.TestCase, TestOptionConverterMixin):
 
2434
 
 
2435
    def get_option(self):
 
2436
        return config.Option('foo', help='An integer in SI units.',
 
2437
                             from_unicode=config.int_SI_from_store)
 
2438
 
 
2439
    def test_convert_invalid(self):
 
2440
        opt = self.get_option()
 
2441
        self.assertConvertInvalid(opt, u'not-a-unit')
 
2442
        self.assertConvertInvalid(opt, u'Gb') # Forgot the int
 
2443
        self.assertConvertInvalid(opt, u'1b') # Forgot the unit
 
2444
        self.assertConvertInvalid(opt, u'1GG')
 
2445
        self.assertConvertInvalid(opt, u'1Mbb')
 
2446
        self.assertConvertInvalid(opt, u'1MM')
 
2447
 
 
2448
    def test_convert_valid(self):
 
2449
        opt = self.get_option()
 
2450
        self.assertConverted(int(5e3), opt, u'5kb')
 
2451
        self.assertConverted(int(5e6), opt, u'5M')
 
2452
        self.assertConverted(int(5e6), opt, u'5MB')
 
2453
        self.assertConverted(int(5e9), opt, u'5g')
 
2454
        self.assertConverted(int(5e9), opt, u'5gB')
 
2455
        self.assertConverted(100, opt, u'100')
 
2456
 
 
2457
 
 
2458
class TestListOption(tests.TestCase, TestOptionConverterMixin):
 
2459
 
 
2460
    def get_option(self):
 
2461
        return config.ListOption('foo', help='A list.')
2524
2462
 
2525
2463
    def test_convert_invalid(self):
2526
2464
        opt = self.get_option()
2676
2614
    def setUp(self):
2677
2615
        super(TestCommandLineStore, self).setUp()
2678
2616
        self.store = config.CommandLineStore()
 
2617
        self.overrideAttr(config, 'option_registry', config.OptionRegistry())
2679
2618
 
2680
2619
    def get_section(self):
2681
2620
        """Get the unique section for the command line overrides."""
2696
2635
        self.assertEqual('b', section.get('a'))
2697
2636
 
2698
2637
    def test_list_override(self):
 
2638
        opt = config.ListOption('l')
 
2639
        config.option_registry.register(opt)
2699
2640
        self.store._from_cmdline(['l=1,2,3'])
2700
2641
        val = self.get_section().get('l')
2701
2642
        self.assertEqual('1,2,3', val)
2702
2643
        # Reminder: lists should be registered as such explicitely, otherwise
2703
2644
        # the conversion needs to be done afterwards.
2704
 
        self.assertEqual(['1', '2', '3'], config.list_from_store(val))
 
2645
        self.assertEqual(['1', '2', '3'],
 
2646
                         opt.convert_from_unicode(self.store, val))
2705
2647
 
2706
2648
    def test_multiple_overrides(self):
2707
2649
        self.store._from_cmdline(['a=b', 'x=y'])
2713
2655
        self.assertRaises(errors.BzrCommandError,
2714
2656
                          self.store._from_cmdline, ['a=b', 'c'])
2715
2657
 
 
2658
class TestStoreMinimalAPI(tests.TestCaseWithTransport):
 
2659
 
 
2660
    scenarios = [(key, {'get_store': builder}) for key, builder
 
2661
                 in config.test_store_builder_registry.iteritems()] + [
 
2662
        ('cmdline', {'get_store': lambda test: config.CommandLineStore()})]
 
2663
 
 
2664
    def test_id(self):
 
2665
        store = self.get_store(self)
 
2666
        if type(store) == config.TransportIniFileStore:
 
2667
            raise tests.TestNotApplicable(
 
2668
                "%s is not a concrete Store implementation"
 
2669
                " so it doesn't need an id" % (store.__class__.__name__,))
 
2670
        self.assertIsNot(None, store.id)
 
2671
 
2716
2672
 
2717
2673
class TestStore(tests.TestCaseWithTransport):
2718
2674
 
2761
2717
        self.assertRaises(AssertionError, store._load_from_string, 'bar=baz')
2762
2718
 
2763
2719
 
 
2720
class TestStoreQuoting(TestStore):
 
2721
 
 
2722
    scenarios = [(key, {'get_store': builder}) for key, builder
 
2723
                 in config.test_store_builder_registry.iteritems()]
 
2724
 
 
2725
    def setUp(self):
 
2726
        super(TestStoreQuoting, self).setUp()
 
2727
        self.store = self.get_store(self)
 
2728
        # We need a loaded store but any content will do
 
2729
        self.store._load_from_string('')
 
2730
 
 
2731
    def assertIdempotent(self, s):
 
2732
        """Assert that quoting an unquoted string is a no-op and vice-versa.
 
2733
 
 
2734
        What matters here is that option values, as they appear in a store, can
 
2735
        be safely round-tripped out of the store and back.
 
2736
 
 
2737
        :param s: A string, quoted if required.
 
2738
        """
 
2739
        self.assertEquals(s, self.store.quote(self.store.unquote(s)))
 
2740
        self.assertEquals(s, self.store.unquote(self.store.quote(s)))
 
2741
 
 
2742
    def test_empty_string(self):
 
2743
        if isinstance(self.store, config.IniFileStore):
 
2744
            # configobj._quote doesn't handle empty values
 
2745
            self.assertRaises(AssertionError,
 
2746
                              self.assertIdempotent, '')
 
2747
        else:
 
2748
            self.assertIdempotent('')
 
2749
        # But quoted empty strings are ok
 
2750
        self.assertIdempotent('""')
 
2751
 
 
2752
    def test_embedded_spaces(self):
 
2753
        self.assertIdempotent('" a b c "')
 
2754
 
 
2755
    def test_embedded_commas(self):
 
2756
        self.assertIdempotent('" a , b c "')
 
2757
 
 
2758
    def test_simple_comma(self):
 
2759
        if isinstance(self.store, config.IniFileStore):
 
2760
            # configobj requires that lists are special-cased
 
2761
           self.assertRaises(AssertionError,
 
2762
                             self.assertIdempotent, ',')
 
2763
        else:
 
2764
            self.assertIdempotent(',')
 
2765
        # When a single comma is required, quoting is also required
 
2766
        self.assertIdempotent('","')
 
2767
 
 
2768
    def test_list(self):
 
2769
        if isinstance(self.store, config.IniFileStore):
 
2770
            # configobj requires that lists are special-cased
 
2771
            self.assertRaises(AssertionError,
 
2772
                              self.assertIdempotent, 'a,b')
 
2773
        else:
 
2774
            self.assertIdempotent('a,b')
 
2775
 
 
2776
 
 
2777
class TestDictFromStore(tests.TestCase):
 
2778
 
 
2779
    def test_unquote_not_string(self):
 
2780
        conf = config.MemoryStack('x=2\n[a_section]\na=1\n')
 
2781
        value = conf.get('a_section')
 
2782
        # Urgh, despite 'conf' asking for the no-name section, we get the
 
2783
        # content of another section as a dict o_O
 
2784
        self.assertEquals({'a': '1'}, value)
 
2785
        unquoted = conf.store.unquote(value)
 
2786
        # Which cannot be unquoted but shouldn't crash either (the use cases
 
2787
        # are getting the value or displaying it. In the later case, '%s' will
 
2788
        # do).
 
2789
        self.assertEquals({'a': '1'}, unquoted)
 
2790
        self.assertEquals("{u'a': u'1'}", '%s' % (unquoted,))
 
2791
 
 
2792
 
2764
2793
class TestIniFileStoreContent(tests.TestCaseWithTransport):
2765
2794
    """Simulate loading a config store with content of various encodings.
2766
2795
 
2971
3000
        self.assertLength(1, calls)
2972
3001
        self.assertEquals((store,), calls[0])
2973
3002
 
 
3003
    def test_set_mark_dirty(self):
 
3004
        stack = config.MemoryStack('')
 
3005
        self.assertLength(0, stack.store.dirty_sections)
 
3006
        stack.set('foo', 'baz')
 
3007
        self.assertLength(1, stack.store.dirty_sections)
 
3008
        self.assertTrue(stack.store._need_saving())
 
3009
 
 
3010
    def test_remove_mark_dirty(self):
 
3011
        stack = config.MemoryStack('foo=bar')
 
3012
        self.assertLength(0, stack.store.dirty_sections)
 
3013
        stack.remove('foo')
 
3014
        self.assertLength(1, stack.store.dirty_sections)
 
3015
        self.assertTrue(stack.store._need_saving())
 
3016
 
 
3017
 
 
3018
class TestStoreSaveChanges(tests.TestCaseWithTransport):
 
3019
    """Tests that config changes are kept in memory and saved on-demand."""
 
3020
 
 
3021
    def setUp(self):
 
3022
        super(TestStoreSaveChanges, self).setUp()
 
3023
        self.transport = self.get_transport()
 
3024
        # Most of the tests involve two stores pointing to the same persistent
 
3025
        # storage to observe the effects of concurrent changes
 
3026
        self.st1 = config.TransportIniFileStore(self.transport, 'foo.conf')
 
3027
        self.st2 = config.TransportIniFileStore(self.transport, 'foo.conf')
 
3028
        self.warnings = []
 
3029
        def warning(*args):
 
3030
            self.warnings.append(args[0] % args[1:])
 
3031
        self.overrideAttr(trace, 'warning', warning)
 
3032
 
 
3033
    def has_store(self, store):
 
3034
        store_basename = urlutils.relative_url(self.transport.external_url(),
 
3035
                                               store.external_url())
 
3036
        return self.transport.has(store_basename)
 
3037
 
 
3038
    def get_stack(self, store):
 
3039
        # Any stack will do as long as it uses the right store, just a single
 
3040
        # no-name section is enough
 
3041
        return config.Stack([store.get_sections], store)
 
3042
 
 
3043
    def test_no_changes_no_save(self):
 
3044
        s = self.get_stack(self.st1)
 
3045
        s.store.save_changes()
 
3046
        self.assertEquals(False, self.has_store(self.st1))
 
3047
 
 
3048
    def test_unrelated_concurrent_update(self):
 
3049
        s1 = self.get_stack(self.st1)
 
3050
        s2 = self.get_stack(self.st2)
 
3051
        s1.set('foo', 'bar')
 
3052
        s2.set('baz', 'quux')
 
3053
        s1.store.save()
 
3054
        # Changes don't propagate magically
 
3055
        self.assertEquals(None, s1.get('baz'))
 
3056
        s2.store.save_changes()
 
3057
        self.assertEquals('quux', s2.get('baz'))
 
3058
        # Changes are acquired when saving
 
3059
        self.assertEquals('bar', s2.get('foo'))
 
3060
        # Since there is no overlap, no warnings are emitted
 
3061
        self.assertLength(0, self.warnings)
 
3062
 
 
3063
    def test_concurrent_update_modified(self):
 
3064
        s1 = self.get_stack(self.st1)
 
3065
        s2 = self.get_stack(self.st2)
 
3066
        s1.set('foo', 'bar')
 
3067
        s2.set('foo', 'baz')
 
3068
        s1.store.save()
 
3069
        # Last speaker wins
 
3070
        s2.store.save_changes()
 
3071
        self.assertEquals('baz', s2.get('foo'))
 
3072
        # But the user get a warning
 
3073
        self.assertLength(1, self.warnings)
 
3074
        warning = self.warnings[0]
 
3075
        self.assertStartsWith(warning, 'Option foo in section None')
 
3076
        self.assertEndsWith(warning, 'was changed from <CREATED> to bar.'
 
3077
                            ' The baz value will be saved.')
 
3078
 
 
3079
    def test_concurrent_deletion(self):
 
3080
        self.st1._load_from_string('foo=bar')
 
3081
        self.st1.save()
 
3082
        s1 = self.get_stack(self.st1)
 
3083
        s2 = self.get_stack(self.st2)
 
3084
        s1.remove('foo')
 
3085
        s2.remove('foo')
 
3086
        s1.store.save_changes()
 
3087
        # No warning yet
 
3088
        self.assertLength(0, self.warnings)
 
3089
        s2.store.save_changes()
 
3090
        # Now we get one
 
3091
        self.assertLength(1, self.warnings)
 
3092
        warning = self.warnings[0]
 
3093
        self.assertStartsWith(warning, 'Option foo in section None')
 
3094
        self.assertEndsWith(warning, 'was changed from bar to <CREATED>.'
 
3095
                            ' The <DELETED> value will be saved.')
 
3096
 
 
3097
 
 
3098
class TestQuotingIniFileStore(tests.TestCaseWithTransport):
 
3099
 
 
3100
    def get_store(self):
 
3101
        return config.TransportIniFileStore(self.get_transport(), 'foo.conf')
 
3102
 
 
3103
    def test_get_quoted_string(self):
 
3104
        store = self.get_store()
 
3105
        store._load_from_string('foo= " abc "')
 
3106
        stack = config.Stack([store.get_sections])
 
3107
        self.assertEquals(' abc ', stack.get('foo'))
 
3108
 
 
3109
    def test_set_quoted_string(self):
 
3110
        store = self.get_store()
 
3111
        stack = config.Stack([store.get_sections], store)
 
3112
        stack.set('foo', ' a b c ')
 
3113
        store.save()
 
3114
        self.assertFileEqual('foo = " a b c "' + os.linesep, 'foo.conf')
 
3115
 
2974
3116
 
2975
3117
class TestTransportIniFileStore(TestStore):
2976
3118
 
3321
3463
        self.assertLength(0, sections)
3322
3464
 
3323
3465
 
3324
 
class TestStackGet(tests.TestCase):
3325
 
 
3326
 
    # FIXME: This should be parametrized for all known Stack or dedicated
3327
 
    # paramerized tests created to avoid bloating -- vila 2011-03-31
3328
 
 
3329
 
    def overrideOptionRegistry(self):
 
3466
class TestBaseStackGet(tests.TestCase):
 
3467
 
 
3468
    def setUp(self):
 
3469
        super(TestBaseStackGet, self).setUp()
3330
3470
        self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3331
3471
 
3332
 
    def test_single_config_get(self):
3333
 
        conf = dict(foo='bar')
3334
 
        conf_stack = config.Stack([conf])
3335
 
        self.assertEquals('bar', conf_stack.get('foo'))
 
3472
    def test_get_first_definition(self):
 
3473
        store1 = config.IniFileStore()
 
3474
        store1._load_from_string('foo=bar')
 
3475
        store2 = config.IniFileStore()
 
3476
        store2._load_from_string('foo=baz')
 
3477
        conf = config.Stack([store1.get_sections, store2.get_sections])
 
3478
        self.assertEquals('bar', conf.get('foo'))
3336
3479
 
3337
3480
    def test_get_with_registered_default_value(self):
3338
 
        conf_stack = config.Stack([dict()])
3339
 
        opt = config.Option('foo', default='bar')
3340
 
        self.overrideOptionRegistry()
3341
 
        config.option_registry.register('foo', opt)
 
3481
        config.option_registry.register(config.Option('foo', default='bar'))
 
3482
        conf_stack = config.Stack([])
3342
3483
        self.assertEquals('bar', conf_stack.get('foo'))
3343
3484
 
3344
3485
    def test_get_without_registered_default_value(self):
3345
 
        conf_stack = config.Stack([dict()])
3346
 
        opt = config.Option('foo')
3347
 
        self.overrideOptionRegistry()
3348
 
        config.option_registry.register('foo', opt)
 
3486
        config.option_registry.register(config.Option('foo'))
 
3487
        conf_stack = config.Stack([])
3349
3488
        self.assertEquals(None, conf_stack.get('foo'))
3350
3489
 
3351
3490
    def test_get_without_default_value_for_not_registered(self):
3352
 
        conf_stack = config.Stack([dict()])
3353
 
        opt = config.Option('foo')
3354
 
        self.overrideOptionRegistry()
 
3491
        conf_stack = config.Stack([])
3355
3492
        self.assertEquals(None, conf_stack.get('foo'))
3356
3493
 
3357
 
    def test_get_first_definition(self):
3358
 
        conf1 = dict(foo='bar')
3359
 
        conf2 = dict(foo='baz')
3360
 
        conf_stack = config.Stack([conf1, conf2])
3361
 
        self.assertEquals('bar', conf_stack.get('foo'))
3362
 
 
3363
 
    def test_get_embedded_definition(self):
3364
 
        conf1 = dict(yy='12')
3365
 
        conf2 = config.Stack([dict(xx='42'), dict(foo='baz')])
3366
 
        conf_stack = config.Stack([conf1, conf2])
3367
 
        self.assertEquals('baz', conf_stack.get('foo'))
3368
 
 
3369
3494
    def test_get_for_empty_section_callable(self):
3370
3495
        conf_stack = config.Stack([lambda : []])
3371
3496
        self.assertEquals(None, conf_stack.get('foo'))
3372
3497
 
3373
3498
    def test_get_for_broken_callable(self):
3374
3499
        # Trying to use and invalid callable raises an exception on first use
3375
 
        conf_stack = config.Stack([lambda : object()])
 
3500
        conf_stack = config.Stack([object])
3376
3501
        self.assertRaises(TypeError, conf_stack.get, 'foo')
3377
3502
 
3378
3503
 
 
3504
class TestStackWithSimpleStore(tests.TestCase):
 
3505
 
 
3506
    def setUp(self):
 
3507
        super(TestStackWithSimpleStore, self).setUp()
 
3508
        self.overrideAttr(config, 'option_registry', config.OptionRegistry())
 
3509
        self.registry = config.option_registry
 
3510
 
 
3511
    def get_conf(self, content=None):
 
3512
        return config.MemoryStack(content)
 
3513
 
 
3514
    def test_override_value_from_env(self):
 
3515
        self.registry.register(
 
3516
            config.Option('foo', default='bar', override_from_env=['FOO']))
 
3517
        self.overrideEnv('FOO', 'quux')
 
3518
        # Env variable provides a default taking over the option one
 
3519
        conf = self.get_conf('foo=store')
 
3520
        self.assertEquals('quux', conf.get('foo'))
 
3521
 
 
3522
    def test_first_override_value_from_env_wins(self):
 
3523
        self.registry.register(
 
3524
            config.Option('foo', default='bar',
 
3525
                          override_from_env=['NO_VALUE', 'FOO', 'BAZ']))
 
3526
        self.overrideEnv('FOO', 'foo')
 
3527
        self.overrideEnv('BAZ', 'baz')
 
3528
        # The first env var set wins
 
3529
        conf = self.get_conf('foo=store')
 
3530
        self.assertEquals('foo', conf.get('foo'))
 
3531
 
 
3532
 
 
3533
class TestMemoryStack(tests.TestCase):
 
3534
 
 
3535
    def test_get(self):
 
3536
        conf = config.MemoryStack('foo=bar')
 
3537
        self.assertEquals('bar', conf.get('foo'))
 
3538
 
 
3539
    def test_set(self):
 
3540
        conf = config.MemoryStack('foo=bar')
 
3541
        conf.set('foo', 'baz')
 
3542
        self.assertEquals('baz', conf.get('foo'))
 
3543
 
 
3544
    def test_no_content(self):
 
3545
        conf = config.MemoryStack()
 
3546
        # No content means no loading
 
3547
        self.assertFalse(conf.store.is_loaded())
 
3548
        self.assertRaises(NotImplementedError, conf.get, 'foo')
 
3549
        # But a content can still be provided
 
3550
        conf.store._load_from_string('foo=bar')
 
3551
        self.assertEquals('bar', conf.get('foo'))
 
3552
 
 
3553
 
3379
3554
class TestStackWithTransport(tests.TestCaseWithTransport):
3380
3555
 
3381
3556
    scenarios = [(key, {'get_stack': builder}) for key, builder
3411
3586
        self.assertEquals((self.conf, 'foo', 'bar'), calls[0])
3412
3587
 
3413
3588
 
3414
 
class TestStackGetWithConverter(tests.TestCaseWithTransport):
 
3589
class TestStackGetWithConverter(tests.TestCase):
3415
3590
 
3416
3591
    def setUp(self):
3417
3592
        super(TestStackGetWithConverter, self).setUp()
3418
3593
        self.overrideAttr(config, 'option_registry', config.OptionRegistry())
3419
3594
        self.registry = config.option_registry
3420
 
        # We just want a simple stack with a simple store so we can inject
3421
 
        # whatever content the tests need without caring about what section
3422
 
        # names are valid for a given store/stack.
3423
 
        store = config.TransportIniFileStore(self.get_transport(), 'foo.conf')
3424
 
        self.conf = config.Stack([store.get_sections], store)
 
3595
 
 
3596
    def get_conf(self, content=None):
 
3597
        return config.MemoryStack(content)
3425
3598
 
3426
3599
    def register_bool_option(self, name, default=None, default_from_env=None):
3427
3600
        b = config.Option(name, help='A boolean.',
3431
3604
 
3432
3605
    def test_get_default_bool_None(self):
3433
3606
        self.register_bool_option('foo')
3434
 
        self.assertEquals(None, self.conf.get('foo'))
 
3607
        conf = self.get_conf('')
 
3608
        self.assertEquals(None, conf.get('foo'))
3435
3609
 
3436
3610
    def test_get_default_bool_True(self):
3437
3611
        self.register_bool_option('foo', u'True')
3438
 
        self.assertEquals(True, self.conf.get('foo'))
 
3612
        conf = self.get_conf('')
 
3613
        self.assertEquals(True, conf.get('foo'))
3439
3614
 
3440
3615
    def test_get_default_bool_False(self):
3441
3616
        self.register_bool_option('foo', False)
3442
 
        self.assertEquals(False, self.conf.get('foo'))
 
3617
        conf = self.get_conf('')
 
3618
        self.assertEquals(False, conf.get('foo'))
3443
3619
 
3444
3620
    def test_get_default_bool_False_as_string(self):
3445
3621
        self.register_bool_option('foo', u'False')
3446
 
        self.assertEquals(False, self.conf.get('foo'))
 
3622
        conf = self.get_conf('')
 
3623
        self.assertEquals(False, conf.get('foo'))
3447
3624
 
3448
3625
    def test_get_default_bool_from_env_converted(self):
3449
3626
        self.register_bool_option('foo', u'True', default_from_env=['FOO'])
3450
3627
        self.overrideEnv('FOO', 'False')
3451
 
        self.assertEquals(False, self.conf.get('foo'))
 
3628
        conf = self.get_conf('')
 
3629
        self.assertEquals(False, conf.get('foo'))
3452
3630
 
3453
3631
    def test_get_default_bool_when_conversion_fails(self):
3454
3632
        self.register_bool_option('foo', default='True')
3455
 
        self.conf.store._load_from_string('foo=invalid boolean')
3456
 
        self.assertEquals(True, self.conf.get('foo'))
 
3633
        conf = self.get_conf('foo=invalid boolean')
 
3634
        self.assertEquals(True, conf.get('foo'))
3457
3635
 
3458
3636
    def register_integer_option(self, name,
3459
3637
                                default=None, default_from_env=None):
3464
3642
 
3465
3643
    def test_get_default_integer_None(self):
3466
3644
        self.register_integer_option('foo')
3467
 
        self.assertEquals(None, self.conf.get('foo'))
 
3645
        conf = self.get_conf('')
 
3646
        self.assertEquals(None, conf.get('foo'))
3468
3647
 
3469
3648
    def test_get_default_integer(self):
3470
3649
        self.register_integer_option('foo', 42)
3471
 
        self.assertEquals(42, self.conf.get('foo'))
 
3650
        conf = self.get_conf('')
 
3651
        self.assertEquals(42, conf.get('foo'))
3472
3652
 
3473
3653
    def test_get_default_integer_as_string(self):
3474
3654
        self.register_integer_option('foo', u'42')
3475
 
        self.assertEquals(42, self.conf.get('foo'))
 
3655
        conf = self.get_conf('')
 
3656
        self.assertEquals(42, conf.get('foo'))
3476
3657
 
3477
3658
    def test_get_default_integer_from_env(self):
3478
3659
        self.register_integer_option('foo', default_from_env=['FOO'])
3479
3660
        self.overrideEnv('FOO', '18')
3480
 
        self.assertEquals(18, self.conf.get('foo'))
 
3661
        conf = self.get_conf('')
 
3662
        self.assertEquals(18, conf.get('foo'))
3481
3663
 
3482
3664
    def test_get_default_integer_when_conversion_fails(self):
3483
3665
        self.register_integer_option('foo', default='12')
3484
 
        self.conf.store._load_from_string('foo=invalid integer')
3485
 
        self.assertEquals(12, self.conf.get('foo'))
 
3666
        conf = self.get_conf('foo=invalid integer')
 
3667
        self.assertEquals(12, conf.get('foo'))
3486
3668
 
3487
3669
    def register_list_option(self, name, default=None, default_from_env=None):
3488
 
        l = config.Option(name, help='A list.',
3489
 
                          default=default, default_from_env=default_from_env,
3490
 
                          from_unicode=config.list_from_store)
 
3670
        l = config.ListOption(name, help='A list.', default=default,
 
3671
                              default_from_env=default_from_env)
3491
3672
        self.registry.register(l)
3492
3673
 
3493
3674
    def test_get_default_list_None(self):
3494
3675
        self.register_list_option('foo')
3495
 
        self.assertEquals(None, self.conf.get('foo'))
 
3676
        conf = self.get_conf('')
 
3677
        self.assertEquals(None, conf.get('foo'))
3496
3678
 
3497
3679
    def test_get_default_list_empty(self):
3498
3680
        self.register_list_option('foo', '')
3499
 
        self.assertEquals([], self.conf.get('foo'))
 
3681
        conf = self.get_conf('')
 
3682
        self.assertEquals([], conf.get('foo'))
3500
3683
 
3501
3684
    def test_get_default_list_from_env(self):
3502
3685
        self.register_list_option('foo', default_from_env=['FOO'])
3503
3686
        self.overrideEnv('FOO', '')
3504
 
        self.assertEquals([], self.conf.get('foo'))
 
3687
        conf = self.get_conf('')
 
3688
        self.assertEquals([], conf.get('foo'))
3505
3689
 
3506
3690
    def test_get_with_list_converter_no_item(self):
3507
3691
        self.register_list_option('foo', None)
3508
 
        self.conf.store._load_from_string('foo=,')
3509
 
        self.assertEquals([], self.conf.get('foo'))
 
3692
        conf = self.get_conf('foo=,')
 
3693
        self.assertEquals([], conf.get('foo'))
3510
3694
 
3511
3695
    def test_get_with_list_converter_many_items(self):
3512
3696
        self.register_list_option('foo', None)
3513
 
        self.conf.store._load_from_string('foo=m,o,r,e')
3514
 
        self.assertEquals(['m', 'o', 'r', 'e'], self.conf.get('foo'))
 
3697
        conf = self.get_conf('foo=m,o,r,e')
 
3698
        self.assertEquals(['m', 'o', 'r', 'e'], conf.get('foo'))
3515
3699
 
3516
3700
    def test_get_with_list_converter_embedded_spaces_many_items(self):
3517
3701
        self.register_list_option('foo', None)
3518
 
        self.conf.store._load_from_string('foo=" bar", "baz "')
3519
 
        self.assertEquals([' bar', 'baz '], self.conf.get('foo'))
 
3702
        conf = self.get_conf('foo=" bar", "baz "')
 
3703
        self.assertEquals([' bar', 'baz '], conf.get('foo'))
3520
3704
 
3521
3705
    def test_get_with_list_converter_stripped_spaces_many_items(self):
3522
3706
        self.register_list_option('foo', None)
3523
 
        self.conf.store._load_from_string('foo= bar ,  baz ')
3524
 
        self.assertEquals(['bar', 'baz'], self.conf.get('foo'))
 
3707
        conf = self.get_conf('foo= bar ,  baz ')
 
3708
        self.assertEquals(['bar', 'baz'], conf.get('foo'))
3525
3709
 
3526
3710
 
3527
3711
class TestIterOptionRefs(tests.TestCase):
3640
3824
list={foo},{bar},{baz}
3641
3825
''')
3642
3826
        self.registry.register(
3643
 
            config.Option('list', from_unicode=config.list_from_store))
 
3827
            config.ListOption('list'))
3644
3828
        self.assertEquals(['start', 'middle', 'end'],
3645
3829
                           self.conf.get('list', expand=True))
3646
3830
 
3652
3836
list={foo}
3653
3837
''')
3654
3838
        self.registry.register(
3655
 
            config.Option('list', from_unicode=config.list_from_store))
 
3839
            config.ListOption('list'))
3656
3840
        self.assertEquals(['start', 'middle', 'end'],
3657
3841
                           self.conf.get('list', expand=True))
3658
3842
 
3667
3851
''')
3668
3852
        # What matters is what the registration says, the conversion happens
3669
3853
        # only after all expansions have been performed
3670
 
        self.registry.register(
3671
 
            config.Option('hidden', from_unicode=config.list_from_store))
 
3854
        self.registry.register(config.ListOption('hidden'))
3672
3855
        self.assertEquals(['bin', 'go'],
3673
3856
                          self.conf.get('hidden', expand=True))
3674
3857
 
4559
4742
class EmailOptionTests(tests.TestCase):
4560
4743
 
4561
4744
    def test_default_email_uses_BZR_EMAIL(self):
 
4745
        conf = config.MemoryStack('email=jelmer@debian.org')
4562
4746
        # BZR_EMAIL takes precedence over EMAIL
4563
4747
        self.overrideEnv('BZR_EMAIL', 'jelmer@samba.org')
4564
4748
        self.overrideEnv('EMAIL', 'jelmer@apache.org')
4565
 
        self.assertEquals('jelmer@samba.org', config.default_email())
 
4749
        self.assertEquals('jelmer@samba.org', conf.get('email'))
4566
4750
 
4567
4751
    def test_default_email_uses_EMAIL(self):
 
4752
        conf = config.MemoryStack('')
4568
4753
        self.overrideEnv('BZR_EMAIL', None)
4569
4754
        self.overrideEnv('EMAIL', 'jelmer@apache.org')
4570
 
        self.assertEquals('jelmer@apache.org', config.default_email())
 
4755
        self.assertEquals('jelmer@apache.org', conf.get('email'))
4571
4756
 
4572
4757
    def test_BZR_EMAIL_overrides(self):
 
4758
        conf = config.MemoryStack('email=jelmer@debian.org')
4573
4759
        self.overrideEnv('BZR_EMAIL', 'jelmer@apache.org')
4574
 
        self.assertEquals('jelmer@apache.org',
4575
 
            config.email_from_store('jelmer@debian.org'))
 
4760
        self.assertEquals('jelmer@apache.org', conf.get('email'))
4576
4761
        self.overrideEnv('BZR_EMAIL', None)
4577
4762
        self.overrideEnv('EMAIL', 'jelmer@samba.org')
4578
 
        self.assertEquals('jelmer@debian.org',
4579
 
            config.email_from_store('jelmer@debian.org'))
 
4763
        self.assertEquals('jelmer@debian.org', conf.get('email'))