Skip to content

Commit 95dd61e

Browse files
chennu2020cmaddela
authored andcommitted
JUnit5 support (apache#35688)
* JUnit 5 support. --------- Co-authored-by: cmaddela <chennakeshavlu.maddela@davita.com>
1 parent 884ae58 commit 95dd61e

File tree

11 files changed

+482
-9
lines changed

11 files changed

+482
-9
lines changed

CHANGES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@
7474

7575
* X feature added (Java/Python) ([#X](https://github.com/apache/beam/issues/X)).
7676
* BigtableRead Connector for BeamYaml added with new Config Param ([#35696](https://github.com/apache/beam/pull/35696))
77+
* Introduced a dedicated module for JUnit-based testing support: `sdks/java/testing/junit`, which provides `TestPipelineExtension` for JUnit 5 while maintaining backward compatibility with existing JUnit 4 `TestRule`-based tests (Java) ([#18733](https://github.com/apache/beam/issues/18733), [#35688](https://github.com/apache/beam/pull/35688)).
78+
- To use JUnit 5 with Beam tests, add a test-scoped dependency on `org.apache.beam:beam-sdks-java-testing-junit`.
7779

7880
## Breaking Changes
7981

sdks/java/core/build.gradle

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,11 @@ project.tasks.compileTestJava {
130130
// TODO: fix other places with warnings in tests and delete this option
131131
options.compilerArgs += ['-Xlint:-rawtypes']
132132
}
133+
134+
// Configure test task to use JUnit 4. JUnit 5 support is provided in module
135+
// sdks/java/testing/junit, which configures useJUnitPlatform(). Submodules that
136+
// need to run both JUnit 4 and 5 via the JUnit Platform must also add the
137+
// Vintage engine explicitly.
138+
test {
139+
useJUnit()
140+
}

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

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,11 @@
8282
* </ul>
8383
*
8484
* <p>Use {@link PAssert} for tests, as it integrates with this test harness in both direct and
85-
* remote execution modes. For example:
85+
* remote execution modes.
86+
*
87+
* <h3>JUnit 4 Usage</h3>
88+
*
89+
* For JUnit 4 tests, use this class as a TestRule:
8690
*
8791
* <pre><code>
8892
* {@literal @Rule}
@@ -97,6 +101,25 @@
97101
* }
98102
* </code></pre>
99103
*
104+
* <h3>JUnit5 Usage</h3>
105+
*
106+
* For JUnit5 tests, use {@link TestPipelineExtension} from the module <code>
107+
* sdks/java/testing/junit</code> (artifact <code>org.apache.beam:beam-sdks-java-testing-junit
108+
* </code>):
109+
*
110+
* <pre><code>
111+
* {@literal @ExtendWith}(TestPipelineExtension.class)
112+
* class MyPipelineTest {
113+
* {@literal @Test}
114+
* {@literal @Category}(NeedsRunner.class)
115+
* void myPipelineTest(TestPipeline pipeline) {
116+
* final PCollection&lt;String&gt; pCollection = pipeline.apply(...)
117+
* PAssert.that(pCollection).containsInAnyOrder(...);
118+
* pipeline.run();
119+
* }
120+
* }
121+
* </code></pre>
122+
*
100123
* <p>For pipeline runners, it is required that they must throw an {@link AssertionError} containing
101124
* the message from the {@link PAssert} that failed.
102125
*
@@ -108,7 +131,7 @@ public class TestPipeline extends Pipeline implements TestRule {
108131

109132
private final PipelineOptions options;
110133

111-
private static class PipelineRunEnforcement {
134+
static class PipelineRunEnforcement {
112135

113136
@SuppressWarnings("WeakerAccess")
114137
protected boolean enableAutoRunIfMissing;
@@ -117,7 +140,7 @@ private static class PipelineRunEnforcement {
117140

118141
protected boolean runAttempted;
119142

120-
private PipelineRunEnforcement(final Pipeline pipeline) {
143+
PipelineRunEnforcement(final Pipeline pipeline) {
121144
this.pipeline = pipeline;
122145
}
123146

@@ -138,7 +161,7 @@ protected void afterUserCodeFinished() {
138161
}
139162
}
140163

141-
private static class PipelineAbandonedNodeEnforcement extends PipelineRunEnforcement {
164+
static class PipelineAbandonedNodeEnforcement extends PipelineRunEnforcement {
142165

143166
// Null until the pipeline has been run
144167
private @MonotonicNonNull List<TransformHierarchy.Node> runVisitedNodes;
@@ -164,7 +187,7 @@ public void visitPrimitiveTransform(final TransformHierarchy.Node node) {
164187
}
165188
}
166189

167-
private PipelineAbandonedNodeEnforcement(final TestPipeline pipeline) {
190+
PipelineAbandonedNodeEnforcement(final TestPipeline pipeline) {
168191
super(pipeline);
169192
runVisitedNodes = null;
170193
}
@@ -574,7 +597,7 @@ public static void verifyPAssertsSucceeded(Pipeline pipeline, PipelineResult pip
574597
}
575598
}
576599

577-
private static class IsEmptyVisitor extends PipelineVisitor.Defaults {
600+
static class IsEmptyVisitor extends PipelineVisitor.Defaults {
578601
private boolean empty = true;
579602

580603
public boolean isEmpty() {

sdks/java/core/src/test/java/org/apache/beam/sdk/io/TextIOWriteTest.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -731,11 +731,20 @@ public void testWriteUnboundedWithCustomBatchParameters() throws Exception {
731731
input.apply(write);
732732
p.run();
733733

734+
// On some environments/runners, the exact shard filenames may not be materialized
735+
// deterministically by the time we assert. Verify shard count via a glob, then
736+
// validate contents using pattern matching.
737+
String pattern = baseFilename.toString() + "*";
738+
List<MatchResult> matches = FileSystems.match(Collections.singletonList(pattern));
739+
List<Metadata> found = new ArrayList<>(Iterables.getOnlyElement(matches).metadata());
740+
assertEquals(3, found.size());
741+
742+
// Now assert file contents irrespective of exact shard indices.
734743
assertOutputFiles(
735744
LINES2_ARRAY,
736745
null,
737746
null,
738-
3,
747+
0, // match all files by prefix
739748
baseFilename,
740749
DefaultFilenamePolicy.DEFAULT_UNWINDOWED_SHARD_TEMPLATE,
741750
false);

sdks/java/harness/src/test/java/org/apache/beam/fn/harness/control/ExecutionStateSamplerTest.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -809,8 +809,12 @@ public Long answer(InvocationOnMock invocation) throws Throwable {
809809
// and unblock the state transition once a certain number of samples
810810
// have been taken.
811811
waitTillActive.await();
812-
waitForSamples.countDown();
813-
currentTime += Duration.standardMinutes(1).getMillis();
812+
// Freeze time after the desired number of samples to avoid races where
813+
// the sampling loop spins and exceeds the timeout before we deactivate.
814+
if (waitForSamples.getCount() > 0) {
815+
waitForSamples.countDown();
816+
currentTime += Duration.standardMinutes(1).getMillis();
817+
}
814818
return currentTime;
815819
}
816820
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* License); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
plugins { id 'org.apache.beam.module' }
20+
21+
applyJavaNature(
22+
exportJavadoc: false,
23+
automaticModuleName: 'org.apache.beam.sdk.testing.junit',
24+
archivesBaseName: 'beam-sdks-java-testing-junit'
25+
)
26+
27+
description = "Apache Beam :: SDKs :: Java :: Testing :: JUnit"
28+
29+
dependencies {
30+
implementation enforcedPlatform(library.java.google_cloud_platform_libraries_bom)
31+
implementation project(path: ":sdks:java:core", configuration: "shadow")
32+
implementation library.java.vendored_guava_32_1_2_jre
33+
// Needed to resolve TestPipeline's JUnit 4 TestRule type and @Category at compile time,
34+
// but should not leak to consumers at runtime.
35+
provided library.java.junit
36+
37+
// JUnit 5 API needed to compile the extension; not packaged for consumers of core.
38+
provided library.java.jupiter_api
39+
40+
testImplementation project(path: ":sdks:java:core", configuration: "shadow")
41+
testImplementation library.java.jupiter_api
42+
testImplementation library.java.junit
43+
testRuntimeOnly library.java.jupiter_engine
44+
testRuntimeOnly project(path: ":runners:direct-java", configuration: "shadow")
45+
}
46+
47+
// This module runs JUnit 5 tests using the JUnit Platform.
48+
test {
49+
useJUnitPlatform()
50+
}

0 commit comments

Comments
 (0)