diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 00000000..1a6f7b71
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,20 @@
+version: 2
+updates:
+ - package-ecosystem: "maven"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ groups:
+ dependencies:
+ applies-to: version-updates
+ patterns:
+ - "*"
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "monthly"
+ groups:
+ github-actions:
+ applies-to: version-updates
+ patterns:
+ - "*"
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 00000000..6b7e2ae7
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,37 @@
+name: CI
+
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+ branches:
+ - main
+
+jobs:
+ test:
+ name: "JDK ${{ matrix.java }}"
+ strategy:
+ matrix:
+ java: [ 8, 11, 17, 21 ]
+ runs-on: ubuntu-latest
+ steps:
+ # Cancel any previous runs for the same branch that are still running.
+ - name: 'Cancel previous runs'
+ uses: styfle/cancel-workflow-action@85880fa0301c86cca9da44039ee3bb12d3bedbfa
+ with:
+ access_token: ${{ github.token }}
+ - name: 'Check out repository'
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+ - name: 'Set up JDK ${{ matrix.java }}'
+ uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00
+ with:
+ java-version: ${{ matrix.java }}
+ distribution: 'zulu'
+ cache: 'maven'
+ - name: 'Install'
+ shell: bash
+ run: mvn -B -U install clean --fail-never --quiet -DskipTests=true -Dinvoker.skip=true
+ - name: 'Test'
+ shell: bash
+ run: mvn -B verify
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 0995cc00..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,14 +0,0 @@
-language: java
-install: mvn -B -U install clean --fail-never --quiet -DskipTests=true -Dinvoker.skip=true
-script: mvn -B verify
-
-jdk:
- - oraclejdk8
-
-notifications:
- email: false
-
-branches:
- only:
- - master
- - /^release.*$/
diff --git a/README.md b/README.md
index 2fb5cec8..a7fa2b74 100644
--- a/README.md
+++ b/README.md
@@ -1,21 +1,11 @@
Compile Testing
===============
-A library for testing javac compilation with or without annotation processors. See the [javadoc][package-info] for usage examples.
+[![Build Status][ci-shield]][ci-link]
+[![Maven Release][maven-shield]][maven-link]
+[![Javadoc][javadoc-shield]][javadoc-link]
-Latest Release
---------------
-
-The latest release is version `0.8`. Include it as a [Maven](http://maven.apache.org/) dependency with the following snippet:
-
-```
-
- com.google.testing.compile
- compile-testing
- 0.8
- test
-
-```
+A library for testing javac compilation with or without annotation processors. See the [javadoc][javadoc-link] for usage examples.
License
-------
@@ -34,4 +24,9 @@ License
See the License for the specific language governing permissions and
limitations under the License.
-[package-info]: https://github.com/google/compile-testing/blob/master/src/main/java/com/google/testing/compile/package-info.java
+[ci-shield]: https://github.com/google/compile-testing/actions/workflows/ci.yml/badge.svg?branch=main
+[ci-link]: https://github.com/google/compile-testing/actions
+[maven-shield]: https://img.shields.io/maven-central/v/com.google.testing.compile/compile-testing.png
+[maven-link]: https://search.maven.org/artifact/com.google.testing.compile/compile-testing
+[javadoc-shield]: https://javadoc.io/badge/com.google.testing.compile/compile-testing.svg?color=blue
+[javadoc-link]: https://javadoc.io/doc/com.google.testing.compile/compile-testing
diff --git a/pom.xml b/pom.xml
index 90a328d9..efa72c06 100644
--- a/pom.xml
+++ b/pom.xml
@@ -9,25 +9,24 @@
com.google.testing.compilecompile-testing
- 1.0-SNAPSHOT
+ HEAD-SNAPSHOTCompile TestingUtilities for testing compilation.
- 1.3
- 21.0
- 0.35
- 4.12
- 3.0.1
+ 1.4.4http://github.com/google/compile-testing
+
GitHubhttp://github.com/google/compile-testing/issues
+
2013
+
The Apache Software License, Version 2.0
@@ -35,20 +34,23 @@
repo
+
- 3.1.1
+ 3.5.0
+
scm:git:http://github.com/google/compile-testingscm:git:git@github.com:google/compile-testing.githttp://github.com/google/compile-testingHEAD
+
junitjunit
- ${junit.version}
+ 4.13.2com.google.truth
@@ -59,42 +61,50 @@
com.google.truth.extensionstruth-java8-extension${truth.version}
+ testcom.google.guavaguava
- ${guava.version}
-
-
- com.sun
- tools
- ${java.version}
- system
- ${toolsjar}
-
-
- com.google.code.findbugs
- jsr305
- ${jsr305.version}
- true
+ 33.4.8-jrecom.google.errorproneerror_prone_annotations
- 2.0.8
+ 2.38.0providedcom.google.auto.valueauto-value
- ${auto-value.version}
+ 1.11.0
+
+
+ com.google.auto
+ auto-common
+ 1.2.2
+
+
+ org.jspecify
+ jspecify
+ 1.0.0
+
+
+
+ .
+
+ LICENSE.txt
+
+ META-INF
+
+ maven-compiler-plugin
- 3.1
+ 3.14.01.81.8
@@ -105,53 +115,92 @@
maven-release-plugin
- 2.5.1
+ 3.1.1
- release
+ sonatype-oss-releasedeploymaven-jar-plugin
- 2.5
+ 3.4.2
+
+
+ maven-javadoc-plugin
+ 3.11.2
+
+
+ --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
+ --add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED
+ --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
+
+
+
+
+ maven-site-plugin
+ 3.21.0
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 3.5.3
+
+ ${test.jvm.flags}
+
+
- default-profile
+ tools-jar
- true
+ false${java.home}/../lib/tools.jar
-
- ${java.home}/../lib/tools.jar
-
+
+
+ com.sun
+ tools
+ ${java.version}
+ system
+ ${java.home}/../lib/tools.jar
+
+
- mac-profile
+ classes-jarfalse${java.home}/../Classes/classes.jar
-
- ${java.home}/../Classes/classes.jar
-
+
+
+ com.sun
+ tools
+ ${java.version}
+ system
+
+
+ ${java.home}/../Classes/classes.jar
+
+
- release
+ sonatype-oss-releasefalse
+ org.apache.maven.pluginsmaven-gpg-plugin
- 1.4
+ 3.2.7sign-artifacts
@@ -161,8 +210,9 @@
+ org.apache.maven.pluginsmaven-source-plugin
- 2.1.2
+ 3.3.1attach-sources
@@ -171,8 +221,9 @@
+ org.apache.maven.pluginsmaven-javadoc-plugin
- 2.8
+ 3.11.2attach-docs
@@ -183,5 +234,21 @@
+
+ add-exports
+
+ [9,)
+
+
+
+ --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
+ --add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED
+ --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
+ --add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED
+ --add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED
+ --add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED
+
+
+
diff --git a/src/main/java/com/google/testing/compile/Breadcrumbs.java b/src/main/java/com/google/testing/compile/Breadcrumbs.java
index a65b18b9..b5adef93 100644
--- a/src/main/java/com/google/testing/compile/Breadcrumbs.java
+++ b/src/main/java/com/google/testing/compile/Breadcrumbs.java
@@ -19,7 +19,6 @@
import com.google.common.base.Joiner;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.Lists;
-
import com.sun.source.tree.BlockTree;
import com.sun.source.tree.BreakTree;
import com.sun.source.tree.ClassTree;
@@ -37,7 +36,6 @@
import com.sun.source.tree.VariableTree;
import com.sun.source.util.SimpleTreeVisitor;
import com.sun.source.util.TreePath;
-
import java.util.List;
/**
@@ -54,9 +52,6 @@ private Breadcrumbs() {}
* Returns a string describing the {@link TreePath} given.
*/
static String describeTreePath(TreePath path) {
- // TODO(spratt) The number of extra strings this creates by building a list of strings,
- // and then joining it is at least a little wasteful. Consider modifying the BreadcrumbVisitor
- // to take a StringBuilder and traverse the whole path.
return Joiner.on("->").join(getBreadcrumbList(path));
}
diff --git a/src/main/java/com/google/testing/compile/Compilation.java b/src/main/java/com/google/testing/compile/Compilation.java
index 1893b0cc..57342b41 100644
--- a/src/main/java/com/google/testing/compile/Compilation.java
+++ b/src/main/java/com/google/testing/compile/Compilation.java
@@ -17,6 +17,7 @@
import static com.google.common.base.Preconditions.checkState;
import static com.google.testing.compile.JavaFileObjects.asByteSource;
+import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.toList;
import static javax.tools.Diagnostic.Kind.ERROR;
@@ -155,9 +156,8 @@ public Optional generatedFile(Location location, String path) {
// We're relying on the implementation of location.getName() to be equivalent to the first
// part of the path.
String expectedFilename = String.format("%s/%s", location.getName(), path);
- return generatedFiles()
- .stream()
- .filter(generated -> generated.toUri().getPath().endsWith(expectedFilename))
+ return generatedFiles().stream()
+ .filter(generated -> requireNonNull(generated.toUri().getPath()).endsWith(expectedFilename))
.findFirst();
}
@@ -220,16 +220,12 @@ public String toString() {
/** Returns a description of the why the compilation failed. */
String describeFailureDiagnostics() {
- ImmutableList> errors = errors();
- if (errors.isEmpty()) {
- return "Compilation produced no errors.\n";
- }
- StringBuilder message = new StringBuilder("Compilation produced the following errors:\n");
- errors.stream().forEach(error -> message.append(error).append('\n'));
- // If we compiled with -Werror we should output the warnings too
- if (compiler.options().contains("-Werror")) {
- warnings().stream().forEach(warning -> message.append(warning).append('\n'));
+ ImmutableList> diagnostics = diagnostics();
+ if (diagnostics.isEmpty()) {
+ return "Compilation produced no diagnostics.\n";
}
+ StringBuilder message = new StringBuilder("Compilation produced the following diagnostics:\n");
+ diagnostics.forEach(diagnostic -> message.append(diagnostic).append('\n'));
return message.toString();
}
@@ -269,7 +265,6 @@ private String describeGeneratedFile(JavaFileObject generatedFile) {
}
}
- // TODO(dpb): Use Guava's toImmutableList() once that's available. MOE:strip_line
private static Collector> toImmutableList() {
return collectingAndThen(toList(), ImmutableList::copyOf);
}
diff --git a/src/main/java/com/google/testing/compile/CompilationRule.java b/src/main/java/com/google/testing/compile/CompilationRule.java
index 81df84f0..d54e05ba 100644
--- a/src/main/java/com/google/testing/compile/CompilationRule.java
+++ b/src/main/java/com/google/testing/compile/CompilationRule.java
@@ -81,7 +81,7 @@ public Elements getElements() {
* @throws IllegalStateException if this method is invoked outside the execution of the rule.
*/
public Types getTypes() {
- checkState(elements != null, "Not running within the rule");
+ checkState(types != null, "Not running within the rule");
return types;
}
diff --git a/src/main/java/com/google/testing/compile/CompilationSubject.java b/src/main/java/com/google/testing/compile/CompilationSubject.java
index 7222e8a1..22b093ba 100644
--- a/src/main/java/com/google/testing/compile/CompilationSubject.java
+++ b/src/main/java/com/google/testing/compile/CompilationSubject.java
@@ -16,14 +16,17 @@
package com.google.testing.compile;
import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Predicates.notNull;
import static com.google.common.collect.Iterables.size;
-import static com.google.common.collect.Streams.mapWithIndex;
+import static com.google.common.truth.Fact.fact;
+import static com.google.common.truth.Fact.simpleFact;
import static com.google.common.truth.Truth.assertAbout;
import static com.google.testing.compile.Compilation.Status.FAILURE;
import static com.google.testing.compile.Compilation.Status.SUCCESS;
import static com.google.testing.compile.JavaFileObjectSubject.javaFileObjects;
import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
@@ -35,9 +38,10 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
-import com.google.common.truth.FailureStrategy;
+import com.google.common.collect.Streams;
+import com.google.common.truth.Fact;
+import com.google.common.truth.FailureMetadata;
import com.google.common.truth.Subject;
-import com.google.common.truth.SubjectFactory;
import com.google.common.truth.Truth;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.io.IOException;
@@ -53,15 +57,16 @@
import javax.tools.JavaFileManager.Location;
import javax.tools.JavaFileObject;
import javax.tools.StandardLocation;
+import org.jspecify.annotations.Nullable;
/** A {@link Truth} subject for a {@link Compilation}. */
-public final class CompilationSubject extends Subject {
+public final class CompilationSubject extends Subject {
- private static final SubjectFactory FACTORY =
+ private static final Subject.Factory FACTORY =
new CompilationSubjectFactory();
- /** Returns a {@link SubjectFactory} for a {@link Compilation}. */
- public static SubjectFactory compilations() {
+ /** Returns a {@link Subject.Factory} for a {@link Compilation}. */
+ public static Subject.Factory compilations() {
return FACTORY;
}
@@ -70,15 +75,19 @@ public static CompilationSubject assertThat(Compilation actual) {
return assertAbout(compilations()).that(actual);
}
- CompilationSubject(FailureStrategy failureStrategy, Compilation actual) {
- super(failureStrategy, actual);
+ private final @Nullable Compilation actual;
+
+ CompilationSubject(FailureMetadata failureMetadata, @Nullable Compilation actual) {
+ super(failureMetadata, actual);
+ this.actual = actual;
}
/** Asserts that the compilation succeeded. */
public void succeeded() {
- if (actual().status().equals(FAILURE)) {
- failureStrategy.fail(
- actual().describeFailureDiagnostics() + actual().describeGeneratedSourceFiles());
+ Compilation actual = actualNotNull();
+ if (actual.status().equals(FAILURE)) {
+ failWithoutActual(
+ simpleFact(actual.describeFailureDiagnostics() + actual.describeGeneratedSourceFiles()));
}
}
@@ -90,10 +99,11 @@ public void succeededWithoutWarnings() {
/** Asserts that the compilation failed. */
public void failed() {
- if (actual().status().equals(SUCCESS)) {
- failureStrategy.fail(
- "Compilation was expected to fail, but contained no errors.\n\n"
- + actual().describeGeneratedSourceFiles());
+ if (actualNotNull().status().equals(SUCCESS)) {
+ failWithoutActual(
+ simpleFact(
+ "Compilation was expected to fail, but contained no errors.\n\n"
+ + actualNotNull().describeGeneratedSourceFiles()));
}
}
@@ -169,17 +179,18 @@ public DiagnosticInFile hadNoteContainingMatch(Pattern expectedPattern) {
private void checkDiagnosticCount(
int expectedCount, Diagnostic.Kind kind, Diagnostic.Kind... more) {
Iterable> diagnostics =
- actual().diagnosticsOfKind(kind, more);
+ actualNotNull().diagnosticsOfKind(kind, more);
int actualCount = size(diagnostics);
if (actualCount != expectedCount) {
- failureStrategy.fail(
- messageListing(
- diagnostics,
- "Expected %d %s, but found the following %d %s:",
- expectedCount,
- kindPlural(kind),
- actualCount,
- kindPlural(kind)));
+ failWithoutActual(
+ simpleFact(
+ messageListing(
+ diagnostics,
+ "Expected %d %s, but found the following %d %s:",
+ expectedCount,
+ kindPlural(kind),
+ actualCount,
+ kindPlural(kind))));
}
}
@@ -277,15 +288,17 @@ private ImmutableList> findMatchingDiagnost
Diagnostic.Kind kind,
Diagnostic.Kind... more) {
ImmutableList> diagnosticsOfKind =
- actual().diagnosticsOfKind(kind, more);
+ actualNotNull().diagnosticsOfKind(kind, more);
ImmutableList> diagnosticsWithMessage =
diagnosticsOfKind
.stream()
.filter(diagnostic -> expectedPattern.matcher(diagnostic.getMessage(null)).find())
.collect(toImmutableList());
if (diagnosticsWithMessage.isEmpty()) {
- failureStrategy.fail(
- messageListing(diagnosticsOfKind, "Expected %s, but only found:", expectedDiagnostic));
+ failWithoutActual(
+ simpleFact(
+ messageListing(
+ diagnosticsOfKind, "Expected %s, but only found:", expectedDiagnostic)));
}
return diagnosticsWithMessage;
}
@@ -297,20 +310,14 @@ private ImmutableList> findMatchingDiagnost
@CanIgnoreReturnValue
public JavaFileObjectSubject generatedFile(
Location location, String packageName, String fileName) {
- return checkGeneratedFile(
- actual().generatedFile(location, packageName, fileName),
- location,
- "named \"%s\" in %s",
- fileName,
- packageName.isEmpty()
- ? "the default package"
- : String.format("package \"%s\"", packageName));
+ String path = packageName.isEmpty() ? fileName : packageName.replace('.', '/') + '/' + fileName;
+ return generatedFile(location, path);
}
/** Asserts that compilation generated a file at {@code path}. */
@CanIgnoreReturnValue
public JavaFileObjectSubject generatedFile(Location location, String path) {
- return checkGeneratedFile(actual().generatedFile(location, path), location, path);
+ return checkGeneratedFile(actualNotNull().generatedFile(location, path), location, path);
}
/** Asserts that compilation generated a source file for a type with a given qualified name. */
@@ -320,20 +327,32 @@ public JavaFileObjectSubject generatedSourceFile(String qualifiedName) {
StandardLocation.SOURCE_OUTPUT, qualifiedName.replaceAll("\\.", "/") + ".java");
}
+ private static final JavaFileObject ALREADY_FAILED =
+ JavaFileObjects.forSourceLines(
+ "compile.Failure", "package compile;", "", "final class Failure {}");
+
private JavaFileObjectSubject checkGeneratedFile(
- Optional generatedFile, Location location, String format, Object... args) {
+ Optional generatedFile, Location location, String path) {
if (!generatedFile.isPresent()) {
- StringBuilder builder = new StringBuilder("generated the file ");
- builder.append(args.length == 0 ? format : String.format(format, args));
- builder.append("; it generated:\n");
- for (JavaFileObject generated : actual().generatedFiles()) {
- if (generated.toUri().getPath().contains(location.getName())) {
- builder.append(" ").append(generated.toUri().getPath()).append('\n');
+ // TODO(b/132162475): Use Facts if it becomes public API.
+ ImmutableList.Builder facts = ImmutableList.builder();
+ facts.add(fact("in location", location.getName()));
+ facts.add(simpleFact("it generated:"));
+ for (JavaFileObject generated : actualNotNull().generatedFiles()) {
+ if (requireNonNull(generated.toUri().getPath()).contains(location.getName())) {
+ facts.add(simpleFact(" " + generated.toUri().getPath()));
}
}
- fail(builder.toString());
+ failWithoutActual(
+ fact("expected to generate file", "/" + path), facts.build().toArray(new Fact[0]));
+ return ignoreCheck().about(javaFileObjects()).that(ALREADY_FAILED);
}
- return check().about(javaFileObjects()).that(generatedFile.get());
+ return check("generatedFile(/%s)", path).about(javaFileObjects()).that(generatedFile.get());
+ }
+
+ private Compilation actualNotNull() {
+ isNotNull();
+ return checkNotNull(actual);
}
private static Collector> toImmutableList() {
@@ -371,11 +390,12 @@ Stream mapDiagnostics(Function super Diagnostic extends JavaFileObjec
}
protected void failExpectingMatchingDiagnostic(String format, Object... args) {
- failureStrategy.fail(
- new StringBuilder("Expected ")
- .append(expectedDiagnostic)
- .append(String.format(format, args))
- .toString());
+ failWithoutActual(
+ simpleFact(
+ new StringBuilder("Expected ")
+ .append(expectedDiagnostic)
+ .append(String.format(format, args))
+ .toString()));
}
}
@@ -402,7 +422,8 @@ private ImmutableList> findDiagnosticsInFil
filterDiagnostics(
diagnostic -> {
JavaFileObject source = diagnostic.getSource();
- return source != null && source.toUri().getPath().equals(expectedFilePath);
+ return source != null
+ && requireNonNull(source.toUri().getPath()).equals(expectedFilePath);
});
if (diagnosticsInFile.isEmpty()) {
failExpectingMatchingDiagnostic(
@@ -445,7 +466,7 @@ ImmutableList linesInFile() {
}
return lines;
}
-
+
/**
* Returns a {@link Collector} that lists the file lines numbered by the input stream (1-based).
*/
@@ -455,9 +476,12 @@ ImmutableList linesInFile() {
/** Lists the line at a line number (1-based). */
String listLine(long lineNumber) {
- return lineNumber == Diagnostic.NOPOS
- ? "(no associated line)"
- : String.format("%4d: %s", lineNumber, linesInFile().get((int) (lineNumber - 1)));
+ if (lineNumber == Diagnostic.NOPOS) {
+ return "(no associated line)";
+ }
+ checkArgument(lineNumber > 0 && lineNumber <= linesInFile().size(),
+ "Invalid line number %s; number of lines is only %s", lineNumber, linesInFile().size());
+ return String.format("%4d: %s", lineNumber, linesInFile().get((int) (lineNumber - 1)));
}
}
@@ -496,8 +520,9 @@ public void onLineContaining(String expectedLineSubstring) {
* expectedLineSubstring}
*/
private long findLineContainingSubstring(String expectedLineSubstring) {
+ // The explicit type arguments below are needed by our nullness checker.
ImmutableSet matchingLines =
- mapWithIndex(
+ Streams.mapWithIndex(
linesInFile.linesInFile().stream(),
(line, index) -> line.contains(expectedLineSubstring) ? index : null)
.filter(notNull())
diff --git a/src/main/java/com/google/testing/compile/CompilationSubjectFactory.java b/src/main/java/com/google/testing/compile/CompilationSubjectFactory.java
index 396a0a37..b2ff5ff4 100644
--- a/src/main/java/com/google/testing/compile/CompilationSubjectFactory.java
+++ b/src/main/java/com/google/testing/compile/CompilationSubjectFactory.java
@@ -15,15 +15,17 @@
*/
package com.google.testing.compile;
-import com.google.common.truth.FailureStrategy;
-import com.google.common.truth.SubjectFactory;
+import com.google.common.truth.FailureMetadata;
+import com.google.common.truth.Subject;
import com.google.common.truth.Truth;
+import org.jspecify.annotations.Nullable;
/** A {@link Truth} subject factory for a {@link Compilation}. */
-final class CompilationSubjectFactory extends SubjectFactory {
+final class CompilationSubjectFactory implements Subject.Factory {
@Override
- public CompilationSubject getSubject(FailureStrategy fs, Compilation that) {
- return new CompilationSubject(fs, that);
+ public CompilationSubject createSubject(
+ FailureMetadata failureMetadata, @Nullable Compilation that) {
+ return new CompilationSubject(failureMetadata, that);
}
}
diff --git a/src/main/java/com/google/testing/compile/Compiler.java b/src/main/java/com/google/testing/compile/Compiler.java
index 02d11f61..827f2912 100644
--- a/src/main/java/com/google/testing/compile/Compiler.java
+++ b/src/main/java/com/google/testing/compile/Compiler.java
@@ -16,30 +16,41 @@
package com.google.testing.compile;
import static com.google.common.base.Functions.toStringFunction;
+import static com.google.common.collect.ImmutableList.toImmutableList;
import static java.nio.charset.StandardCharsets.UTF_8;
import static javax.tools.ToolProvider.getSystemJavaCompiler;
import com.google.auto.value.AutoValue;
-import com.google.common.base.Joiner;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Splitter;
+import com.google.common.base.StandardSystemProperty;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
import com.google.testing.compile.Compilation.Status;
+import java.io.File;
+import java.io.IOException;
+import java.io.UncheckedIOException;
import java.net.URL;
import java.net.URLClassLoader;
-import java.util.ArrayList;
import java.util.LinkedHashSet;
-import java.util.List;
import java.util.Locale;
+import java.util.Optional;
import java.util.Set;
import javax.annotation.processing.Processor;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject;
+import javax.tools.StandardJavaFileManager;
+import javax.tools.StandardLocation;
+import org.jspecify.annotations.Nullable;
/** An object that can {@link #compile} Java source files. */
@AutoValue
+// clashes with java.lang.Compiler (which is deprecated for removal in 9)
+@SuppressWarnings("JavaLangClash")
public abstract class Compiler {
/** Returns the {@code javac} compiler. */
@@ -49,7 +60,8 @@ public static Compiler javac() {
/** Returns a {@link Compiler} that uses a given {@link JavaCompiler} instance. */
public static Compiler compiler(JavaCompiler javaCompiler) {
- return new AutoValue_Compiler(javaCompiler, ImmutableList.of(), ImmutableList.of());
+ return new AutoValue_Compiler(
+ javaCompiler, ImmutableList.of(), ImmutableList.of(), Optional.empty(), Optional.empty());
}
abstract JavaCompiler javaCompiler();
@@ -60,6 +72,14 @@ public static Compiler compiler(JavaCompiler javaCompiler) {
/** The options passed to the compiler. */
public abstract ImmutableList options();
+ /** The compilation class path. If not present, the system class path is used. */
+ public abstract Optional> classPath();
+
+ /**
+ * The annotation processor path. If not present, the system annotation processor path is used.
+ */
+ public abstract Optional> annotationProcessorPath();
+
/**
* Uses annotation processors during compilation. These replace any previously specified.
*
@@ -79,7 +99,8 @@ public final Compiler withProcessors(Processor... processors) {
* @return a new instance with the same options and the given processors
*/
public final Compiler withProcessors(Iterable extends Processor> processors) {
- return copy(ImmutableList.copyOf(processors), options());
+ return copy(
+ ImmutableList.copyOf(processors), options(), classPath(), annotationProcessorPath());
}
/**
@@ -96,22 +117,52 @@ public final Compiler withOptions(Object... options) {
*
* @return a new instance with the same processors and the given options
*/
- public final Compiler withOptions(Iterable> options) {
- return copy(processors(), FluentIterable.from(options).transform(toStringFunction()).toList());
+ public final Compiler withOptions(Iterable extends Object> options) {
+ return copy(
+ processors(),
+ FluentIterable.from(options).transform(toStringFunction()).toList(),
+ classPath(),
+ annotationProcessorPath());
}
/**
- * Uses the classpath from the passed on classloader (and its parents) for the compilation
- * instead of the system classpath.
+ * Uses the classpath from the passed on classloader (and its parents) for the compilation instead
+ * of the system classpath.
*
* @throws IllegalArgumentException if the given classloader had classpaths which we could not
* determine or use for compilation.
+ * @deprecated prefer {@link #withClasspath(Iterable)}. This method only supports {@link
+ * URLClassLoader} and the default system classloader, and {@link File}s are usually a more
+ * natural way to expression compilation classpaths than class loaders.
*/
+ @Deprecated
public final Compiler withClasspathFrom(ClassLoader classloader) {
- String classpath = getClasspathFromClassloader(classloader);
- ImmutableList options =
- ImmutableList.builder().add("-classpath").add(classpath).addAll(options()).build();
- return copy(processors(), options);
+ return copy(
+ processors(),
+ options(),
+ Optional.of(getClasspathFromClassloader(classloader)),
+ annotationProcessorPath());
+ }
+
+ /** Uses the given classpath for the compilation instead of the system classpath. */
+ public final Compiler withClasspath(Iterable classPath) {
+ return copy(
+ processors(),
+ options(),
+ Optional.of(ImmutableList.copyOf(classPath)),
+ annotationProcessorPath());
+ }
+
+ /**
+ * Uses the given annotation processor path for the compilation instead of the system annotation
+ * processor path.
+ */
+ public final Compiler withAnnotationProcessorPath(Iterable annotationProcessorPath) {
+ return copy(
+ processors(),
+ options(),
+ classPath(),
+ Optional.of(ImmutableList.copyOf(annotationProcessorPath)));
}
/**
@@ -130,31 +181,57 @@ public final Compilation compile(JavaFileObject... files) {
*/
public final Compilation compile(Iterable extends JavaFileObject> files) {
DiagnosticCollector diagnosticCollector = new DiagnosticCollector<>();
- InMemoryJavaFileManager fileManager =
- new InMemoryJavaFileManager(
- javaCompiler().getStandardFileManager(diagnosticCollector, Locale.getDefault(), UTF_8));
- CompilationTask task =
- javaCompiler()
- .getTask(
- null, // use the default because old versions of javac log some output on stderr
- fileManager,
- diagnosticCollector,
- options(),
- ImmutableSet.of(),
- files);
- task.setProcessors(processors());
- boolean succeeded = task.call();
- Compilation compilation =
- new Compilation(
- this,
- files,
- succeeded,
- diagnosticCollector.getDiagnostics(),
- fileManager.getOutputFiles());
- if (compilation.status().equals(Status.FAILURE) && compilation.errors().isEmpty()) {
- throw new CompilationFailureException(compilation);
+ try (StandardJavaFileManager standardFileManager = standardFileManager(diagnosticCollector);
+ InMemoryJavaFileManager fileManager = new InMemoryJavaFileManager(standardFileManager)) {
+ fileManager.addSourceFiles(files);
+ classPath().ifPresent(path -> setLocation(fileManager, StandardLocation.CLASS_PATH, path));
+ annotationProcessorPath()
+ .ifPresent(
+ path -> setLocation(fileManager, StandardLocation.ANNOTATION_PROCESSOR_PATH, path));
+
+ CompilationTask task =
+ javaCompiler()
+ .getTask(
+ null, // use the default because old versions of javac log some output on stderr
+ fileManager,
+ diagnosticCollector,
+ options(),
+ ImmutableSet.of(),
+ files);
+ task.setProcessors(processors());
+ boolean succeeded = task.call();
+ Compilation compilation =
+ new Compilation(
+ this,
+ files,
+ succeeded,
+ diagnosticCollector.getDiagnostics(),
+ fileManager.getOutputFiles());
+ if (compilation.status().equals(Status.FAILURE) && compilation.errors().isEmpty()) {
+ throw new CompilationFailureException(compilation);
+ }
+ return compilation;
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ private StandardJavaFileManager standardFileManager(
+ DiagnosticCollector diagnosticCollector) {
+ return javaCompiler().getStandardFileManager(diagnosticCollector, Locale.getDefault(), UTF_8);
+ }
+
+ @VisibleForTesting
+ static final @Nullable ClassLoader platformClassLoader = getPlatformClassLoader();
+
+ private static @Nullable ClassLoader getPlatformClassLoader() {
+ try {
+ // JDK >= 9
+ return (ClassLoader) ClassLoader.class.getMethod("getPlatformClassLoader").invoke(null);
+ } catch (ReflectiveOperationException e) {
+ // Java <= 8
+ return null;
}
- return compilation;
}
/**
@@ -163,41 +240,63 @@ public final Compilation compile(Iterable extends JavaFileObject> files) {
* @throws IllegalArgumentException if the given classloader had classpaths which we could not
* determine or use for compilation.
*/
- private static String getClasspathFromClassloader(ClassLoader currentClassloader) {
+ private static ImmutableList getClasspathFromClassloader(ClassLoader classloader) {
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
- // Add all URLClassloaders in the hirearchy till the system classloader.
- List classloaders = new ArrayList<>();
- while(true) {
- if (currentClassloader instanceof URLClassLoader) {
- // We only know how to extract classpaths from URLClassloaders.
- classloaders.add((URLClassLoader) currentClassloader);
- } else {
- throw new IllegalArgumentException("Classpath for compilation could not be extracted "
- + "since given classloader is not an instance of URLClassloader");
- }
+ // Concatenate search paths from all classloaders in the hierarchy 'till the system classloader.
+ Set classpaths = new LinkedHashSet<>();
+ for (ClassLoader currentClassloader = classloader;
+ ;
+ currentClassloader = currentClassloader.getParent()) {
if (currentClassloader == systemClassLoader) {
+ Iterables.addAll(
+ classpaths,
+ Splitter.on(StandardSystemProperty.PATH_SEPARATOR.value())
+ .split(StandardSystemProperty.JAVA_CLASS_PATH.value()));
break;
}
- currentClassloader = currentClassloader.getParent();
- }
-
- Set classpaths = new LinkedHashSet<>();
- for (URLClassLoader classLoader : classloaders) {
- for (URL url : classLoader.getURLs()) {
- if (url.getProtocol().equals("file")) {
- classpaths.add(url.getPath());
- } else {
- throw new IllegalArgumentException("Given classloader consists of classpaths which are "
- + "unsupported for compilation.");
+ if (currentClassloader == platformClassLoader) {
+ break;
+ }
+ if (currentClassloader instanceof URLClassLoader) {
+ // We only know how to extract classpaths from URLClassloaders.
+ for (URL url : ((URLClassLoader) currentClassloader).getURLs()) {
+ if (url.getProtocol().equals("file")) {
+ classpaths.add(url.getPath());
+ } else {
+ throw new IllegalArgumentException(
+ "Given classloader consists of classpaths which are "
+ + "unsupported for compilation.");
+ }
}
+ } else {
+ throw new IllegalArgumentException(
+ String.format(
+ "Classpath for compilation could not be extracted "
+ + "since %s is not an instance of URLClassloader",
+ currentClassloader));
}
}
- return Joiner.on(':').join(classpaths);
+ return classpaths.stream().map(File::new).collect(toImmutableList());
+ }
+
+ private static void setLocation(
+ InMemoryJavaFileManager fileManager, StandardLocation location, ImmutableList path) {
+ try {
+ fileManager.setLocation(location, path);
+ } catch (IOException e) {
+ // impossible by specification
+ throw new UncheckedIOException(e);
+ }
}
- private Compiler copy(ImmutableList processors, ImmutableList options) {
- return new AutoValue_Compiler(javaCompiler(), processors, options);
+ private Compiler copy(
+ ImmutableList processors,
+ ImmutableList options,
+ Optional> classPath,
+ Optional> annotationProcessorPath) {
+ return new AutoValue_Compiler(
+ javaCompiler(), processors, options, classPath, annotationProcessorPath);
}
}
diff --git a/src/main/java/com/google/testing/compile/ForwardingStandardJavaFileManager.java b/src/main/java/com/google/testing/compile/ForwardingStandardJavaFileManager.java
new file mode 100644
index 00000000..9b612b9f
--- /dev/null
+++ b/src/main/java/com/google/testing/compile/ForwardingStandardJavaFileManager.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2018 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 java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.nio.file.Path;
+import java.util.Collection;
+import javax.tools.ForwardingJavaFileManager;
+import javax.tools.JavaFileManager.Location;
+import javax.tools.JavaFileObject;
+import javax.tools.StandardJavaFileManager;
+
+/**
+ * Forwards calls to a given {@link StandardJavaFileManager}. Subclasses of this class might
+ * override some of these methods and might also provide additional fields and methods.
+ */
+public class ForwardingStandardJavaFileManager
+ extends ForwardingJavaFileManager implements StandardJavaFileManager {
+
+ /**
+ * Creates a new instance of ForwardingStandardJavaFileManager.
+ *
+ * @param fileManager delegate to this file manager
+ */
+ protected ForwardingStandardJavaFileManager(StandardJavaFileManager fileManager) {
+ super(fileManager);
+ }
+
+ @Override
+ public Iterable extends JavaFileObject> getJavaFileObjectsFromFiles(
+ Iterable extends File> files) {
+ return fileManager.getJavaFileObjectsFromFiles(files);
+ }
+
+ @Override
+ public Iterable extends JavaFileObject> getJavaFileObjects(File... files) {
+ return fileManager.getJavaFileObjects(files);
+ }
+
+ @Override
+ public Iterable extends JavaFileObject> getJavaFileObjects(String... names) {
+ return fileManager.getJavaFileObjects(names);
+ }
+
+ @Override
+ public Iterable extends JavaFileObject> getJavaFileObjectsFromStrings(Iterable names) {
+ return fileManager.getJavaFileObjectsFromStrings(names);
+ }
+
+ @Override
+ public void setLocation(Location location, Iterable extends File> path) throws IOException {
+ fileManager.setLocation(location, path);
+ }
+
+ @Override
+ public Iterable extends File> getLocation(Location location) {
+ return fileManager.getLocation(location);
+ }
+
+ // @Override for JDK 9 only
+ public void setLocationFromPaths(Location location, Collection extends Path> searchpath)
+ throws IOException {
+ Method setLocationFromPaths;
+ try {
+ setLocationFromPaths =
+ fileManager
+ .getClass()
+ .getMethod("setLocationFromPaths", Location.class, Collection.class);
+ } catch (ReflectiveOperationException e) {
+ // JDK < 9
+ return;
+ }
+ try {
+ setLocationFromPaths.invoke(fileManager, location, searchpath);
+ } catch (ReflectiveOperationException e) {
+ throw new LinkageError(e.getMessage(), e);
+ }
+ }
+}
diff --git a/src/main/java/com/google/testing/compile/InMemoryJavaFileManager.java b/src/main/java/com/google/testing/compile/InMemoryJavaFileManager.java
index 9d6442bb..b2641b3a 100644
--- a/src/main/java/com/google/testing/compile/InMemoryJavaFileManager.java
+++ b/src/main/java/com/google/testing/compile/InMemoryJavaFileManager.java
@@ -15,8 +15,10 @@
*/
package com.google.testing.compile;
+import static com.google.common.collect.MoreCollectors.toOptional;
+import static java.util.Objects.requireNonNull;
+
import com.google.common.base.MoreObjects;
-import com.google.common.base.Optional;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
@@ -32,14 +34,16 @@
import java.io.Writer;
import java.net.URI;
import java.nio.charset.Charset;
-import java.util.Map.Entry;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
import javax.tools.FileObject;
-import javax.tools.ForwardingJavaFileManager;
-import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.JavaFileObject.Kind;
import javax.tools.SimpleJavaFileObject;
+import javax.tools.StandardJavaFileManager;
import javax.tools.StandardLocation;
+import org.jspecify.annotations.Nullable;
/**
* A file manager implementation that stores all output in memory.
@@ -47,16 +51,20 @@
* @author Gregory Kick
*/
// TODO(gak): under java 1.7 this could all be done with a PathFileManager
-final class InMemoryJavaFileManager extends ForwardingJavaFileManager {
- private final LoadingCache inMemoryFileObjects =
- CacheBuilder.newBuilder().build(new CacheLoader() {
- @Override
- public JavaFileObject load(URI key) {
- return new InMemoryJavaFileObject(key);
- }
- });
+final class InMemoryJavaFileManager extends ForwardingStandardJavaFileManager {
+ private final LoadingCache inMemoryOutputs =
+ CacheBuilder.newBuilder()
+ .build(
+ new CacheLoader() {
+ @Override
+ public JavaFileObject load(URI key) {
+ return new InMemoryJavaFileObject(key);
+ }
+ });
+
+ private final Map inMemoryInputs = new HashMap<>();
- InMemoryJavaFileManager(JavaFileManager fileManager) {
+ InMemoryJavaFileManager(StandardJavaFileManager fileManager) {
super(fileManager);
}
@@ -84,44 +92,68 @@ public boolean isSameFile(FileObject a, FileObject b) {
}
@Override
- public FileObject getFileForInput(Location location, String packageName,
- String relativeName) throws IOException {
+ public @Nullable FileObject getFileForInput(
+ Location location, String packageName, String relativeName) throws IOException {
if (location.isOutputLocation()) {
- return inMemoryFileObjects.getIfPresent(
- uriForFileObject(location, packageName, relativeName));
- } else {
- return super.getFileForInput(location, packageName, relativeName);
+ return inMemoryOutputs.getIfPresent(uriForFileObject(location, packageName, relativeName));
}
+ Optional inMemoryInput = findInMemoryInput(packageName, relativeName);
+ if (inMemoryInput.isPresent()) {
+ return inMemoryInput.get();
+ }
+ return super.getFileForInput(location, packageName, relativeName);
}
@Override
- public JavaFileObject getJavaFileForInput(Location location, String className, Kind kind)
- throws IOException {
+ public @Nullable JavaFileObject getJavaFileForInput(
+ Location location, String className, Kind kind) throws IOException {
if (location.isOutputLocation()) {
- return inMemoryFileObjects.getIfPresent(uriForJavaFileObject(location, className, kind));
- } else {
- return super.getJavaFileForInput(location, className, kind);
+ return inMemoryOutputs.getIfPresent(uriForJavaFileObject(location, className, kind));
+ }
+ Optional inMemoryInput = findInMemoryInput(className);
+ if (inMemoryInput.isPresent()) {
+ return inMemoryInput.get();
}
+ return super.getJavaFileForInput(location, className, kind);
+ }
+
+ private Optional findInMemoryInput(String className) {
+ int lastDot = className.lastIndexOf('.');
+ return findInMemoryInput(
+ lastDot == -1 ? "" : className.substring(0, lastDot - 1),
+ className.substring(lastDot + 1) + ".java");
+ }
+
+ private Optional findInMemoryInput(String packageName, String relativeName) {
+ // Assume each input file's URI ends with the package/relative name. It might have other parts
+ // to the left.
+ String suffix =
+ packageName.isEmpty() ? relativeName : packageName.replace('.', '/') + "/" + relativeName;
+ return inMemoryInputs.entrySet().stream()
+ .filter(entry -> requireNonNull(entry.getKey().getPath()).endsWith(suffix))
+ .map(Map.Entry::getValue)
+ .collect(toOptional()); // Might have problems if more than one input file matches.
}
@Override
public FileObject getFileForOutput(Location location, String packageName,
String relativeName, FileObject sibling) throws IOException {
URI uri = uriForFileObject(location, packageName, relativeName);
- return inMemoryFileObjects.getUnchecked(uri);
+ return inMemoryOutputs.getUnchecked(uri);
}
@Override
public JavaFileObject getJavaFileForOutput(Location location, String className, final Kind kind,
FileObject sibling) throws IOException {
URI uri = uriForJavaFileObject(location, className, kind);
- return inMemoryFileObjects.getUnchecked(uri);
+ return inMemoryOutputs.getUnchecked(uri);
}
ImmutableList getGeneratedSources() {
ImmutableList.Builder result = ImmutableList.builder();
- for (Entry entry : inMemoryFileObjects.asMap().entrySet()) {
- if (entry.getKey().getPath().startsWith("/" + StandardLocation.SOURCE_OUTPUT.name())
+ for (Map.Entry entry : inMemoryOutputs.asMap().entrySet()) {
+ if (requireNonNull(entry.getKey().getPath())
+ .startsWith("/" + StandardLocation.SOURCE_OUTPUT.name())
&& (entry.getValue().getKind() == Kind.SOURCE)) {
result.add(entry.getValue());
}
@@ -130,13 +162,20 @@ ImmutableList getGeneratedSources() {
}
ImmutableList getOutputFiles() {
- return ImmutableList.copyOf(inMemoryFileObjects.asMap().values());
+ return ImmutableList.copyOf(inMemoryOutputs.asMap().values());
+ }
+
+ /** Adds files that should be available in the source path. */
+ void addSourceFiles(Iterable extends JavaFileObject> files) {
+ for (JavaFileObject file : files) {
+ inMemoryInputs.put(file.toUri(), file);
+ }
}
private static final class InMemoryJavaFileObject extends SimpleJavaFileObject
implements JavaFileObject {
private long lastModified = 0L;
- private Optional data = Optional.absent();
+ private Optional data = Optional.empty();
InMemoryJavaFileObject(URI uri) {
super(uri, JavaFileObjects.deduceKind(uri));
@@ -202,7 +241,7 @@ public long getLastModified() {
@Override
public boolean delete() {
- this.data = Optional.absent();
+ this.data = Optional.empty();
this.lastModified = 0L;
return true;
}
diff --git a/src/main/java/com/google/testing/compile/JavaFileObjectSubject.java b/src/main/java/com/google/testing/compile/JavaFileObjectSubject.java
index b4fc9fcc..80df4894 100644
--- a/src/main/java/com/google/testing/compile/JavaFileObjectSubject.java
+++ b/src/main/java/com/google/testing/compile/JavaFileObjectSubject.java
@@ -15,49 +15,55 @@
*/
package com.google.testing.compile;
+import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.Iterables.getOnlyElement;
+import static com.google.common.truth.Fact.fact;
import static com.google.common.truth.Truth.assertAbout;
import static com.google.testing.compile.JavaFileObjects.asByteSource;
import static com.google.testing.compile.TreeDiffer.diffCompilationUnits;
+import static com.google.testing.compile.TreeDiffer.matchCompilationUnits;
import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.Objects.requireNonNull;
-import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.io.ByteSource;
-import com.google.common.truth.FailureStrategy;
+import com.google.common.truth.FailureMetadata;
import com.google.common.truth.StringSubject;
import com.google.common.truth.Subject;
-import com.google.common.truth.SubjectFactory;
import com.google.testing.compile.Parser.ParseResult;
import com.sun.source.tree.CompilationUnitTree;
import java.io.IOException;
import java.nio.charset.Charset;
-import javax.annotation.Nullable;
+import java.util.function.BiFunction;
import javax.tools.JavaFileObject;
+import org.jspecify.annotations.Nullable;
/** Assertions about {@link JavaFileObject}s. */
-public final class JavaFileObjectSubject extends Subject {
+public final class JavaFileObjectSubject extends Subject {
- private static final SubjectFactory FACTORY =
+ private static final Subject.Factory FACTORY =
new JavaFileObjectSubjectFactory();
- /** Returns a {@link SubjectFactory} for {@link JavaFileObjectSubject}s. */
- public static SubjectFactory javaFileObjects() {
+ /** Returns a {@link Subject.Factory} for {@link JavaFileObjectSubject}s. */
+ public static Subject.Factory javaFileObjects() {
return FACTORY;
}
/** Starts making assertions about a {@link JavaFileObject}. */
- public static JavaFileObjectSubject assertThat(JavaFileObject actual) {
+ public static JavaFileObjectSubject assertThat(@Nullable JavaFileObject actual) {
return assertAbout(FACTORY).that(actual);
}
- JavaFileObjectSubject(FailureStrategy failureStrategy, JavaFileObject actual) {
- super(failureStrategy, actual);
+ private final @Nullable JavaFileObject actual;
+
+ JavaFileObjectSubject(FailureMetadata failureMetadata, @Nullable JavaFileObject actual) {
+ super(failureMetadata, actual);
+ this.actual = actual;
}
@Override
protected String actualCustomStringRepresentation() {
- return actual().toUri().getPath();
+ return requireNonNull(actualNotNull().toUri().getPath());
}
/**
@@ -68,12 +74,13 @@ protected String actualCustomStringRepresentation() {
public void isEqualTo(@Nullable Object other) {
if (!(other instanceof JavaFileObject)) {
super.isEqualTo(other);
+ return;
}
JavaFileObject otherFile = (JavaFileObject) other;
try {
- if (!asByteSource(actual()).contentEquals(asByteSource(otherFile))) {
- fail("is equal to", other);
+ if (!asByteSource(actualNotNull()).contentEquals(asByteSource(otherFile))) {
+ failWithActual("expected to be equal to", other);
}
} catch (IOException e) {
throw new RuntimeException(e);
@@ -83,8 +90,8 @@ public void isEqualTo(@Nullable Object other) {
/** Asserts that the actual file's contents are equal to {@code expected}. */
public void hasContents(ByteSource expected) {
try {
- if (!asByteSource(actual()).contentEquals(expected)) {
- fail("has contents", expected);
+ if (!asByteSource(actualNotNull()).contentEquals(expected)) {
+ failWithActual("expected to have contents", expected);
}
} catch (IOException e) {
throw new RuntimeException(e);
@@ -97,9 +104,8 @@ public void hasContents(ByteSource expected) {
*/
public StringSubject contentsAsString(Charset charset) {
try {
- return check()
- .that(JavaFileObjects.asByteSource(actual()).asCharSource(charset).read())
- .named("the contents of " + actualAsString());
+ return check("contents()")
+ .that(JavaFileObjects.asByteSource(actualNotNull()).asCharSource(charset).read());
} catch (IOException e) {
throw new RuntimeException(e);
}
@@ -114,17 +120,65 @@ public StringSubject contentsAsUtf8String() {
}
/**
- * Asserts that the actual file is a source file with contents equivalent to {@code
+ * Asserts that the actual file is a source file that has an equivalent AST to that of {@code
* expectedSource}.
*/
public void hasSourceEquivalentTo(JavaFileObject expectedSource) {
- ParseResult actualResult = Parser.parse(ImmutableList.of(actual()));
+ performTreeDifference(
+ expectedSource,
+ "expected to be equivalent to",
+ "expected",
+ (expectedResult, actualResult) ->
+ diffCompilationUnits(
+ getOnlyElement(expectedResult.compilationUnits()),
+ getOnlyElement(actualResult.compilationUnits())));
+ }
+
+ /**
+ * Asserts that the every node in the AST of {@code expectedPattern}
+ * exists in the actual file's AST, in the same order.
+ *
+ *
Methods, constructors, fields, and types that are in the pattern must have the exact same
+ * modifiers and annotations as the actual AST. Ordering of AST nodes is also important (i.e. a
+ * type with identical members in a different order will fail the assertion). Types must match the
+ * entire type declaration: type parameters, {@code extends}/{@code implements} clauses, etc.
+ * Methods must also match the throws clause as well.
+ *
+ *
The body of a method or constructor, or field initializer in the actual AST must match the
+ * pattern in entirety if the member is present in the pattern.
+ *
+ *
Said in another way (from a graph-theoretic perspective): the pattern AST must be a subgraph
+ * of the actual AST. If a method, constructor, or field is in the pattern, that entire subtree,
+ * including modifiers and annotations, must be equal to the corresponding subtree in the actual
+ * AST (no proper subgraphs).
+ */
+ public void containsElementsIn(JavaFileObject expectedPattern) {
+ performTreeDifference(
+ expectedPattern,
+ "expected to contain elements in",
+ "expected pattern",
+ (expectedResult, actualResult) ->
+ matchCompilationUnits(
+ getOnlyElement(expectedResult.compilationUnits()),
+ actualResult.trees(),
+ getOnlyElement(actualResult.compilationUnits()),
+ expectedResult.trees()));
+ }
+
+ private void performTreeDifference(
+ JavaFileObject expected,
+ String failureVerb,
+ String expectedTitle,
+ BiFunction differencingFunction) {
+ ParseResult actualResult = Parser.parse(ImmutableList.of(actualNotNull()), "*actual* source");
CompilationUnitTree actualTree = getOnlyElement(actualResult.compilationUnits());
- ParseResult expectedResult = Parser.parse(ImmutableList.of(expectedSource));
+ ParseResult expectedResult = Parser.parse(ImmutableList.of(expected), "*expected* source");
CompilationUnitTree expectedTree = getOnlyElement(expectedResult.compilationUnits());
- TreeDifference treeDifference = diffCompilationUnits(expectedTree, actualTree);
+ TreeDifference treeDifference = differencingFunction.apply(expectedResult, actualResult);
if (!treeDifference.isEmpty()) {
String diffReport =
@@ -132,29 +186,21 @@ public void hasSourceEquivalentTo(JavaFileObject expectedSource) {
new TreeContext(expectedTree, expectedResult.trees()),
new TreeContext(actualTree, actualResult.trees()));
try {
- fail(
- Joiner.on('\n')
- .join(
- String.format("is equivalent to <%s>.", expectedSource.toUri().getPath()),
- "",
- "Diffs:",
- "======",
- "",
- diffReport,
- "",
- "Expected Source:",
- "================",
- "",
- expectedSource.getCharContent(false),
- "",
- "Actual Source:",
- "==============",
- "",
- actual().getCharContent(false)));
+ failWithoutActual(
+ fact("for file", actualNotNull().toUri().getPath()),
+ fact(failureVerb, expected.toUri().getPath()),
+ fact("diff", diffReport),
+ fact(expectedTitle, expected.getCharContent(false)),
+ fact("but was", actualNotNull().getCharContent(false)));
} catch (IOException e) {
throw new IllegalStateException(
"Couldn't read from JavaFileObject when it was already in memory.", e);
}
}
}
+
+ private JavaFileObject actualNotNull() {
+ isNotNull();
+ return checkNotNull(actual);
+ }
}
diff --git a/src/main/java/com/google/testing/compile/JavaFileObjectSubjectFactory.java b/src/main/java/com/google/testing/compile/JavaFileObjectSubjectFactory.java
index 6207b546..174c29af 100644
--- a/src/main/java/com/google/testing/compile/JavaFileObjectSubjectFactory.java
+++ b/src/main/java/com/google/testing/compile/JavaFileObjectSubjectFactory.java
@@ -15,16 +15,18 @@
*/
package com.google.testing.compile;
-import com.google.common.truth.FailureStrategy;
-import com.google.common.truth.SubjectFactory;
+import com.google.common.truth.FailureMetadata;
+import com.google.common.truth.Subject;
import javax.tools.JavaFileObject;
+import org.jspecify.annotations.Nullable;
/** A factory for {@link JavaFileObjectSubject}s. */
final class JavaFileObjectSubjectFactory
- extends SubjectFactory {
+ implements Subject.Factory {
@Override
- public JavaFileObjectSubject getSubject(FailureStrategy fs, JavaFileObject that) {
- return new JavaFileObjectSubject(fs, that);
+ public JavaFileObjectSubject createSubject(
+ FailureMetadata failureMetadata, @Nullable JavaFileObject that) {
+ return new JavaFileObjectSubject(failureMetadata, that);
}
}
diff --git a/src/main/java/com/google/testing/compile/JavaFileObjects.java b/src/main/java/com/google/testing/compile/JavaFileObjects.java
index 81900c32..174bd1e5 100644
--- a/src/main/java/com/google/testing/compile/JavaFileObjects.java
+++ b/src/main/java/com/google/testing/compile/JavaFileObjects.java
@@ -17,6 +17,7 @@
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
+import static java.util.Objects.requireNonNull;
import static javax.tools.JavaFileObject.Kind.SOURCE;
import com.google.common.base.CharMatcher;
@@ -162,7 +163,7 @@ public static JavaFileObject forResource(String resourceName) {
}
static Kind deduceKind(URI uri) {
- String path = uri.getPath();
+ String path = requireNonNull(uri.getPath());
for (Kind kind : Kind.values()) {
if (path.endsWith(kind.extension)) {
return kind;
diff --git a/src/main/java/com/google/testing/compile/JavaSourceSubjectFactory.java b/src/main/java/com/google/testing/compile/JavaSourceSubjectFactory.java
index 80e8041c..ba477913 100644
--- a/src/main/java/com/google/testing/compile/JavaSourceSubjectFactory.java
+++ b/src/main/java/com/google/testing/compile/JavaSourceSubjectFactory.java
@@ -15,19 +15,19 @@
*/
package com.google.testing.compile;
-import com.google.common.truth.FailureStrategy;
-import com.google.common.truth.SubjectFactory;
-
+import com.google.common.truth.FailureMetadata;
+import com.google.common.truth.Subject;
import javax.tools.JavaFileObject;
+import org.jspecify.annotations.Nullable;
/**
- * A Truth {@link SubjectFactory} similar to
+ * A Truth {@link Subject.Factory} similar to
* {@link JavaSourcesSubjectFactory}, but for working with single source files.
*
* @author Gregory Kick
*/
public final class JavaSourceSubjectFactory
- extends SubjectFactory {
+ implements Subject.Factory {
public static JavaSourceSubjectFactory javaSource() {
return new JavaSourceSubjectFactory();
}
@@ -35,8 +35,8 @@ public static JavaSourceSubjectFactory javaSource() {
private JavaSourceSubjectFactory() {}
@Override
- public JavaSourcesSubject.SingleSourceAdapter getSubject(FailureStrategy failureStrategy,
- JavaFileObject subject) {
- return new JavaSourcesSubject.SingleSourceAdapter(failureStrategy, subject);
+ public JavaSourcesSubject.SingleSourceAdapter createSubject(
+ FailureMetadata failureMetadata, @Nullable JavaFileObject subject) {
+ return new JavaSourcesSubject.SingleSourceAdapter(failureMetadata, subject);
}
}
diff --git a/src/main/java/com/google/testing/compile/JavaSourcesSubject.java b/src/main/java/com/google/testing/compile/JavaSourcesSubject.java
index f9759e70..1687c2e8 100644
--- a/src/main/java/com/google/testing/compile/JavaSourcesSubject.java
+++ b/src/main/java/com/google/testing/compile/JavaSourcesSubject.java
@@ -15,16 +15,18 @@
*/
package com.google.testing.compile;
+import static com.google.common.base.Preconditions.checkNotNull;
+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 static com.google.testing.compile.TypeEnumerator.getTopLevelTypes;
+import static java.util.stream.Collectors.collectingAndThen;
+import static java.util.stream.Collectors.toList;
-import com.google.common.base.Function;
+import com.google.auto.value.AutoValue;
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;
@@ -32,7 +34,7 @@
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.io.ByteSource;
-import com.google.common.truth.FailureStrategy;
+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;
@@ -40,34 +42,39 @@
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 java.util.Optional;
+import java.util.stream.Collector;
import javax.annotation.processing.Processor;
import javax.tools.Diagnostic;
import javax.tools.Diagnostic.Kind;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
+import org.jspecify.annotations.Nullable;
/**
* A Truth {@link Subject} that evaluates the result
- * of a {@code javac} compilation. See {@link com.google.testing.compile} for usage examples
+ * 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>
+public final class JavaSourcesSubject extends Subject
implements CompileTester, ProcessedCompileTesterFactory {
- private final List options = new ArrayList(Arrays.asList("-Xlint"));
+ private final @Nullable Iterable extends JavaFileObject> actual;
+ private final List options = new ArrayList<>(Arrays.asList("-Xlint"));
@Nullable private ClassLoader classLoader;
+ @Nullable private ImmutableList classPath;
- JavaSourcesSubject(FailureStrategy failureStrategy, Iterable extends JavaFileObject> subject) {
- super(failureStrategy, subject);
+ JavaSourcesSubject(
+ FailureMetadata failureMetadata, @Nullable Iterable extends JavaFileObject> actual) {
+ super(failureMetadata, actual);
+ this.actual = actual;
}
@Override
@@ -82,12 +89,24 @@ public JavaSourcesSubject withCompilerOptions(String... 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));
@@ -126,7 +145,7 @@ private final class CompilationClause implements CompileTester {
private final ImmutableSet processors;
private CompilationClause() {
- this(ImmutableSet.of());
+ this(ImmutableSet.of());
}
private CompilationClause(Iterable extends Processor> processors) {
@@ -135,11 +154,13 @@ private CompilationClause(Iterable extends Processor> processors) {
@Override
public void parsesAs(JavaFileObject first, JavaFileObject... rest) {
- if (Iterables.isEmpty(actual())) {
- failureStrategy.fail(
- "Compilation generated no additional source files, though some were expected.");
+ if (Iterables.isEmpty(actualNotNull())) {
+ failWithoutActual(
+ simpleFact(
+ "Compilation generated no additional source files, though some were expected."));
+ return;
}
- ParseResult actualResult = Parser.parse(actual());
+ ParseResult actualResult = Parser.parse(actualNotNull(), "*actual* source");
ImmutableList> errors =
actualResult.diagnosticsByKind().get(Kind.ERROR);
if (!errors.isEmpty()) {
@@ -148,116 +169,116 @@ public void parsesAs(JavaFileObject first, JavaFileObject... rest) {
message.append('\n');
message.append(error);
}
- failureStrategy.fail(message.toString());
+ failWithoutActual(simpleFact(message.toString()));
+ return;
}
- final ParseResult expectedResult = Parser.parse(Lists.asList(first, rest));
- final FluentIterable extends CompilationUnitTree> actualTrees = FluentIterable.from(
- actualResult.compilationUnits());
- final FluentIterable extends CompilationUnitTree> expectedTrees = FluentIterable.from(
- expectedResult.compilationUnits());
-
- Function super CompilationUnitTree, ImmutableSet> getTypesFunction =
- new Function>() {
- @Override public ImmutableSet apply(CompilationUnitTree compilationUnit) {
- return TypeEnumerator.getTopLevelTypes(compilationUnit);
- }
- };
-
- final ImmutableMap extends CompilationUnitTree, ImmutableSet> expectedTreeTypes =
- Maps.toMap(expectedTrees, getTypesFunction);
- final ImmutableMap extends CompilationUnitTree, ImmutableSet> actualTreeTypes =
- Maps.toMap(actualTrees, getTypesFunction);
- final ImmutableMap extends CompilationUnitTree, Optional extends CompilationUnitTree>>
- matchedTrees = Maps.toMap(expectedTrees,
- new Function>() {
- @Override public Optional extends CompilationUnitTree> apply(
- final CompilationUnitTree expectedTree) {
- return Iterables.tryFind(actualTrees,
- new Predicate() {
- @Override public boolean apply(CompilationUnitTree actualTree) {
- return expectedTreeTypes.get(expectedTree).equals(
- actualTreeTypes.get(actualTree));
+ ParseResult expectedResult = Parser.parse(Lists.asList(first, rest), "*expected* source");
+ ImmutableList actualTrees =
+ actualResult.compilationUnits().stream()
+ .map(TypedCompilationUnit::create)
+ .collect(toImmutableList());
+ ImmutableList expectedTrees =
+ expectedResult.compilationUnits().stream()
+ .map(TypedCompilationUnit::create)
+ .collect(toImmutableList());
+
+ ImmutableMap> matchedTrees =
+ Maps.toMap(
+ expectedTrees,
+ expectedTree ->
+ actualTrees.stream()
+ .filter(actualTree -> expectedTree.types().equals(actualTree.types()))
+ .findFirst());
+
+ matchedTrees.forEach(
+ (expectedTree, maybeActualTree) -> {
+ if (!maybeActualTree.isPresent()) {
+ failNoCandidates(expectedTree.types(), expectedTree.tree(), actualTrees);
+ return;
+ }
+ TypedCompilationUnit actualTree = maybeActualTree.get();
+ TreeDifference treeDifference =
+ TreeDiffer.diffCompilationUnits(expectedTree.tree(), actualTree.tree());
+ if (!treeDifference.isEmpty()) {
+ String diffReport =
+ treeDifference.getDiffReport(
+ new TreeContext(expectedTree.tree(), expectedResult.trees()),
+ new TreeContext(actualTree.tree(), actualResult.trees()));
+ failWithCandidate(
+ expectedTree.tree().getSourceFile(),
+ actualTree.tree().getSourceFile(),
+ diffReport);
}
});
- }
- });
-
- for (Map.Entry extends CompilationUnitTree, Optional extends CompilationUnitTree>>
- 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,
+ private void failNoCandidates(
+ ImmutableSet expectedTypes,
CompilationUnitTree expectedTree,
- final ImmutableMap extends CompilationUnitTree, ImmutableSet> actualTypes,
- FluentIterable extends CompilationUnitTree> 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());
- failureStrategy.fail(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,
- ""));
+ ImmutableList actualTrees) {
+ String generatedTypesReport =
+ Joiner.on('\n')
+ .join(
+ actualTrees.stream()
+ .map(
+ generated ->
+ String.format(
+ "- %s in <%s>",
+ generated.types(),
+ generated.tree().getSourceFile().toUri().getPath()))
+ .collect(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) {
+ private void failWithCandidate(
+ JavaFileObject expectedSource, JavaFileObject actualSource, String diffReport) {
try {
- failureStrategy.fail(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()));
+ 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);
+ throw new IllegalStateException(
+ "Couldn't read from JavaFileObject when it was already " + "in memory.", e);
}
}
@@ -265,7 +286,7 @@ private void failWithCandidate(JavaFileObject expectedSource,
@Override
public SuccessfulCompilationClause compilesWithoutError() {
Compilation compilation = compilation();
- check().about(compilations()).that(compilation).succeeded();
+ check("compilation()").about(compilations()).that(compilation).succeeded();
return new SuccessfulCompilationBuilder(compilation);
}
@@ -273,7 +294,7 @@ public SuccessfulCompilationClause compilesWithoutError() {
@Override
public CleanCompilationClause compilesWithoutWarnings() {
Compilation compilation = compilation();
- check().about(compilations()).that(compilation).succeededWithoutWarnings();
+ check("compilation()").about(compilations()).that(compilation).succeededWithoutWarnings();
return new CleanCompilationBuilder(compilation);
}
@@ -281,7 +302,7 @@ public CleanCompilationClause compilesWithoutWarnings() {
@Override
public UnsuccessfulCompilationClause failsToCompile() {
Compilation compilation = compilation();
- check().about(compilations()).that(compilation).failed();
+ check("compilation()").about(compilations()).that(compilation).failed();
return new UnsuccessfulCompilationBuilder(compilation);
}
@@ -290,7 +311,21 @@ private Compilation compilation() {
if (classLoader != null) {
compiler = compiler.withClasspathFrom(classLoader);
}
- return compiler.compile(actual());
+ if (classPath != null) {
+ compiler = compiler.withClasspath(classPath);
+ }
+ return compiler.compile(actualNotNull());
+ }
+ }
+
+ @AutoValue
+ abstract static class TypedCompilationUnit {
+ abstract CompilationUnitTree tree();
+
+ abstract ImmutableSet types();
+
+ static TypedCompilationUnit create(CompilationUnitTree tree) {
+ return new AutoValue_JavaSourcesSubject_TypedCompilationUnit(tree, getTopLevelTypes(tree));
}
}
@@ -305,8 +340,8 @@ private CompilationClause newCompilationClause(Iterable extends Processor> pro
/**
* 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}.
+ * @param 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;
@@ -318,7 +353,7 @@ protected CompilationWithWarningsBuilder(Compilation compilation) {
@CanIgnoreReturnValue
@Override
public T withNoteCount(int noteCount) {
- check().about(compilations()).that(compilation).hadNoteCount(noteCount);
+ check("compilation()").about(compilations()).that(compilation).hadNoteCount(noteCount);
return thisObject();
}
@@ -326,13 +361,16 @@ public T withNoteCount(int noteCount) {
@Override
public FileClause withNoteContaining(String messageFragment) {
return new FileBuilder(
- check().about(compilations()).that(compilation).hadNoteContaining(messageFragment));
+ check("compilation()")
+ .about(compilations())
+ .that(compilation)
+ .hadNoteContaining(messageFragment));
}
@CanIgnoreReturnValue
@Override
public T withWarningCount(int warningCount) {
- check().about(compilations()).that(compilation).hadWarningCount(warningCount);
+ check("compilation()").about(compilations()).that(compilation).hadWarningCount(warningCount);
return thisObject();
}
@@ -340,24 +378,28 @@ public T withWarningCount(int warningCount) {
@Override
public FileClause withWarningContaining(String messageFragment) {
return new FileBuilder(
- check().about(compilations()).that(compilation).hadWarningContaining(messageFragment));
+ check("compilation()")
+ .about(compilations())
+ .that(compilation)
+ .hadWarningContaining(messageFragment));
}
@CanIgnoreReturnValue
public T withErrorCount(int errorCount) {
- check().about(compilations()).that(compilation).hadErrorCount(errorCount);
+ check("compilation()").about(compilations()).that(compilation).hadErrorCount(errorCount);
return thisObject();
}
@CanIgnoreReturnValue
public FileClause withErrorContaining(String messageFragment) {
return new FileBuilder(
- check().about(compilations()).that(compilation).hadErrorContaining(messageFragment));
+ check("compilation()")
+ .about(compilations())
+ .that(compilation)
+ .hadErrorContaining(messageFragment));
}
- /**
- * Returns this object, cast to {@code T}.
- */
+ /** Returns this object, cast to {@code T}. */
@SuppressWarnings("unchecked")
protected final T thisObject() {
return (T) this;
@@ -408,10 +450,10 @@ public ChainingClause atColumn(long columnNumber) {
}
/**
- * Base implementation of {@link GeneratedPredicateClause GeneratedPredicateClause} and
- * {@link ChainingClause ChainingClause>}.
+ * Base implementation of {@link GeneratedPredicateClause GeneratedPredicateClause} and {@link
+ * ChainingClause ChainingClause>}.
*
- * @param T the type parameter to {@link GeneratedPredicateClause}. {@code this} must be an
+ * @param the type parameter to {@link GeneratedPredicateClause}. {@code this} must be an
* instance of {@code T}.
*/
private abstract class GeneratedCompilationBuilder extends CompilationWithWarningsBuilder
@@ -424,7 +466,9 @@ protected GeneratedCompilationBuilder(Compilation compilation) {
@CanIgnoreReturnValue
@Override
public T generatesSources(JavaFileObject first, JavaFileObject... rest) {
- new JavaSourcesSubject(failureStrategy, compilation.generatedSourceFiles())
+ check("generatedSourceFiles()")
+ .about(javaSources())
+ .that(compilation.generatedSourceFiles())
.parsesAs(first, rest);
return thisObject();
}
@@ -434,8 +478,8 @@ public T generatesSources(JavaFileObject first, JavaFileObject... rest) {
public T generatesFiles(JavaFileObject first, JavaFileObject... rest) {
for (JavaFileObject expected : Lists.asList(first, rest)) {
if (!wasGenerated(expected)) {
- failureStrategy.fail("Did not find a generated file corresponding to "
- + expected.getName());
+ failWithoutActual(
+ simpleFact("Did not find a generated file corresponding to " + expected.getName()));
}
}
return thisObject();
@@ -461,7 +505,7 @@ boolean wasGenerated(JavaFileObject expected) {
public SuccessfulFileClause generatesFileNamed(
JavaFileManager.Location location, String packageName, String relativeName) {
final JavaFileObjectSubject javaFileObjectSubject =
- check()
+ check("compilation()")
.about(compilations())
.that(compilation)
.generatedFile(location, packageName, relativeName);
@@ -531,21 +575,39 @@ public static JavaSourcesSubject assertThat(JavaFileObject javaFileObject) {
public static JavaSourcesSubject assertThat(
JavaFileObject javaFileObject, JavaFileObject... javaFileObjects) {
return assertAbout(javaSources())
- .that(ImmutableList.builder()
- .add(javaFileObject)
- .add(javaFileObjects)
- .build());
+ .that(
+ ImmutableList.builder()
+ .add(javaFileObject)
+ .add(javaFileObjects)
+ .build());
}
- public static final class SingleSourceAdapter
- extends Subject
+ private Iterable extends JavaFileObject> actualNotNull() {
+ isNotNull();
+ return checkNotNull(actual);
+ }
+
+ private static Collector> toImmutableList() {
+ return collectingAndThen(toList(), ImmutableList::copyOf);
+ }
+
+ public static final class SingleSourceAdapter extends Subject
implements CompileTester, ProcessedCompileTesterFactory {
private final JavaSourcesSubject delegate;
- SingleSourceAdapter(FailureStrategy failureStrategy, JavaFileObject subject) {
- super(failureStrategy, subject);
+ SingleSourceAdapter(FailureMetadata failureMetadata, @Nullable 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 =
- new JavaSourcesSubject(failureStrategy, ImmutableList.of(subject));
+ check("delegate()").about(javaSources()).that(ImmutableList.of(checkNotNull(subject)));
}
@Override
@@ -558,11 +620,22 @@ 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));
diff --git a/src/main/java/com/google/testing/compile/JavaSourcesSubjectFactory.java b/src/main/java/com/google/testing/compile/JavaSourcesSubjectFactory.java
index 645f242a..517e21e1 100644
--- a/src/main/java/com/google/testing/compile/JavaSourcesSubjectFactory.java
+++ b/src/main/java/com/google/testing/compile/JavaSourcesSubjectFactory.java
@@ -15,19 +15,19 @@
*/
package com.google.testing.compile;
-import com.google.common.truth.FailureStrategy;
-import com.google.common.truth.SubjectFactory;
-
+import com.google.common.truth.FailureMetadata;
+import com.google.common.truth.Subject;
import javax.tools.JavaFileObject;
+import org.jspecify.annotations.Nullable;
/**
- * A Truth {@link SubjectFactory} for creating
+ * A Truth {@link Subject.Factory} for creating
* {@link JavaSourcesSubject} instances.
*
* @author Gregory Kick
*/
public final class JavaSourcesSubjectFactory
- extends SubjectFactory> {
+ implements Subject.Factory> {
public static JavaSourcesSubjectFactory javaSources() {
return new JavaSourcesSubjectFactory();
}
@@ -35,8 +35,8 @@ public static JavaSourcesSubjectFactory javaSources() {
private JavaSourcesSubjectFactory() {}
@Override
- public JavaSourcesSubject getSubject(FailureStrategy failureStrategy,
- Iterable extends JavaFileObject> subject) {
- return new JavaSourcesSubject(failureStrategy, subject);
+ public JavaSourcesSubject createSubject(
+ FailureMetadata failureMetadata, @Nullable Iterable extends JavaFileObject> subject) {
+ return new JavaSourcesSubject(failureMetadata, subject);
}
}
diff --git a/src/main/java/com/google/testing/compile/MoreTrees.java b/src/main/java/com/google/testing/compile/MoreTrees.java
index 7ffec537..9c4fd1d6 100644
--- a/src/main/java/com/google/testing/compile/MoreTrees.java
+++ b/src/main/java/com/google/testing/compile/MoreTrees.java
@@ -15,7 +15,6 @@
*/
package com.google.testing.compile;
-import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
@@ -35,7 +34,8 @@
import com.sun.source.util.TreePath;
import com.sun.source.util.TreePathScanner;
import java.util.Arrays;
-import javax.annotation.Nullable;
+import java.util.Optional;
+import org.jspecify.annotations.Nullable;
/**
* A class containing methods which are useful for gaining access to {@code Tree} instances from
@@ -52,7 +52,7 @@ static CompilationUnitTree parseLinesToTree(String... source) {
/** Parses the source given into a {@link CompilationUnitTree}. */
static CompilationUnitTree parseLinesToTree(Iterable source) {
Iterable extends CompilationUnitTree> parseResults =
- Parser.parse(ImmutableList.of(JavaFileObjects.forSourceLines("", source)))
+ Parser.parse(ImmutableList.of(JavaFileObjects.forSourceLines("", source)), "source")
.compilationUnits();
return Iterables.getOnlyElement(parseResults);
}
@@ -64,13 +64,13 @@ static ParseResult parseLines(String... source) {
/** Parses the source given and produces a {@link ParseResult}. */
static ParseResult parseLines(Iterable source) {
- return Parser.parse(ImmutableList.of(JavaFileObjects.forSourceLines("", source)));
+ return Parser.parse(ImmutableList.of(JavaFileObjects.forSourceLines("", source)), "source");
}
/**
* Finds the first instance of the given {@link Tree.Kind} that is a subtree of the root provided.
*
- * @throw IllegalArgumentException if no such subtree exists.
+ * @throws IllegalArgumentException if no such subtree exists.
*/
static Tree findSubtree(CompilationUnitTree root, Tree.Kind treeKind) {
return findSubtree(root, treeKind, null);
@@ -82,10 +82,10 @@ static Tree findSubtree(CompilationUnitTree root, Tree.Kind treeKind) {
*
*
See the doc on {@link #findSubtreePath} for details on the identifier param.
*
- * @throw IllegalArgumentException if no such subtree exists.
+ * @throws IllegalArgumentException if no such subtree exists.
*/
- static Tree findSubtree(CompilationUnitTree root, Tree.Kind treeKind,
- @Nullable String identifier) {
+ static Tree findSubtree(
+ CompilationUnitTree root, Tree.Kind treeKind, @Nullable String identifier) {
return findSubtreePath(root, treeKind, identifier).getLeaf();
}
@@ -93,7 +93,7 @@ static Tree findSubtree(CompilationUnitTree root, Tree.Kind treeKind,
* Finds a path to the first instance of the given {@link Tree.Kind} that is a subtree of the root
* provided.
*
- * @throw IllegalArgumentException if no such subtree exists.
+ * @throws IllegalArgumentException if no such subtree exists.
*/
static TreePath findSubtreePath(CompilationUnitTree root, Tree.Kind treeKind) {
return findSubtreePath(root, treeKind, null);
@@ -121,18 +121,15 @@ static TreePath findSubtreePath(CompilationUnitTree root, Tree.Kind treeKind) {
*/
static TreePath findSubtreePath(CompilationUnitTree root, Tree.Kind treeKind,
@Nullable String identifier) {
- SearchScanner subtreeFinder = new SearchScanner(treeKind,
- (identifier == null) ? Optional.absent() : Optional.of(identifier));
+ SearchScanner subtreeFinder = new SearchScanner(treeKind, Optional.ofNullable(identifier));
Optional res = subtreeFinder.scan(root, null);
Preconditions.checkArgument(res.isPresent(), "Couldn't find any subtree matching the given "
+ "criteria. Root: %s, Class: %s, Identifier: %s", root, treeKind, identifier);
return res.get();
}
- /**
- * A {@link TreePathScanner} to power the subtree searches in this class
- */
- static final class SearchScanner extends TreePathScanner, Void> {
+ /** A {@link TreePathScanner} to power the subtree searches in this class */
+ static final class SearchScanner extends TreePathScanner, @Nullable Void> {
private final Optional identifier;
private final Tree.Kind kindSought;
@@ -152,8 +149,7 @@ private boolean isMatch(Tree node, Optional