diff --git a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FileSource.java b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FileSource.java index 577f602659db..4f6e93ddda4b 100644 --- a/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FileSource.java +++ b/junit-platform-engine/src/main/java/org/junit/platform/engine/support/descriptor/FileSource.java @@ -10,6 +10,7 @@ package org.junit.platform.engine.support.descriptor; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; import static org.apiguardian.api.API.Status.STABLE; import java.io.File; @@ -44,7 +45,7 @@ public class FileSource implements FileSystemSource { * @param file the source file; must not be {@code null} */ public static FileSource from(File file) { - return new FileSource(file); + return from(file, null); } /** @@ -55,25 +56,22 @@ public static FileSource from(File file) { * @param filePosition the position in the source file; may be {@code null} */ public static FileSource from(File file, @Nullable FilePosition filePosition) { - return new FileSource(file, filePosition); + Preconditions.notNull(file, "file must not be null"); + try { + File canonicalFile = file.getCanonicalFile(); + return new FileSource(canonicalFile, filePosition); + } + catch (IOException ex) { + throw new JUnitException("Failed to retrieve canonical path for file: " + file, ex); + } } private final File file; private final @Nullable FilePosition filePosition; - private FileSource(File file) { - this(file, null); - } - private FileSource(File file, @Nullable FilePosition filePosition) { - Preconditions.notNull(file, "file must not be null"); - try { - this.file = file.getCanonicalFile(); - } - catch (IOException ex) { - throw new JUnitException("Failed to retrieve canonical path for file: " + file, ex); - } + this.file = file; this.filePosition = filePosition; } @@ -104,6 +102,20 @@ public final Optional getPosition() { return Optional.ofNullable(this.filePosition); } + /** + * Return a new {@code FileSource} based on this instance but with a different + * {@link FilePosition}. This avoids redundant canonical path resolution + * by reusing the already-canonical file. + * + * @param filePosition the new {@code FilePosition}; must not be {@code null} + * @return a new {@code FileSource} with the same file and updated position + */ + @API(status = EXPERIMENTAL, since = "6.0") + public FileSource withPosition(FilePosition filePosition) { + Preconditions.notNull(filePosition, "filePosition must not be null"); + return new FileSource(this.file, filePosition); + } + @Override public boolean equals(Object o) { if (this == o) { diff --git a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/FileSystemSourceTests.java b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/FileSystemSourceTests.java index 9f6723b85e31..e46bac867686 100644 --- a/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/FileSystemSourceTests.java +++ b/platform-tests/src/test/java/org/junit/platform/engine/support/descriptor/FileSystemSourceTests.java @@ -76,6 +76,22 @@ void fileWithPosition() { assertThat(source.getPosition()).hasValue(position); } + @Test + void fileReuseWithPosition() { + var file = new File("test.txt"); + var position = FilePosition.from(42, 23); + var source = FileSource.from(file); + var sourceWithPosition = source.withPosition(position); + + assertThat(source.getUri()).isEqualTo(file.getAbsoluteFile().toURI()); + assertThat(source.getFile()).isEqualTo(file.getAbsoluteFile()); + assertThat(source.getPosition()).isEmpty(); + + assertThat(source).isNotSameAs(sourceWithPosition); + assertThat(source.getFile()).isSameAs(sourceWithPosition.getFile()); + assertThat(sourceWithPosition.getPosition()).hasValue(position); + } + @Test void equalsAndHashCodeForFileSource() { var file1 = new File("foo.txt");