-
Notifications
You must be signed in to change notification settings - Fork 19
feat: add integration tests for Java generator #220
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,95 @@ | ||||||
| package main | ||||||
|
|
||||||
| import ( | ||||||
| "context" | ||||||
| "fmt" | ||||||
| "os" | ||||||
| "path/filepath" | ||||||
|
|
||||||
| "dagger.io/dagger" | ||||||
| "github.com/open-feature/cli/test/integration" | ||||||
| ) | ||||||
|
|
||||||
| // Test implements the integration test for the Java generator | ||||||
| type Test struct { | ||||||
| // ProjectDir is the absolute path to the root of the project | ||||||
| ProjectDir string | ||||||
| // TestDir is the absolute path to the test directory | ||||||
| TestDir string | ||||||
| } | ||||||
|
|
||||||
| // New creates a new Test | ||||||
| func New(projectDir, testDir string) *Test { | ||||||
| return &Test{ | ||||||
| ProjectDir: projectDir, | ||||||
| TestDir: testDir, | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| // Run executes the Java integration test using Dagger | ||||||
| func (t *Test) Run(ctx context.Context, client *dagger.Client) (*dagger.Container, error) { | ||||||
| // Source code container | ||||||
| source := client.Host().Directory(t.ProjectDir) | ||||||
| testFiles := client.Host().Directory(t.TestDir, dagger.HostDirectoryOpts{ | ||||||
| Include: []string{"pom.xml", "src/**/*.java"}, | ||||||
| }) | ||||||
|
|
||||||
| // Build the CLI | ||||||
| cli := client.Container(). | ||||||
| From("golang:1.24-alpine"). | ||||||
| WithExec([]string{"apk", "add", "--no-cache", "git"}). | ||||||
| WithDirectory("/src", source). | ||||||
| WithWorkdir("/src"). | ||||||
| WithExec([]string{"go", "mod", "tidy"}). | ||||||
| WithExec([]string{"go", "mod", "download"}). | ||||||
| WithExec([]string{"go", "build", "-o", "cli", "./cmd/openfeature"}) | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The build command for the CLI is missing the While this seems to be a pre-existing pattern in other integration tests, it would be a good improvement to start aligning them. You could pass the commit hash from the host (e.g., from an environment variable) and use it to construct the For example: commit := os.Getenv("COMMIT_SHA") // assuming it's passed from the calling script
ldflags := fmt.Sprintf("-s -w -X 'main.commit=%s'", commit)
// ...
cli := client.Container().
// ...
WithExec([]string{"go", "build", "-ldflags", ldflags, "-o", "cli", "./cmd/openfeature"})This would require changes in how the test is invoked to pass the commit hash. References
|
||||||
|
|
||||||
| // Generate Java client | ||||||
| generated := cli.WithExec([]string{ | ||||||
| "./cli", "generate", "java", | ||||||
| "--manifest=/src/sample/sample_manifest.json", | ||||||
| "--output=/tmp/generated", | ||||||
| "--package-name=dev.openfeature.generated", | ||||||
| }) | ||||||
|
|
||||||
| // Get generated files | ||||||
| generatedFiles := generated.Directory("/tmp/generated") | ||||||
|
|
||||||
| // Test Java compilation with the generated files | ||||||
| javaContainer := client.Container(). | ||||||
| From("maven:3.9-eclipse-temurin-21-alpine"). | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To align with the goal of supporting Java 11 as the minimum version (as mentioned in the PR description and the suggested change in
Suggested change
|
||||||
| WithWorkdir("/app"). | ||||||
| WithDirectory("/app", testFiles). | ||||||
| WithDirectory("/app/src/main/java/dev/openfeature/generated", generatedFiles). | ||||||
| WithExec([]string{"mvn", "clean", "compile", "-B", "-q"}). | ||||||
| WithExec([]string{"mvn", "exec:java", "-Dexec.mainClass=dev.openfeature.Main", "-q"}) | ||||||
|
|
||||||
| return javaContainer, nil | ||||||
| } | ||||||
|
|
||||||
| // Name returns the name of the integration test | ||||||
| func (t *Test) Name() string { | ||||||
| return "java" | ||||||
| } | ||||||
|
|
||||||
| func main() { | ||||||
| ctx := context.Background() | ||||||
|
|
||||||
| // Get project root | ||||||
| projectDir, err := os.Getwd() | ||||||
| if err != nil { | ||||||
| fmt.Fprintf(os.Stderr, "Failed to get project dir: %v\n", err) | ||||||
| os.Exit(1) | ||||||
| } | ||||||
|
|
||||||
| // Get test directory | ||||||
| testDir := filepath.Join(projectDir, "test/java-integration") | ||||||
|
|
||||||
| // Create and run the Java integration test | ||||||
| test := New(projectDir, testDir) | ||||||
|
|
||||||
| if err := integration.RunTest(ctx, test); err != nil { | ||||||
| fmt.Fprintf(os.Stderr, "Error: %v\n", err) | ||||||
| os.Exit(1) | ||||||
| } | ||||||
| } | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -45,6 +45,15 @@ func main() { | |
| os.Exit(1) | ||
| } | ||
|
|
||
| // Run the Java integration test | ||
| javaCmd := exec.Command("go", "run", "github.com/open-feature/cli/test/integration/cmd/java") | ||
| javaCmd.Stdout = os.Stdout | ||
| javaCmd.Stderr = os.Stderr | ||
| if err := javaCmd.Run(); err != nil { | ||
| fmt.Fprintf(os.Stderr, "Error running Java integration test: %v\n", err) | ||
| os.Exit(1) | ||
| } | ||
|
Comment on lines
+49
to
+55
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. While this works, the pattern for running each integration test is duplicated multiple times in this For example, the func main() {
fmt.Println("=== Running all integration tests ===")
tests := []string{
"csharp",
"go",
"nodejs",
"angular",
"java",
}
for _, test := range tests {
cmd := exec.Command("go", "run", "github.com/open-feature/cli/test/integration/cmd/"+test)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
fmt.Fprintf(os.Stderr, "Error running %s integration test: %v\n", test, err)
os.Exit(1)
}
}
fmt.Println("=== All integration tests passed successfully ===")
} |
||
|
|
||
| // Add more tests here as they are available | ||
|
|
||
| fmt.Println("=== All integration tests passed successfully ===") | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,48 @@ | ||||||||||||||||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||||||||||||||||
| <project xmlns="http://maven.apache.org/POM/4.0.0" | ||||||||||||||||||
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||||||||||||||||||
| xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||||||||||||||||||
| <modelVersion>4.0.0</modelVersion> | ||||||||||||||||||
|
|
||||||||||||||||||
| <groupId>dev.openfeature</groupId> | ||||||||||||||||||
| <artifactId>cli-java-integration-test</artifactId> | ||||||||||||||||||
| <version>1.0-SNAPSHOT</version> | ||||||||||||||||||
|
|
||||||||||||||||||
| <properties> | ||||||||||||||||||
| <maven.compiler.source>21</maven.compiler.source> | ||||||||||||||||||
| <maven.compiler.target>21</maven.compiler.target> | ||||||||||||||||||
|
Comment on lines
+12
to
+13
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The PR description states that the minimum Java version is 11, but this
Suggested change
Comment on lines
+12
to
+13
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The PR description mentions that the Java generator targets a minimum version of Java 11. However, the
Suggested change
|
||||||||||||||||||
| <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ||||||||||||||||||
| <openfeature.sdk.version>1.14.0</openfeature.sdk.version> | ||||||||||||||||||
| <maven-compiler-plugin.version>3.11.0</maven-compiler-plugin.version> | ||||||||||||||||||
| <exec-maven-plugin.version>3.1.0</exec-maven-plugin.version> | ||||||||||||||||||
| </properties> | ||||||||||||||||||
|
|
||||||||||||||||||
| <dependencies> | ||||||||||||||||||
| <!-- OpenFeature Java SDK --> | ||||||||||||||||||
| <dependency> | ||||||||||||||||||
| <groupId>dev.openfeature</groupId> | ||||||||||||||||||
| <artifactId>sdk</artifactId> | ||||||||||||||||||
| <version>${openfeature.sdk.version}</version> | ||||||||||||||||||
| </dependency> | ||||||||||||||||||
| </dependencies> | ||||||||||||||||||
|
|
||||||||||||||||||
| <build> | ||||||||||||||||||
| <plugins> | ||||||||||||||||||
| <plugin> | ||||||||||||||||||
| <groupId>org.apache.maven.plugins</groupId> | ||||||||||||||||||
| <artifactId>maven-compiler-plugin</artifactId> | ||||||||||||||||||
| <version>${maven-compiler-plugin.version}</version> | ||||||||||||||||||
| <configuration> | ||||||||||||||||||
| </configuration> | ||||||||||||||||||
| </plugin> | ||||||||||||||||||
| <plugin> | ||||||||||||||||||
| <groupId>org.codehaus.mojo</groupId> | ||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||||||
| <artifactId>exec-maven-plugin</artifactId> | ||||||||||||||||||
| <version>${exec-maven-plugin.version}</version> | ||||||||||||||||||
| <configuration> | ||||||||||||||||||
| <mainClass>dev.openfeature.Main</mainClass> | ||||||||||||||||||
| </configuration> | ||||||||||||||||||
| </plugin> | ||||||||||||||||||
| </plugins> | ||||||||||||||||||
| </build> | ||||||||||||||||||
| </project> | ||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,103 @@ | ||||||||||||||||||||||||
| package dev.openfeature; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| import dev.openfeature.generated.*; | ||||||||||||||||||||||||
| import dev.openfeature.sdk.*; | ||||||||||||||||||||||||
| import dev.openfeature.sdk.providers.memory.Flag; | ||||||||||||||||||||||||
| import dev.openfeature.sdk.providers.memory.InMemoryProvider; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| import java.util.Map; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| public class Main { | ||||||||||||||||||||||||
| public static void main(String[] args) { | ||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||
| run(); | ||||||||||||||||||||||||
| System.out.println("Generated Java code compiles successfully!"); | ||||||||||||||||||||||||
| } catch (Exception e) { | ||||||||||||||||||||||||
| System.err.println("Error: " + e.getMessage()); | ||||||||||||||||||||||||
| e.printStackTrace(); | ||||||||||||||||||||||||
| System.exit(1); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| private static void run() throws Exception { | ||||||||||||||||||||||||
| // Set up the in-memory provider with test flags | ||||||||||||||||||||||||
| Map<String, Object> themeConfig = Map.of( | ||||||||||||||||||||||||
| "primaryColor", "#007bff", | ||||||||||||||||||||||||
| "secondaryColor", "#6c757d" | ||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| Map<String, Flag<?>> flags = Map.of( | ||||||||||||||||||||||||
| "discountPercentage", Flag.builder() | ||||||||||||||||||||||||
| .variant("default", 0.15) | ||||||||||||||||||||||||
| .defaultVariant("default") | ||||||||||||||||||||||||
| .build(), | ||||||||||||||||||||||||
| "enableFeatureA", Flag.builder() | ||||||||||||||||||||||||
| .variant("default", false) | ||||||||||||||||||||||||
| .defaultVariant("default") | ||||||||||||||||||||||||
| .build(), | ||||||||||||||||||||||||
| "greetingMessage", Flag.builder() | ||||||||||||||||||||||||
| .variant("default", "Hello there!") | ||||||||||||||||||||||||
| .defaultVariant("default") | ||||||||||||||||||||||||
| .build(), | ||||||||||||||||||||||||
| "usernameMaxLength", Flag.builder() | ||||||||||||||||||||||||
| .variant("default", 50) | ||||||||||||||||||||||||
| .defaultVariant("default") | ||||||||||||||||||||||||
| .build(), | ||||||||||||||||||||||||
| "themeCustomization", Flag.builder() | ||||||||||||||||||||||||
| .variant("default", new Value(themeConfig)) | ||||||||||||||||||||||||
| .defaultVariant("default") | ||||||||||||||||||||||||
| .build() | ||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| InMemoryProvider provider = new InMemoryProvider(flags); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // Set the provider | ||||||||||||||||||||||||
| OpenFeatureAPI.getInstance().setProviderAndWait(provider); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| Client client = OpenFeatureAPI.getInstance().getClient(); | ||||||||||||||||||||||||
| MutableContext evalContext = new MutableContext(); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // Use the generated code for all flag evaluations | ||||||||||||||||||||||||
| Boolean enableFeatureA = EnableFeatureA.value(client, evalContext); | ||||||||||||||||||||||||
| System.out.println("enableFeatureA: " + enableFeatureA); | ||||||||||||||||||||||||
| FlagEvaluationDetails<Boolean> enableFeatureADetails = EnableFeatureA.valueWithDetails(client, evalContext); | ||||||||||||||||||||||||
| if (enableFeatureADetails.getErrorCode() != null) { | ||||||||||||||||||||||||
| throw new Exception("Error evaluating boolean flag: " + enableFeatureADetails.getFlagKey()); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
Comment on lines
64
to
66
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The error message "Error evaluating boolean flag" is generic. It would be more helpful to include the flag key in the error message to quickly identify which flag caused the issue, especially when dealing with multiple flags.
Suggested change
Comment on lines
+63
to
+66
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This error-checking logic is repeated for each flag type evaluation. To improve code readability and maintainability, you could extract this into a private helper method. For example, you could add this method to the private static <T> void checkDetails(FlagEvaluationDetails<T> details, String flagType) throws Exception {
if (details.getErrorCode() != null) {
throw new Exception("Error evaluating " + flagType + " flag: " + details.getFlagKey());
}
}Then you can replace this block and similar ones with a single call to this new helper method.
Suggested change
|
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| Double discount = DiscountPercentage.value(client, evalContext); | ||||||||||||||||||||||||
| System.out.printf("Discount Percentage: %.2f%n", discount); | ||||||||||||||||||||||||
| FlagEvaluationDetails<Double> discountDetails = DiscountPercentage.valueWithDetails(client, evalContext); | ||||||||||||||||||||||||
| if (discountDetails.getErrorCode() != null) { | ||||||||||||||||||||||||
| throw new Exception("Failed to get discount for flag: " + discountDetails.getFlagKey()); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
Comment on lines
71
to
73
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similar to the boolean flag, including the flag key in the error message "Failed to get discount" would improve debugging by pinpointing the exact flag that failed.
Suggested change
|
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| String greetingMessage = GreetingMessage.value(client, evalContext); | ||||||||||||||||||||||||
| System.out.println("greetingMessage: " + greetingMessage); | ||||||||||||||||||||||||
| FlagEvaluationDetails<String> greetingDetails = GreetingMessage.valueWithDetails(client, evalContext); | ||||||||||||||||||||||||
| if (greetingDetails.getErrorCode() != null) { | ||||||||||||||||||||||||
| throw new Exception("Error evaluating string flag: " + greetingDetails.getFlagKey()); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
Comment on lines
78
to
80
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Adding the flag key to the error message "Error evaluating string flag" will make it easier to diagnose issues when multiple string flags are being evaluated.
Suggested change
|
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| Integer usernameMaxLength = UsernameMaxLength.value(client, evalContext); | ||||||||||||||||||||||||
| System.out.println("usernameMaxLength: " + usernameMaxLength); | ||||||||||||||||||||||||
| FlagEvaluationDetails<Integer> usernameDetails = UsernameMaxLength.valueWithDetails(client, evalContext); | ||||||||||||||||||||||||
| if (usernameDetails.getErrorCode() != null) { | ||||||||||||||||||||||||
| throw new Exception("Error evaluating int flag: " + usernameDetails.getFlagKey()); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
Comment on lines
85
to
87
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Including the flag key in the error message "Error evaluating int flag" would provide more specific context for debugging integer flag evaluation failures.
Suggested change
|
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| Value themeCustomization = ThemeCustomization.value(client, evalContext); | ||||||||||||||||||||||||
| FlagEvaluationDetails<Value> themeDetails = ThemeCustomization.valueWithDetails(client, evalContext); | ||||||||||||||||||||||||
| if (themeDetails.getErrorCode() != null) { | ||||||||||||||||||||||||
| throw new Exception("Error evaluating object flag: " + themeDetails.getFlagKey()); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
Comment on lines
91
to
93
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For object flags, a more descriptive error message including the flag key would be beneficial for troubleshooting, e.g., "Error evaluating object flag: themeCustomization".
Suggested change
|
||||||||||||||||||||||||
| System.out.println("themeCustomization: " + themeCustomization); | ||||||||||||||||||||||||
|
Comment on lines
+61
to
+94
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The error checking logic for You can add the following helper method to the private static <T> void checkDetails(FlagEvaluationDetails<T> details, String flagType) throws Exception {
if (details.getErrorCode() != null) {
throw new Exception("Error evaluating " + flagType + " flag: " + details.getFlagKey());
}
}Then, you can simplify the evaluation checks as shown in the suggestion. Boolean enableFeatureA = EnableFeatureA.value(client, evalContext);
System.out.println("enableFeatureA: " + enableFeatureA);
checkDetails(EnableFeatureA.valueWithDetails(client, evalContext), "boolean");
Double discount = DiscountPercentage.value(client, evalContext);
System.out.printf("Discount Percentage: %.2f%n", discount);
checkDetails(DiscountPercentage.valueWithDetails(client, evalContext), "double");
String greetingMessage = GreetingMessage.value(client, evalContext);
System.out.println("greetingMessage: " + greetingMessage);
checkDetails(GreetingMessage.valueWithDetails(client, evalContext), "string");
Integer usernameMaxLength = UsernameMaxLength.value(client, evalContext);
System.out.println("usernameMaxLength: " + usernameMaxLength);
checkDetails(UsernameMaxLength.valueWithDetails(client, evalContext), "int");
Value themeCustomization = ThemeCustomization.value(client, evalContext);
checkDetails(ThemeCustomization.valueWithDetails(client, evalContext), "object");
System.out.println("themeCustomization: " + themeCustomization); |
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // Test the getKey() method functionality for all flags | ||||||||||||||||||||||||
| System.out.println("enableFeatureA flag key: " + EnableFeatureA.getKey()); | ||||||||||||||||||||||||
| System.out.println("discountPercentage flag key: " + DiscountPercentage.getKey()); | ||||||||||||||||||||||||
| System.out.println("greetingMessage flag key: " + GreetingMessage.getKey()); | ||||||||||||||||||||||||
| System.out.println("usernameMaxLength flag key: " + UsernameMaxLength.getKey()); | ||||||||||||||||||||||||
| System.out.println("themeCustomization flag key: " + ThemeCustomization.getKey()); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
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.
It's generally recommended to specify a full version tag for base images in production or CI environments to ensure reproducibility and prevent unexpected changes if
golang:1.24-alpineis updated to a newer minor version that introduces breaking changes. For example,golang:1.24.2-alpine.