Skip to content

Commit 0a9a3f5

Browse files
committed
feat: generating test index to docs
Signed-off-by: Attila Mészáros <[email protected]>
1 parent 3281072 commit 0a9a3f5

File tree

8 files changed

+279
-0
lines changed

8 files changed

+279
-0
lines changed

operator-framework/pom.xml

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,13 @@
8484
<artifactId>kube-api-test-client-inject</artifactId>
8585
<scope>test</scope>
8686
</dependency>
87+
<dependency>
88+
<groupId>io.javaoperatorsdk</groupId>
89+
<artifactId>sample-index-processor</artifactId>
90+
<version>${project.version}</version>
91+
<scope>test</scope>
92+
<optional>true</optional>
93+
</dependency>
8794
</dependencies>
8895

8996
<build>
@@ -106,6 +113,23 @@
106113
</compilerArgs>
107114
</configuration>
108115
</execution>
116+
<!-- Enable annotation processing for test compilation to generate samples.md -->
117+
<execution>
118+
<id>default-testCompile</id>
119+
<goals>
120+
<goal>testCompile</goal>
121+
</goals>
122+
<phase>test-compile</phase>
123+
<configuration>
124+
<annotationProcessorPaths>
125+
<path>
126+
<groupId>io.javaoperatorsdk</groupId>
127+
<artifactId>sample-index-processor</artifactId>
128+
<version>${project.version}</version>
129+
</path>
130+
</annotationProcessorPaths>
131+
</configuration>
132+
</execution>
109133
</executions>
110134
</plugin>
111135
<plugin>
@@ -138,6 +162,50 @@
138162
<groupId>org.apache.maven.plugins</groupId>
139163
<artifactId>maven-surefire-plugin</artifactId>
140164
</plugin>
165+
<plugin>
166+
<groupId>org.apache.maven.plugins</groupId>
167+
<artifactId>maven-resources-plugin</artifactId>
168+
<executions>
169+
<execution>
170+
<id>copy-samples-to-docs</id>
171+
<goals>
172+
<goal>copy-resources</goal>
173+
</goals>
174+
<phase>process-test-classes</phase>
175+
<configuration>
176+
<outputDirectory>${project.basedir}/../docs/content/en/docs/testindex</outputDirectory>
177+
<resources>
178+
<resource>
179+
<directory>${project.build.directory}/generated-test-sources/test-annotations</directory>
180+
<includes>
181+
<include>samples.md</include>
182+
</includes>
183+
<filtering>false</filtering>
184+
</resource>
185+
</resources>
186+
</configuration>
187+
</execution>
188+
</executions>
189+
</plugin>
190+
<plugin>
191+
<groupId>org.apache.maven.plugins</groupId>
192+
<artifactId>maven-antrun-plugin</artifactId>
193+
<version>3.1.0</version>
194+
<executions>
195+
<execution>
196+
<id>rename-samples-to-index</id>
197+
<goals>
198+
<goal>run</goal>
199+
</goals>
200+
<phase>process-test-classes</phase>
201+
<configuration>
202+
<target>
203+
<move failonerror="false" file="${project.basedir}/../docs/content/en/docs/testindex/samples.md" tofile="${project.basedir}/../docs/content/en/docs/testindex/_index.md"/>
204+
</target>
205+
</configuration>
206+
</execution>
207+
</executions>
208+
</plugin>
141209
</plugins>
142210
</build>
143211
</project>

operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/clusterscopedresource/ClusterScopedResourceIT.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,20 @@
77

88
import io.fabric8.kubernetes.api.model.ConfigMap;
99
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
10+
import io.javaoperatorsdk.annotation.Sample;
1011
import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension;
1112

1213
import static io.javaoperatorsdk.operator.IntegrationTestConstants.GARBAGE_COLLECTION_TIMEOUT_SECONDS;
1314
import static org.assertj.core.api.Assertions.assertThat;
1415
import static org.awaitility.Awaitility.await;
1516

