Skip to content

Commit

Permalink
Merge pull request #1094 from hcoles/feature/find_kotlin_sources
Browse files Browse the repository at this point in the history
search for source outside of package dirs
  • Loading branch information
hcoles committed Oct 7, 2022
2 parents 5bf72c6 + 014d82d commit f285f93
Show file tree
Hide file tree
Showing 3 changed files with 183 additions and 82 deletions.
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;
}
}

0 comments on commit f285f93

Please sign in to comment.