This is an automated email from the ASF dual-hosted git repository. tballison pushed a commit to branch TIKA-4809-stage-1 in repository https://gitbox.apache.org/repos/asf/tika.git
commit 0df56b8e26310ec5dd0e8955a0a9992980414075 Author: tallison <[email protected]> AuthorDate: Fri Aug 7 10:19:28 2026 -0400 TIKA-4809: Fix IntegrationTestBase shared static port and TempDir lifecycle --- .../tika/server/core/IntegrationTestBase.java | 29 +++++++------ .../core/TikaServerPipesIntegrationTest.java | 16 +++++-- .../src/test/resources/junit-platform.properties | 50 ++++++++++++++++++++++ 3 files changed, 78 insertions(+), 17 deletions(-) diff --git a/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/IntegrationTestBase.java b/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/IntegrationTestBase.java index 19ee324ef0..e5f644188f 100644 --- a/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/IntegrationTestBase.java +++ b/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/IntegrationTestBase.java @@ -17,9 +17,7 @@ package org.apache.tika.server.core; import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.StandardCopyOption; import java.time.Duration; import java.time.Instant; import java.util.ArrayList; @@ -33,12 +31,17 @@ import org.apache.cxf.common.logging.LogUtils; import org.apache.cxf.jaxrs.client.WebClient; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.io.TempDir; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.tika.TikaTest; +// PER_CLASS so subclasses' state (notably TEMP_WORKING_DIR below) is isolated +// per test class instead of shared via one static field on this common base -- +// a prerequisite for running subclasses' test classes concurrently. +@TestInstance(TestInstance.Lifecycle.PER_CLASS) public class IntegrationTestBase extends TikaTest { static final String TEST_HELLO_WORLD = "test-documents/mock/hello_world.xml"; @@ -52,24 +55,24 @@ public class IntegrationTestBase extends TikaTest { static final String STATUS_PATH = "/status"; static final long MAX_WAIT_MS = 60000; - static final int integrationTestPort = TestPortAllocator.findFreePort(); - static final String INTEGRATION_TEST_PORT = String.valueOf(integrationTestPort); - protected static final String endPoint = "http://localhost:" + INTEGRATION_TEST_PORT; private static final Logger LOG = LoggerFactory.getLogger(IntegrationTestBase.class); + // Instance (not static) so each test method -- JUnit5 creates a fresh test + // instance per @Test method by default -- gets its own port. These used to be + // `static final`, computed once for the whole JVM fork and shared by every + // subclass; harmless serially, but a guaranteed bind collision if two + // subclasses' tests ever ran concurrently in the same fork. + final int integrationTestPort = TestPortAllocator.findFreePort(); + final String INTEGRATION_TEST_PORT = String.valueOf(integrationTestPort); + protected final String endPoint = "http://localhost:" + INTEGRATION_TEST_PORT; + @TempDir - static Path TEMP_WORKING_DIR; - static Path LOG_FILE; - static Path STREAMS_DIR; + Path TEMP_WORKING_DIR; protected Process process = null; @BeforeAll - public static void staticSetup() throws Exception { + public void setUp() throws Exception { LogUtils.setLoggerClass(NullWebClientLogger.class); - - LOG_FILE = Files.createTempFile(TEMP_WORKING_DIR, "tika-server-integration", ".xml"); - Files.copy(TikaServerIntegrationTest.class.getResourceAsStream("/logging/log4j2_forked.xml"), LOG_FILE, StandardCopyOption.REPLACE_EXISTING); - STREAMS_DIR = Files.createTempDirectory(TEMP_WORKING_DIR, "tika-server-integration"); } @AfterEach diff --git a/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/TikaServerPipesIntegrationTest.java b/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/TikaServerPipesIntegrationTest.java index 4827641de2..01637dd308 100644 --- a/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/TikaServerPipesIntegrationTest.java +++ b/tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/TikaServerPipesIntegrationTest.java @@ -38,6 +38,7 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import org.apache.tika.metadata.Metadata; import org.apache.tika.parser.ParseContext; @@ -52,6 +53,13 @@ import org.apache.tika.utils.ProcessUtils; public class TikaServerPipesIntegrationTest extends IntegrationTestBase { + // Own dedicated static @TempDir, not the inherited (instance, per-test-method) + // TEMP_WORKING_DIR from IntegrationTestBase -- @BeforeAll needs a directory + // available before any test method runs, and instance-field @TempDir isn't + // populated until right before each @Test method regardless of test-instance + // lifecycle. + @TempDir + private static Path SETUP_DIR; private static Path TEMP_OUTPUT_DIR; private static Path TIKA_CONFIG; private static Path TIKA_CONFIG_TIMEOUT; @@ -59,16 +67,16 @@ public class TikaServerPipesIntegrationTest extends IntegrationTestBase { @BeforeAll public static void setUpBeforeClass() throws Exception { - Path inputDir = TEMP_WORKING_DIR.resolve("input"); - TEMP_OUTPUT_DIR = TEMP_WORKING_DIR.resolve("output"); + Path inputDir = SETUP_DIR.resolve("input"); + TEMP_OUTPUT_DIR = SETUP_DIR.resolve("output"); Files.createDirectories(inputDir); Files.createDirectories(TEMP_OUTPUT_DIR); for (String mockFile : FILES) { Files.copy(TikaPipesTest.class.getResourceAsStream("/test-documents/mock/" + mockFile), inputDir.resolve(mockFile)); } - TIKA_CONFIG = TEMP_WORKING_DIR.resolve("tika-config.json"); - TIKA_CONFIG_TIMEOUT = TEMP_WORKING_DIR.resolve("tika-config-timeout.json"); + TIKA_CONFIG = SETUP_DIR.resolve("tika-config.json"); + TIKA_CONFIG_TIMEOUT = SETUP_DIR.resolve("tika-config-timeout.json"); CXFTestBase.createPluginsConfig(TIKA_CONFIG, inputDir, TEMP_OUTPUT_DIR, null, 5000L); CXFTestBase.createPluginsConfig(TIKA_CONFIG_TIMEOUT, inputDir, TEMP_OUTPUT_DIR, null, 500L); diff --git a/tika-server/tika-server-core/src/test/resources/junit-platform.properties b/tika-server/tika-server-core/src/test/resources/junit-platform.properties new file mode 100644 index 0000000000..d4dd0c5f28 --- /dev/null +++ b/tika-server/tika-server-core/src/test/resources/junit-platform.properties @@ -0,0 +1,50 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Tried and disabled -- three attempts now, each finding a real blocker: +# +# 1. Most classes here extend CXFTestBase, which used to route through +# TikaResource's static (process-wide singleton) config fields -- +# concurrently-running CXFTestBase-derived classes stomped on each other's +# TikaResource state (500s, null responses, DirectoryNotEmpty races). +# FIXED: TikaResource no longer holds any static state; it's +# constructor-injected per instance now. +# +# 2. TikaServerIntegrationTest and TikaServerPipesIntegrationTest (each forks +# its own separate tika-server OS process) failed when run concurrently +# with each other, even after fix #1: PipesConfig's CPU auto-sizing is +# computed independently per forked process, with no awareness of sibling +# OS processes also running on the same machine. +# +# Attempted fix: pin -XX:ActiveProcessorCount explicitly in both classes' +# pipes configs instead of leaving it to auto-sizing (pins the forked +# *child* workers' CPU view). This was NOT sufficient -- running both +# classes concurrently still produced real failures: a +# java.nio.file.NoSuchFileException creating a forked pipes-server's temp +# dir (a race, not CPU-sizing), and two ~60s server-startup timeouts +# (test1WayTLS/test2WayTLS in TikaServerIntegrationTest). Pinning the +# forked children's ActiveProcessorCount doesn't address CPU contention +# between the two *parent* tika-server JVMs during their own startup -- +# that's a separate, still-open problem. Reverted the concurrency +# annotations and config changes; left this disabled rather than merge +# something unreliable. Revisit only with a fix for parent-JVM startup +# contention (and the temp-dir race) in hand, not just child CPU pinning. +junit.jupiter.execution.parallel.enabled = false +junit.jupiter.execution.parallel.mode.default = same_thread +junit.jupiter.execution.parallel.mode.classes.default = same_thread +junit.jupiter.execution.parallel.config.strategy = fixed +junit.jupiter.execution.parallel.config.fixed.parallelism = 4
