def test_load_empty_file_results_in_empty_config(self): config_file = os.path.join(self.make_dir(), "config") with open(config_file, "w"): pass # Write nothing to the file. config = ConfigurationFile(config_file) config.load() self.assertItemsEqual(set(config), set())
def test_opened_configuration_file_saves_on_exit(self): # ConfigurationFile.open() returns a context manager that will save an # updated configuration on a clean exit. config_file = os.path.join(self.make_dir(), "config") config_key = factory.make_name("key") config_value = factory.make_name("value") with ConfigurationFile.open_for_update(config_file) as config: config[config_key] = config_value self.assertEqual({config_key: config_value}, config.config) self.assertTrue(config.dirty) with ConfigurationFile.open(config_file) as config: self.assertEqual(config_value, config[config_key])
def test_opened_configuration_file_does_not_save_on_unclean_exit(self): config_file = os.path.join(self.make_dir(), "config") config_key = factory.make_name("key") config_value = factory.make_name("value") exception_type = factory.make_exception_type() # Set a configuration option, then crash. with ExpectedException(exception_type): with ConfigurationFile.open_for_update(config_file) as config: config[config_key] = config_value raise exception_type() # No value has been saved for `config_key`. with ConfigurationFile.open(config_file) as config: self.assertRaises(KeyError, lambda: config[config_key])
def test_open_takes_exclusive_lock(self): config_file = os.path.join(self.make_dir(), "config") config_lock = RunLock(config_file) self.assertFalse(config_lock.is_locked()) with ConfigurationFile.open_for_update(config_file): self.assertTrue(config_lock.is_locked()) self.assertFalse(config_lock.is_locked())
def test_replacing_configuration_option(self): config = ConfigurationFile(sentinel.filename, mutable=True) config["alice"] = {"abc": 123} config["alice"] = {"def": 456} self.assertEqual({"alice"}, set(config)) self.assertEqual({"def": 456}, config["alice"]) self.assertTrue(config.dirty)
def test_open_permissions_new_database(self): # ConfigurationFile.open() applies restrictive file permissions to # newly created configuration databases. config_file = os.path.join(self.make_dir(), "config") with ConfigurationFile.open(config_file): perms = FilePath(config_file).getPermissions() self.assertEqual("rw-r-----", perms.shorthand())
def test_configuration_pristine(self): # A pristine configuration has no entries. config = ConfigurationFile(sentinel.filename) self.assertThat( config, MatchesStructure.byEquality(config={}, dirty=False, path=sentinel.filename))
def test_open_yields_immutable_backend(self): config_file = os.path.join(self.make_dir(), "config") config_key = factory.make_name("key") with ConfigurationFile.open(config_file) as config: with ExpectedException(ConfigurationImmutable): config[config_key] = factory.make_name("value") with ExpectedException(ConfigurationImmutable): del config[config_key]
def test_load_file_with_non_mapping_crashes(self): config_file = os.path.join(self.make_dir(), "config") with open(config_file, "w") as fd: yaml.safe_dump([1, 2, 3], stream=fd) config = ConfigurationFile(config_file) error = self.assertRaises(ValueError, config.load) self.assertDocTestMatches( "Configuration in /.../config is not a mapping: [1, 2, 3]", str(error))
def test_open_and_close(self): # ConfigurationFile.open() returns a context manager. config_file = os.path.join(self.make_dir(), "config") config_ctx = ConfigurationFile.open(config_file) self.assertIsInstance(config_ctx, contextlib._GeneratorContextManager) with config_ctx as config: self.assertIsInstance(config, ConfigurationFile) self.assertThat(config_file, FileExists()) self.assertEqual({}, config.config) self.assertFalse(config.dirty) self.assertThat(config_file, FileContains(""))
def test_unmodified_database_retains_permissions(self): # ConfigurationFile.open() leaves the file permissions of existing # configuration databases if they're not modified. config_file = os.path.join(self.make_dir(), "config") open(config_file, "wb").close() # touch. os.chmod(config_file, 0o644) # u=rw,go=r with ConfigurationFile.open_for_update(config_file): perms = FilePath(config_file).getPermissions() self.assertEqual("rw-r--r--", perms.shorthand()) perms = FilePath(config_file).getPermissions() self.assertEqual("rw-r--r--", perms.shorthand())
def test_modified_database_uses_safe_permissions_if_file_missing(self): # ConfigurationFile.open() uses a sensible u=rw,g=r file mode when # saving if the database file has been inexplicably removed. This is # the same mode as used when opening a new database. config_file = os.path.join(self.make_dir(), "config") open(config_file, "wb").close() # touch. os.chmod(config_file, 0o644) # u=rw,go=r with ConfigurationFile.open_for_update(config_file) as config: config["foobar"] = "I am a modification" os.unlink(config_file) perms = FilePath(config_file).getPermissions() self.assertEqual("rw-r-----", perms.shorthand())
def test_mutable(self): config_file = os.path.join(self.make_dir(), "config") config = ConfigurationFile(config_file, mutable=True) config["alice"] = 1234 del config["alice"]
def test_immutable(self): config_file = os.path.join(self.make_dir(), "config") config = ConfigurationFile(config_file, mutable=False) self.assertRaises(ConfigurationImmutable, setitem, config, "alice", 1) self.assertRaises(ConfigurationImmutable, delitem, config, "alice")
def test_as_string(self): config_file = os.path.join(self.make_dir(), "config") config = ConfigurationFile(config_file) self.assertThat(str(config), Equals("ConfigurationFile(%r)" % config_file))
def test_getting_configuration_option(self): config = ConfigurationFile(sentinel.filename, mutable=True) config["alice"] = {"abc": 123} self.assertEqual({"abc": 123}, config["alice"])
def test_getting_non_existent_configuration_option(self): config = ConfigurationFile(sentinel.filename) self.assertRaises(KeyError, lambda: config["alice"])
def test_removing_configuration_option(self): config = ConfigurationFile(sentinel.filename, mutable=True) config["alice"] = {"abc": 123} del config["alice"] self.assertEqual(set(), set(config)) self.assertTrue(config.dirty)
def test_load_non_existent_file_crashes(self): config_file = os.path.join(self.make_dir(), "config") config = ConfigurationFile(config_file) self.assertRaises(IOError, config.load)
def test_open_for_update_yields_mutable_backend(self): config_file = os.path.join(self.make_dir(), "config") config_key = factory.make_name("key") with ConfigurationFile.open_for_update(config_file) as config: config[config_key] = factory.make_name("value") del config[config_key]
def make_file_store(self): return ConfigurationFile(self.make_file(), mutable=True)