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()
test_string_round_trips()
test_int_round_trips()
test_bool_true_round_trips()
test_bool_false_round_trips()
test_list_round_trips()
test_dict_round_trips()
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
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
ConfigSectionToleranceTests
Bases: TestCase
Legacy/bare values decode without raising, per the documented fallback.
Methods:
setUp()
test_legacy_single_quoted_string_is_decoded()
test_legacy_single_quoted_list_is_decoded()
test_bare_windows_path_returned_as_is()
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
test_bare_bool_token_decodes_regardless_of_case()
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
ConfigSectionMissingVsEmptyTests
Bases: TestCase
Only a missing key falls back to the default; a stored value never does.
Methods:
setUp()
test_missing_key_returns_default()
test_missing_key_returns_supplied_default()
test_stored_empty_string_is_a_real_value_not_default()
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
ConfigSectionMutationTests
Bases: TestCase
has_option / remove_option reflect the backing store.
Methods:
setUp()
test_has_option_true_after_set()
test_remove_option_removes_the_key()
ConfigSectionsTests
Bases: TestCase
The section container resolves and manages sections on the default config.
Methods:
setUp()
test_get_section_round_trips_a_value()
test_has_section_reflects_stored_keys()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
test_added_section_is_visible_before_any_option_is_set()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
test_remove_section_clears_all_its_keys()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
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
test_remove_section_keeps_similarly_named_siblings()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
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
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
test_attribute_access_returns_an_existing_section()
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
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()
test_section_writes_reach_the_store_that_replaced_the_original()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
test_section_reads_see_the_replacing_store()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
test_subsection_also_follows_the_reload()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
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
ConfigSubsectionTests
Bases: TestCase
A subsection is discoverable as soon as it is added.
Methods:
setUp()
test_added_subsection_is_visible_before_any_option_is_set()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
test_added_subsection_round_trips_a_value()
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
test_bool_round_trips_as_bool()
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
test_list_round_trips()
test_missing_key_returns_default_value()
test_stored_empty_string_is_not_the_default()
test_matches_config_section_decoding()
test_typed_property_is_written_through_the_service()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
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
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
test_typed_property_assignment_is_skipped()
test_set_option_stores_nothing()
test_unknown_attribute_assignment_stores_nothing()
test_remove_option_reports_no_removal()
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
tearDown()
test_missing_key_returns_the_supplied_default()
test_stored_empty_value_is_not_the_default()
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
test_set_raw_value_creates_the_section()
test_remove_missing_option_returns_false()
test_config_section_round_trips_against_the_real_backend()
Source code in pyrevitlib/pyrevit/unittests/test_config_roundtrip.py
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
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.