Skip to content

test_config_roundtrip

Round-trip tests for the pyRevit Python config bridge.

Exercises the encode/decode logic in :mod:pyrevit.coreutils.configparser (ConfigSection / ConfigSections), which every script touches through script.get_config(), and the matching surface on userconfig._SectionCompatWrapper, which extensions reach through user_config.core. Covers the Python side of the bridge: the JSON round-trip, the legacy single-quote fallback, and the missing-key-vs-stored-empty-string distinction.

A dict-backed fake IConfiguration models the C# contract (raw strings in, None only for a missing key) so most of these tests are hermetic: they never touch the user's real config file, disk, or install-scope detection, and run identically under IronPython 2/3 and CPython.

RealBackendContractTests is the exception. It runs the same assertions against the real INI backend over a temp file, so the fake cannot drift from the contract it claims to model without something failing. It skips when the labs assemblies are not loadable.

Run from Revit via the pyRevit DevTools "Config Module Tests" button.

Classes

ConfigSectionRoundTripTests

Bases: TestCase

Typed values survive a set_option/get_option round-trip unchanged.

Methods:

setUp()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def setUp(self):
    self.config = _FakeConfiguration()
    self.section = ConfigSection("core", self.config)
test_string_round_trips()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_string_round_trips(self):
    self.assertEqual("hello", self._roundtrip("hello"))
test_int_round_trips()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_int_round_trips(self):
    self.assertEqual(10, self._roundtrip(10))
test_bool_true_round_trips()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_bool_true_round_trips(self):
    self.assertIs(True, self._roundtrip(True))
test_bool_false_round_trips()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_bool_false_round_trips(self):
    self.assertIs(False, self._roundtrip(False))
test_list_round_trips()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_list_round_trips(self):
    value = [r"C:\Tools\ext1", r"D:\ext2"]
    self.assertEqual(value, self._roundtrip(value))
test_dict_round_trips()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_dict_round_trips(self):
    value = {"master": r"C:\Users\u\pyRevit-Master"}
    self.assertEqual(value, self._roundtrip(value))
test_unicode_round_trips()

ensure_ascii=False must preserve non-ASCII, not \uXXXX-escape or mangle it (guards against the known IPY3 stdout mojibake class).

Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_unicode_round_trips(self):
    """ensure_ascii=False must preserve non-ASCII, not \\uXXXX-escape or mangle it (guards against the known IPY3 stdout mojibake class)."""
    value = "café"
    self.assertEqual(value, self._roundtrip(value))
test_string_is_stored_as_canonical_compact_json()

Matches the C# symmetric-JSON encoding: quoted, no incidental spaces.

Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_string_is_stored_as_canonical_compact_json(self):
    """Matches the C# symmetric-JSON encoding: quoted, no incidental spaces."""
    self.section.set_option("key", "hello")
    self.assertEqual('"hello"', self.config.GetRawValueOrDefault("core", "key"))
test_dict_is_stored_as_compact_json()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_dict_is_stored_as_compact_json(self):
    self.section.set_option("key", {"a": 1})
    self.assertEqual('{"a":1}', self.config.GetRawValueOrDefault("core", "key"))

ConfigSectionToleranceTests

Bases: TestCase

Legacy/bare values decode without raising, per the documented fallback.

Methods:

setUp()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def setUp(self):
    self.config = _FakeConfiguration()
    self.section = ConfigSection("core", self.config)
test_legacy_single_quoted_string_is_decoded()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_legacy_single_quoted_string_is_decoded(self):
    self.config.SetRawValue("core", "key", "'hello'")
    self.assertEqual("hello", self.section.get_option("key"))
test_legacy_single_quoted_list_is_decoded()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_legacy_single_quoted_list_is_decoded(self):
    self.config.SetRawValue("core", "key", "['a','b']")
    self.assertEqual(["a", "b"], self.section.get_option("key"))
test_bare_windows_path_returned_as_is()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_bare_windows_path_returned_as_is(self):
    raw = r"C:\Tools\ext"
    self.config.SetRawValue("core", "key", raw)
    self.assertEqual(raw, self.section.get_option("key"))
test_bare_python_bool_token_decodes_to_a_bool()

The C# readers parse these spellings as bools; returning the raw string would make "False" truthy, so a legacy disabled=False would read as disabled by one reader and enabled by the other.

Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_bare_python_bool_token_decodes_to_a_bool(self):
    """The C# readers parse these spellings as bools; returning the raw string would make "False" truthy, so a legacy disabled=False would read as disabled by one reader and enabled by the other."""
    self.config.SetRawValue("core", "key", "True")
    self.assertIs(True, self.section.get_option("key"))
    self.config.SetRawValue("core", "key", "False")
    self.assertIs(False, self.section.get_option("key"))
