Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

search for source outside of package dirs #1094

Merged
merged 6 commits into from
Oct 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
Expand All @@ -25,74 +24,78 @@
import java.nio.file.Path;
import java.util.Collection;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Stream;

import org.pitest.classinfo.ClassName;
import org.pitest.functional.Streams;
import org.pitest.mutationtest.SourceLocator;
import org.pitest.util.Unchecked;

public class DirectorySourceLocator implements SourceLocator {

private final Path root;
private final Function<Path, Optional<Reader>> fileToReader;

private static final class FileToReader implements Function<Path, Optional<Reader>> {

private final Path root;
private final Charset inputCharset;

private FileToReader(Charset inputCharset) {
this.inputCharset = inputCharset;
public DirectorySourceLocator(Path root, Charset inputCharset) {
this.root = root;
this.inputCharset = inputCharset;
}

@Override
public Optional<Reader> apply(final Path f) {
if (Files.exists(f)) {
try {
return Optional.of(new InputStreamReader(new BufferedInputStream(Files.newInputStream(f)),
inputCharset));
} catch (final FileNotFoundException e) {
return Optional.empty();
} catch (IOException ex) {
throw Unchecked.translateCheckedException(ex);
public Optional<Reader> locate(Collection<String> classes, String fileName) {

if (!Files.exists(root)) {
return Optional.empty();
}
}
return Optional.empty();
}

}
// look for matching filename in directories matching its package.
Optional<Path> path = classes.stream()
.map(ClassName::fromString)
.map(ClassName::getPackage)
.distinct()
.map(c -> toFileName(c, fileName))
.map(file -> root.resolve(file))
.filter(Files::exists)
.filter(Files::isRegularFile)
.findFirst();

DirectorySourceLocator(Path root, Function<Path, Optional<Reader>> fileToReader) {
this.root = root;
this.fileToReader = fileToReader;
}
// If there is no file in the expected location (kotlin file?), search from the root, but
// in this case we cannot know if we have the right file if the same name occurs more than once in the file tree
// (cannot do this as an or as only introduced in java 9)
if (path.isPresent()) {
return path
.map(this::toReader);
} else {
return searchFromRoot(fileName)
.map(this::toReader);
}
}

public DirectorySourceLocator(Path root, Charset inputCharset) {
this(root, new FileToReader(inputCharset));
}
private String toFileName(ClassName packge, String fileName) {
if (packge.asJavaName().equals("")) {
return fileName;
}
return packge.asJavaName().replace(".", File.separator) + File.separator + fileName;
}

@Override
public Optional<Reader> locate(Collection<String> classes, String fileName) {
final Stream<Reader> matches = classes.stream().flatMap(classNameToSourceFileReader(fileName));
return matches.findFirst();
}
private Reader toReader(Path path) {
try {
return new InputStreamReader(new BufferedInputStream(Files.newInputStream(path)),
inputCharset);
} catch (IOException e) {
throw Unchecked.translateCheckedException(e);
}
}

private Function<String, Stream<Reader>> classNameToSourceFileReader(
final String fileName) {
return className -> {
if (className.contains(".")) {
ClassName classPackage = ClassName.fromString(className).getPackage();
String path = classPackage.asJavaName().replace(".", File.separator);
return locate(path + File.separator + fileName);
} else {
return locate(fileName);
}
};
}
private Optional<Path> searchFromRoot(String fileName) {
try {
try (Stream<Path> matches = Files.find(root, 100,
(path, attributes) -> path.getFileName().toString().equals(fileName) && attributes.isRegularFile())) {
return matches.findFirst();
}

private Stream<Reader> locate(final String fileName) {
return Streams.fromOptional(this.fileToReader.apply(root.resolve(fileName)));
}
} catch (IOException e) {
throw Unchecked.translateCheckedException(e);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Collections;
import java.util.function.Function;

import org.pitest.functional.FCollection;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.pitest.mutationtest.SourceLocator;
import org.pitest.util.Unchecked;
Expand All @@ -50,8 +52,13 @@ private Function<Path, Collection<Path>> collectChildren(final int depth) {

private Collection<Path> collectDirectories(Path root, int depth) {
try {
return Files.find(root, depth, (unused, attributes) -> attributes.isDirectory())
.collect(Collectors.toList());
if (!Files.exists(root)) {
return Collections.emptyList();
}

try (Stream<Path> matches = Files.find(root, depth, (unused, attributes) -> attributes.isDirectory())) {
return matches.collect(Collectors.toList());
}

} catch (IOException ex) {
throw Unchecked.translateCheckedException(ex);
Expand All @@ -60,8 +67,7 @@ private Collection<Path> collectDirectories(Path root, int depth) {
}

@Override
public Optional<Reader> locate(final Collection<String> classes,
final String fileName) {
public Optional<Reader> locate(Collection<String> classes, String fileName) {
for (final SourceLocator each : this.children) {
final Optional<Reader> reader = each.locate(classes, fileName);
if (reader.isPresent()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,52 +14,144 @@
*/
package org.pitest.mutationtest.tooling;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;

import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.function.Function;
import java.util.Collection;
import java.util.Optional;

import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.Optional;
import org.junit.rules.TemporaryFolder;

public class DirectorySourceLocatorTest {

private DirectorySourceLocator testee;
private Path root;
@Rule
public TemporaryFolder folder = new TemporaryFolder();
Path root;

@Mock
Function<Path, Optional<Reader>> locator;
private DirectorySourceLocator testee;

@Before
public void setUp() {
MockitoAnnotations.openMocks(this);
this.root = Paths.get(".");
this.testee = new DirectorySourceLocator(this.root, this.locator);
when(this.locator.apply(any(Path.class)))
.thenReturn(Optional.<Reader> empty());
root = folder.getRoot().toPath();
this.testee = new DirectorySourceLocator(this.root, StandardCharsets.UTF_8);
}

@Test
public void locatesSourceForClassesInDefaultPackage() throws Exception {
createFile(root.resolve("Foo.java"), "foo");
createFile(root.resolve("Bar.java"), "bar");
Optional<Reader> actual = testee.locate(singletonList("Foo"), "Foo.java");
assertThat(actual).isPresent();
assertThat(content(actual)).isEqualTo("foo");
}

@Test
public void locatesSourceForClassesInNamedPackages() throws Exception {
createFile(root.resolve("com/example/Foo.java"), "foo");
createFile(root.resolve("com/example/Bar.java"), "bar");
Optional<Reader> actual = testee.locate(singletonList("com.example.Foo"), "Foo.java");
assertThat(content(actual)).isEqualTo("foo");
}

@Test
public void shouldLocateSourceForClassesInDefaultPackage() {
this.testee.locate(Collections.singletonList("Foo"), "Foo.java");
Path expected = root.resolve("Foo.java");
verify(this.locator).apply(expected);
public void findsFileInPackageBeforeOneAtRoot() throws Exception {
createFile(root.resolve("com/example/Foo.java"), "this one");
createFile(root.resolve("Foo.java"), "not this one");
Optional<Reader> actual = testee.locate(singletonList("com.example.Foo"), "Foo.java");
assertThat(content(actual)).isEqualTo("this one");
}

@Test
public void findsFileInCorrectPackageBeforeWronglyPackagedOnes() throws Exception {
createFile(root.resolve("com/example/correct/Foo.java"), "correct");
createFile(root.resolve("Foo.java"), "in package default");
createFile(root.resolve("com/example/Foo.java"), "example");
createFile(root.resolve("com/example/wrong/Foo.java"), "not this one");
createFile(root.resolve("com/example/correct/wrong/Foo.java"), "not this one");

assertThat(findFor("com.example.correct.Foo", "Foo.java")).isEqualTo("correct");
assertThat(findFor("Foo", "Foo.java")).isEqualTo("in package default");
assertThat(findFor("com.example.Foo", "Foo.java")).isEqualTo("example");
}

@Test
public void shouldLocateSourceForClassesInNamedPackages() {
this.testee
.locate(Collections.singletonList("com.example.Foo"), "Foo.java");
Path expected = root.resolve("com").resolve("example").resolve("Foo.java");
verify(this.locator).apply(expected);
@Ignore
// Docs suggest that Files.walk/find should search depth first, but behaviour seems
// to be OS dependent in practice. Windows ci on azure looks to search depth first, linux
// and mac find the root file. Fortunately we don't actually care about the behaviour in this case
// either file might be the one the user intended
public void findsFileInWrongPackageBeforeRoot() throws Exception {
createFile(root.resolve("com/example/other/Foo.java"), "this one");
createFile(root.resolve("Foo.java"), "not this one");
Optional<Reader> actual = testee.locate(singletonList("com.example.Foo"), "Foo.java");
assertThat(content(actual)).isEqualTo("this one");
}

@Test
public void usesFileInRightPAckage() throws Exception {
createFile(root.resolve("com/example/other/Foo.java"), "not this one");
createFile(root.resolve("com/example/correct/Foo.java"), "this one");
Optional<Reader> actual = testee.locate(singletonList("com.example.correct.Foo"), "Foo.java");
assertThat(content(actual)).isEqualTo("this one");
}

@Test
public void doesNotTryToReadDirectories() throws Exception {
Files.createDirectories(root.resolve("com/example/Foo.java"));
createFile(root.resolve("Foo.java"), "this one");
Optional<Reader> actual = testee.locate(singletonList("com.example.Foo"), "Foo.java");
assertThat(content(actual)).isEqualTo("this one");
}

@Test
public void doesNotErrorWhenRootDoesNotExist() {
testee = new DirectorySourceLocator(this.root.resolve("doesNotExist"), StandardCharsets.UTF_8);
assertThatCode(() -> testee.locate(singletonList("com"), "Bar.java"))
.doesNotThrowAnyException();
}

private String findFor(String clazz, String file) throws Exception {
return findFor(singletonList(clazz), file);
}

private String findFor(Collection<String> classes, String file) throws Exception {
Optional<Reader> actual = testee.locate(classes, file);
return content(actual);
}


private void createFile(Path file, String content) throws IOException {
if (file.getParent() != null) {
Files.createDirectories(file.getParent());
}

Files.write(file, content.getBytes(StandardCharsets.UTF_8));
}

private String content(Optional<Reader> reader) throws Exception {
if (reader.isPresent()) {
return content(reader.get());
}
return "";
}

private String content(Reader reader) throws Exception {
String s = "";
int ch;
while ((ch = reader.read()) != -1) {
s += (char) ch;
}
reader.close();
return s;
}
}