Skip to content

Commit 42d5648

Browse files
committed
Fix JUnit 5 module
* Exercise tests on precommit * Publish javadoc
1 parent 17c8515 commit 42d5648

File tree

5 files changed

+72
-101
lines changed

5 files changed

+72
-101
lines changed

build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,7 @@ tasks.register("javaPreCommit") {
323323
dependsOn(":sdks:java:managed:build")
324324
dependsOn(":sdks:java:testing:expansion-service:build")
325325
dependsOn(":sdks:java:testing:jpms-tests:build")
326+
dependsOn(":sdks:java:testing:junit:build")
326327
dependsOn(":sdks:java:testing:load-tests:build")
327328
dependsOn(":sdks:java:testing:nexmark:build")
328329
dependsOn(":sdks:java:testing:test-utils:build")

sdks/java/core/src/main/java/org/apache/beam/sdk/testing/TestPipeline.java

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@
2424

2525
import com.fasterxml.jackson.databind.ObjectMapper;
2626
import java.io.IOException;
27+
import java.lang.annotation.Annotation;
2728
import java.util.ArrayList;
29+
import java.util.Collection;
2830
import java.util.List;
2931
import java.util.Map;
3032
import java.util.UUID;
@@ -296,6 +298,13 @@ public static TestPipeline create() {
296298
return fromOptions(testingPipelineOptions());
297299
}
298300

301+
/** */
302+
static TestPipeline createWithEnforcement() {
303+
TestPipeline p = create();
304+
305+
return p;
306+
}
307+
299308
public static TestPipeline fromOptions(PipelineOptions options) {
300309
return new TestPipeline(options);
301310
}
@@ -310,49 +319,55 @@ public PipelineOptions getOptions() {
310319
return this.options;
311320
}
312321

313-
@Override
314-
public Statement apply(final Statement statement, final Description description) {
315-
return new Statement() {
322+
// package private for JUnit5 TestPipelineExtension
323+
void setDeducedEnforcementLevel(Collection<Annotation> annotations) {
324+
// if the enforcement level has not been set by the user do auto-inference
325+
if (!enforcement.isPresent()) {
316326

317-
private void setDeducedEnforcementLevel() {
318-
// if the enforcement level has not been set by the user do auto-inference
319-
if (!enforcement.isPresent()) {
327+
final boolean annotatedWithNeedsRunner =
328+
FluentIterable.from(annotations)
329+
.filter(Annotations.Predicates.isAnnotationOfType(Category.class))
330+
.anyMatch(Annotations.Predicates.isCategoryOf(NeedsRunner.class, true));
320331

321-
final boolean annotatedWithNeedsRunner =
322-
FluentIterable.from(description.getAnnotations())
323-
.filter(Annotations.Predicates.isAnnotationOfType(Category.class))
324-
.anyMatch(Annotations.Predicates.isCategoryOf(NeedsRunner.class, true));
332+
final boolean crashingRunner = CrashingRunner.class.isAssignableFrom(options.getRunner());
325333

326-
final boolean crashingRunner = CrashingRunner.class.isAssignableFrom(options.getRunner());
334+
checkState(
335+
!(annotatedWithNeedsRunner && crashingRunner),
336+
"The test was annotated with a [@%s] / [@%s] while the runner "
337+
+ "was set to [%s]. Please re-check your configuration.",
338+
NeedsRunner.class.getSimpleName(),
339+
ValidatesRunner.class.getSimpleName(),
340+
CrashingRunner.class.getSimpleName());
327341

328-
checkState(
329-
!(annotatedWithNeedsRunner && crashingRunner),
330-
"The test was annotated with a [@%s] / [@%s] while the runner "
331-
+ "was set to [%s]. Please re-check your configuration.",
332-
NeedsRunner.class.getSimpleName(),
333-
ValidatesRunner.class.getSimpleName(),
334-
CrashingRunner.class.getSimpleName());
342+
enableAbandonedNodeEnforcement(annotatedWithNeedsRunner || !crashingRunner);
343+
}
344+
}
335345

336-
enableAbandonedNodeEnforcement(annotatedWithNeedsRunner || !crashingRunner);
337-
}
338-
}
346+
// package private for JUnit5 TestPipelineExtension
347+
void afterUserCodeFinished() {
348+
enforcement.get().afterUserCodeFinished();
349+
}
350+
351+
@Override
352+
public Statement apply(final Statement statement, final Description description) {
353+
return new Statement() {
339354

340355
@Override
341356
public void evaluate() throws Throwable {
342357
options.as(ApplicationNameOptions.class).setAppName(getAppName(description));
343358

344-
setDeducedEnforcementLevel();
359+
setDeducedEnforcementLevel(description.getAnnotations());
345360

346361
// statement.evaluate() essentially runs the user code contained in the unit test at hand.
347362
// Exceptions thrown during the execution of the user's test code will propagate here,
348363
// unless the user explicitly handles them with a "catch" clause in his code. If the
349-
// exception is handled by a user's "catch" clause, is does not interrupt the flow and
364+
// exception is handled by a user's "catch" clause, it does not interrupt the flow, and
350365
// we move on to invoking the configured enforcements.
351366
// If the user does not handle a thrown exception, it will propagate here and interrupt
352367
// the flow, preventing the enforcement(s) from being activated.
353368
// The motivation for this is avoiding enforcements over faulty pipelines.
354369
statement.evaluate();
355-
enforcement.get().afterUserCodeFinished();
370+
afterUserCodeFinished();
356371
}
357372
};
358373
}

sdks/java/testing/junit/build.gradle

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
plugins { id 'org.apache.beam.module' }
2020

2121
applyJavaNature(
22-
exportJavadoc: false,
2322
automaticModuleName: 'org.apache.beam.sdk.testing.junit',
2423
archivesBaseName: 'beam-sdks-java-testing-junit'
2524
)
@@ -37,7 +36,6 @@ dependencies {
3736
// JUnit 5 API needed to compile the extension; not packaged for consumers of core.
3837
provided library.java.jupiter_api
3938

40-
testImplementation project(path: ":sdks:java:core", configuration: "shadow")
4139
testImplementation library.java.jupiter_api
4240
testImplementation library.java.junit
4341
testRuntimeOnly library.java.jupiter_engine

sdks/java/testing/junit/src/main/java/org/apache/beam/sdk/testing/TestPipelineExtension.java

Lines changed: 27 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,17 @@
1717
*/
1818
package org.apache.beam.sdk.testing;
1919

20-
import static org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.base.Preconditions.checkState;
20+
import static org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.base.Preconditions.checkNotNull;
2121

2222
import java.lang.annotation.Annotation;
2323
import java.lang.reflect.Method;
24-
import java.util.Arrays;
24+
import java.util.Collection;
2525
import java.util.Optional;
2626
import org.apache.beam.sdk.options.ApplicationNameOptions;
2727
import org.apache.beam.sdk.options.PipelineOptions;
28-
import org.apache.beam.sdk.testing.TestPipeline.PipelineAbandonedNodeEnforcement;
2928
import org.apache.beam.sdk.testing.TestPipeline.PipelineRunEnforcement;
30-
import org.junit.experimental.categories.Category;
29+
import org.apache.beam.vendor.grpc.v1p69p0.com.google.common.collect.ImmutableList;
30+
import org.checkerframework.checker.nullness.qual.Nullable;
3131
import org.junit.jupiter.api.extension.AfterEachCallback;
3232
import org.junit.jupiter.api.extension.BeforeEachCallback;
3333
import org.junit.jupiter.api.extension.ExtensionContext;
@@ -86,16 +86,16 @@ public static TestPipelineExtension fromOptions(PipelineOptions options) {
8686
return new TestPipelineExtension(options);
8787
}
8888

89-
private TestPipeline testPipeline;
89+
private @Nullable PipelineOptions options;
9090

9191
/** Creates a TestPipelineExtension with default options. */
9292
public TestPipelineExtension() {
93-
this.testPipeline = TestPipeline.create();
93+
this.options = null;
9494
}
9595

9696
/** Creates a TestPipelineExtension with custom options. */
9797
public TestPipelineExtension(PipelineOptions options) {
98-
this.testPipeline = TestPipeline.fromOptions(options);
98+
this.options = options;
9999
}
100100

101101
@Override
@@ -107,43 +107,38 @@ public boolean supportsParameter(
107107
@Override
108108
public Object resolveParameter(
109109
ParameterContext parameterContext, ExtensionContext extensionContext) {
110-
if (this.testPipeline == null) {
111-
return getOrCreateTestPipeline(extensionContext);
112-
} else {
113-
return this.testPipeline;
114-
}
110+
return getOrCreateTestPipeline(extensionContext);
115111
}
116112

117113
@Override
118-
public void beforeEach(ExtensionContext context) throws Exception {
119-
TestPipeline pipeline;
120-
121-
if (this.testPipeline != null) {
122-
pipeline = this.testPipeline;
123-
} else {
124-
pipeline = getOrCreateTestPipeline(context);
125-
}
114+
public void beforeEach(ExtensionContext context) {
115+
TestPipeline pipeline = getOrCreateTestPipeline(context);
126116

127117
// Set application name based on test method
128118
String appName = getAppName(context);
129119
pipeline.getOptions().as(ApplicationNameOptions.class).setAppName(appName);
130120

131121
// Set up enforcement based on annotations
132-
setDeducedEnforcementLevel(context, pipeline);
122+
pipeline.setDeducedEnforcementLevel(getAnnotations(context));
133123
}
134124

135125
@Override
136-
public void afterEach(ExtensionContext context) throws Exception {
137-
Optional<PipelineRunEnforcement> enforcement = getEnforcement(context);
138-
if (enforcement.isPresent()) {
139-
enforcement.get().afterUserCodeFinished();
140-
}
126+
public void afterEach(ExtensionContext context) {
127+
TestPipeline pipeline = getRequiredTestPipeline(context);
128+
pipeline.afterUserCodeFinished();
141129
}
142130

143131
private TestPipeline getOrCreateTestPipeline(ExtensionContext context) {
144132
return context
145133
.getStore(NAMESPACE)
146-
.getOrComputeIfAbsent(PIPELINE_KEY, key -> TestPipeline.create(), TestPipeline.class);
134+
.getOrComputeIfAbsent(
135+
PIPELINE_KEY,
136+
key -> options == null ? TestPipeline.create() : TestPipeline.fromOptions(options),
137+
TestPipeline.class);
138+
}
139+
140+
private TestPipeline getRequiredTestPipeline(ExtensionContext context) {
141+
return checkNotNull(context.getStore(NAMESPACE).get(PIPELINE_KEY, TestPipeline.class));
147142
}
148143

149144
private Optional<PipelineRunEnforcement> getEnforcement(ExtensionContext context) {
@@ -161,53 +156,10 @@ private String getAppName(ExtensionContext context) {
161156
return className + "-" + methodName;
162157
}
163158

164-
private void setDeducedEnforcementLevel(ExtensionContext context, TestPipeline pipeline) {
165-
// If enforcement level has not been set, do auto-inference
166-
if (!getEnforcement(context).isPresent()) {
167-
boolean annotatedWithNeedsRunner = hasNeedsRunnerAnnotation(context);
168-
169-
PipelineOptions options = pipeline.getOptions();
170-
boolean crashingRunner = CrashingRunner.class.isAssignableFrom(options.getRunner());
171-
172-
checkState(
173-
!(annotatedWithNeedsRunner && crashingRunner),
174-
"The test was annotated with a [@%s] / [@%s] while the runner "
175-
+ "was set to [%s]. Please re-check your configuration.",
176-
NeedsRunner.class.getSimpleName(),
177-
ValidatesRunner.class.getSimpleName(),
178-
CrashingRunner.class.getSimpleName());
179-
180-
if (annotatedWithNeedsRunner || !crashingRunner) {
181-
setEnforcement(context, new PipelineAbandonedNodeEnforcement(pipeline));
182-
}
183-
}
184-
}
185-
186-
private boolean hasNeedsRunnerAnnotation(ExtensionContext context) {
187-
// Check method annotations
188-
Method testMethod = context.getTestMethod().orElse(null);
189-
if (testMethod != null) {
190-
if (hasNeedsRunnerCategory(testMethod.getAnnotations())) {
191-
return true;
192-
}
193-
}
194-
195-
// Check class annotations
196-
Class<?> testClass = context.getTestClass().orElse(null);
197-
if (testClass != null) {
198-
if (hasNeedsRunnerCategory(testClass.getAnnotations())) {
199-
return true;
200-
}
201-
}
202-
203-
return false;
204-
}
205-
206-
private boolean hasNeedsRunnerCategory(Annotation[] annotations) {
207-
return Arrays.stream(annotations)
208-
.filter(annotation -> annotation instanceof Category)
209-
.map(annotation -> (Category) annotation)
210-
.flatMap(category -> Arrays.stream(category.value()))
211-
.anyMatch(categoryClass -> NeedsRunner.class.isAssignableFrom(categoryClass));
159+
private static Collection<Annotation> getAnnotations(ExtensionContext context) {
160+
ImmutableList.Builder<Annotation> builder = ImmutableList.builder();
161+
context.getTestMethod().ifPresent(testMethod -> builder.add(testMethod.getAnnotations()));
162+
context.getTestClass().ifPresent(testClass -> builder.add(testClass.getAnnotations()));
163+
return builder.build();
212164
}
213165
}

sdks/java/testing/junit/src/test/java/org/apache/beam/sdk/testing/TestPipelineExtensionTest.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717
*/
1818
package org.apache.beam.sdk.testing;
1919

20+
import static org.junit.jupiter.api.Assertions.assertEquals;
2021
import static org.junit.jupiter.api.Assertions.assertNotNull;
2122

23+
import org.apache.beam.sdk.options.ApplicationNameOptions;
2224
import org.apache.beam.sdk.transforms.Create;
2325
import org.apache.beam.sdk.values.PCollection;
2426
import org.junit.jupiter.api.Test;
@@ -33,6 +35,9 @@ public void testPipelineInjection(TestPipeline pipeline) {
3335
// Verify that the pipeline is injected and not null
3436
assertNotNull(pipeline);
3537
assertNotNull(pipeline.getOptions());
38+
assertEquals(
39+
"TestPipelineExtensionTest-testPipelineInjection",
40+
pipeline.getOptions().as(ApplicationNameOptions.class).getAppName());
3641
}
3742

3843
@Test

0 commit comments

Comments
 (0)