Skip to content

Commit

Permalink
Make BugPattern{,Test}Extractor tests more maintainable
Browse files Browse the repository at this point in the history
As we're moving to a Java-based website generator located in the same
package as the `Extractor` implementations, there is no need to validate
the exact format of generated files; only that the data can be
deserialized again.

While there, track the source file from which data is extracted.
  • Loading branch information
Stephan202 authored and rickie committed Jan 10, 2024
1 parent c7a288c commit 5d9c0a8
Show file tree
Hide file tree
Showing 18 changed files with 335 additions and 274 deletions.
8 changes: 8 additions & 0 deletions documentation-support/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-guava</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-parameter-names</artifactId>
</dependency>
<dependency>
<groupId>com.google.auto</groupId>
<artifactId>auto-common</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import static com.google.common.collect.ImmutableList.toImmutableList;
import static java.util.Objects.requireNonNull;

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.google.auto.common.AnnotationMirrors;
import com.google.auto.service.AutoService;
import com.google.auto.value.AutoValue;
Expand All @@ -17,6 +18,7 @@
import com.sun.source.tree.ClassTree;
import com.sun.tools.javac.code.Attribute;
import com.sun.tools.javac.code.Symbol.ClassSymbol;
import java.net.URI;
import java.util.Optional;
import javax.lang.model.element.AnnotationValue;
import tech.picnic.errorprone.documentation.BugPatternExtractor.BugPatternDocumentation;
Expand Down Expand Up @@ -45,7 +47,8 @@ public Optional<BugPatternDocumentation> tryExtract(ClassTree tree, VisitorState
}

