Skip to content

Commit c726a6b

Browse files
authored
Fix tests that flake when building in parallel (#3124)
1 parent 3fb998e commit c726a6b

7 files changed

Lines changed: 118 additions & 22 deletions

File tree

tika-grpc/src/test/java/org/apache/tika/pipes/grpc/TikaGrpcServerTest.java

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
import java.util.UUID;
4040
import java.util.concurrent.atomic.AtomicBoolean;
4141
import java.util.concurrent.atomic.AtomicInteger;
42+
import java.util.stream.Collectors;
43+
import java.util.stream.Stream;
4244

4345
import com.asarkar.grpc.test.GrpcCleanupExtension;
4446
import com.asarkar.grpc.test.Resources;
@@ -55,6 +57,7 @@
5557
import org.apache.commons.io.FileUtils;
5658
import org.jetbrains.annotations.NotNull;
5759
import org.junit.jupiter.api.AfterAll;
60+
import org.junit.jupiter.api.AfterEach;
5861
import org.junit.jupiter.api.Assertions;
5962
import org.junit.jupiter.api.BeforeAll;
6063
import org.junit.jupiter.api.Test;
@@ -141,6 +144,47 @@ static void clean() {
141144
}
142145
}
143146

147+
private final List<TikaGrpcServerImpl> services = new ArrayList<>();
148+
149+
/**
150+
* Every service built here is closed by {@link #closeServices()}. Only the manager that
151+
* started a pipes-server child can delete that child's temp dir, so a service the test
152+
* drops on the floor -- including on an assertion failure -- orphans it.
153+
*/
154+
private TikaGrpcServerImpl newService(Path config) throws Exception {
155+
TikaGrpcServerImpl service = new TikaGrpcServerImpl(config.toAbsolutePath().toString());
156+
services.add(service);
157+
return service;
158+
}
159+
160+
@AfterEach
161+
void closeServices() throws Exception {
162+
for (TikaGrpcServerImpl service : services) {
163+
//both are idempotent, so a test that already shut down in-line is fine
164+
service.shutdown();
165+
service.postShutdown();
166+
}
167+
services.clear();
168+
assertNoOrphanedServerTempDirs();
169+
}
170+
171+
/**
172+
* Fails the test that leaked rather than leaving it for whoever notices the temp dir later.
173+
* Reliable because surefire gives each module its own java.io.tmpdir and runs these
174+
* classes one at a time.
175+
*/
176+
private static void assertNoOrphanedServerTempDirs() throws Exception {
177+
Path tmp = Paths.get(System.getProperty("java.io.tmpdir"));
178+
try (Stream<Path> paths = Files.list(tmp)) {
179+
List<String> orphans = paths
180+
.map(p -> p.getFileName().toString())
181+
.filter(n -> n.startsWith("pipes-server-"))
182+
.sorted()
183+
.collect(Collectors.toList());
184+
assertTrue(orphans.isEmpty(), "orphaned pipes-server temp dirs: " + orphans);
185+
}
186+
}
187+
144188
static final int NUM_FETCHERS_TO_CREATE = 10;
145189

146190
@Test
@@ -150,7 +194,7 @@ public void testFetcherCrud(Resources resources) throws Exception {
150194
Server server = InProcessServerBuilder
151195
.forName(serverName)
152196
.directExecutor()
153-
.addService(new TikaGrpcServerImpl(tikaConfigUnlocked.toAbsolutePath().toString()))
197+
.addService(newService(tikaConfigUnlocked))
154198
.build()
155199
.start();
156200
resources.register(server, Duration.ofSeconds(10));
@@ -451,13 +495,14 @@ public void testSavePipesIteratorValidatesType(Resources resources) throws Excep
451495
assertNotNull(reply.getMessage());
452496
}
453497

