ADR: Preferences Architecture and Testable Store Injection¶
Status¶
Proposed (Date: 2026-07-12)
Pull Request: #2110
Context¶
org.omegat.util.Preferences is a static facade over one mutable store, built
lazily once per JVM behind a didInit guard. That is correct for a launch, but
in tests it means the first test to touch preferences fixes the store for every
test that follows, so mutated values leak and cause order-dependent failures.
The concrete symptom was EditorControllerTest asserting offsets that depend on
MARK_PARA_DELIMITATIONS, which the console tests leave as false (fragile
assertions from #1326). There
was no ADR or specification for the mechanism, and the first fix was an ad hoc
resetPreferencesForTest() back door into private state.
Decision¶
Give Preferences one explicit installation point for its store and let tests
inject an isolated store, mirroring org.omegat.core.data.CoreState
(ADR 2025002) and RuntimePreferenceStore.
Specification: static accessors delegate to an installed IPreferences.
PreferencesImpl holds values in memory over an IPrefsPersistence backing
(PreferencesXML on disk in production). init() builds the production store
against the config dir and is idempotent via didInit.
Implementation:
@VisibleForTesting static synchronized setPreferences(IPreferences)becomes the sole assignment point;init()routes through it. Installing setsdidInit, so a laterinit()keeps the injected store.TestPreferencesis an empty, never-persisted in-memory store;TestPreferencesInitializerinstalls a fresh one per test.The
resetPreferencesForTest()back door is removed.
Production behavior is unchanged.
Consequences¶
Tests become isolated and order-independent, and the full suite passes.
Preferences follow the same testable-singleton seam as CoreState. Tests that
need on-disk persistence still construct PreferencesImpl/PreferencesXML
directly. Preferences stays a static facade; full dependency injection is out
of scope.
Alternatives Considered¶
Pin the one preference in the failing test — patches one case, leaves the leak.
resetPreferencesForTest()back door — fixes the leak but is a bespoke private-state mutator, off-pattern.Full dependency injection — cleanest long-term, but a large cross-cutting change.
References¶
org.omegat.core.data.CoreState,org.omegat.core.data.RuntimePreferenceStoreOrigin of the fragile offset assertions: #1326