3000
3000
self.assertLength(1, calls)
3001
3001
self.assertEquals((store,), calls[0])
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())
3010
def test_remove_mark_dirty(self):
3011
stack = config.MemoryStack('foo=bar')
3012
self.assertLength(0, stack.store.dirty_sections)
3014
self.assertLength(1, stack.store.dirty_sections)
3015
self.assertTrue(stack.store._need_saving())
3018
class TestStoreSaveChanges(tests.TestCaseWithTransport):
3019
"""Tests that config changes are kept in memory and saved on-demand."""
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')
3030
self.warnings.append(args[0] % args[1:])
3031
self.overrideAttr(trace, 'warning', warning)
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)
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)
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))
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')
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)
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')
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.')
3079
def test_concurrent_deletion(self):
3080
self.st1._load_from_string('foo=bar')
3082
s1 = self.get_stack(self.st1)
3083
s2 = self.get_stack(self.st2)
3086
s1.store.save_changes()
3088
self.assertLength(0, self.warnings)
3089
s2.store.save_changes()
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.')
3004
3098
class TestQuotingIniFileStore(tests.TestCaseWithTransport):