test_bare_bool_token_decodes_regardless_of_case()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_bare_bool_token_decodes_regardless_of_case(self):
    self.config.SetRawValue("core", "key", "FALSE")
    self.assertIs(False, self.section.get_option("key"))
test_quoted_bool_word_stays_a_string()

A value that merely spells "True" is stored quoted, keeping the bare-token rule from swallowing real string values.

Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_quoted_bool_word_stays_a_string(self):
    """A value that merely spells "True" is stored quoted, keeping the bare-token rule from swallowing real string values."""
    self.section.set_option("key", "True")
    self.assertEqual("True", self.section.get_option("key"))

ConfigSectionMissingVsEmptyTests

Bases: TestCase

Only a missing key falls back to the default; a stored value never does.

Methods:

setUp()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def setUp(self):
    self.config = _FakeConfiguration()
    self.section = ConfigSection("core", self.config)
test_missing_key_returns_default()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_missing_key_returns_default(self):
    self.assertIsNone(self.section.get_option("absent"))
test_missing_key_returns_supplied_default()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_missing_key_returns_supplied_default(self):
    self.assertEqual(
        "fallback", self.section.get_option("absent", default_value="fallback")
    )
test_stored_empty_string_is_a_real_value_not_default()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_stored_empty_string_is_a_real_value_not_default(self):
    self.section.set_option("key", "")
    self.assertEqual("", self.section.get_option("key", default_value="fallback"))
test_raw_empty_string_is_not_treated_as_missing()

A key present with an empty raw value is real; it must not collapse to the default the way an absent key does.

Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_raw_empty_string_is_not_treated_as_missing(self):
    """A key present with an empty raw value is real; it must not collapse to the default the way an absent key does."""
    self.config.SetRawValue("core", "key", "")
    self.assertEqual("", self.section.get_option("key", default_value="fallback"))

ConfigSectionMutationTests

Bases: TestCase

has_option / remove_option reflect the backing store.

Methods:

setUp()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def setUp(self):
    self.config = _FakeConfiguration()
    self.section = ConfigSection("core", self.config)
test_has_option_true_after_set()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_has_option_true_after_set(self):
    self.section.set_option("key", "value")
    self.assertTrue(self.section.has_option("key"))
test_remove_option_removes_the_key()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_remove_option_removes_the_key(self):
    self.section.set_option("key", "value")
    self.assertTrue(self.section.remove_option("key"))
    self.assertFalse(self.section.has_option("key"))
test_remove_missing_option_returns_false()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_remove_missing_option_returns_false(self):
    self.assertFalse(self.section.remove_option("absent"))

ConfigSectionsTests

Bases: TestCase

The section container resolves and manages sections on the default config.

Methods:

setUp()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def setUp(self):
    self.config = _FakeConfiguration()
    self.service = _FakeConfigurationService(self.config)
    self.sections = ConfigSections(self.service)
test_get_section_round_trips_a_value()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_get_section_round_trips_a_value(self):
    section = self.sections.add_section("mytool")
    section.set_option("enabled", True)
    self.assertIs(True, self.sections.get_section("mytool").get_option("enabled"))
test_has_section_reflects_stored_keys()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_has_section_reflects_stored_keys(self):
    self.sections.add_section("mytool").set_option("enabled", True)
    self.assertTrue(self.sections.has_section("mytool"))
    self.assertFalse(self.sections.has_section("other"))
test_added_section_is_visible_before_any_option_is_set()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_added_section_is_visible_before_any_option_is_set(self):
    self.sections.add_section("mytool")
    self.assertTrue(self.sections.has_section("mytool"))
    self.assertEqual("mytool", self.sections.get_section("mytool").header)
test_remove_section_clears_all_its_keys()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_remove_section_clears_all_its_keys(self):
    section = self.sections.add_section("mytool")
    section.set_option("a", 1)
    section.set_option("b", 2)
    self.sections.remove_section("mytool")
    self.assertFalse(self.sections.has_section("mytool"))
test_remove_section_also_removes_its_subsections()

