-
Notifications
You must be signed in to change notification settings - Fork 50
test: improve gherkin test suite only relying on the newest version, with data loading #1578
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
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Summary of Changes
Hello @aepfli, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!
This pull request significantly refactors and enhances the Gherkin test suite to align with the latest OpenFeature specification, particularly regarding flag evaluation and context handling. It transitions from hardcoded test flags to dynamic loading from a JSON file, introduces a Common Expression Language (CEL) based context evaluator for advanced targeting, and expands the Gherkin step definitions to support more complex context scenarios and detailed evaluation result assertions.
Highlights
- Dynamic Test Flag Loading: Test flags are now loaded from 'test-flags.json' using Jackson, enabling more flexible and spec-aligned test data, and replacing previous hardcoded flag definitions.
- CEL-based Context Evaluation: A new 'CelContextEvaluator' is introduced, allowing for sophisticated flag targeting logic within tests using Common Expression Language (CEL).
- Enhanced Gherkin Steps: New Gherkin steps are added to define contexts with null values, specific types, targeting keys, and nested properties, as well as to assert flag keys, error codes, and error messages in evaluation results.
- Specification Update: The OpenFeature specification submodule has been updated, reflecting changes that necessitate these testing improvements and ensuring alignment with the latest spec.
- Improved Flag Evaluation Logic: The 'InMemoryProvider' now handles 'ContextEvaluator' failures gracefully by falling back to the default variant and correctly setting the evaluation reason, improving robustness.
Using Gemini Code Assist
The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.
Invoking Gemini
You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command>
or @gemini-code-assist <command>
. Below is a summary of the supported commands.
Feature | Command | Description |
---|---|---|
Code Review | /gemini review |
Performs a code review for the current pull request in its current state. |
Pull Request Summary | /gemini summary |
Provides a summary of the current pull request in its current state. |
Comment | @gemini-code-assist | Responds in comments when explicitly tagged, both in issue comments and review comments. |
Help | /gemini help |
Displays a list of available commands. |
Customization
To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/
folder in the base of the repository. Detailed instructions can be found here.
Limitations & Feedback
Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.
You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.
Footnotes
-
Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This pull request updates the Gherkin test suite to align with a newer version of the OpenFeature specification, notably by loading test flags from a JSON file instead of defining them in code. The changes are extensive, introducing Jackson for deserialization and the CEL library for context evaluation in tests.
My review focuses on improving robustness and efficiency. I've identified a potential issue in the InMemoryProvider
where exceptions are swallowed, which could hide bugs. I've also suggested performance improvements for the new CelContextEvaluator
and pointed out some dead/duplicated code in the test utilities that could be cleaned up.
src/main/java/dev/openfeature/sdk/providers/memory/InMemoryProvider.java
Outdated
Show resolved
Hide resolved
src/test/java/dev/openfeature/sdk/testutils/TestFlagsUtils.java
Outdated
Show resolved
Hide resolved
src/test/java/dev/openfeature/sdk/testutils/jackson/CelContextEvaluator.java
Outdated
Show resolved
Hide resolved
src/test/java/dev/openfeature/sdk/testutils/jackson/ImmutableMetadataMixin.java
Outdated
Show resolved
Hide resolved
97a6b30
to
82a7491
Compare
/gemini review |
…with data loading Signed-off-by: Simon Schrottner <[email protected]>
82a7491
to
4b3c267
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This pull request is a significant improvement, aligning the Gherkin test suite with the latest OpenFeature specification by loading test data from JSON and using CEL for dynamic evaluation. The changes are well-structured and greatly enhance test maintainability. I've identified a few areas for improvement, including logging a swallowed exception in the InMemoryProvider
, fixing a bug in a Gherkin step definition, and improving the robustness of the new test utility classes. I've provided detailed suggestions for each point. Overall, this is an excellent enhancement to the SDK's testing framework.
src/test/java/dev/openfeature/sdk/testutils/jackson/VariantsMapDeserializer.java
Outdated
Show resolved
Hide resolved
src/main/java/dev/openfeature/sdk/providers/memory/InMemoryProvider.java
Show resolved
Hide resolved
src/test/java/dev/openfeature/sdk/testutils/jackson/CelContextEvaluator.java
Outdated
Show resolved
Hide resolved
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Simon Schrottner <[email protected]>
…Evaluator.java Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Simon Schrottner <[email protected]>
3225636
to
770ebac
Compare
…with data loading Signed-off-by: Simon Schrottner <[email protected]> diff --git c/src/test/java/dev/openfeature/sdk/e2e/steps/ContextSteps.java i/src/test/java/dev/openfeature/sdk/e2e/steps/ContextSteps.java index e4cd603..121b567 100644 --- c/src/test/java/dev/openfeature/sdk/e2e/steps/ContextSteps.java +++ i/src/test/java/dev/openfeature/sdk/e2e/steps/ContextSteps.java @@ -114,9 +114,9 @@ public class ContextSteps { public void a_context_containing_a_key_with_type_and_with_value(String key, String type, String value) throws ClassNotFoundException, InstantiationException { Map<String, Value> map = state.context.asMap(); - Map<String, Value> map = state.context.asMap(); map.put(key, new Value(Utils.convert(value, type))); state.context = new MutableContext(state.context.getTargetingKey(), map); + } @given("a context containing a targeting key with value {string}") public void a_context_containing_a_targeting_key_with_value(String string) { diff --git c/src/test/java/dev/openfeature/sdk/testutils/jackson/CelContextEvaluator.java i/src/test/java/dev/openfeature/sdk/testutils/jackson/CelContextEvaluator.java index 20f3f5f..138c23f 100644 --- c/src/test/java/dev/openfeature/sdk/testutils/jackson/CelContextEvaluator.java +++ i/src/test/java/dev/openfeature/sdk/testutils/jackson/CelContextEvaluator.java @@ -46,7 +46,6 @@ public class CelContextEvaluator<T> implements ContextEvaluator<T> { // Evaluate with context, overriding defaults. objectMap.putAll(evaluationContext.asObjectMap()); } - } Object result = program.eval(objectMap); diff --git c/src/test/java/dev/openfeature/sdk/testutils/jackson/VariantsMapDeserializer.java i/src/test/java/dev/openfeature/sdk/testutils/jackson/VariantsMapDeserializer.java index 339d8c8..9a63f58 100644 --- c/src/test/java/dev/openfeature/sdk/testutils/jackson/VariantsMapDeserializer.java +++ i/src/test/java/dev/openfeature/sdk/testutils/jackson/VariantsMapDeserializer.java @@ -50,6 +50,7 @@ public class VariantsMapDeserializer extends JsonDeserializer<Map<String, Object } private Object convertJsonNodeToValue(JsonNode node) throws InstantiationException { + if (node.isNull()) { return null; } else if (node.isBoolean()) { @@ -60,25 +61,8 @@ public class VariantsMapDeserializer extends JsonDeserializer<Map<String, Object return node.asDouble(); } else if (node.isTextual()) { return node.asText(); - } else if (node.isArray()) { - List<Object> list = new ArrayList<>(); - for (JsonNode item : node) { - list.add(convertJsonNodeToValue(item)); - } - return list; } else if (node.isObject()) { - Map<String, Value> map = new HashMap<>(); - Iterator<Map.Entry<String, JsonNode>> fields = node.fields(); - while (fields.hasNext()) { - Map.Entry<String, JsonNode> field = fields.next(); - Object o = convertJsonNodeToValue(field.getValue()); - if (o instanceof Value) { - map.put(field.getKey(), (Value) o); - } else { - map.put(field.getKey(), new Value(o)); - } - } - return new Value(new MutableStructure(map)); + return Value.objectToValue(node); } throw new IllegalArgumentException("Unsupported JSON node type: " + node.getNodeType()); Signed-off-by: Simon Schrottner <[email protected]>
770ebac
to
c57e3da
Compare
/gemini review |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This pull request improves the Gherkin test suite by loading test data from JSON files, aligning with the latest OpenFeature specification. The changes are well-structured, introducing custom Jackson deserializers and a CEL-based context evaluator for dynamic flag evaluation. My review includes suggestions to improve dependency management in pom.xml
, enhance thread-safety in test utilities, improve exception handling, and increase code clarity in test steps.
src/test/java/dev/openfeature/sdk/testutils/TestFlagsUtils.java
Outdated
Show resolved
Hide resolved
src/main/java/dev/openfeature/sdk/providers/memory/InMemoryProvider.java
Show resolved
Hide resolved
src/test/java/dev/openfeature/sdk/e2e/steps/FlagStepDefinitions.java
Outdated
Show resolved
Hide resolved
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1578 +/- ##
============================================
+ Coverage 92.82% 93.16% +0.34%
- Complexity 487 506 +19
============================================
Files 46 50 +4
Lines 1170 1229 +59
Branches 103 108 +5
============================================
+ Hits 1086 1145 +59
Misses 54 54
Partials 30 30
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
…s.java Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Simon Schrottner <[email protected]>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Simon Schrottner <[email protected]>
bcb53a0
to
131a24d
Compare
…with data loading Signed-off-by: Simon Schrottner <[email protected]> diff --git c/src/test/java/dev/openfeature/sdk/e2e/steps/ContextSteps.java i/src/test/java/dev/openfeature/sdk/e2e/steps/ContextSteps.java index e4cd603..121b567 100644 --- c/src/test/java/dev/openfeature/sdk/e2e/steps/ContextSteps.java +++ i/src/test/java/dev/openfeature/sdk/e2e/steps/ContextSteps.java @@ -114,9 +114,9 @@ public class ContextSteps { public void a_context_containing_a_key_with_type_and_with_value(String key, String type, String value) throws ClassNotFoundException, InstantiationException { Map<String, Value> map = state.context.asMap(); - Map<String, Value> map = state.context.asMap(); map.put(key, new Value(Utils.convert(value, type))); state.context = new MutableContext(state.context.getTargetingKey(), map); + } @given("a context containing a targeting key with value {string}") public void a_context_containing_a_targeting_key_with_value(String string) { diff --git c/src/test/java/dev/openfeature/sdk/testutils/jackson/CelContextEvaluator.java i/src/test/java/dev/openfeature/sdk/testutils/jackson/CelContextEvaluator.java index 20f3f5f..138c23f 100644 --- c/src/test/java/dev/openfeature/sdk/testutils/jackson/CelContextEvaluator.java +++ i/src/test/java/dev/openfeature/sdk/testutils/jackson/CelContextEvaluator.java @@ -46,7 +46,6 @@ public class CelContextEvaluator<T> implements ContextEvaluator<T> { // Evaluate with context, overriding defaults. objectMap.putAll(evaluationContext.asObjectMap()); } - } Object result = program.eval(objectMap); diff --git c/src/test/java/dev/openfeature/sdk/testutils/jackson/VariantsMapDeserializer.java i/src/test/java/dev/openfeature/sdk/testutils/jackson/VariantsMapDeserializer.java index 339d8c8..9a63f58 100644 --- c/src/test/java/dev/openfeature/sdk/testutils/jackson/VariantsMapDeserializer.java +++ i/src/test/java/dev/openfeature/sdk/testutils/jackson/VariantsMapDeserializer.java @@ -50,6 +50,7 @@ public class VariantsMapDeserializer extends JsonDeserializer<Map<String, Object } private Object convertJsonNodeToValue(JsonNode node) throws InstantiationException { + if (node.isNull()) { return null; } else if (node.isBoolean()) { @@ -60,25 +61,8 @@ public class VariantsMapDeserializer extends JsonDeserializer<Map<String, Object return node.asDouble(); } else if (node.isTextual()) { return node.asText(); - } else if (node.isArray()) { - List<Object> list = new ArrayList<>(); - for (JsonNode item : node) { - list.add(convertJsonNodeToValue(item)); - } - return list; } else if (node.isObject()) { - Map<String, Value> map = new HashMap<>(); - Iterator<Map.Entry<String, JsonNode>> fields = node.fields(); - while (fields.hasNext()) { - Map.Entry<String, JsonNode> field = fields.next(); - Object o = convertJsonNodeToValue(field.getValue()); - if (o instanceof Value) { - map.put(field.getKey(), (Value) o); - } else { - map.put(field.getKey(), new Value(o)); - } - } - return new Value(new MutableStructure(map)); + return Value.objectToValue(node); } throw new IllegalArgumentException("Unsupported JSON node type: " + node.getNodeType()); Signed-off-by: Simon Schrottner <[email protected]> diff --git c/src/test/java/dev/openfeature/sdk/e2e/Utils.java i/src/test/java/dev/openfeature/sdk/e2e/Utils.java index 1500d99..565968c 100644 --- c/src/test/java/dev/openfeature/sdk/e2e/Utils.java +++ i/src/test/java/dev/openfeature/sdk/e2e/Utils.java @@ -7,6 +7,8 @@ import java.util.Objects; public final class Utils { + public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private Utils() {} public static Object convert(String value, String type) { @@ -27,7 +29,7 @@ public final class Utils { return Long.parseLong(value); case "object": try { - return Value.objectToValue(new ObjectMapper().readValue(value, Object.class)); + return Value.objectToValue(OBJECT_MAPPER.readValue(value, Object.class)); } catch (JsonProcessingException e) { throw new RuntimeException(e); } diff --git c/src/test/java/dev/openfeature/sdk/testutils/TestFlagsUtils.java i/src/test/java/dev/openfeature/sdk/testutils/TestFlagsUtils.java index 78a2fc5..13fe32f 100644 --- c/src/test/java/dev/openfeature/sdk/testutils/TestFlagsUtils.java +++ i/src/test/java/dev/openfeature/sdk/testutils/TestFlagsUtils.java @@ -17,6 +17,8 @@ import java.util.Map; import lombok.experimental.UtilityClass; import lombok.extern.slf4j.Slf4j; +import static dev.openfeature.sdk.e2e.Utils.OBJECT_MAPPER; + /** * Test flags utils. */ @@ -41,7 +43,8 @@ public class TestFlagsUtils { */ public static synchronized Map<String, Flag<?>> buildFlags() { if (flags == null) { - ObjectMapper objectMapper = new ObjectMapper(); + if (flags == null) { + ObjectMapper objectMapper = OBJECT_MAPPER; objectMapper.configure(StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION.mappedFeature(), true); objectMapper.addMixIn(Flag.class, InMemoryFlagMixin.class); objectMapper.addMixIn(Flag.FlagBuilder.class, InMemoryFlagMixin.FlagBuilderMixin.class); Signed-off-by: Simon Schrottner <[email protected]>
131a24d
to
f3512f9
Compare
Signed-off-by: Simon Schrottner <[email protected]>
d145f75
to
e097032
Compare
src/test/java/dev/openfeature/sdk/testutils/jackson/VariantsMapDeserializer.java
Show resolved
Hide resolved
src/test/java/dev/openfeature/sdk/testutils/jackson/CelContextEvaluator.java
Show resolved
Hide resolved
Signed-off-by: Simon Schrottner <[email protected]>
6a4535c
to
3788ac4
Compare
Signed-off-by: Simon Schrottner <[email protected]>
3788ac4
to
4a3faf1
Compare
Signed-off-by: Simon Schrottner <[email protected]>
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🙏
based on: open-feature/spec#336
depends on: open-feature/spec#336