17+
@Sample(
18+
tldr = "Cluster-scoped resource reconciliation",
19+
description =
20+
"Demonstrates how to reconcile cluster-scoped custom resources (non-namespaced). This"
21+
+ " test shows CRUD operations on cluster-scoped resources and verifies that"
22+
+ " dependent resources are created, updated, and properly cleaned up when the"
23+
+ " primary resource is deleted.")
1624
class ClusterScopedResourceIT {
1725

1826
public static final String TEST_NAME = "test1";

operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/simple/ReconcilerExecutorIT.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,20 @@
77
import org.junit.jupiter.api.extension.RegisterExtension;
88

99
import io.fabric8.kubernetes.api.model.ConfigMap;
10+
import io.javaoperatorsdk.annotation.Sample;
1011
import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension;
1112
import io.javaoperatorsdk.operator.support.TestUtils;
1213

1314
import static org.assertj.core.api.Assertions.assertThat;
1415
import static org.awaitility.Awaitility.await;
1516

17+
@Sample(
18+
tldr = "Basic reconciler execution",
19+
description =
20+
"Demonstrates the basic reconciler execution flow including resource creation, status"
21+
+ " updates, and cleanup. This test verifies that a reconciler can create dependent"
22+
+ " resources (ConfigMap), update status, and properly handle cleanup when resources"
23+
+ " are deleted.")
1624
class ReconcilerExecutorIT {
1725

1826
@RegisterExtension

pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
<module>sample-operators</module>
3737
<module>caffeine-bounded-cache-support</module>
3838
<module>bootstrapper-maven-plugin</module>
39+
<module>sample-index-processor</module>
3940
</modules>
4041

4142
<scm>

sample-index-processor/pom.xml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<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">
3+
<modelVersion>4.0.0</modelVersion>
4+
5+
<parent>
6+
<groupId>io.javaoperatorsdk</groupId>
7+
<artifactId>java-operator-sdk</artifactId>
8+
<version>5.1.6-SNAPSHOT</version>
9+
</parent>
10+
11+
<artifactId>sample-index-processor</artifactId>
12+
<name>Sample Index Annotation Processor</name>
13+
<description>Annotation processor for generating integration test sample documentation</description>
14+
15+
<dependencies>
16+
<!-- No dependencies needed for a simple annotation processor -->
17+
</dependencies>
18+
19+
<build>
20+
<plugins>
21+
<plugin>
22+
<groupId>org.apache.maven.plugins</groupId>
23+
<artifactId>maven-compiler-plugin</artifactId>
24+
<configuration>
25+
<!-- Disable annotation processing for the processor itself -->
26+
<compilerArgument>-proc:none</compilerArgument>
27+
</configuration>
28+
</plugin>
29+
</plugins>
30+
</build>
31+
</project>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package io.javaoperatorsdk.annotation;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
/**
9+
* Annotation to mark integration tests as samples for documentation generation. Tests annotated
10+
* with @Sample will be included in the generated samples.md documentation file.
11+
*/
12+
@Retention(RetentionPolicy.SOURCE)
13+
@Target(ElementType.TYPE)
14+
public @interface Sample {
15+
16+
/**
17+
* A short title describing the test sample.
18+
*
19+
* @return the short title
20+
*/
21+
String tldr();
22+
23+
/**
24+
* A detailed description of what the test does and demonstrates.
25+
*
26+
* @return the detailed description
27+
*/
28+
String description();
29+
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package io.javaoperatorsdk.processor;
2+
3+
import java.io.BufferedWriter;
4+
import java.io.IOException;
5+
import java.util.ArrayList;
6+
import java.util.Comparator;
7+
import java.util.List;
8+
import java.util.Set;
9+
10+
import javax.annotation.processing.AbstractProcessor;
11+
import javax.annotation.processing.RoundEnvironment;
12+
import javax.annotation.processing.SupportedAnnotationTypes;
13+
import javax.annotation.processing.SupportedSourceVersion;
14+
import javax.lang.model.SourceVersion;
15+
import javax.lang.model.element.Element;
16+
import javax.lang.model.element.TypeElement;
17+
import javax.tools.Diagnostic;
18+
import javax.tools.FileObject;
19+
import javax.tools.StandardLocation;
20+
21+
import io.javaoperatorsdk.annotation.Sample;
22+
23+
/**
24+
* Annotation processor that generates a samples.md file containing documentation for all
25+
* integration tests annotated with @Sample.
26+
*/
27+
@SupportedAnnotationTypes("io.javaoperatorsdk.annotation.Sample")
28+
@SupportedSourceVersion(SourceVersion.RELEASE_17)
29+
public class SampleProcessor extends AbstractProcessor {
30+
31+
private final List<SampleInfo> samples = new ArrayList<>();
32+
private boolean processed = false;
33+
34+
@Override
35+
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
36+
if (processed) {
37+
return true;
38+
}
39+
40+
// Collect all @Sample annotated elements
41+
for (Element element : roundEnv.getElementsAnnotatedWith(Sample.class)) {
42+
if (element instanceof TypeElement) {
43+
TypeElement typeElement = (TypeElement) element;
44+
Sample sample = element.getAnnotation(Sample.class);
45+
46+
String className = typeElement.getQualifiedName().toString();
47+
String simpleName = typeElement.getSimpleName().toString();
48+
String tldr = sample.tldr();
49+
String description = sample.description();
50+
51+
samples.add(new SampleInfo(className, simpleName, tldr, description));
52+
}
53+
}
54+
55+
// Generate the markdown file in the last round
56+
if (roundEnv.processingOver() && !samples.isEmpty()) {
57+
generateMarkdownFile();
58+
processed = true;
59+
}
60+
61+
return true;
62+
}
63+
64+
private void generateMarkdownFile() {
65+
try {
66+
// Sort samples by class name for consistent ordering
67+
samples.sort(Comparator.comparing(s -> s.className));
68+
69+
// Create the samples.md file in the source output location
70+
FileObject file =
71+
processingEnv.getFiler().createResource(StandardLocation.SOURCE_OUTPUT, "", "samples.md");
72+
73+
try (BufferedWriter writer = new BufferedWriter(file.openWriter())) {
74+
writer.write("---\n");
75+
writer.write("title: Test Index\n");
76+
writer.write("weight: 105\n");
77+
writer.write("---\n\n");
78+
writer.write(
79+
"This document provides an index of all integration tests annotated with @Sample.\n\n");
80+
writer.write("---\n\n");
81+
82+
for (SampleInfo sample : samples) {
83+
writer.write("## " + sample.simpleName + "\n\n");
84+
writer.write("**" + sample.tldr + "**\n\n");
85+
writer.write(sample.description + "\n\n");
86+
writer.write("**Package:** " + getGitHubPackageLink(sample.className) + "\n\n");
87+
writer.write("---\n\n");
88+
}
89+
}
90+
91+
processingEnv
92+
.getMessager()
93+
.printMessage(
94+
Diagnostic.Kind.NOTE, "Generated samples.md with " + samples.size() + " samples");
95+
} catch (IOException e) {
96+
processingEnv
97+
.getMessager()
98+
.printMessage(Diagnostic.Kind.ERROR, "Failed to generate samples.md: " + e.getMessage());
99+
}
100+
}
101+
102+
private String getGitHubPackageLink(String className) {
103+
// Extract package name by removing the simple class name
104+
int lastDot = className.lastIndexOf('.');
105+
if (lastDot == -1) {
106+
return "[root package]";
107+
}
108+
109+
String packageName = className.substring(0, lastDot);
110+
String packagePath = packageName.replace('.', '/');
111+
112+
// GitHub repository base URL
113+
String baseUrl = "https://github.com/operator-framework/java-operator-sdk/tree/main";
114+
String sourcePath = "operator-framework/src/test/java";
115+
116+
String githubUrl = baseUrl + "/" + sourcePath + "/" + packagePath;
117+
return "[" + packageName + "](" + githubUrl + ")";
118+
}
119+
120+
private static class SampleInfo {
121+
final String className;
122+
final String simpleName;
123+
final String tldr;
124+
final String description;
125+
126+
SampleInfo(String className, String simpleName, String tldr, String description) {
127+
this.className = className;
128+
this.simpleName = simpleName;
129+
this.tldr = tldr;
130+
this.description = description;
131+
}
132+
}
133+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
io.javaoperatorsdk.processor.SampleProcessor

0 commit comments

Comments
 (0)