A left-behind subsection would be inherited by the next install of a tool that believes it is starting from a clean config.

Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_remove_section_also_removes_its_subsections(self):
    """A left-behind subsection would be inherited by the next install of a tool that believes it is starting from a clean config."""
    section = self.sections.add_section("mytool")
    section.add_subsection("sub").set_option("enabled", True)
    self.sections.remove_section("mytool")
    self.assertFalse(self.sections.has_section("mytool.sub"))
test_remove_section_keeps_similarly_named_siblings()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_remove_section_keeps_similarly_named_siblings(self):
    self.sections.add_section("mytool").set_option("a", 1)
    self.sections.add_section("mytoolbox").set_option("b", 2)
    self.sections.remove_section("mytool")
    self.assertTrue(self.sections.has_section("mytoolbox"))
test_remove_section_removes_orphaned_subsections()

has_section is false for a parent that only exists as a dotted prefix, so the cleanup cannot depend on the parent being there.

Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_remove_section_removes_orphaned_subsections(self):
    """has_section is false for a parent that only exists as a dotted prefix, so the cleanup cannot depend on the parent being there."""
    self.sections.add_section("mytool.sub").set_option("enabled", True)
    self.sections.remove_section("mytool")
    self.assertFalse(self.sections.has_section("mytool.sub"))
test_get_missing_section_raises()

A missing section must not resolve to an empty section object, so callers can tell "absent" from "present but empty".

Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_get_missing_section_raises(self):
    """A missing section must not resolve to an empty section object, so callers can tell "absent" from "present but empty"."""
    self.assertRaises(AttributeError, self.sections.get_section, "absent")
test_attribute_access_returns_an_existing_section()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_attribute_access_returns_an_existing_section(self):
    self.sections.add_section("mytool").set_option("x", "y")
    self.assertEqual("y", self.sections.mytool.get_option("x"))
test_attribute_access_to_missing_section_raises()

getattr-with-default and hasattr rely on getattr raising.

Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_attribute_access_to_missing_section_raises(self):
    """getattr-with-default and hasattr rely on __getattr__ raising."""
    self.assertRaises(AttributeError, getattr, self.sections, "absent")
    self.assertEqual("fallback", getattr(self.sections, "absent", "fallback"))
    self.assertFalse(hasattr(self.sections, "absent"))

ConfigSectionReloadTests

Bases: TestCase

A section keeps writing to the live store after a reload swaps it.

script.get_config() hands a section to a script, which may hold it across an unrelated user_config.reload(). If the section stayed bound to the replaced store, its edits would be flushed from the wrong object and silently lost.

Methods:

setUp()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def setUp(self):
    self.config = _FakeConfiguration()
    self.service = _FakeConfigurationService(self.config)
    self.sections = ConfigSections(lambda: self.service)
test_section_writes_reach_the_store_that_replaced_the_original()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_section_writes_reach_the_store_that_replaced_the_original(self):
    section = self.sections.add_section("mytool")
    self._reload()
    section.set_option("enabled", True)
    self.assertEqual("true", self.config.GetRawValueOrDefault("mytool", "enabled"))
test_section_reads_see_the_replacing_store()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_section_reads_see_the_replacing_store(self):
    section = self.sections.add_section("mytool")
    section.set_option("enabled", True)
    self._reload()
    self.config.SetRawValue("mytool", "enabled", "false")
    self.assertIs(False, section.get_option("enabled"))
test_subsection_also_follows_the_reload()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_subsection_also_follows_the_reload(self):
    subsection = self.sections.add_section("mytool").add_subsection("sub")
    self._reload()
    subsection.set_option("enabled", True)
    self.assertEqual(
        "true", self.config.GetRawValueOrDefault("mytool.sub", "enabled")
    )
test_direct_configuration_still_supported()

ConfigSection must still accept a plain configuration object, which is what the C# service hands it outside the reload-aware path.

Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_direct_configuration_still_supported(self):
    """ConfigSection must still accept a plain configuration object, which is what the C# service hands it outside the reload-aware path."""
    config = _FakeConfiguration()
    section = ConfigSection("core", config)
    section.set_option("key", "value")
    self.assertEqual("value", section.get_option("key"))

ConfigSubsectionTests

Bases: TestCase

A subsection is discoverable as soon as it is added.

Methods:

setUp()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def setUp(self):
    self.config = _FakeConfiguration()
    self.section = ConfigSection("core", self.config)
test_added_subsection_is_visible_before_any_option_is_set()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_added_subsection_is_visible_before_any_option_is_set(self):
    self.section.add_subsection("sub")
    self.assertTrue(self.section.has_subsection("sub"))
    self.assertEqual(["core.sub"], [str(s) for s in self.section.get_subsections()])
