Repository navigation
test(flagd): adopt the OpenFeature Provider TCK #1847
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+388
−2
Merged
Changes from all commits
Commits
Show all changes
38 commits
Select commit
Hold shift + click to select a range
29f1746
test(flagd): adopt the OpenFeature Provider TCK
aepfli 55c82c9
test(flagd): declare the numeric-coercion gap as a defect, not a choice
aepfli 79dd48e
test(flagd): record what flagd-testbed does not serve, and why that i…
aepfli cbfee90
test(flagd): withhold @reinitialization, and settle @stale by evidence
aepfli a88ca7b
test(flagd): withhold @large-integers explicitly
aepfli bbce9d4
docs(flagd): say what the new tags gate here, and what the testbed st…
aepfli 34adc67
test(flagd): declare @disabled-flags, on the evidence of both resolvers
aepfli b561e62
test(flagd): follow the provider-tck -> tck rename
aepfli 7fa90df
test(flagd): give the in-process resolver a deadline its sync can meet
aepfli e039520
test(flagd): declare numeric coercion and let its failure show
aepfli 7ba5279
docs(flagd): say that the TCK suites are Docker-gated and hand-run
aepfli 05c8675
test(flagd): keep the TCK suites out of CI, which the e2e profile was…
aepfli 1733cbb
docs(flagd): point at Appendix F for the CI-exclusion reasoning
aepfli c6f8a85
test(flagd): record that both resolvers report the standard reasons
aepfli 1184862
test(flagd): stop withholding a capability nobody in Java can hold
aepfli 206c932
docs(flagd): cite the declaring rule this adoption argued out in long…
aepfli fa0e4e0
test(flagd): run the conformance suites from a step of their own
aepfli af56256
test(flagd): give the conformance adoption a package of its own
aepfli 5f94dad
docs(flagd): cut the conformance section to what is this adoption's
aepfli 505c757
test(flagd): one Compose file for both adoptions, and cut the prose a…
aepfli 0458757
test(flagd): follow @disabled-flags' corrected gating question
aepfli 9796aea
test(flagd): record the intermittent testbed failure observed here too
aepfli 7ab871a
test(tck): run the conformance suite against flagd-testbed v3.10.1
aepfli 7c45645
test(flagd): declare @string-typing, on a run rather than on the default
aepfli d4cbd9a
test(flagd): declare @fully-typed-values too, on a run in both modes
aepfli 595e2f2
test(flagd): run against flagd-testbed v3.10.3
aepfli b6dc030
test(flagd): trim the TCK comments to this adoption's own facts
aepfli 495b1a0
test(flagd): name the object factory the config suite needs
aepfli 644f1d7
test(flagd): run the conformance suites from the root tck profile
aepfli 8080ad3
docs(flagd): follow the merged Provider TCK job name
aepfli 8ec4405
test(flagd): run the TCK against the testbed's own compose file
aepfli 61925a5
test(flagd): record what v3.11.0 leaves failing, which is one scenario
aepfli ff774db
test(flagd): the in-process coercion deviation is resolved, so stop d…
aepfli fb05beb
docs(flagd): trim the TCK suite comments to what the code needs
aepfli 32d1ae9
feat(tck): let a containerised suite tell its Compose stack a host path
aepfli 37f2712
test(flagd): run the Provider TCK against the file resolver too
aepfli 368ad4b
style(flagd): apply spotless to the unavailable-provider seam
aepfli 8b661e9
fixup: drop stale testbed readiness note
toddbaert File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
129 changes: 129 additions & 0 deletions
129
...flagd/src/test/java/dev/openfeature/contrib/providers/flagd/tck/AbstractResolverTest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,129 @@ | ||
| package dev.openfeature.contrib.providers.flagd.tck; | ||
|
|
||
| import dev.openfeature.contrib.providers.flagd.Config; | ||
| import dev.openfeature.contrib.providers.flagd.FlagdOptions; | ||
| import dev.openfeature.contrib.providers.flagd.FlagdProvider; | ||
| import dev.openfeature.contrib.tools.tck.BackendEndpoint; | ||
| import dev.openfeature.contrib.tools.tck.Capability; | ||
| import dev.openfeature.contrib.tools.tck.ContainerizedProviderTckTest; | ||
| import dev.openfeature.sdk.FeatureProvider; | ||
| import java.io.File; | ||
| import java.util.Collections; | ||
| import java.util.List; | ||
| import java.util.Set; | ||
|
|
||
| /** | ||
| * Shared configuration for running the OpenFeature Provider TCK against the flagd provider. | ||
| * | ||
| * <p>flagd resolves flags in three quite different ways, and all three are worth conforming: RPC | ||
| * evaluates remotely over gRPC, in-process syncs the ruleset and evaluates locally, and file reads | ||
| * the ruleset off the disk. All three share a backend stack; the two gRPC modes differ only in | ||
| * resolver and port, and {@link FileTest} says what the third needs instead. | ||
| */ | ||
| abstract class AbstractResolverTest extends ContainerizedProviderTckTest { | ||
|
|
||
| /** A port nothing listens on; an outage on the Compose stack belongs to the control API. */ | ||
| private static final int UNAVAILABLE_PORT = 9999; | ||
|
|
||
| /** | ||
| * gRPC deadline for a provider that is expected to connect. | ||
| * | ||
| * <p>flagd <em>doubles</em> it, and in-process must sync the entire ruleset before it reports | ||
| * ready; 5000 was not enough for that. | ||
| */ | ||
| private static final int CONNECTED_DEADLINE_MS = 15000; | ||
|
|
||
| /** gRPC deadline for a dead port; these scenarios assert that the failure is reported promptly. */ | ||
| private static final int UNAVAILABLE_DEADLINE_MS = 1000; | ||
|
|
||
| /** The resolver under test. */ | ||
| protected abstract Config.Resolver resolver(); | ||
|
|
||
| /** | ||
| * The container-internal port this mode waits on before the first scenario. | ||
| * | ||
| * <p>The two gRPC resolvers dial it. {@link FileTest} does not, and names flagd's sync listener | ||
| * only because a flagd that is listening is a flagd that has written its flag file. | ||
| */ | ||
| protected abstract int backendPort(); | ||
|
|
||
| /** | ||
| * {@inheritDoc} | ||
| * | ||
| * <p>The flagd-testbed's own stack, from the {@code test-harness} submodule the legacy e2e suites | ||
| * also run, so the image tag follows the submodule pin instead of a second copy that can drift. | ||
| */ | ||
| @Override | ||
| public File composeFile() { | ||
| return new File("test-harness/docker-compose.yaml"); | ||
| } | ||
|
|
||
| /** The testbed's compose file names the flagd service {@code flagd}. */ | ||
| @Override | ||
| public String backendService() { | ||
| return "flagd"; | ||
| } | ||
|
|
||
| @Override | ||
| public List<Integer> backendPorts() { | ||
| return Collections.singletonList(backendPort()); | ||
| } | ||
|
|
||
| @Override | ||
| public FeatureProvider createProvider(BackendEndpoint endpoint) { | ||
| return new FlagdProvider(flagSource(baseOptions().deadline(CONNECTED_DEADLINE_MS), endpoint) | ||
| .build()); | ||
| } | ||
|
|
||
| @Override | ||
| public FeatureProvider createUnavailableProvider() { | ||
| return new FlagdProvider(missingFlagSource(baseOptions().deadline(UNAVAILABLE_DEADLINE_MS)) | ||
| .build()); | ||
| } | ||
|
|
||
| /** | ||
| * Points the options at the running stack; a resolver that reads no socket overrides this. | ||
| * | ||
| * @param options the options being built, with the connected deadline already set | ||
| * @param endpoint host and mapped ports of the running stack | ||
| * @return the same builder, told where to find the flags | ||
| */ | ||
| protected FlagdOptions.FlagdOptionsBuilder flagSource( | ||
| FlagdOptions.FlagdOptionsBuilder options, BackendEndpoint endpoint) { | ||
| return options.host(endpoint.host()).port(endpoint.port(backendPort())); | ||
| } | ||
|
|
||
| /** | ||
| * Points the options at a flag source that is not there, for the {@code @unavailable} scenarios. | ||
| * | ||
| * @param options the options being built, with the short deadline already set | ||
| * @return the same builder, told to look somewhere nothing answers | ||
| */ | ||
| protected FlagdOptions.FlagdOptionsBuilder missingFlagSource(FlagdOptions.FlagdOptionsBuilder options) { | ||
| return options.host("localhost").port(UNAVAILABLE_PORT); | ||
| } | ||
|
|
||
| /** | ||
| * {@inheritDoc} | ||
| * | ||
| * <p>Everything declarable except {@link Capability#REINITIALIZATION}, which is a fact about the | ||
| * provider rather than a defect in it: {@code shutdown()} never clears {@code isInitialized}, so a | ||
| * later {@code initialize()} returns at its first check without rebuilding the resolver, the | ||
| * {@code shutdownNow()}'d channel or the terminated executors. A shut-down flagd provider is | ||
| * terminally shut down. {@link FileTest} narrows this further, for the reason it gives. | ||
| * | ||
| * <p>{@link Capability#declarableExcept} and not {@code EnumSet.complementOf}, whose complement | ||
| * would include reserved tags the suite refuses at startup. | ||
| */ | ||
| @Override | ||
| public Set<Capability> capabilities() { | ||
| return Capability.declarableExcept(Capability.REINITIALIZATION); | ||
| } | ||
|
|
||
| private FlagdOptions.FlagdOptionsBuilder baseOptions() { | ||
| return FlagdOptions.builder() | ||
| .resolverType(resolver()) | ||
| .retryGracePeriod(2) | ||
| .retryBackoffMs(500); | ||
| } | ||
| } |
116 changes: 116 additions & 0 deletions
116
providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/tck/FileTest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,116 @@ | ||
| package dev.openfeature.contrib.providers.flagd.tck; | ||
|
|
||
| import dev.openfeature.contrib.providers.flagd.Config; | ||
| import dev.openfeature.contrib.providers.flagd.FlagdOptions; | ||
| import dev.openfeature.contrib.tools.tck.BackendEndpoint; | ||
| import dev.openfeature.contrib.tools.tck.Capability; | ||
| import java.io.IOException; | ||
| import java.io.UncheckedIOException; | ||
| import java.nio.file.Files; | ||
| import java.nio.file.Path; | ||
| import java.nio.file.Paths; | ||
| import java.util.Collections; | ||
| import java.util.Map; | ||
| import java.util.Set; | ||
| import org.apache.commons.io.FileUtils; | ||
|
|
||
| /** | ||
| * Runs the OpenFeature Provider TCK against the flagd provider in file mode. | ||
| * | ||
| * <p>This resolver reads its ruleset from a file instead of a socket, so the stack has to put that | ||
| * file where this JVM can see it. The testbed's Compose file mounts {@code $FLAGS_DIR} at | ||
| * {@code /flags}, where its launchpad writes {@code allFlags.json}, so naming a host directory | ||
| * through {@link #composeEnvironment()} is all it takes. | ||
| */ | ||
| public class FileTest extends AbstractResolverTest { | ||
|
|
||
| /** | ||
| * Where the testbed writes its flags and this provider reads them. | ||
| * | ||
| * <p>A fixed name under the module's gitignored {@code tmp/}, cleared on the way in, so a crashed | ||
| * run leaves one directory behind instead of one more each time. Static because | ||
| * {@code beginSuite()} validates the declarations on a throwaway harness with no stack. | ||
| */ | ||
| private static final Path FLAGS_DIR = flagsDir(); | ||
|
|
||
| /** The file flagd's launchpad combines the ruleset into; see {@code launchpad/pkg/json.go}. */ | ||
| private static final String FLAG_FILE = "allFlags.json"; | ||
|
|
||
| /** | ||
| * How often to restat the flag file. | ||
| * | ||
| * <p>Far below {@code Config.DEFAULT_OFFLINE_POLL_MS}, which is sane for a long-lived provider and | ||
| * far too slow for a scenario: polling is the only way this resolver learns of a change, so | ||
| * {@code @configuration-change} waits out a whole interval before it can pass. | ||
| */ | ||
| private static final int POLL_INTERVAL_MS = 100; | ||
|
|
||
| /** | ||
| * {@inheritDoc} | ||
| * | ||
| * <p>Stated rather than derived, for the reason {@link RpcTest#configuration()} gives. | ||
| */ | ||
| @Override | ||
| public String configuration() { | ||
| return "flagd-file"; | ||
| } | ||
|
|
||
| @Override | ||
| public Config.Resolver resolver() { | ||
| return Config.Resolver.FILE; | ||
| } | ||
|
|
||
| /** flagd's sync listener; this resolver dials nothing, see {@link AbstractResolverTest}. */ | ||
| @Override | ||
| protected int backendPort() { | ||
| return 8015; | ||
| } | ||
|
|
||
| /** {@inheritDoc} */ | ||
| @Override | ||
| public Map<String, String> composeEnvironment() { | ||
| return Collections.singletonMap("FLAGS_DIR", FLAGS_DIR.toString()); | ||
| } | ||
|
|
||
| /** The endpoint is ignored: the ruleset arrives on the filesystem, not over a socket. */ | ||
| @Override | ||
| protected FlagdOptions.FlagdOptionsBuilder flagSource( | ||
| FlagdOptions.FlagdOptionsBuilder options, BackendEndpoint endpoint) { | ||
| return options.offlineFlagSourcePath(FLAGS_DIR.resolve(FLAG_FILE).toString()) | ||
| .offlinePollIntervalMs(POLL_INTERVAL_MS); | ||
| } | ||
|
|
||
| /** A path rather than a closed port, which is what "unreachable" means to this resolver. */ | ||
| @Override | ||
| protected FlagdOptions.FlagdOptionsBuilder missingFlagSource(FlagdOptions.FlagdOptionsBuilder options) { | ||
| return options.offlineFlagSourcePath(FLAGS_DIR.resolve("not-existing").toString()) | ||
| .offlinePollIntervalMs(POLL_INTERVAL_MS); | ||
| } | ||
|
|
||
| /** | ||
| * {@inheritDoc} | ||
| * | ||
| * <p>{@link AbstractResolverTest#capabilities()} minus {@link Capability#STALE}. The provider can | ||
| * go stale here, but the only outage the control API can stage is stopping flagd's listeners, and | ||
| * a file already on disk survives that, so nothing would cause the stale event the scenario waits | ||
| * for. | ||
| * | ||
| * <p>{@link Capability#CONFIGURATION_CHANGE} is declared: the control API rewrites the files in | ||
| * the mounted volume, so the change reaches this provider through {@link #POLL_INTERVAL_MS} | ||
| * rather than over a stream. | ||
| */ | ||
| @Override | ||
| public Set<Capability> capabilities() { | ||
| return Capability.declarableExcept(Capability.REINITIALIZATION, Capability.STALE); | ||
| } | ||
|
|
||
| private static Path flagsDir() { | ||
| Path directory = Paths.get("tmp", "tck-file"); | ||
| try { | ||
| FileUtils.deleteDirectory(directory.toFile()); | ||
| return Files.createDirectories(directory).toAbsolutePath(); | ||
| } catch (IOException e) { | ||
| throw new UncheckedIOException("Could not prepare the directory the testbed writes its flags to", e); | ||
| } | ||
| } | ||
| } |
27 changes: 27 additions & 0 deletions
27
providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/tck/InProcessTest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| package dev.openfeature.contrib.providers.flagd.tck; | ||
|
|
||
| import dev.openfeature.contrib.providers.flagd.Config; | ||
|
|
||
| /** Runs the OpenFeature Provider TCK against the flagd provider in in-process mode. */ | ||
| public class InProcessTest extends AbstractResolverTest { | ||
|
|
||
| /** | ||
| * {@inheritDoc} | ||
| * | ||
| * <p>Stated rather than derived, for the reason {@link RpcTest#configuration()} gives. | ||
| */ | ||
| @Override | ||
| public String configuration() { | ||
| return "flagd-in-process"; | ||
| } | ||
|
|
||
| @Override | ||
| protected Config.Resolver resolver() { | ||
| return Config.Resolver.IN_PROCESS; | ||
| } | ||
|
|
||
| @Override | ||
| protected int backendPort() { | ||
| return 8015; | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.