Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.google.testing.compile.JavaSourcesSubject Maven / Gradle / Ivy
/*
* Copyright (C) 2013 Google, Inc.
*
* Licensed 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.
*/
package com.google.testing.compile;
import static com.google.common.truth.Fact.simpleFact;
import static com.google.common.truth.Truth.assertAbout;
import static com.google.testing.compile.CompilationSubject.compilations;
import static com.google.testing.compile.Compiler.javac;
import static com.google.testing.compile.JavaSourcesSubjectFactory.javaSources;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.io.ByteSource;
import com.google.common.truth.FailureMetadata;
import com.google.common.truth.Subject;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.testing.compile.CompilationSubject.DiagnosticAtColumn;
import com.google.testing.compile.CompilationSubject.DiagnosticInFile;
import com.google.testing.compile.CompilationSubject.DiagnosticOnLine;
import com.google.testing.compile.Parser.ParseResult;
import com.sun.source.tree.CompilationUnitTree;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import javax.annotation.processing.Processor;
import javax.tools.Diagnostic;
import javax.tools.Diagnostic.Kind;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
/**
* A Truth {@link Subject} that evaluates the result
* of a {@code javac} compilation. See {@link com.google.testing.compile} for usage examples
*
* @author Gregory Kick
*/
@SuppressWarnings("restriction") // Sun APIs usage intended
public final class JavaSourcesSubject
extends Subject>
implements CompileTester, ProcessedCompileTesterFactory {
private final List options = new ArrayList(Arrays.asList("-Xlint"));
@Nullable private ClassLoader classLoader;
@Nullable private ImmutableList classPath;
JavaSourcesSubject(FailureMetadata failureMetadata, Iterable subject) {
super(failureMetadata, subject);
}
@Override
public JavaSourcesSubject withCompilerOptions(Iterable options) {
Iterables.addAll(this.options, options);
return this;
}
@Override
public JavaSourcesSubject withCompilerOptions(String... options) {
this.options.addAll(Arrays.asList(options));
return this;
}
/**
* @deprecated prefer {@link #withClasspath(Iterable)}. This method only supports {@link
* java.net.URLClassLoader} and the default system classloader, and {@link File}s are usually
* a more natural way to expression compilation classpaths than class loaders.
*/
@Deprecated
@Override
public JavaSourcesSubject withClasspathFrom(ClassLoader classLoader) {
this.classLoader = classLoader;
return this;
}
@Override
public JavaSourcesSubject withClasspath(Iterable classPath) {
this.classPath = ImmutableList.copyOf(classPath);
return this;
}
@Override
public CompileTester processedWith(Processor first, Processor... rest) {
return processedWith(Lists.asList(first, rest));
}
@Override
public CompileTester processedWith(Iterable processors) {
return new CompilationClause(processors);
}
@Override
public void parsesAs(JavaFileObject first, JavaFileObject... rest) {
new CompilationClause().parsesAs(first, rest);
}
@CanIgnoreReturnValue
@Override
public SuccessfulCompilationClause compilesWithoutError() {
return new CompilationClause().compilesWithoutError();
}
@CanIgnoreReturnValue
@Override
public CleanCompilationClause compilesWithoutWarnings() {
return new CompilationClause().compilesWithoutWarnings();
}
@CanIgnoreReturnValue
@Override
public UnsuccessfulCompilationClause failsToCompile() {
return new CompilationClause().failsToCompile();
}
/** The clause in the fluent API for testing compilations. */
private final class CompilationClause implements CompileTester {
private final ImmutableSet processors;
private CompilationClause() {
this(ImmutableSet.of());
}
private CompilationClause(Iterable processors) {
this.processors = ImmutableSet.copyOf(processors);
}
@Override
public void parsesAs(JavaFileObject first, JavaFileObject... rest) {
if (Iterables.isEmpty(actual())) {
failWithoutActual(
simpleFact(
"Compilation generated no additional source files, though some were expected."));
return;
}
ParseResult actualResult = Parser.parse(actual());
ImmutableList> errors =
actualResult.diagnosticsByKind().get(Kind.ERROR);
if (!errors.isEmpty()) {
StringBuilder message = new StringBuilder("Parsing produced the following errors:\n");
for (Diagnostic error : errors) {
message.append('\n');
message.append(error);
}
failWithoutActual(simpleFact(message.toString()));
return;
}
final ParseResult expectedResult = Parser.parse(Lists.asList(first, rest));
final FluentIterable actualTrees =
FluentIterable.from(actualResult.compilationUnits());
final FluentIterable expectedTrees =
FluentIterable.from(expectedResult.compilationUnits());
Function> getTypesFunction =
new Function>() {
@Override
public ImmutableSet apply(CompilationUnitTree compilationUnit) {
return TypeEnumerator.getTopLevelTypes(compilationUnit);
}
};
final ImmutableMap> expectedTreeTypes =
Maps.toMap(expectedTrees, getTypesFunction);
final ImmutableMap> actualTreeTypes =
Maps.toMap(actualTrees, getTypesFunction);
final ImmutableMap>
matchedTrees =
Maps.toMap(
expectedTrees,
new Function>() {
@Override
public Optional apply(
final CompilationUnitTree expectedTree) {
return Iterables.tryFind(
actualTrees,
new Predicate() {
@Override
public boolean apply(CompilationUnitTree actualTree) {
return expectedTreeTypes
.get(expectedTree)
.equals(actualTreeTypes.get(actualTree));
}
});
}
});
for (Map.Entry>
matchedTreePair : matchedTrees.entrySet()) {
final CompilationUnitTree expectedTree = matchedTreePair.getKey();
if (!matchedTreePair.getValue().isPresent()) {
failNoCandidates(
expectedTreeTypes.get(expectedTree), expectedTree, actualTreeTypes, actualTrees);
} else {
CompilationUnitTree actualTree = matchedTreePair.getValue().get();
TreeDifference treeDifference = TreeDiffer.diffCompilationUnits(expectedTree, actualTree);
if (!treeDifference.isEmpty()) {
String diffReport =
treeDifference.getDiffReport(
new TreeContext(expectedTree, expectedResult.trees()),
new TreeContext(actualTree, actualResult.trees()));
failWithCandidate(expectedTree.getSourceFile(), actualTree.getSourceFile(), diffReport);
}
}
}
}
/** Called when the {@code generatesSources()} verb fails with no diff candidates. */
private void failNoCandidates(
ImmutableSet expectedTypes,
CompilationUnitTree expectedTree,
final ImmutableMap> actualTypes,
FluentIterable actualTrees) {
String generatedTypesReport =
Joiner.on('\n')
.join(
actualTrees
.transform(
new Function() {
@Override
public String apply(CompilationUnitTree generated) {
return String.format(
"- %s in <%s>",
actualTypes.get(generated),
generated.getSourceFile().toUri().getPath());
}
})
.toList());
failWithoutActual(
simpleFact(
Joiner.on('\n')
.join(
"",
"An expected source declared one or more top-level types that were not "
+ "present.",
"",
String.format("Expected top-level types: <%s>", expectedTypes),
String.format(
"Declared by expected file: <%s>",
expectedTree.getSourceFile().toUri().getPath()),
"",
"The top-level types that were present are as follows: ",
"",
generatedTypesReport,
"")));
}
/** Called when the {@code generatesSources()} verb fails with a diff candidate. */
private void failWithCandidate(
JavaFileObject expectedSource, JavaFileObject actualSource, String diffReport) {
try {
failWithoutActual(
simpleFact(
Joiner.on('\n')
.join(
"",
"Source declared the same top-level types of an expected source, but",
"didn't match exactly.",
"",
String.format("Expected file: <%s>", expectedSource.toUri().getPath()),
String.format("Actual file: <%s>", actualSource.toUri().getPath()),
"",
"Diffs:",
"======",
"",
diffReport,
"",
"Expected Source: ",
"================",
"",
expectedSource.getCharContent(false).toString(),
"",
"Actual Source:",
"=================",
"",
actualSource.getCharContent(false).toString())));
} catch (IOException e) {
throw new IllegalStateException(
"Couldn't read from JavaFileObject when it was already " + "in memory.", e);
}
}
@CanIgnoreReturnValue
@Override
public SuccessfulCompilationClause compilesWithoutError() {
Compilation compilation = compilation();
check("compilation()").about(compilations()).that(compilation).succeeded();
return new SuccessfulCompilationBuilder(compilation);
}
@CanIgnoreReturnValue
@Override
public CleanCompilationClause compilesWithoutWarnings() {
Compilation compilation = compilation();
check("compilation()").about(compilations()).that(compilation).succeededWithoutWarnings();
return new CleanCompilationBuilder(compilation);
}
@CanIgnoreReturnValue
@Override
public UnsuccessfulCompilationClause failsToCompile() {
Compilation compilation = compilation();
check("compilation()").about(compilations()).that(compilation).failed();
return new UnsuccessfulCompilationBuilder(compilation);
}
private Compilation compilation() {
Compiler compiler = javac().withProcessors(processors).withOptions(options);
if (classLoader != null) {
compiler = compiler.withClasspathFrom(classLoader);
}
if (classPath != null) {
compiler = compiler.withClasspath(classPath);
}
return compiler.compile(actual());
}
}
/**
* A helper method for {@link SingleSourceAdapter} to ensure that the inner class is created
* correctly.
*/
private CompilationClause newCompilationClause(Iterable processors) {
return new CompilationClause(processors);
}
/**
* Base implementation of {@link CompilationWithWarningsClause}.
*
* @param T the type parameter for {@link CompilationWithWarningsClause}. {@code this} must be an
* instance of {@code T}; otherwise some calls will throw {@link ClassCastException}.
*/
abstract class CompilationWithWarningsBuilder implements CompilationWithWarningsClause {
protected final Compilation compilation;
protected CompilationWithWarningsBuilder(Compilation compilation) {
this.compilation = compilation;
}
@CanIgnoreReturnValue
@Override
public T withNoteCount(int noteCount) {
check("compilation()").about(compilations()).that(compilation).hadNoteCount(noteCount);
return thisObject();
}
@CanIgnoreReturnValue
@Override
public FileClause withNoteContaining(String messageFragment) {
return new FileBuilder(
check("compilation()")
.about(compilations())
.that(compilation)
.hadNoteContaining(messageFragment));
}
@CanIgnoreReturnValue
@Override
public T withWarningCount(int warningCount) {
check("compilation()").about(compilations()).that(compilation).hadWarningCount(warningCount);
return thisObject();
}
@CanIgnoreReturnValue
@Override
public FileClause withWarningContaining(String messageFragment) {
return new FileBuilder(
check("compilation()")
.about(compilations())
.that(compilation)
.hadWarningContaining(messageFragment));
}
@CanIgnoreReturnValue
public T withErrorCount(int errorCount) {
check("compilation()").about(compilations()).that(compilation).hadErrorCount(errorCount);
return thisObject();
}
@CanIgnoreReturnValue
public FileClause withErrorContaining(String messageFragment) {
return new FileBuilder(
check("compilation()")
.about(compilations())
.that(compilation)
.hadErrorContaining(messageFragment));
}
/** Returns this object, cast to {@code T}. */
@SuppressWarnings("unchecked")
protected final T thisObject() {
return (T) this;
}
private final class FileBuilder implements FileClause {
private final DiagnosticInFile diagnosticInFile;
private FileBuilder(DiagnosticInFile diagnosticInFile) {
this.diagnosticInFile = diagnosticInFile;
}
@Override
public T and() {
return thisObject();
}
@Override
public LineClause in(JavaFileObject file) {
final DiagnosticOnLine diagnosticOnLine = diagnosticInFile.inFile(file);
return new LineClause() {
@Override
public T and() {
return thisObject();
}
@Override
public ColumnClause onLine(long lineNumber) {
final DiagnosticAtColumn diagnosticAtColumn = diagnosticOnLine.onLine(lineNumber);
return new ColumnClause() {
@Override
public T and() {
return thisObject();
}
@Override
public ChainingClause atColumn(long columnNumber) {
diagnosticAtColumn.atColumn(columnNumber);
return this;
}
};
}
};
}
}
}
/**
* Base implementation of {@link GeneratedPredicateClause GeneratedPredicateClause} and {@link
* ChainingClause ChainingClause>}.
*
* @param T the type parameter to {@link GeneratedPredicateClause}. {@code this} must be an
* instance of {@code T}.
*/
private abstract class GeneratedCompilationBuilder extends CompilationWithWarningsBuilder
implements GeneratedPredicateClause, ChainingClause> {
protected GeneratedCompilationBuilder(Compilation compilation) {
super(compilation);
}
@CanIgnoreReturnValue
@Override
public T generatesSources(JavaFileObject first, JavaFileObject... rest) {
check("generatedSourceFiles()")
.about(javaSources())
.that(compilation.generatedSourceFiles())
.parsesAs(first, rest);
return thisObject();
}
@CanIgnoreReturnValue
@Override
public T generatesFiles(JavaFileObject first, JavaFileObject... rest) {
for (JavaFileObject expected : Lists.asList(first, rest)) {
if (!wasGenerated(expected)) {
failWithoutActual(
simpleFact("Did not find a generated file corresponding to " + expected.getName()));
}
}
return thisObject();
}
boolean wasGenerated(JavaFileObject expected) {
ByteSource expectedByteSource = JavaFileObjects.asByteSource(expected);
for (JavaFileObject generated : compilation.generatedFiles()) {
try {
if (generated.getKind().equals(expected.getKind())
&& expectedByteSource.contentEquals(JavaFileObjects.asByteSource(generated))) {
return true;
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return false;
}
@CanIgnoreReturnValue
@Override
public SuccessfulFileClause generatesFileNamed(
JavaFileManager.Location location, String packageName, String relativeName) {
final JavaFileObjectSubject javaFileObjectSubject =
check("compilation()")
.about(compilations())
.that(compilation)
.generatedFile(location, packageName, relativeName);
return new SuccessfulFileClause() {
@Override
public GeneratedPredicateClause and() {
return GeneratedCompilationBuilder.this;
}
@Override
public SuccessfulFileClause withContents(ByteSource expectedByteSource) {
javaFileObjectSubject.hasContents(expectedByteSource);
return this;
}
@Override
public SuccessfulFileClause withStringContents(Charset charset, String expectedString) {
javaFileObjectSubject.contentsAsString(charset).isEqualTo(expectedString);
return this;
}
};
}
@Override
public GeneratedPredicateClause and() {
return this;
}
}
final class CompilationBuilder extends GeneratedCompilationBuilder {
CompilationBuilder(Compilation compilation) {
super(compilation);
}
}
private final class UnsuccessfulCompilationBuilder
extends CompilationWithWarningsBuilder
implements UnsuccessfulCompilationClause {
UnsuccessfulCompilationBuilder(Compilation compilation) {
super(compilation);
}
}
private final class SuccessfulCompilationBuilder
extends GeneratedCompilationBuilder
implements SuccessfulCompilationClause {
SuccessfulCompilationBuilder(Compilation compilation) {
super(compilation);
}
}
private final class CleanCompilationBuilder
extends GeneratedCompilationBuilder
implements CleanCompilationClause {
CleanCompilationBuilder(Compilation compilation) {
super(compilation);
}
}
public static JavaSourcesSubject assertThat(JavaFileObject javaFileObject) {
return assertAbout(javaSources()).that(ImmutableList.of(javaFileObject));
}
public static JavaSourcesSubject assertThat(
JavaFileObject javaFileObject, JavaFileObject... javaFileObjects) {
return assertAbout(javaSources())
.that(
ImmutableList.builder()
.add(javaFileObject)
.add(javaFileObjects)
.build());
}
public static final class SingleSourceAdapter extends Subject
implements CompileTester, ProcessedCompileTesterFactory {
private final JavaSourcesSubject delegate;
SingleSourceAdapter(FailureMetadata failureMetadata, JavaFileObject subject) {
super(failureMetadata, subject);
/*
* TODO(b/131918061): It would make more sense to eliminate SingleSourceAdapter entirely.
* Users can already use assertThat(JavaFileObject, JavaFileObject...) above for a single
* file. Anyone who needs a Subject.Factory could fall back to
* `about(javaSources()).that(ImmutableSet.of(source))`.
*
* We could take that on, or we could wait for JavaSourcesSubject to go away entirely in favor
* of CompilationSubject.
*/
this.delegate = check("delegate()").about(javaSources()).that(ImmutableList.of(subject));
}
@Override
public JavaSourcesSubject withCompilerOptions(Iterable options) {
return delegate.withCompilerOptions(options);
}
@Override
public JavaSourcesSubject withCompilerOptions(String... options) {
return delegate.withCompilerOptions(options);
}
/**
* @deprecated prefer {@link #withClasspath(Iterable)}. This method only supports {@link
* java.net.URLClassLoader} and the default system classloader, and {@link File}s are
* usually a more natural way to expression compilation classpaths than class loaders.
*/
@Deprecated
@Override
public JavaSourcesSubject withClasspathFrom(ClassLoader classLoader) {
return delegate.withClasspathFrom(classLoader);
}
@Override
public JavaSourcesSubject withClasspath(Iterable classPath) {
return delegate.withClasspath(classPath);
}
@Override
public CompileTester processedWith(Processor first, Processor... rest) {
return delegate.newCompilationClause(Lists.asList(first, rest));
}
@Override
public CompileTester processedWith(Iterable processors) {
return delegate.newCompilationClause(processors);
}
@CanIgnoreReturnValue
@Override
public SuccessfulCompilationClause compilesWithoutError() {
return delegate.compilesWithoutError();
}
@CanIgnoreReturnValue
@Override
public CleanCompilationClause compilesWithoutWarnings() {
return delegate.compilesWithoutWarnings();
}
@CanIgnoreReturnValue
@Override
public UnsuccessfulCompilationClause failsToCompile() {
return delegate.failsToCompile();
}
@Override
public void parsesAs(JavaFileObject first, JavaFileObject... rest) {
delegate.parsesAs(first, rest);
}
}
}