test_added_subsection_round_trips_a_value()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_added_subsection_round_trips_a_value(self):
    self.section.add_subsection("sub").set_option("enabled", True)
    self.assertIs(True, self.section.get_subsection("sub").get_option("enabled"))

SectionCompatWrapperTests

Bases: TestCase

The typed-section wrapper decodes like ConfigSection does.

Extensions hold whichever of the two a given entry point handed them, so a value stored through one must read back identically through the other.

Methods:

setUp()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def setUp(self):
    self.config = _FakeConfiguration()
    self.service = _FakeConfigurationService(self.config)
    self.wrapper = _SectionCompatWrapper(
        "core", _FakeTypedSection(), self.config, self.service
    )
    self.section = ConfigSection("core", self.config)
test_bool_round_trips_as_bool()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_bool_round_trips_as_bool(self):
    self.wrapper.set_option("key", False)
    self.assertIs(False, self.wrapper.get_option("key"))
test_legacy_python_bool_decodes_to_a_bool()

Both bridges share one decoder, so neither can drift into reporting a legacy False as a truthy string.

Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_legacy_python_bool_decodes_to_a_bool(self):
    """Both bridges share one decoder, so neither can drift into reporting a legacy False as a truthy string."""
    self.config.SetRawValue("core", "key", "False")
    self.assertIs(False, self.wrapper.get_option("key"))
test_list_round_trips()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_list_round_trips(self):
    self.wrapper.set_option("key", ["a", "b"])
    self.assertEqual(["a", "b"], self.wrapper.get_option("key"))
test_missing_key_returns_default_value()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_missing_key_returns_default_value(self):
    self.assertEqual(
        "fallback", self.wrapper.get_option("absent", default_value="fallback")
    )
test_stored_empty_string_is_not_the_default()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_stored_empty_string_is_not_the_default(self):
    self.wrapper.set_option("key", "")
    self.assertEqual("", self.wrapper.get_option("key", default_value="fallback"))
test_matches_config_section_decoding()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_matches_config_section_decoding(self):
    self.section.set_option("key", False)
    self.assertIs(self.section.get_option("key"), self.wrapper.get_option("key"))
test_typed_property_is_written_through_the_service()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_typed_property_is_written_through_the_service(self):
    self.wrapper.RocketMode = True
    self.assertEqual(1, len(self.service.applied))
    pending = self.service.applied[0]
    self.assertIs(True, pending.RocketMode)
test_typed_property_assigned_none_is_ignored()

None reaches neither the store nor the snapshot the service caches process-wide; remove_option is how a stored key gets cleared.

Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_typed_property_assigned_none_is_ignored(self):
    """None reaches neither the store nor the snapshot the service caches process-wide; remove_option is how a stored key gets cleared."""
    self.wrapper.RocketMode = None
    self.assertEqual([], self.service.applied)

SectionCompatWrapperReadOnlyTests

Bases: TestCase

An admin-locked config drops writes instead of reporting a false success.

save_changes() skips the flush for a read-only config, so a write accepted here would be reported to the caller and then silently lost.

Methods:

setUp()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def setUp(self):
    self.config = _FakeConfiguration()
    self.service = _FakeConfigurationService(self.config, read_only=True)
    self.wrapper = _SectionCompatWrapper(
        "core", _FakeTypedSection(), self.config, self.service
    )
test_typed_property_assignment_is_skipped()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_typed_property_assignment_is_skipped(self):
    self.wrapper.RocketMode = True
    self.assertEqual([], self.service.applied)
test_set_option_stores_nothing()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_set_option_stores_nothing(self):
    self.wrapper.set_option("key", "value")
    self.assertIsNone(self.wrapper.get_option("key"))
test_unknown_attribute_assignment_stores_nothing()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_unknown_attribute_assignment_stores_nothing(self):
    self.wrapper.notaschemakey = "value"
    self.assertIsNone(self.wrapper.get_option("notaschemakey"))
test_remove_option_reports_no_removal()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_remove_option_reports_no_removal(self):
    self.config.SetRawValue("core", "key", '"value"')
    self.assertIs(False, self.wrapper.remove_option("key"))
    self.assertEqual("value", self.wrapper.get_option("key"))

RealBackendContractTests

Bases: TestCase

Pins the IConfiguration behaviors that _FakeConfiguration models.

The fake is hand-written, so nothing otherwise stops it from drifting from the real backend while every test above keeps passing. Each case here corresponds to a branch the Python decode ladder depends on.