454-
private static TikaGrpc.TikaBlockingStub startServer(Resources resources, Path config)
498+
//non-static: newService tracks the service on the per-test instance so it gets closed
499+
private TikaGrpc.TikaBlockingStub startServer(Resources resources, Path config)
455500
throws Exception {
456501
String serverName = InProcessServerBuilder.generateName();
457502
Server server = InProcessServerBuilder
458503
.forName(serverName)
459504
.directExecutor()
460-
.addService(new TikaGrpcServerImpl(config.toAbsolutePath().toString()))
505+
.addService(newService(config))
461506
.build()
462507
.start();
463508
resources.register(server, Duration.ofSeconds(10));
@@ -481,7 +526,7 @@ public void testServerSideStreamingSendsTerminalSignal(Resources resources) thro
481526
Server server = InProcessServerBuilder
482527
.forName(serverName)
483528
.directExecutor()
484-
.addService(new TikaGrpcServerImpl(tikaConfigUnlocked.toAbsolutePath().toString()))
529+
.addService(newService(tikaConfigUnlocked))
485530
.build()
486531
.start();
487532
resources.register(server, Duration.ofSeconds(10));
@@ -544,7 +589,7 @@ public void onCompleted() {
544589
public void testBiStream(Resources resources) throws Exception {
545590
String serverName = InProcessServerBuilder.generateName();
546591

547-
TikaGrpcServerImpl tikaGrpcServerImpl = new TikaGrpcServerImpl(tikaConfigUnlocked.toAbsolutePath().toString());
592+
TikaGrpcServerImpl tikaGrpcServerImpl = newService(tikaConfigUnlocked);
548593
Server server = InProcessServerBuilder
549594
.forName(serverName)
550595
.directExecutor()

tika-parent/pom.xml

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,13 @@
310310
<puppycrawl.version>12.3.1</puppycrawl.version>
311311
<rat.version>0.18</rat.version>
312312
<scm.version>2.2.1</scm.version>
313+
<!-- Per-module temp dir for the surefire JVM. Keeps one module's temp files out of
314+
every other module's view: under -T1C the modules share one java.io.tmpdir, and
315+
a test that counts apache-tika-* there races whatever else is building
316+
(TestContainerAwareDetector.testRemovalTempfiles flaked exactly this way).
317+
Created by the create-test-tmpdir antrun execution below, because the JVM will
318+
not create a missing java.io.tmpdir; it throws NoSuchFileException. -->
319+
<tika.test.tmpdir>${project.build.directory}/test-tmp</tika.test.tmpdir>
313320
<checkstyle.configLocation>${maven.multiModuleProjectDirectory}/tika-parent/checkstyle.xml</checkstyle.configLocation>
314321
<spotless.goal>apply</spotless.goal>
315322
<spotless.header.file>${maven.multiModuleProjectDirectory}/tika-parent/license-header.txt</spotless.header.file>
@@ -1506,9 +1513,34 @@
15061513
<configuration>
15071514
<!-- for manual testing of i18n, try for example: -Duser.language=zh -Duser.region=CN or
15081515
-Duser.language=de -Duser.country=DE -->
1509-
<argLine>-Xmx4g -Djava.awt.headless=true @{surefireArgLine}</argLine>
1516+
<!-- java.io.tmpdir MUST be quoted: argLine is split on whitespace, and the Windows
1517+
job deliberately checks out into a path containing a space. It must also be a
1518+
real -D on the command line, not <systemPropertyVariables>: Files.createTempFile
1519+
reads the startup value, so a System.setProperty after JVM start is a no-op. -->
1520+
<argLine>-Xmx4g -Djava.awt.headless=true "-Djava.io.tmpdir=${tika.test.tmpdir}" @{surefireArgLine}</argLine>
15101521
</configuration>
15111522
</plugin>
1523+
<plugin>
1524+
<groupId>org.apache.maven.plugins</groupId>
1525+
<artifactId>maven-antrun-plugin</artifactId>
1526+
<version>${maven.antrun.version}</version>
1527+
<executions>
1528+
<execution>
1529+
<id>create-test-tmpdir</id>
1530+
<phase>process-test-resources</phase>
1531+
<goals>
1532+
<goal>run</goal>
1533+
</goals>
1534+
<configuration>
1535+
<target>
1536+
<!-- delete first so an incremental (unclean) build still starts empty -->
1537+
<delete dir="${tika.test.tmpdir}" />
1538+
<mkdir dir="${tika.test.tmpdir}" />
1539+
</target>
1540+
</configuration>
1541+
</execution>
1542+
</executions>
1543+
</plugin>
15121544
<plugin>
15131545
<groupId>org.apache.maven.plugins</groupId>
15141546
<artifactId>maven-shade-plugin</artifactId>

tika-parsers/tika-parsers-extended/tika-parser-scientific-module/pom.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,8 @@ May the gods of dependency management fix this in the future.
111111
<plugin>
112112
<artifactId>maven-surefire-plugin</artifactId>
113113
<configuration>
114-
<argLine>-Dcom.google.protobuf.use_unsafe_pre22_gencode</argLine>
114+
<!-- this argLine replaces tika-parent's, so it has to repeat the tmpdir isolation -->
115+
<argLine>-Dcom.google.protobuf.use_unsafe_pre22_gencode "-Djava.io.tmpdir=${tika.test.tmpdir}"</argLine>
115116
</configuration>
116117
</plugin>
117118
<plugin>

tika-parsers/tika-parsers-standard/tika-parsers-standard-integration-tests/src/test/java/org/apache/tika/detect/TestContainerAwareDetector.java

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,16 @@
3030
import java.nio.file.Path;
3131
import java.util.ArrayList;
3232
import java.util.List;
33-
import java.util.Objects;
3433
import java.util.Random;
34+
import java.util.stream.Stream;
3535

3636
import org.apache.commons.io.IOUtils;
3737
import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream;
3838
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
3939
import org.junit.jupiter.api.AfterEach;
4040
import org.junit.jupiter.api.Disabled;
4141
import org.junit.jupiter.api.Test;
42+
import org.junit.jupiter.api.io.TempDir;
4243

4344
import org.apache.tika.MultiThreadedTikaTest;
4445
import org.apache.tika.Tika;
@@ -49,6 +50,7 @@
4950
import org.apache.tika.detect.zip.StreamingZipContainerDetector;
5051
import org.apache.tika.detect.zip.ZipContainerDetector;
5152
import org.apache.tika.exception.TikaException;
53+
import org.apache.tika.io.TemporaryResources;
5254
import org.apache.tika.io.TikaInputStream;
5355
import org.apache.tika.metadata.HttpHeaders;
5456
import org.apache.tika.metadata.Metadata;
@@ -71,6 +73,8 @@ public class TestContainerAwareDetector extends MultiThreadedTikaTest {
7173
private final StreamingZipContainerDetector streamingZipDetector =
7274
new StreamingZipContainerDetector();
7375

76+
@TempDir
77+
private Path tempDir;
7478

7579
@AfterEach
7680
public void tearDown() throws TikaException {
@@ -416,23 +420,33 @@ public void testRemovalTempfiles() throws Exception {
416420
assertRemovalTempfiles("test-documents.zip");
417421
}
418422

419-
private int countTemporaryFiles() {
420-
//TODO: fix this. This can prevent multiple parallel builds
421-
//from running at the same time because there can be more than one
422-
//process writing to apache-tika-*
423-
return Objects.requireNonNull(new File(System.getProperty("java.io.tmpdir"))
424-
.listFiles((dir, name) -> name.startsWith("apache-tika-"))).length;
423+
/**
424+
* Counts spool files in {@link #tempDir}. The shared java.io.tmpdir cannot be counted
425+
* reliably: parallel module builds (-T1C) have other JVMs writing apache-tika-* there.
426+
*/
427+
private int countTemporaryFiles() throws IOException {
428+
try (Stream<Path> files = Files.list(tempDir)) {
429+
return (int) files.count();
430+
}
425431
}
426432

427433
private void assertRemovalTempfiles(String fileName) throws Exception {
428-
int numberOfTempFiles = countTemporaryFiles();
429-
430-
try (TikaInputStream tis = TikaInputStream
431-
.get(getResourceAsUrl("/test-documents/" + fileName))) {
432-
detector.detect(tis, new Metadata(), new ParseContext());
434+
Metadata metadata = new Metadata();
435+
metadata.set(TikaCoreProperties.RESOURCE_NAME_KEY, fileName);
436+
TemporaryResources tmp = new TemporaryResources();
437+
tmp.setTemporaryFileDirectory(tempDir);
438+
439+
try (InputStream is = getClass().getResourceAsStream("/test-documents/" + fileName)) {
440+
assertNotNull(is);
441+
try (TikaInputStream tis = TikaInputStream.get(is, tmp, metadata)) {
442+
detector.detect(tis, metadata, new ParseContext());
443+
//force the spool so this test can't pass vacuously
444+
tis.getPath();
445+
assertEquals(1, countTemporaryFiles());
446+
}
433447
}
434448

435-
assertEquals(numberOfTempFiles, countTemporaryFiles());
449+
assertEquals(0, countTemporaryFiles());
436450
}
437451

438452
@Test

tika-pipes/tika-pipes-config-store-ignite/pom.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,9 @@
144144
<groupId>org.apache.maven.plugins</groupId>
145145
<artifactId>maven-surefire-plugin</artifactId>
146146
<configuration>
147+
<!-- this argLine replaces tika-parent's, so it has to repeat the tmpdir isolation -->
147148
<argLine>
149+
"-Djava.io.tmpdir=${tika.test.tmpdir}"
148150
--add-opens java.base/java.nio=ALL-UNNAMED
149151
--add-opens java.base/java.util=ALL-UNNAMED
150152
--add-opens java.base/java.lang=ALL-UNNAMED

tika-server/tika-server-core/pom.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,8 @@
152152
<artifactId>maven-surefire-plugin</artifactId>
153153
<configuration>
154154
<redirectTestOutputToFile>true</redirectTestOutputToFile>
155-
<argLine>-da -XX:+HeapDumpOnOutOfMemoryError -Xmx512m</argLine>
155+
<!-- this argLine replaces tika-parent's, so it has to repeat the tmpdir isolation -->
156+
<argLine>-da -XX:+HeapDumpOnOutOfMemoryError -Xmx512m "-Djava.io.tmpdir=${tika.test.tmpdir}"</argLine>
156157
<systemPropertyVariables>
157158
<java.util.logging.config.file>
158159
${basedir}/src/main/resources/commons-logging.properties

tika-server/tika-server-standard/pom.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,8 @@
122122
<artifactId>maven-surefire-plugin</artifactId>
123123
<configuration>
124124
<redirectTestOutputToFile>true</redirectTestOutputToFile>
125-
<argLine>-da -XX:+HeapDumpOnOutOfMemoryError -Xmx512m</argLine>
125+
<!-- this argLine replaces tika-parent's, so it has to repeat the tmpdir isolation -->
126+
<argLine>-da -XX:+HeapDumpOnOutOfMemoryError -Xmx512m "-Djava.io.tmpdir=${tika.test.tmpdir}"</argLine>
126127
<systemPropertyVariables>
127128
<java.util.logging.config.file>
128129
${basedir}/src/main/resources/log4j2.xml

0 commit comments

Comments
 (0)