From f2a326b7ec9de802e227befa1c30bbe472fc4320 Mon Sep 17 00:00:00 2001 From: Michael Kutz Date: Mon, 23 Feb 2026 15:45:28 +0100 Subject: [PATCH 1/4] Disambiguate test source path resolution Exclude bin and out directories from file search and prefer paths containing "src" when multiple matches are found. Throw an error when the source file location is still ambiguous. Closes #200 --- .../approve/StackTraceTestFinderUtil.java | 20 ++++++-- .../approve/StackTraceTestFinderUtilTest.java | 46 +++++++++++++++++++ 2 files changed, 61 insertions(+), 5 deletions(-) diff --git a/modules/core/src/main/java/org/approvej/approve/StackTraceTestFinderUtil.java b/modules/core/src/main/java/org/approvej/approve/StackTraceTestFinderUtil.java index 8018603..15f093e 100644 --- a/modules/core/src/main/java/org/approvej/approve/StackTraceTestFinderUtil.java +++ b/modules/core/src/main/java/org/approvej/approve/StackTraceTestFinderUtil.java @@ -6,6 +6,7 @@ import java.lang.reflect.Method; import java.nio.file.Files; import java.nio.file.Path; +import java.util.List; import java.util.Optional; import java.util.stream.Stream; import org.jspecify.annotations.NullMarked; @@ -67,7 +68,7 @@ public static Path findTestSourcePath(Method testMethod) { .replace("-classes", ""); String packagePath = declaringClass.getPackageName().replace(".", "/"); String pathRegex = - "(?!build|target).*%s.*/%s/%s\\.(java|kt|groovy|scala)$" + "(?!build|target|bin|out).*%s.*/%s/%s\\.(java|kt|groovy|scala)$" .formatted(sourceSetName, packagePath, declaringClass.getSimpleName()); try (Stream pathStream = Files.find( @@ -75,10 +76,19 @@ public static Path findTestSourcePath(Method testMethod) { packageDepth + 10, (path, attributes) -> attributes.isRegularFile() && path.normalize().toString().matches(pathRegex))) { - return pathStream - .findFirst() - .map(Path::normalize) - .orElseThrow(() -> new FileApproverError("Could not locate test source file")); + List matches = pathStream.map(Path::normalize).toList(); + return switch (matches.size()) { + case 0 -> throw new FileApproverError("Could not locate test source file"); + case 1 -> matches.getFirst(); + default -> { + List srcMatches = + matches.stream().filter(path -> path.toString().contains("src")).toList(); + if (srcMatches.size() == 1) { + yield srcMatches.getFirst(); + } + throw new FileApproverError("Found multiple test source files: %s".formatted(matches)); + } + }; } catch (IOException e) { throw new FileApproverError("Could not traverse code directory", e); } diff --git a/modules/core/src/test/java/org/approvej/approve/StackTraceTestFinderUtilTest.java b/modules/core/src/test/java/org/approvej/approve/StackTraceTestFinderUtilTest.java index 1e9dd5e..0dde372 100644 --- a/modules/core/src/test/java/org/approvej/approve/StackTraceTestFinderUtilTest.java +++ b/modules/core/src/test/java/org/approvej/approve/StackTraceTestFinderUtilTest.java @@ -3,6 +3,7 @@ import static java.nio.file.Files.copy; import static java.nio.file.Files.createDirectories; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.io.IOException; import java.lang.reflect.Method; @@ -92,4 +93,49 @@ void findTestSourcePath_file_in_target() throws IOException { assertThat(testSourcePath).isEqualTo(thisTestSourcePath.normalize()); } + + @Test + void findTestSourcePath_file_in_bin() throws IOException { + Path wrongTestSourcePath = + Path.of("bin/test/org/approvej/approve/StackTraceTestFinderUtilTest.java"); + wrongTestSourcePathsToCleanup.add(wrongTestSourcePath); + createDirectories(wrongTestSourcePath.getParent()); + copy(thisTestSourcePath, wrongTestSourcePath); + + Path testSourcePath = + StackTraceTestFinderUtil.findTestSourcePath( + StackTraceTestFinderUtil.currentTestMethod().method()); + + assertThat(testSourcePath).isEqualTo(thisTestSourcePath.normalize()); + } + + @Test + void findTestSourcePath_prefers_src_path() throws IOException { + Path wrongTestSourcePath = + Path.of("other/test/java/org/approvej/approve/StackTraceTestFinderUtilTest.java"); + wrongTestSourcePathsToCleanup.add(wrongTestSourcePath); + createDirectories(wrongTestSourcePath.getParent()); + copy(thisTestSourcePath, wrongTestSourcePath); + + Path testSourcePath = + StackTraceTestFinderUtil.findTestSourcePath( + StackTraceTestFinderUtil.currentTestMethod().method()); + + assertThat(testSourcePath).isEqualTo(thisTestSourcePath.normalize()); + } + + @Test + void findTestSourcePath_ambiguous() throws IOException { + Path wrongTestSourcePath = + Path.of("other/src/test/java/org/approvej/approve/StackTraceTestFinderUtilTest.java"); + wrongTestSourcePathsToCleanup.add(wrongTestSourcePath); + createDirectories(wrongTestSourcePath.getParent()); + copy(thisTestSourcePath, wrongTestSourcePath); + + Method method = StackTraceTestFinderUtil.currentTestMethod().method(); + + assertThatThrownBy(() -> StackTraceTestFinderUtil.findTestSourcePath(method)) + .isInstanceOf(FileApproverError.class) + .hasMessageStartingWith("Found multiple test source files:"); + } } From 61821de31c3148af18ba0966a2bbe91ad62f5a53 Mon Sep 17 00:00:00 2001 From: Michael Kutz Date: Mon, 23 Feb 2026 15:49:15 +0100 Subject: [PATCH 2/4] Add test for missing source file case --- .../approvej/approve/StackTraceTestFinderUtilTest.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/modules/core/src/test/java/org/approvej/approve/StackTraceTestFinderUtilTest.java b/modules/core/src/test/java/org/approvej/approve/StackTraceTestFinderUtilTest.java index 0dde372..7c56ad6 100644 --- a/modules/core/src/test/java/org/approvej/approve/StackTraceTestFinderUtilTest.java +++ b/modules/core/src/test/java/org/approvej/approve/StackTraceTestFinderUtilTest.java @@ -109,6 +109,16 @@ void findTestSourcePath_file_in_bin() throws IOException { assertThat(testSourcePath).isEqualTo(thisTestSourcePath.normalize()); } + @Test + void findTestSourcePath_no_source_file() throws NoSuchMethodException { + Method method = + org.assertj.core.api.Assertions.class.getDeclaredMethod("assertThat", boolean.class); + + assertThatThrownBy(() -> StackTraceTestFinderUtil.findTestSourcePath(method)) + .isInstanceOf(FileApproverError.class) + .hasMessage("Could not locate test source file"); + } + @Test void findTestSourcePath_prefers_src_path() throws IOException { Path wrongTestSourcePath = From 048773779c1752bad18387777f60ebb237d19de5 Mon Sep 17 00:00:00 2001 From: Michael Kutz Date: Mon, 23 Feb 2026 15:56:41 +0100 Subject: [PATCH 3/4] Parameterize duplicate file tests --- .../approve/StackTraceTestFinderUtilTest.java | 61 +++---------------- 1 file changed, 10 insertions(+), 51 deletions(-) diff --git a/modules/core/src/test/java/org/approvej/approve/StackTraceTestFinderUtilTest.java b/modules/core/src/test/java/org/approvej/approve/StackTraceTestFinderUtilTest.java index 7c56ad6..9c1244a 100644 --- a/modules/core/src/test/java/org/approvej/approve/StackTraceTestFinderUtilTest.java +++ b/modules/core/src/test/java/org/approvej/approve/StackTraceTestFinderUtilTest.java @@ -62,42 +62,16 @@ void findTestSourcePath() { assertThat(testSourcePath).isEqualTo(thisTestSourcePath.normalize()); } - @Test - void findTestSourcePath_file_in_build() throws IOException { - Path wrongTestSourcePath = - Path.of( - "build/spotless-clean/spotlessJava/java/test/org/approvej/approve/StackTraceTestFinderUtilTest.java"); - wrongTestSourcePathsToCleanup.add(wrongTestSourcePath); - createDirectories(wrongTestSourcePath.getParent()); - copy(thisTestSourcePath, wrongTestSourcePath); - - Path testSourcePath = - StackTraceTestFinderUtil.findTestSourcePath( - StackTraceTestFinderUtil.currentTestMethod().method()); - - assertThat(testSourcePath).isEqualTo(thisTestSourcePath.normalize()); - } - - @Test - void findTestSourcePath_file_in_target() throws IOException { - Path wrongTestSourcePath = - Path.of( - "target/spotless-clean/spotlessJava/java/test/org/approvej/approve/StackTraceTestFinderUtilTest.java"); - wrongTestSourcePathsToCleanup.add(wrongTestSourcePath); - createDirectories(wrongTestSourcePath.getParent()); - copy(thisTestSourcePath, wrongTestSourcePath); - - Path testSourcePath = - StackTraceTestFinderUtil.findTestSourcePath( - StackTraceTestFinderUtil.currentTestMethod().method()); - - assertThat(testSourcePath).isEqualTo(thisTestSourcePath.normalize()); - } - - @Test - void findTestSourcePath_file_in_bin() throws IOException { - Path wrongTestSourcePath = - Path.of("bin/test/org/approvej/approve/StackTraceTestFinderUtilTest.java"); + @ParameterizedTest + @ValueSource( + strings = { + "build/spotless-clean/spotlessJava/java/test/org/approvej/approve/StackTraceTestFinderUtilTest.java", + "target/spotless-clean/spotlessJava/java/test/org/approvej/approve/StackTraceTestFinderUtilTest.java", + "bin/test/org/approvej/approve/StackTraceTestFinderUtilTest.java", + "other/test/java/org/approvej/approve/StackTraceTestFinderUtilTest.java", + }) + void findTestSourcePath_duplicate_file(String wrongPath) throws IOException { + Path wrongTestSourcePath = Path.of(wrongPath); wrongTestSourcePathsToCleanup.add(wrongTestSourcePath); createDirectories(wrongTestSourcePath.getParent()); copy(thisTestSourcePath, wrongTestSourcePath); @@ -119,21 +93,6 @@ void findTestSourcePath_no_source_file() throws NoSuchMethodException { .hasMessage("Could not locate test source file"); } - @Test - void findTestSourcePath_prefers_src_path() throws IOException { - Path wrongTestSourcePath = - Path.of("other/test/java/org/approvej/approve/StackTraceTestFinderUtilTest.java"); - wrongTestSourcePathsToCleanup.add(wrongTestSourcePath); - createDirectories(wrongTestSourcePath.getParent()); - copy(thisTestSourcePath, wrongTestSourcePath); - - Path testSourcePath = - StackTraceTestFinderUtil.findTestSourcePath( - StackTraceTestFinderUtil.currentTestMethod().method()); - - assertThat(testSourcePath).isEqualTo(thisTestSourcePath.normalize()); - } - @Test void findTestSourcePath_ambiguous() throws IOException { Path wrongTestSourcePath = From 2a7562201504246fb76af6880a5d073d121d62da Mon Sep 17 00:00:00 2001 From: Michael Kutz Date: Mon, 23 Feb 2026 16:03:47 +0100 Subject: [PATCH 4/4] Fix regex precision and improve error message Use path separator in negative lookahead to avoid excluding directories that merely start with excluded names. Include src match count in ambiguity error message. Add out directory to parameterized test. --- .../java/org/approvej/approve/StackTraceTestFinderUtil.java | 6 ++++-- .../org/approvej/approve/StackTraceTestFinderUtilTest.java | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/modules/core/src/main/java/org/approvej/approve/StackTraceTestFinderUtil.java b/modules/core/src/main/java/org/approvej/approve/StackTraceTestFinderUtil.java index 15f093e..6e1f0b4 100644 --- a/modules/core/src/main/java/org/approvej/approve/StackTraceTestFinderUtil.java +++ b/modules/core/src/main/java/org/approvej/approve/StackTraceTestFinderUtil.java @@ -68,7 +68,7 @@ public static Path findTestSourcePath(Method testMethod) { .replace("-classes", ""); String packagePath = declaringClass.getPackageName().replace(".", "/"); String pathRegex = - "(?!build|target|bin|out).*%s.*/%s/%s\\.(java|kt|groovy|scala)$" + "(?!(?:build|target|bin|out)/).*%s.*/%s/%s\\.(java|kt|groovy|scala)$" .formatted(sourceSetName, packagePath, declaringClass.getSimpleName()); try (Stream pathStream = Files.find( @@ -86,7 +86,9 @@ public static Path findTestSourcePath(Method testMethod) { if (srcMatches.size() == 1) { yield srcMatches.getFirst(); } - throw new FileApproverError("Found multiple test source files: %s".formatted(matches)); + throw new FileApproverError( + "Found multiple test source files (%d contain 'src'): %s" + .formatted(srcMatches.size(), matches)); } }; } catch (IOException e) { diff --git a/modules/core/src/test/java/org/approvej/approve/StackTraceTestFinderUtilTest.java b/modules/core/src/test/java/org/approvej/approve/StackTraceTestFinderUtilTest.java index 9c1244a..d14bd0f 100644 --- a/modules/core/src/test/java/org/approvej/approve/StackTraceTestFinderUtilTest.java +++ b/modules/core/src/test/java/org/approvej/approve/StackTraceTestFinderUtilTest.java @@ -68,6 +68,7 @@ void findTestSourcePath() { "build/spotless-clean/spotlessJava/java/test/org/approvej/approve/StackTraceTestFinderUtilTest.java", "target/spotless-clean/spotlessJava/java/test/org/approvej/approve/StackTraceTestFinderUtilTest.java", "bin/test/org/approvej/approve/StackTraceTestFinderUtilTest.java", + "out/test/org/approvej/approve/StackTraceTestFinderUtilTest.java", "other/test/java/org/approvej/approve/StackTraceTestFinderUtilTest.java", }) void findTestSourcePath_duplicate_file(String wrongPath) throws IOException { @@ -105,6 +106,6 @@ void findTestSourcePath_ambiguous() throws IOException { assertThatThrownBy(() -> StackTraceTestFinderUtil.findTestSourcePath(method)) .isInstanceOf(FileApproverError.class) - .hasMessageStartingWith("Found multiple test source files:"); + .hasMessageStartingWith("Found multiple test source files ("); } }