Methods:

setUp()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def setUp(self):
    if _INI_BACKEND is None:
        self.skipTest("pyRevitLabs.Configurations.Ini is not loadable")
    handle, self.path = tempfile.mkstemp(suffix=".ini")
    os.close(handle)
    self.config = _INI_BACKEND.Create(self.path)
tearDown()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def tearDown(self):
    try:
        os.remove(self.path)
    except OSError:
        pass
test_missing_key_returns_the_supplied_default()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_missing_key_returns_the_supplied_default(self):
    self.assertEqual(
        "fallback", self.config.GetRawValueOrDefault("core", "absent", "fallback")
    )
test_stored_empty_value_is_not_the_default()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_stored_empty_value_is_not_the_default(self):
    self.config.SetRawValue("core", "key", "")
    self.assertEqual(
        "", self.config.GetRawValueOrDefault("core", "key", "fallback")
    )
test_raw_value_round_trips_verbatim()

Python owns its own encoding, so the backend must store the text as handed over rather than re-encoding it.

Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_raw_value_round_trips_verbatim(self):
    """Python owns its own encoding, so the backend must store the text as handed over rather than re-encoding it."""
    raw = '["C:\\\\Tools\\\\ext1"]'
    self.config.SetRawValue("core", "key", raw)
    self.assertEqual(raw, self.config.GetRawValueOrDefault("core", "key"))
test_set_raw_value_creates_the_section()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_set_raw_value_creates_the_section(self):
    self.assertFalse(self.config.HasSection("mytool"))
    self.config.SetRawValue("mytool", "key", "1")
    self.assertTrue(self.config.HasSection("mytool"))
test_remove_missing_option_returns_false()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_remove_missing_option_returns_false(self):
    self.assertFalse(self.config.RemoveOption("core", "absent"))
test_config_section_round_trips_against_the_real_backend()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_config_section_round_trips_against_the_real_backend(self):
    section = ConfigSection("core", self.config)
    for value in (10, "hello", ["a", "b"], {"k": "v"}):
        section.set_option("key", value)
        self.assertEqual(value, section.get_option("key"))
test_bools_stay_bools_against_the_real_backend()

AssertIs rather than assertEqual: True == 1, so an equality check would accept a decode that handed back an int.

Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_bools_stay_bools_against_the_real_backend(self):
    """AssertIs rather than assertEqual: True == 1, so an equality check would accept a decode that handed back an int."""
    section = ConfigSection("core", self.config)
    section.set_option("key", True)
    self.assertIs(True, section.get_option("key"))
    section.set_option("key", False)
    self.assertIs(False, section.get_option("key"))

StandaloneConfigFileTests

Bases: TestCase

Covers open_config_file, the entry point for a tool's own ini file.

Exercises the full chain (labs assemblies, builder, ConfigSections) rather than a fake, because the point of the helper is that it hands back sections backed by a real file that survives being closed and reopened.

Methods:

setUp()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def setUp(self):
    if _INI_BACKEND is None:
        self.skipTest("pyRevitLabs.Configurations.Ini is not loadable")
    handle, self.path = tempfile.mkstemp(suffix=".ini")
    os.close(handle)
tearDown()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def tearDown(self):
    try:
        os.remove(self.path)
    except OSError:
        pass
test_written_options_survive_a_reopen()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_written_options_survive_a_reopen(self):
    from pyrevit.coreutils.configparser import open_config_file

    sections = open_config_file(self.path)
    section = sections.add_section("mytool")
    section.set_option("count", 3)
    section.set_option("name", "legend")
    sections.save()

    reopened = open_config_file(self.path)
    self.assertTrue(reopened.has_section("mytool"))
    restored = reopened.get_section("mytool")
    self.assertEqual(3, restored.get_option("count"))
    self.assertEqual("legend", restored.get_option("name"))
test_missing_section_raises_attribute_error()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_missing_section_raises_attribute_error(self):
    from pyrevit.coreutils.configparser import open_config_file

    sections = open_config_file(self.path)
    with self.assertRaises(AttributeError):
        sections.get_section("absent")
test_unsaved_edits_do_not_reach_the_file()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
def test_unsaved_edits_do_not_reach_the_file(self):
    from pyrevit.coreutils.configparser import open_config_file

    sections = open_config_file(self.path)
    sections.add_section("mytool").set_option("count", 3)

    self.assertFalse(open_config_file(self.path).has_section("mytool"))