311
317
with expected_exception:
312
318
config.foo = "bar"
314
def test_opens_using_backend(self):
320
def test_open_uses_backend_as_context_manager(self):
315
321
config_file = self.make_file()
316
322
backend = self.patch(ExampleConfiguration, "backend")
317
with backend.open(config_file) as config:
318
backend_ctx = backend.open.return_value
323
with ExampleConfiguration.open(config_file) as config:
324
# The backend was opened using open() too.
325
self.assertThat(backend.open, MockCalledOnceWith(config_file))
319
326
# The object returned from backend.open() has been used as the
320
327
# context manager, providing `config`.
321
self.assertThat(config, Is(backend_ctx.__enter__.return_value))
322
# We're within the context, as expected.
323
self.assertThat(backend_ctx.__exit__, MockNotCalled())
324
# The context has been exited.
328
backend_ctx = backend.open.return_value
329
self.assertThat(config.store, Is(
330
backend_ctx.__enter__.return_value))
331
# We're within the context, as expected.
332
self.assertThat(backend_ctx.__exit__, MockNotCalled())
333
# The backend context has also been exited.
335
backend_ctx.__exit__,
336
MockCalledOnceWith(None, None, None))
338
def test_open_for_update_uses_backend_as_context_manager(self):
339
config_file = self.make_file()
340
backend = self.patch(ExampleConfiguration, "backend")
341
with ExampleConfiguration.open_for_update(config_file) as config:
342
# The backend was opened using open_for_update() too.
344
backend.open_for_update, MockCalledOnceWith(config_file))
345
# The object returned from backend.open_for_update() has been used
346
# as the context manager, providing `config`.
347
backend_ctx = backend.open_for_update.return_value
348
self.assertThat(config.store, Is(
349
backend_ctx.__enter__.return_value))
350
# We're within the context, as expected.
351
self.assertThat(backend_ctx.__exit__, MockNotCalled())
352
# The backend context has also been exited.
326
354
backend_ctx.__exit__,
327
355
MockCalledOnceWith(None, None, None))
338
366
def make_database_store(self):
339
367
database = sqlite3.connect(":memory:")
340
368
self.addCleanup(database.close)
341
return ConfigurationDatabase(database)
369
return ConfigurationDatabase(database, mutable=True)
343
371
def make_file_store(self):
344
return ConfigurationFile(self.make_file())
372
return ConfigurationFile(self.make_file(), mutable=True)
346
374
def make_config(self):
347
375
store = self.make_store(self)
400
428
def test_adding_configuration_option(self):
401
429
database = sqlite3.connect(":memory:")
402
config = ConfigurationDatabase(database)
430
config = ConfigurationDatabase(database, mutable=True)
403
431
config["alice"] = {"abc": 123}
404
432
self.assertEqual({"alice"}, set(config))
405
433
self.assertEqual({"abc": 123}, config["alice"])
407
435
def test_replacing_configuration_option(self):
408
436
database = sqlite3.connect(":memory:")
409
config = ConfigurationDatabase(database)
437
config = ConfigurationDatabase(database, mutable=True)
410
438
config["alice"] = {"abc": 123}
411
439
config["alice"] = {"def": 456}
412
440
self.assertEqual({"alice"}, set(config))
415
443
def test_getting_configuration_option(self):
416
444
database = sqlite3.connect(":memory:")
417
config = ConfigurationDatabase(database)
445
config = ConfigurationDatabase(database, mutable=True)
418
446
config["alice"] = {"abc": 123}
419
447
self.assertEqual({"abc": 123}, config["alice"])
426
454
def test_removing_configuration_option(self):
427
455
database = sqlite3.connect(":memory:")
428
config = ConfigurationDatabase(database)
456
config = ConfigurationDatabase(database, mutable=True)
429
457
config["alice"] = {"abc": 123}
430
458
del config["alice"]
431
459
self.assertEqual(set(), set(config))
434
462
# ConfigurationDatabase.open() returns a context manager that closes
435
463
# the database on exit.
436
464
config_file = os.path.join(self.make_dir(), "config")
437
config = ConfigurationDatabase.open(config_file)
465
config = ConfigurationDatabase.open_for_update(config_file)
438
466
self.assertIsInstance(config, contextlib.GeneratorContextManager)
439
467
with config as config:
440
468
self.assertIsInstance(config, ConfigurationDatabase)
465
493
config_file = os.path.join(self.make_dir(), "config")
466
494
config_key = factory.make_name("key")
467
495
config_value = factory.make_name("value")
468
with ConfigurationDatabase.open(config_file) as config:
496
with ConfigurationDatabase.open_for_update(config_file) as config:
469
497
config[config_key] = config_value
470
498
with ConfigurationDatabase.open(config_file) as config:
471
499
self.assertEqual(config_value, config[config_key])
477
505
exception_type = factory.make_exception_type()
478
506
# Set a configuration option, then crash.
479
507
with ExpectedException(exception_type):
480
with ConfigurationDatabase.open(config_file) as config:
508
with ConfigurationDatabase.open_for_update(config_file) as config:
481
509
config[config_key] = config_value
482
510
raise exception_type()
483
511
# No value has been saved for `config_key`.
484
512
with ConfigurationDatabase.open(config_file) as config:
485
513
self.assertRaises(KeyError, lambda: config[config_key])
515
def test_as_string(self):
516
database = sqlite3.connect(":memory:")
517
config = ConfigurationDatabase(database)
518
self.assertThat(unicode(config), Equals(
519
"ConfigurationDatabase(main=:memory:)"))
522
class TestConfigurationDatabaseMutability(MAASTestCase):
523
"""Tests for `ConfigurationDatabase` mutability."""
525
def test_immutable(self):
526
database = sqlite3.connect(":memory:")
527
config = ConfigurationDatabase(database, mutable=False)
528
self.assertRaises(ConfigurationImmutable, setitem, config, "alice", 1)
529
self.assertRaises(ConfigurationImmutable, delitem, config, "alice")
531
def test_mutable(self):
532
database = sqlite3.connect(":memory:")
533
config = ConfigurationDatabase(database, mutable=True)
534
config["alice"] = 1234
537
def test_open_yields_immutable_backend(self):
538
config_file = os.path.join(self.make_dir(), "config")
539
config_key = factory.make_name("key")
540
with ConfigurationDatabase.open(config_file) as config:
541
with ExpectedException(ConfigurationImmutable):
542
config[config_key] = factory.make_name("value")
543
with ExpectedException(ConfigurationImmutable):
544
del config[config_key]
546
def test_open_for_update_yields_mutable_backend(self):
547
config_file = os.path.join(self.make_dir(), "config")
548
config_key = factory.make_name("key")
549
with ConfigurationDatabase.open_for_update(config_file) as config:
550
config[config_key] = factory.make_name("value")
551
del config[config_key]
488
554
class TestConfigurationFile(MAASTestCase):
489
555
"""Tests for `ConfigurationFile`."""
496
562
config={}, dirty=False, path=sentinel.filename))
498
564
def test_adding_configuration_option(self):
499
config = ConfigurationFile(sentinel.filename)
565
config = ConfigurationFile(sentinel.filename, mutable=True)
500
566
config["alice"] = {"abc": 123}
501
567
self.assertEqual({"alice"}, set(config))
502
568
self.assertEqual({"abc": 123}, config["alice"])
503
569
self.assertTrue(config.dirty)
505
571
def test_replacing_configuration_option(self):
506
config = ConfigurationFile(sentinel.filename)
572
config = ConfigurationFile(sentinel.filename, mutable=True)
507
573
config["alice"] = {"abc": 123}
508
574
config["alice"] = {"def": 456}
509
575
self.assertEqual({"alice"}, set(config))
511
577
self.assertTrue(config.dirty)
513
579
def test_getting_configuration_option(self):
514
config = ConfigurationFile(sentinel.filename)
580
config = ConfigurationFile(sentinel.filename, mutable=True)
515
581
config["alice"] = {"abc": 123}
516
582
self.assertEqual({"abc": 123}, config["alice"])
520
586
self.assertRaises(KeyError, lambda: config["alice"])
522
588
def test_removing_configuration_option(self):
523
config = ConfigurationFile(sentinel.filename)
589
config = ConfigurationFile(sentinel.filename, mutable=True)
524
590
config["alice"] = {"abc": 123}
525
591
del config["alice"]
526
592
self.assertEqual(set(), set(config))
575
641
config_file = os.path.join(self.make_dir(), "config")
576
642
open(config_file, "wb").close() # touch.
577
643
os.chmod(config_file, 0o644) # u=rw,go=r
578
with ConfigurationFile.open(config_file):
644
with ConfigurationFile.open_for_update(config_file):
579
645
perms = FilePath(config_file).getPermissions()
580
646
self.assertEqual("rw-r--r--", perms.shorthand())
581
647
perms = FilePath(config_file).getPermissions()
587
653
config_file = os.path.join(self.make_dir(), "config")
588
654
open(config_file, "wb").close() # touch.
589
655
os.chmod(config_file, 0o644) # u=rw,go=r
590
with ConfigurationFile.open(config_file) as config:
656
with ConfigurationFile.open_for_update(config_file) as config:
591
657
perms = FilePath(config_file).getPermissions()
592
658
self.assertEqual("rw-r--r--", perms.shorthand())
593
659
config["foobar"] = "I am a modification"
601
667
config_file = os.path.join(self.make_dir(), "config")
602
668
open(config_file, "wb").close() # touch.
603
669
os.chmod(config_file, 0o644) # u=rw,go=r
604
with ConfigurationFile.open(config_file) as config:
670
with ConfigurationFile.open_for_update(config_file) as config:
605
671
config["foobar"] = "I am a modification"
606
672
os.unlink(config_file)
607
673
perms = FilePath(config_file).getPermissions()
613
679
config_file = os.path.join(self.make_dir(), "config")
614
680
config_key = factory.make_name("key")
615
681
config_value = factory.make_name("value")
616
with ConfigurationFile.open(config_file) as config:
682
with ConfigurationFile.open_for_update(config_file) as config:
617
683
config[config_key] = config_value
618
684
self.assertEqual({config_key: config_value}, config.config)
619
685
self.assertTrue(config.dirty)
627
693
exception_type = factory.make_exception_type()
628
694
# Set a configuration option, then crash.
629
695
with ExpectedException(exception_type):
630
with ConfigurationFile.open(config_file) as config:
696
with ConfigurationFile.open_for_update(config_file) as config:
631
697
config[config_key] = config_value
632
698
raise exception_type()
633
699
# No value has been saved for `config_key`.
638
704
config_file = os.path.join(self.make_dir(), "config")
639
705
config_lock = RunLock(config_file)
640
706
self.assertFalse(config_lock.is_locked())
641
with ConfigurationFile.open(config_file):
707
with ConfigurationFile.open_for_update(config_file):
642
708
self.assertTrue(config_lock.is_locked())
643
709
self.assertFalse(config_lock.is_locked())
711
def test_as_string(self):
712
config_file = os.path.join(self.make_dir(), "config")
713
config = ConfigurationFile(config_file)
714
self.assertThat(unicode(config), Equals(
715
"ConfigurationFile(%r)" % config_file))
718
class TestConfigurationFileMutability(MAASTestCase):
719
"""Tests for `ConfigurationFile` mutability."""
721
def test_immutable(self):
722
config_file = os.path.join(self.make_dir(), "config")
723
config = ConfigurationFile(config_file, mutable=False)
724
self.assertRaises(ConfigurationImmutable, setitem, config, "alice", 1)
725
self.assertRaises(ConfigurationImmutable, delitem, config, "alice")
727
def test_mutable(self):
728
config_file = os.path.join(self.make_dir(), "config")
729
config = ConfigurationFile(config_file, mutable=True)
730
config["alice"] = 1234
733
def test_open_yields_immutable_backend(self):
734
config_file = os.path.join(self.make_dir(), "config")
735
config_key = factory.make_name("key")
736
with ConfigurationFile.open(config_file) as config:
737
with ExpectedException(ConfigurationImmutable):
738
config[config_key] = factory.make_name("value")
739
with ExpectedException(ConfigurationImmutable):
740
del config[config_key]
742
def test_open_for_update_yields_mutable_backend(self):
743
config_file = os.path.join(self.make_dir(), "config")
744
config_key = factory.make_name("key")
745
with ConfigurationFile.open_for_update(config_file) as config:
746
config[config_key] = factory.make_name("value")
747
del config[config_key]
646
750
class TestClusterConfiguration(MAASTestCase):
647
751
"""Tests for `ClusterConfiguration`."""