return Optional.of(
new AutoValue_BugPatternExtractor_BugPatternDocumentation(
BugPatternDocumentation.create(
state.getPath().getCompilationUnit().getSourceFile().toUri(),
symbol.getQualifiedName().toString(),
annotation.name().isEmpty() ? tree.getSimpleName().toString() : annotation.name(),
ImmutableList.copyOf(annotation.altNames()),
Expand Down Expand Up @@ -91,7 +94,36 @@ private static <T extends AnnotationValue> T doCast(AnnotationValue value, Class
}

@AutoValue
@JsonDeserialize(as = AutoValue_BugPatternExtractor_BugPatternDocumentation.class)
abstract static class BugPatternDocumentation {
static BugPatternDocumentation create(
URI source,
String fullyQualifiedName,
String name,
ImmutableList<String> altNames,
String link,
ImmutableList<String> tags,
String summary,
String explanation,
SeverityLevel severityLevel,
boolean canDisable,
ImmutableList<String> suppressionAnnotations) {
return new AutoValue_BugPatternExtractor_BugPatternDocumentation(
source,
fullyQualifiedName,
name,
altNames,
link,
tags,
summary,
explanation,
severityLevel,
canDisable,
suppressionAnnotations);
}

abstract URI source();

abstract String fullyQualifiedName();

abstract String name();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@
import static com.google.errorprone.matchers.method.MethodMatchers.staticMethod;
import static java.util.function.Predicate.not;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.google.auto.service.AutoService;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
Expand All @@ -15,6 +22,7 @@
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.util.TreeScanner;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
Expand Down Expand Up @@ -52,7 +60,9 @@ public Optional<TestCases> tryExtract(ClassTree tree, VisitorState state) {
.map(
tests ->
new AutoValue_BugPatternTestExtractor_TestCases(
ASTHelpers.getSymbol(tree).className(), tests));
state.getPath().getCompilationUnit().getSourceFile().toUri(),
ASTHelpers.getSymbol(tree).className(),
tests));
}

private static final class BugPatternTestCollector
Expand Down Expand Up @@ -198,32 +208,78 @@ private static Optional<String> getSourceCode(MethodInvocationTree tree) {
}

@AutoValue
@JsonDeserialize(as = AutoValue_BugPatternTestExtractor_TestCases.class)
abstract static class TestCases {
static TestCases create(URI source, String testClass, ImmutableList<TestCase> testCases) {
return new AutoValue_BugPatternTestExtractor_TestCases(source, testClass, testCases);
}

abstract URI source();

abstract String testClass();

abstract ImmutableList<TestCase> testCases();
}

@AutoValue
@JsonDeserialize(as = AutoValue_BugPatternTestExtractor_TestCase.class)
abstract static class TestCase {
static TestCase create(String classUnderTest, ImmutableList<TestEntry> entries) {
return new AutoValue_BugPatternTestExtractor_TestCase(classUnderTest, entries);
}

abstract String classUnderTest();

abstract ImmutableList<TestEntry> entries();
}

@JsonSubTypes({
@JsonSubTypes.Type(AutoValue_BugPatternTestExtractor_IdentificationTestEntry.class),
@JsonSubTypes.Type(AutoValue_BugPatternTestExtractor_ReplacementTestEntry.class)
})
@JsonTypeInfo(include = As.EXISTING_PROPERTY, property = "type", use = JsonTypeInfo.Id.DEDUCTION)
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonPropertyOrder("type")
interface TestEntry {
TestType type();

String path();

enum TestType {
IDENTIFICATION,
REPLACEMENT
}
}

@AutoValue
abstract static class ReplacementTestEntry implements TestEntry {
abstract String input();
abstract static class IdentificationTestEntry implements TestEntry {
static IdentificationTestEntry create(String path, String code) {
return new AutoValue_BugPatternTestExtractor_IdentificationTestEntry(path, code);
}

abstract String output();
@JsonProperty
@Override
public final TestType type() {
return TestType.IDENTIFICATION;
}

abstract String code();
}

@AutoValue
abstract static class IdentificationTestEntry implements TestEntry {
abstract String code();
abstract static class ReplacementTestEntry implements TestEntry {
static ReplacementTestEntry create(String path, String input, String output) {
return new AutoValue_BugPatternTestExtractor_ReplacementTestEntry(path, input, output);
}

@JsonProperty
@Override
public final TestType type() {
return TestType.REPLACEMENT;
}

abstract String input();

abstract String output();
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
package tech.picnic.errorprone.documentation;

import static java.nio.charset.StandardCharsets.UTF_8;

import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.VisitorState;
import com.sun.source.tree.ClassTree;
Expand All @@ -15,10 +10,7 @@
import com.sun.source.util.TreePath;
import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.util.Context;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
Expand All @@ -39,9 +31,6 @@ final class DocumentationGeneratorTaskListener implements TaskListener {
ServiceLoader.load(
Extractor.class, DocumentationGeneratorTaskListener.class.getClassLoader()));

private static final ObjectMapper OBJECT_MAPPER =
new ObjectMapper().setVisibility(PropertyAccessor.FIELD, Visibility.ANY);

private final Context context;
private final Path docsPath;

Expand Down Expand Up @@ -94,13 +83,7 @@ private void createDocsDirectory() {
}

private <T> void writeToFile(String identifier, String className, T data) {
File file = docsPath.resolve(String.format("%s-%s.json", identifier, className)).toFile();

try (FileWriter fileWriter = new FileWriter(file, UTF_8)) {
OBJECT_MAPPER.writeValue(fileWriter, data);
} catch (IOException e) {
throw new UncheckedIOException(String.format("Cannot write to file '%s'", file.getPath()), e);
}
Json.write(docsPath.resolve(String.format("%s-%s.json", identifier, className)), data);
}

private static String getSimpleClassName(URI path) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package tech.picnic.errorprone.documentation;

import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.guava.GuavaModule;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import com.google.errorprone.annotations.FormatMethod;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Path;

/**
* Utility class that offers mutually consistent JSON serialization and deserialization operations,
* without further specifying the exact schema used.
*/
final class Json {
private static final ObjectMapper OBJECT_MAPPER =
new ObjectMapper()
.setVisibility(PropertyAccessor.FIELD, Visibility.ANY)
.registerModules(new GuavaModule(), new ParameterNamesModule());

private Json() {}

static <T> T read(Path path, Class<T> clazz) {
try {
return OBJECT_MAPPER.readValue(path.toFile(), clazz);
} catch (IOException e) {
throw failure(e, "Failure reading from '%s'", path);
}
}

static <T> void write(Path path, T object) {
try {
OBJECT_MAPPER.writeValue(path.toFile(), object);
} catch (IOException e) {
throw failure(e, "Failure writing to '%s'", path);
}
}

@FormatMethod
private static UncheckedIOException failure(IOException cause, String format, Object... args) {
return new UncheckedIOException(String.format(format, args), cause);
}
}
Loading

0 comments on commit 5d9c0a8

Please sign in to comment.