Skip to content

Commit 177ebe0

Browse files
committed
test(flagd): withhold @reinitialization, and settle @Stale by evidence
Two declarations examined rather than asserted, one of which retracts a mistake. @reinitialization is withheld, and no KnownDeviation accompanies it. shutdown() sets the sync resources' isShutDown flag and never clears isInitialized (FlagdProvider.java:136-155, FlagdProviderSyncResources.java:27-28, 112-115), so a later initialize() returns at its first check without rebuilding the resolver, the gRPC channel it shutdownNow()'d, the retry scheduler it terminated or the final errorExecutor it tore down (FlagdProvider.java:121-125). A shut-down flagd provider is terminally shut down. That is permitted. Requirement 2.5.2 says a provider SHOULD revert to its uninitialized state after shutdown, and its supporting text says "some providers MAY allow reinitialization from this state". Reuse is an option, not an obligation, and declining it is one of the choices the requirement offers. An earlier version of this file recorded it as an untracked KnownDeviation against @lifecycle, which was wrong twice over: the scenario was mandatory only because the spec's assets had not yet gated it, and the entry asserted a defect against a provider behaving inside the requirement. Withholding the tag is the whole of what is owed; the one scenario it gates is now reported as skipped with that reason instead of failing in RPC mode. The lesson is more useful than the correction. Nothing had checked whether 2.5.2 requires reuse before the failure was written up as a defect -- the scenario failed, so a deviation was recorded. Find the numbered requirement first. This is the third rule in the suite found asserted more strongly than the spec states it. @lifecycle stays declared. flagd reaches its backend during initialisation in both modes, so the remaining lifecycle scenarios assert something real, and Java declaring it is what made the cross-language divergence visible in the first place -- Go and JavaScript withhold it and are being changed to match. @Stale is declared for both resolvers and that is correct. PROVIDER_STALE is emitted from FlagdProvider.onError (FlagdProvider.java:258-264), which the shared onProviderEvent switch reaches on PROVIDER_ERROR from either resolver (FlagdProvider.java:197, 236), so the emit is in the provider layer rather than a transport -- and the scenario passes in RPC mode as well as in-process. Go's flagd provider withholds the tag for RPC; on this evidence that is a difference between the implementations, not a property of the transport. Signed-off-by: Simon Schrottner <simon.schrottner@flagsmith.com>
1 parent d83aea8 commit 177ebe0

1 file changed

Lines changed: 35 additions & 3 deletions

File tree

providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/e2e/AbstractFlagdTckTest.java

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,9 @@ public FeatureProvider createUnavailableProvider() {
109109
/**
110110
* {@inheritDoc}
111111
*
112-
* <p>Everything declarable except {@link Capability#NUMERIC_COERCION}. Evaluating
112+
* <p>Everything declarable except {@link Capability#NUMERIC_COERCION} and
113+
* {@link Capability#REINITIALIZATION}. The two omissions are different in kind: the first is a
114+
* defect and is declared as one below, the second is a choice the specification offers. Evaluating
113115
* {@code float-flag} (0.5) through the integer API returns {@code 0} with <em>no</em> error code
114116
* rather than {@code TYPE_MISMATCH} with the code default — the value is silently truncated.
115117
* Coercion as such is permitted, and the capability says so: the rule is that a lossless
@@ -122,7 +124,37 @@ public FeatureProvider createUnavailableProvider() {
122124
*
123125
* <p>That includes {@link Capability#LIFECYCLE}, and legitimately so: flagd reaches its backend
124126
* during initialisation in both modes — an RPC round trip, or a full ruleset sync — so the
125-
* lifecycle scenarios assert something real here rather than passing vacuously.
127+
* lifecycle scenarios assert something real here rather than passing vacuously. Worth stating
128+
* because the Go and JavaScript flagd providers withhold it; Java declaring it is what made that
129+
* divergence visible, and the other two are being changed to match rather than the reverse.
130+
*
131+
* <p><strong>{@link Capability#REINITIALIZATION} is withheld, and that is a fact about the
132+
* provider rather than a defect in it.</strong> {@code shutdown()} sets the sync resources' own
133+
* {@code isShutDown} flag and never clears {@code isInitialized}
134+
* (FlagdProvider.java:136-155, FlagdProviderSyncResources.java:27-28, 112-115), so a later
135+
* {@code initialize()} returns at its first check without rebuilding anything
136+
* (FlagdProvider.java:121-125): the resolver is shut down, the RPC channel was
137+
* {@code shutdownNow()}'d, the retry scheduler is terminated and {@code errorExecutor} is a
138+
* {@code final} field nothing re-creates. A shut-down flagd provider is terminally shut down.
139+
*
140+
* <p>Requirement 2.5.2 says a provider <em>SHOULD</em> revert to its uninitialized state after
141+
* shutdown, and its supporting text says <em>"some providers MAY allow reinitialization from
142+
* this state"</em> — so reuse is permitted, not required, and declining it is one of the options
143+
* the requirement offers. An earlier version of this file recorded it as a {@code KnownDeviation}
144+
* against {@code @lifecycle}, which was wrong twice over: the scenario was mandatory only because
145+
* the spec's assets had not yet gated it, and the entry asserted a defect against a provider
146+
* behaving within the requirement. Withholding the tag is the whole of what is owed here; the
147+
* one scenario it gates is reported as skipped with this reason on every run.
148+
*
149+
* <p>{@link Capability#STALE} is declared for both resolvers, and the declaration is examined
150+
* rather than inherited. {@code PROVIDER_STALE} is emitted from {@code FlagdProvider.onError}
151+
* (FlagdProvider.java:258-264), which the shared {@code onProviderEvent} switch reaches on
152+
* {@code PROVIDER_ERROR} from either resolver (FlagdProvider.java:197, 236), before the grace
153+
* period turns it into {@code PROVIDER_ERROR} — so the emit sits in the provider layer, not in a
154+
* transport, and the scenario "Losing the backend makes the provider stale, regaining it makes
155+
* it ready again" passes in RPC mode as well as in-process. Worth stating because Go's flagd
156+
* provider withholds the tag for its RPC resolver; on this evidence that is a difference between
157+
* the two implementations, not a property of the transport.
126158
*
127159
* <p>{@link Capability#declarableExcept} rather than {@code EnumSet.complementOf}, which is what
128160
* this used to be. The complement of one capability is every other <em>enum constant</em>,
@@ -135,7 +167,7 @@ public FeatureProvider createUnavailableProvider() {
135167
*/
136168
@Override
137169
public Set<Capability> capabilities() {
138-
return Capability.declarableExcept(Capability.NUMERIC_COERCION);
170+
return Capability.declarableExcept(Capability.NUMERIC_COERCION, Capability.REINITIALIZATION);
139171
}
140172

141173
/**

0 commit comments

Comments
 (0)