From 1f30ba28c655ffbb7289d6e41fb0d0650c4f0443 Mon Sep 17 00:00:00 2001 From: Rick Ossendrijver Date: Wed, 8 Feb 2023 09:36:43 +0100 Subject: [PATCH] Introduce `BugPatternTestExtractor` with tests --- .../documentation/BugPatternExtractor.java | 3 +- .../BugPatternTestExtractor.java | 180 +++++++ .../DocumentationGeneratorTaskListener.java | 3 +- .../errorprone/documentation/Extractor.java | 5 +- .../documentation/ExtractorType.java | 8 +- .../BugPatternExtractorTest.java | 2 +- .../BugPatternTestExtractorTest.java | 465 ++++++++++++++++++ ...tation-identification-and-replacement.json | 12 + ...umentation-identification-two-sources.json | 8 + ...ern-test-documentation-identification.json | 7 + ...bugpattern-test-documentation-minimal.json | 5 + ...ltiple-identification-and-replacement.json | 17 + ...entation-replacement-expect-unchanged.json | 10 + ...documentation-replacement-two-sources.json | 14 + ...attern-test-documentation-replacement.json | 10 + 15 files changed, 742 insertions(+), 7 deletions(-) create mode 100644 documentation-support/src/main/java/tech/picnic/errorprone/documentation/BugPatternTestExtractor.java create mode 100644 documentation-support/src/test/java/tech/picnic/errorprone/documentation/BugPatternTestExtractorTest.java create mode 100644 documentation-support/src/test/resources/tech/picnic/errorprone/documentation/bugpattern-test-documentation-identification-and-replacement.json create mode 100644 documentation-support/src/test/resources/tech/picnic/errorprone/documentation/bugpattern-test-documentation-identification-two-sources.json create mode 100644 documentation-support/src/test/resources/tech/picnic/errorprone/documentation/bugpattern-test-documentation-identification.json create mode 100644 documentation-support/src/test/resources/tech/picnic/errorprone/documentation/bugpattern-test-documentation-minimal.json create mode 100644 documentation-support/src/test/resources/tech/picnic/errorprone/documentation/bugpattern-test-documentation-multiple-identification-and-replacement.json create mode 100644 documentation-support/src/test/resources/tech/picnic/errorprone/documentation/bugpattern-test-documentation-replacement-expect-unchanged.json create mode 100644 documentation-support/src/test/resources/tech/picnic/errorprone/documentation/bugpattern-test-documentation-replacement-two-sources.json create mode 100644 documentation-support/src/test/resources/tech/picnic/errorprone/documentation/bugpattern-test-documentation-replacement.json diff --git a/documentation-support/src/main/java/tech/picnic/errorprone/documentation/BugPatternExtractor.java b/documentation-support/src/main/java/tech/picnic/errorprone/documentation/BugPatternExtractor.java index 071b3f91d06..ce1c7c5a38c 100644 --- a/documentation-support/src/main/java/tech/picnic/errorprone/documentation/BugPatternExtractor.java +++ b/documentation-support/src/main/java/tech/picnic/errorprone/documentation/BugPatternExtractor.java @@ -9,6 +9,7 @@ import com.google.common.collect.ImmutableList; import com.google.errorprone.BugPattern; import com.google.errorprone.BugPattern.SeverityLevel; +import com.google.errorprone.VisitorState; import com.google.errorprone.annotations.Immutable; import com.google.errorprone.util.ASTHelpers; import com.sun.source.tree.AnnotationTree; @@ -44,7 +45,7 @@ public BugPatternDocumentation extract(ClassTree tree, Context context) { } @Override - public boolean canExtract(ClassTree tree) { + public boolean canExtract(ClassTree tree, VisitorState state) { return ASTHelpers.hasDirectAnnotationWithSimpleName(tree, BugPattern.class.getSimpleName()); } diff --git a/documentation-support/src/main/java/tech/picnic/errorprone/documentation/BugPatternTestExtractor.java b/documentation-support/src/main/java/tech/picnic/errorprone/documentation/BugPatternTestExtractor.java new file mode 100644 index 00000000000..2374035625b --- /dev/null +++ b/documentation-support/src/main/java/tech/picnic/errorprone/documentation/BugPatternTestExtractor.java @@ -0,0 +1,180 @@ +package tech.picnic.errorprone.documentation; + +import static com.google.errorprone.matchers.Matchers.hasAnnotation; +import static com.google.errorprone.matchers.Matchers.instanceMethod; +import static com.google.errorprone.matchers.method.MethodMatchers.staticMethod; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableList; +import com.google.errorprone.VisitorState; +import com.google.errorprone.annotations.Immutable; +import com.google.errorprone.annotations.Var; +import com.google.errorprone.matchers.Matcher; +import com.google.errorprone.util.ASTHelpers; +import com.sun.source.tree.ClassTree; +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.MemberSelectTree; +import com.sun.source.tree.MethodInvocationTree; +import com.sun.source.tree.MethodTree; +import com.sun.source.util.TreeScanner; +import com.sun.tools.javac.util.Context; +import java.util.ArrayList; +import java.util.List; +import org.jspecify.annotations.Nullable; +import tech.picnic.errorprone.documentation.BugPatternTestExtractor.BugPatternTestDocumentation; + +/** + * An {@link Extractor} that describes how to extract data from a test that tests a {@code + * BugChecker}. + */ +@Immutable +final class BugPatternTestExtractor implements Extractor { + private static final Matcher JUNIT_TEST_METHOD = + hasAnnotation("org.junit.jupiter.api.Test"); + + // XXX: Improve support for correctly extracting multiple sources from a single + // `{BugCheckerRefactoring,Compilation}TestHelper` test. + @Override + public BugPatternTestDocumentation extract(ClassTree tree, Context context) { + VisitorState state = VisitorState.createForUtilityPurposes(context); + CollectBugPatternTests scanner = new CollectBugPatternTests(state); + + tree.getMembers().stream() + .filter(MethodTree.class::isInstance) + .map(MethodTree.class::cast) + .filter(m -> JUNIT_TEST_METHOD.matches(m, state)) + .forEach(m -> scanner.scan(m, null)); + + String className = tree.getSimpleName().toString(); + return new AutoValue_BugPatternTestExtractor_BugPatternTestDocumentation( + className.substring(0, className.lastIndexOf("Test")), + scanner.getIdentificationTests(), + scanner.getReplacementTests()); + } + + // XXX: Further improve the heuristics for determining what a BugPattern test is. + @Override + public boolean canExtract(ClassTree tree, VisitorState state) { + String className = tree.getSimpleName().toString(); + if (!className.endsWith("Test")) { + return false; + } + + ScanBugPatternTest scanBugPatternTest = new ScanBugPatternTest(); + scanBugPatternTest.scan(tree, state); + + String bugPatternName = className.substring(0, className.lastIndexOf("Test")); + return scanBugPatternTest.hasTestUsingClassInstance(bugPatternName); + } + + private static final class ScanBugPatternTest extends TreeScanner<@Nullable Void, VisitorState> { + private static final Matcher BUG_PATTERN_TEST_METHOD = + staticMethod() + .onDescendantOfAny( + "com.google.errorprone.CompilationTestHelper", + "com.google.errorprone.BugCheckerRefactoringTestHelper") + .named("newInstance"); + + private final List encounteredClasses = new ArrayList<>(); + + boolean hasTestUsingClassInstance(String clazz) { + return encounteredClasses.contains(clazz); + } + + @Override + public @Nullable Void visitMethodInvocation(MethodInvocationTree node, VisitorState state) { + if (BUG_PATTERN_TEST_METHOD.matches(node, state)) { + MemberSelectTree firstArgumentTree = (MemberSelectTree) node.getArguments().get(0); + encounteredClasses.add(firstArgumentTree.getExpression().toString()); + } + return super.visitMethodInvocation(node, state); + } + } + + private static final class CollectBugPatternTests + extends TreeScanner<@Nullable Void, @Nullable Void> { + private static final Matcher IDENTIFICATION_SOURCE_LINES = + instanceMethod() + .onDescendantOf("com.google.errorprone.CompilationTestHelper") + .named("addSourceLines"); + private static final Matcher REPLACEMENT_INPUT = + instanceMethod() + .onDescendantOf("com.google.errorprone.BugCheckerRefactoringTestHelper") + .named("addInputLines"); + private static final Matcher REPLACEMENT_OUTPUT = + instanceMethod() + .onDescendantOf("com.google.errorprone.BugCheckerRefactoringTestHelper.ExpectOutput") + .named("addOutputLines"); + + private final VisitorState state; + private final List identificationTests = new ArrayList<>(); + private final List replacementTests = new ArrayList<>(); + + @Var private String replacementOutputLines = ""; + + CollectBugPatternTests(VisitorState state) { + this.state = state; + } + + public ImmutableList getIdentificationTests() { + return ImmutableList.copyOf(identificationTests); + } + + public ImmutableList getReplacementTests() { + return ImmutableList.copyOf(replacementTests); + } + + @Override + public @Nullable Void visitMethodInvocation(MethodInvocationTree node, @Nullable Void unused) { + if (IDENTIFICATION_SOURCE_LINES.matches(node, state)) { + identificationTests.add(getSourceLines(node)); + } else if (REPLACEMENT_INPUT.matches(node, state)) { + /* The visitor starts with `addOutputLines` and in the next visit it will go over the `addInputLines`. */ + replacementTests.add( + BugPatternReplacementTestDocumentation.create( + getSourceLines(node), replacementOutputLines)); + } else if (REPLACEMENT_OUTPUT.matches(node, state)) { + replacementOutputLines = getSourceLines(node); + } + return super.visitMethodInvocation(node, unused); + } + + // XXX: Duplicate from `ErrorProneTestSourceFormat`, should we move this to `SourceCode` util? + private static String getSourceLines(MethodInvocationTree tree) { + List sourceLines = + tree.getArguments().subList(1, tree.getArguments().size()); + StringBuilder source = new StringBuilder(); + + for (ExpressionTree sourceLine : sourceLines) { + Object value = ASTHelpers.constValue(sourceLine); + if (value == null) { + return ""; + } + source.append(value).append('\n'); + } + + return source.toString(); + } + } + + @AutoValue + abstract static class BugPatternTestDocumentation { + abstract String name(); + + abstract ImmutableList identificationTests(); + + abstract ImmutableList replacementTests(); + } + + @AutoValue + abstract static class BugPatternReplacementTestDocumentation { + static BugPatternReplacementTestDocumentation create(String sourceLines, String outputLines) { + return new AutoValue_BugPatternTestExtractor_BugPatternReplacementTestDocumentation( + sourceLines, outputLines); + } + + abstract String inputLines(); + + abstract String outputLines(); + } +} diff --git a/documentation-support/src/main/java/tech/picnic/errorprone/documentation/DocumentationGeneratorTaskListener.java b/documentation-support/src/main/java/tech/picnic/errorprone/documentation/DocumentationGeneratorTaskListener.java index 29fa2203d2e..c84665e1901 100644 --- a/documentation-support/src/main/java/tech/picnic/errorprone/documentation/DocumentationGeneratorTaskListener.java +++ b/documentation-support/src/main/java/tech/picnic/errorprone/documentation/DocumentationGeneratorTaskListener.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.errorprone.VisitorState; import com.sun.source.tree.ClassTree; import com.sun.source.util.TaskEvent; import com.sun.source.util.TaskEvent.Kind; @@ -57,7 +58,7 @@ public void finished(TaskEvent taskEvent) { return; } - ExtractorType.findMatchingType(classTree) + ExtractorType.findMatchingType(classTree, VisitorState.createForUtilityPurposes(context)) .ifPresent( extractorType -> writeToFile( diff --git a/documentation-support/src/main/java/tech/picnic/errorprone/documentation/Extractor.java b/documentation-support/src/main/java/tech/picnic/errorprone/documentation/Extractor.java index 210c84ab94f..a7673c72607 100644 --- a/documentation-support/src/main/java/tech/picnic/errorprone/documentation/Extractor.java +++ b/documentation-support/src/main/java/tech/picnic/errorprone/documentation/Extractor.java @@ -1,5 +1,6 @@ package tech.picnic.errorprone.documentation; +import com.google.errorprone.VisitorState; import com.google.errorprone.annotations.Immutable; import com.sun.source.tree.ClassTree; import com.sun.tools.javac.util.Context; @@ -27,7 +28,9 @@ interface Extractor { * ClassTree}. * * @param tree The {@link ClassTree} of interest. + * @param state A {@link VisitorState} describes the context in which the given {@link ClassTree} + * is found. * @return {@code true} iff data extraction is supported. */ - boolean canExtract(ClassTree tree); + boolean canExtract(ClassTree tree, VisitorState state); } diff --git a/documentation-support/src/main/java/tech/picnic/errorprone/documentation/ExtractorType.java b/documentation-support/src/main/java/tech/picnic/errorprone/documentation/ExtractorType.java index 42f5b48abda..7704bc900d2 100644 --- a/documentation-support/src/main/java/tech/picnic/errorprone/documentation/ExtractorType.java +++ b/documentation-support/src/main/java/tech/picnic/errorprone/documentation/ExtractorType.java @@ -2,13 +2,15 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; +import com.google.errorprone.VisitorState; import com.sun.source.tree.ClassTree; import java.util.EnumSet; import java.util.Optional; /** An enumeration of {@link Extractor} types. */ enum ExtractorType { - BUG_PATTERN("bugpattern", new BugPatternExtractor()); + BUG_PATTERN("bugpattern", new BugPatternExtractor()), + BUG_PATTERN_TEST("bugpattern-test", new BugPatternTestExtractor()); private static final ImmutableSet TYPES = Sets.immutableEnumSet(EnumSet.allOf(ExtractorType.class)); @@ -29,7 +31,7 @@ Extractor getExtractor() { return extractor; } - static Optional findMatchingType(ClassTree tree) { - return TYPES.stream().filter(type -> type.getExtractor().canExtract(tree)).findFirst(); + static Optional findMatchingType(ClassTree tree, VisitorState state) { + return TYPES.stream().filter(type -> type.getExtractor().canExtract(tree, state)).findFirst(); } } diff --git a/documentation-support/src/test/java/tech/picnic/errorprone/documentation/BugPatternExtractorTest.java b/documentation-support/src/test/java/tech/picnic/errorprone/documentation/BugPatternExtractorTest.java index 1737f32ac46..d951eec7600 100644 --- a/documentation-support/src/test/java/tech/picnic/errorprone/documentation/BugPatternExtractorTest.java +++ b/documentation-support/src/test/java/tech/picnic/errorprone/documentation/BugPatternExtractorTest.java @@ -146,7 +146,7 @@ public Description matchClass(ClassTree tree, VisitorState state) { .hasMessage("BugPattern annotation must be present"); return buildDescription(tree) - .setMessage(String.format("Can extract: %s", extractor.canExtract(tree))) + .setMessage(String.format("Can extract: %s", extractor.canExtract(tree, state))) .build(); } } diff --git a/documentation-support/src/test/java/tech/picnic/errorprone/documentation/BugPatternTestExtractorTest.java b/documentation-support/src/test/java/tech/picnic/errorprone/documentation/BugPatternTestExtractorTest.java new file mode 100644 index 00000000000..208b09ee0eb --- /dev/null +++ b/documentation-support/src/test/java/tech/picnic/errorprone/documentation/BugPatternTestExtractorTest.java @@ -0,0 +1,465 @@ +package tech.picnic.errorprone.documentation; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; + +import com.google.common.io.Resources; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +final class BugPatternTestExtractorTest { + @Test + void noBugPatternTest(@TempDir Path outputDirectory) { + JavacTaskCompilation.compile( + outputDirectory, + "TestCheckerWithoutAnnotation.java", + "import com.google.errorprone.bugpatterns.BugChecker;", + "", + "public final class TestCheckerWithoutAnnotation extends BugChecker {}"); + + assertThat(outputDirectory.toAbsolutePath()).isEmptyDirectory(); + } + + @Test + void minimalBugPatternTest(@TempDir Path outputDirectory) throws IOException { + JavacTaskCompilation.compile( + outputDirectory, + "IdentityConversionTest.java", + "import com.google.errorprone.BugCheckerRefactoringTestHelper;", + "import com.google.errorprone.bugpatterns.BugChecker;", + "import com.google.errorprone.CompilationTestHelper;", + "", + "final class IdentityConversionTest {", + " private static class IdentityConversion extends BugChecker {}", + "", + " CompilationTestHelper compilationTestHelper = CompilationTestHelper.newInstance(IdentityConversion.class, getClass());", + "}"); + + verifyFileMatchesResource( + outputDirectory, + "bugpattern-test-IdentityConversionTest.json", + "bugpattern-test-documentation-minimal.json"); + } + + @Test + void differentBugPatternTest(@TempDir Path outputDirectory) { + JavacTaskCompilation.compile( + outputDirectory, + "IdentityConversionTest.java", + "import com.google.errorprone.BugCheckerRefactoringTestHelper;", + "import com.google.errorprone.bugpatterns.BugChecker;", + "import com.google.errorprone.CompilationTestHelper;", + "", + "final class IdentityConversionTest {", + " private static class DifferentBugPattern extends BugChecker {}", + "", + " CompilationTestHelper compilationTestHelper = CompilationTestHelper.newInstance(DifferentBugPattern.class, getClass());", + "}"); + + assertThat(outputDirectory.toAbsolutePath()).isEmptyDirectory(); + } + + @Test + void differentBugPatternWithTest(@TempDir Path outputDirectory) { + JavacTaskCompilation.compile( + outputDirectory, + "IdentityConversionTest.java", + "import com.google.errorprone.BugCheckerRefactoringTestHelper;", + "import com.google.errorprone.bugpatterns.BugChecker;", + "import com.google.errorprone.CompilationTestHelper;", + "import org.junit.jupiter.api.Test;", + "", + "final class IdentityConversionTest {", + " private static class TestChecker extends BugChecker {}", + "", + " @Test", + " void identification() {", + " CompilationTestHelper.newInstance(TestChecker.class, getClass())", + " .addSourceLines(\"A.java\", \"class A {}\")", + " .doTest();", + " }", + "}"); + + assertThat(outputDirectory.toAbsolutePath()).isEmptyDirectory(); + } + + @Test + void bugPatternTestSingleIdentification(@TempDir Path outputDirectory) throws IOException { + JavacTaskCompilation.compile( + outputDirectory, + "IdentityConversionTest.java", + "package pkg;", + "", + "import com.google.errorprone.BugCheckerRefactoringTestHelper;", + "import com.google.errorprone.bugpatterns.BugChecker;", + "import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;", + "import com.google.errorprone.CompilationTestHelper;", + "import org.junit.jupiter.api.Test;", + "", + "final class IdentityConversionTest {", + " private static class IdentityConversion extends BugChecker {}", + "", + " @Test", + " void identification() {", + " CompilationTestHelper.newInstance(IdentityConversion.class, getClass())", + " .addSourceLines(", + " \"A.java\",", + " \"public final class A {\",", + " \" public void m() {\",", + " \" // BUG: Diagnostic contains:\",", + " \" Boolean b = Boolean.valueOf(Boolean.FALSE);\",", + " \" }\",", + " \"}\")", + " .doTest();", + " }", + "}"); + + verifyFileMatchesResource( + outputDirectory, + "bugpattern-test-IdentityConversionTest.json", + "bugpattern-test-documentation-identification.json"); + } + + @Test + void bugPatternTestIdentificationMultipleSourceLines(@TempDir Path outputDirectory) + throws IOException { + JavacTaskCompilation.compile( + outputDirectory, + "IdentityConversionTest.java", + "package pkg;", + "", + "import com.google.errorprone.BugCheckerRefactoringTestHelper;", + "import com.google.errorprone.bugpatterns.BugChecker;", + "import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;", + "import com.google.errorprone.CompilationTestHelper;", + "import org.junit.jupiter.api.Test;", + "", + "final class IdentityConversionTest {", + " private static class IdentityConversion extends BugChecker {}", + "", + " @Test", + " void identification() {", + " CompilationTestHelper.newInstance(IdentityConversion.class, getClass())", + " .addSourceLines(", + " \"A.java\",", + " \"public final class A {}\")", + " .addSourceLines(", + " \"B.java\",", + " \"public final class B {}\")", + " .doTest();", + " }", + "}"); + + verifyFileMatchesResource( + outputDirectory, + "bugpattern-test-IdentityConversionTest.json", + "bugpattern-test-documentation-identification-two-sources.json"); + } + + @Test + void bugPatternTestSingleReplacement(@TempDir Path outputDirectory) throws IOException { + JavacTaskCompilation.compile( + outputDirectory, + "IdentityConversionTest.java", + "package pkg;", + "", + "import com.google.errorprone.BugCheckerRefactoringTestHelper;", + "import com.google.errorprone.BugCheckerRefactoringTestHelper.FixChoosers;", + "import com.google.errorprone.bugpatterns.BugChecker;", + "import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;", + "import com.google.errorprone.CompilationTestHelper;", + "import org.junit.jupiter.api.Test;", + "", + "final class IdentityConversionTest {", + " private static class IdentityConversion extends BugChecker {}", + "", + " @Test", + " void replacementFirstSuggestedFix() {", + " BugCheckerRefactoringTestHelper.newInstance(IdentityConversion.class, getClass())", + " .setFixChooser(FixChoosers.FIRST)", + " .addInputLines(", + " \"A.java\",", + " \"import com.google.common.collect.ImmutableSet;\",", + " \"\",", + " \"public final class A {\",", + " \" public void m() {\",", + " \" ImmutableSet set = ImmutableSet.copyOf(ImmutableSet.of());\",", + " \" }\",", + " \"}\")", + " .addOutputLines(", + " \"A.java\",", + " \"import com.google.common.collect.ImmutableSet;\",", + " \"\",", + " \"public final class A {\",", + " \" public void m() {\",", + " \" ImmutableSet set = ImmutableSet.of();\",", + " \" }\",", + " \"}\")", + " .doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH);", + " }", + "}"); + + verifyFileMatchesResource( + outputDirectory, + "bugpattern-test-IdentityConversionTest.json", + "bugpattern-test-documentation-replacement.json"); + } + + @Test + void bugPatternTestMultipleReplacements(@TempDir Path outputDirectory) throws IOException { + JavacTaskCompilation.compile( + outputDirectory, + "IdentityConversionTest.java", + "package pkg;", + "", + "import com.google.errorprone.BugCheckerRefactoringTestHelper;", + "import com.google.errorprone.BugCheckerRefactoringTestHelper.FixChoosers;", + "import com.google.errorprone.bugpatterns.BugChecker;", + "import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;", + "import com.google.errorprone.CompilationTestHelper;", + "import org.junit.jupiter.api.Test;", + "", + "final class IdentityConversionTest {", + " private static class IdentityConversion extends BugChecker {}", + "", + " @Test", + " void replacementFirstSuggestedFix() {", + " BugCheckerRefactoringTestHelper.newInstance(IdentityConversion.class, getClass())", + " .setFixChooser(FixChoosers.FIRST)", + " .addInputLines(", + " \"A.java\",", + " \"public final class A {}\")", + " .addOutputLines(", + " \"A.java\",", + " \"public final class A {}\")", + " .addInputLines(", + " \"B.java\",", + " \"public final class B {}\")", + " .addOutputLines(", + " \"B.java\",", + " \"public final class B {}\")", + " .doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH);", + " }", + "}"); + + verifyFileMatchesResource( + outputDirectory, + "bugpattern-test-IdentityConversionTest.json", + "bugpattern-test-documentation-replacement-two-sources.json"); + } + + @Test + void bugPatternReplacementExpectUnchanged(@TempDir Path outputDirectory) throws IOException { + JavacTaskCompilation.compile( + outputDirectory, + "IdentityConversionTest.java", + "package pkg;", + "", + "import com.google.errorprone.BugCheckerRefactoringTestHelper;", + "import com.google.errorprone.bugpatterns.BugChecker;", + "import org.junit.jupiter.api.Test;", + "", + "final class IdentityConversionTest {", + " private static class IdentityConversion extends BugChecker {}", + "", + " @Test", + " void replacementFirstSuggestedFix() {", + " BugCheckerRefactoringTestHelper.newInstance(IdentityConversion.class, getClass())", + " .addInputLines(", + " \"A.java\",", + " \"public final class A {}\")", + " .expectUnchanged()", + " .doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH);", + " }", + "}"); + + verifyFileMatchesResource( + outputDirectory, + "bugpattern-test-IdentityConversionTest.json", + "bugpattern-test-documentation-replacement-expect-unchanged.json"); + } + + @Test + void bugPatternTestIdentificationAndReplacement(@TempDir Path outputDirectory) + throws IOException { + JavacTaskCompilation.compile( + outputDirectory, + "IdentityConversionTest.java", + "package pkg;", + "", + "import com.google.errorprone.BugCheckerRefactoringTestHelper;", + "import com.google.errorprone.BugCheckerRefactoringTestHelper.FixChoosers;", + "import com.google.errorprone.bugpatterns.BugChecker;", + "import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;", + "import com.google.errorprone.CompilationTestHelper;", + "import org.junit.jupiter.api.Test;", + "", + "final class IdentityConversionTest {", + " private static class IdentityConversion extends BugChecker {}", + "", + " @Test", + " void identification() {", + " CompilationTestHelper.newInstance(IdentityConversion.class, getClass())", + " .addSourceLines(", + " \"A.java\",", + " \"public final class A {\",", + " \" public void m() {\",", + " \" // BUG: Diagnostic contains:\",", + " \" Boolean b = Boolean.valueOf(Boolean.FALSE);\",", + " \" }\",", + " \"}\")", + " .doTest();", + " }", + "", + " @Test", + " void replacementFirstSuggestedFix() {", + " BugCheckerRefactoringTestHelper.newInstance(IdentityConversion.class, getClass())", + " .setFixChooser(FixChoosers.FIRST)", + " .addInputLines(", + " \"A.java\",", + " \"import com.google.common.collect.ImmutableSet;\",", + " \"\",", + " \"public final class A {\",", + " \" public void m() {\",", + " \" ImmutableSet set = ImmutableSet.copyOf(ImmutableSet.of());\",", + " \" }\",", + " \"}\")", + " .addOutputLines(", + " \"A.java\",", + " \"import com.google.common.collect.ImmutableSet;\",", + " \"\",", + " \"public final class A {\",", + " \" public void m() {\",", + " \" ImmutableSet set = ImmutableSet.of();\",", + " \" }\",", + " \"}\")", + " .doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH);", + " }", + "}"); + + verifyFileMatchesResource( + outputDirectory, + "bugpattern-test-IdentityConversionTest.json", + "bugpattern-test-documentation-identification-and-replacement.json"); + } + + @Test + void bugPatternTestMultipleIdentificationAndReplacement(@TempDir Path outputDirectory) + throws IOException { + JavacTaskCompilation.compile( + outputDirectory, + "IdentityConversionTest.java", + "package pkg;", + "", + "import static com.google.errorprone.BugCheckerRefactoringTestHelper.FixChoosers.SECOND;", + "", + "import com.google.errorprone.BugCheckerRefactoringTestHelper;", + "import com.google.errorprone.bugpatterns.BugChecker;", + "import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;", + "import com.google.errorprone.CompilationTestHelper;", + "import org.junit.jupiter.api.Test;", + "", + "final class IdentityConversionTest {", + " private static class IdentityConversion extends BugChecker {}", + "", + " @Test", + " void identification() {", + " CompilationTestHelper.newInstance(IdentityConversion.class, getClass())", + " .addSourceLines(", + " \"A.java\",", + " \"public final class A {\",", + " \" public void m() {\",", + " \" // BUG: Diagnostic contains:\",", + " \" Boolean b = Boolean.valueOf(Boolean.FALSE);\",", + " \" }\",", + " \"}\")", + " .doTest();", + " }", + "", + " @Test", + " void identification2() {", + " CompilationTestHelper.newInstance(IdentityConversion.class, getClass())", + " .addSourceLines(", + " \"B.java\",", + " \"public final class B {\",", + " \" public void m() {\",", + " \" // BUG: Diagnostic contains:\",", + " \" Boolean b = Boolean.valueOf(Boolean.FALSE);\",", + " \" }\",", + " \"}\")", + " .doTest();", + " }", + "", + " @Test", + " void replacementFirstSuggestedFix() {", + " BugCheckerRefactoringTestHelper.newInstance(IdentityConversion.class, getClass())", + " .addInputLines(", + " \"A.java\",", + " \"import com.google.common.collect.ImmutableSet;\",", + " \"\",", + " \"public final class A {\",", + " \" public void m() {\",", + " \" ImmutableSet set = ImmutableSet.copyOf(ImmutableSet.of());\",", + " \" }\",", + " \"}\")", + " .addOutputLines(", + " \"A.java\",", + " \"import com.google.common.collect.ImmutableSet;\",", + " \"\",", + " \"public final class A {\",", + " \" public void m() {\",", + " \" ImmutableSet set = ImmutableSet.of();\",", + " \" }\",", + " \"}\")", + " .doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH);", + " }", + "", + " @Test", + " void replacementSecondSuggestedFix() {", + " BugCheckerRefactoringTestHelper.newInstance(IdentityConversion.class, getClass())", + " .setFixChooser(SECOND)", + " .addInputLines(", + " \"B.java\",", + " \"import com.google.common.collect.ImmutableSet;\",", + " \"\",", + " \"public final class B {\",", + " \" public void m() {\",", + " \" ImmutableSet set = ImmutableSet.copyOf(ImmutableSet.of());\",", + " \" }\",", + " \"}\")", + " .addOutputLines(", + " \"B.java\",", + " \"import com.google.common.collect.ImmutableSet;\",", + " \"\",", + " \"public final class B {\",", + " \" public void m() {\",", + " \" ImmutableSet set = ImmutableSet.of();\",", + " \" }\",", + " \"}\")", + " .doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH);", + " }", + "}"); + + verifyFileMatchesResource( + outputDirectory, + "bugpattern-test-IdentityConversionTest.json", + "bugpattern-test-documentation-multiple-identification-and-replacement.json"); + } + + private static void verifyFileMatchesResource( + Path outputDirectory, String fileName, String resourceName) throws IOException { + assertThat(Files.readString(outputDirectory.resolve(fileName))) + .isEqualToIgnoringWhitespace(getResource(resourceName)); + } + + // XXX: Once we support only JDK 15+, drop this method in favour of including the resources as + // text blocks in this class. (This also requires renaming the `verifyFileMatchesResource` + // method.) + private static String getResource(String resourceName) throws IOException { + return Resources.toString( + Resources.getResource(BugPatternTestExtractorTest.class, resourceName), UTF_8); + } +} diff --git a/documentation-support/src/test/resources/tech/picnic/errorprone/documentation/bugpattern-test-documentation-identification-and-replacement.json b/documentation-support/src/test/resources/tech/picnic/errorprone/documentation/bugpattern-test-documentation-identification-and-replacement.json new file mode 100644 index 00000000000..93ed79894a2 --- /dev/null +++ b/documentation-support/src/test/resources/tech/picnic/errorprone/documentation/bugpattern-test-documentation-identification-and-replacement.json @@ -0,0 +1,12 @@ +{ + "name": "IdentityConversion", + "identificationTests": [ + "public final class A {\n public void m() {\n // BUG: Diagnostic contains:\n Boolean b = Boolean.valueOf(Boolean.FALSE);\n}\n}\n" + ], + "replacementTests": [ + { + "inputLines": "import com.google.common.collect.ImmutableSet;\n\npublic final class A {\n public void m() {\n ImmutableSet set = ImmutableSet.copyOf(ImmutableSet.of());\n }\n}\n", + "outputLines": "import com.google.common.collect.ImmutableSet;\n\npublic final class A {\n public void m() {\n ImmutableSet set = ImmutableSet.of();\n }\n}\n" + } + ] +} diff --git a/documentation-support/src/test/resources/tech/picnic/errorprone/documentation/bugpattern-test-documentation-identification-two-sources.json b/documentation-support/src/test/resources/tech/picnic/errorprone/documentation/bugpattern-test-documentation-identification-two-sources.json new file mode 100644 index 00000000000..8bbdf7c97c1 --- /dev/null +++ b/documentation-support/src/test/resources/tech/picnic/errorprone/documentation/bugpattern-test-documentation-identification-two-sources.json @@ -0,0 +1,8 @@ +{ + "name": "IdentityConversion", + "identificationTests": [ + "public final class B {}\n", + "public final class A {}\n" + ], + "replacementTests": [] +} diff --git a/documentation-support/src/test/resources/tech/picnic/errorprone/documentation/bugpattern-test-documentation-identification.json b/documentation-support/src/test/resources/tech/picnic/errorprone/documentation/bugpattern-test-documentation-identification.json new file mode 100644 index 00000000000..ef8167b65a9 --- /dev/null +++ b/documentation-support/src/test/resources/tech/picnic/errorprone/documentation/bugpattern-test-documentation-identification.json @@ -0,0 +1,7 @@ +{ + "name": "IdentityConversion", + "identificationTests": [ + "public final class A {\n public void m() {\n // BUG: Diagnostic contains:\n Boolean b = Boolean.valueOf(Boolean.FALSE);\n}\n}\n" + ], + "replacementTests": [] +} diff --git a/documentation-support/src/test/resources/tech/picnic/errorprone/documentation/bugpattern-test-documentation-minimal.json b/documentation-support/src/test/resources/tech/picnic/errorprone/documentation/bugpattern-test-documentation-minimal.json new file mode 100644 index 00000000000..6f11e9f5cf9 --- /dev/null +++ b/documentation-support/src/test/resources/tech/picnic/errorprone/documentation/bugpattern-test-documentation-minimal.json @@ -0,0 +1,5 @@ +{ + "name": "IdentityConversion", + "identificationTests": [], + "replacementTests": [] +} diff --git a/documentation-support/src/test/resources/tech/picnic/errorprone/documentation/bugpattern-test-documentation-multiple-identification-and-replacement.json b/documentation-support/src/test/resources/tech/picnic/errorprone/documentation/bugpattern-test-documentation-multiple-identification-and-replacement.json new file mode 100644 index 00000000000..68e11251bf5 --- /dev/null +++ b/documentation-support/src/test/resources/tech/picnic/errorprone/documentation/bugpattern-test-documentation-multiple-identification-and-replacement.json @@ -0,0 +1,17 @@ +{ + "name": "IdentityConversion", + "identificationTests": [ + "public final class A {\n public void m() {\n // BUG: Diagnostic contains:\n Boolean b = Boolean.valueOf(Boolean.FALSE);\n}\n}\n", + "public final class B {\n public void m() {\n // BUG: Diagnostic contains:\n Boolean b = Boolean.valueOf(Boolean.FALSE);\n}\n}\n" + ], + "replacementTests": [ + { + "inputLines": "import com.google.common.collect.ImmutableSet;\n\npublic final class A {\n public void m() {\n ImmutableSet set = ImmutableSet.copyOf(ImmutableSet.of());\n }\n}\n", + "outputLines": "import com.google.common.collect.ImmutableSet;\n\npublic final class A {\n public void m() {\n ImmutableSet set = ImmutableSet.of();\n }\n}\n" + }, + { + "inputLines": "import com.google.common.collect.ImmutableSet;\n\npublic final class B {\n public void m() {\n ImmutableSet set = ImmutableSet.copyOf(ImmutableSet.of());\n }\n}\n", + "outputLines": "import com.google.common.collect.ImmutableSet;\n\npublic final class B {\n public void m() {\n ImmutableSet set = ImmutableSet.of();\n }\n}\n" + } + ] +} diff --git a/documentation-support/src/test/resources/tech/picnic/errorprone/documentation/bugpattern-test-documentation-replacement-expect-unchanged.json b/documentation-support/src/test/resources/tech/picnic/errorprone/documentation/bugpattern-test-documentation-replacement-expect-unchanged.json new file mode 100644 index 00000000000..3d075301879 --- /dev/null +++ b/documentation-support/src/test/resources/tech/picnic/errorprone/documentation/bugpattern-test-documentation-replacement-expect-unchanged.json @@ -0,0 +1,10 @@ +{ + "name": "IdentityConversion", + "identificationTests": [], + "replacementTests": [ + { + "inputLines": "public final class A {}\n", + "outputLines": "" + } + ] +} diff --git a/documentation-support/src/test/resources/tech/picnic/errorprone/documentation/bugpattern-test-documentation-replacement-two-sources.json b/documentation-support/src/test/resources/tech/picnic/errorprone/documentation/bugpattern-test-documentation-replacement-two-sources.json new file mode 100644 index 00000000000..b100945c29b --- /dev/null +++ b/documentation-support/src/test/resources/tech/picnic/errorprone/documentation/bugpattern-test-documentation-replacement-two-sources.json @@ -0,0 +1,14 @@ +{ + "name": "IdentityConversion", + "identificationTests": [], + "replacementTests": [ + { + "inputLines": "public final class B {}\n", + "outputLines": "public final class B {}\n" + }, + { + "inputLines": "public final class A {}\n", + "outputLines": "public final class A {}\n" + } + ] +} diff --git a/documentation-support/src/test/resources/tech/picnic/errorprone/documentation/bugpattern-test-documentation-replacement.json b/documentation-support/src/test/resources/tech/picnic/errorprone/documentation/bugpattern-test-documentation-replacement.json new file mode 100644 index 00000000000..dcace8d33f3 --- /dev/null +++ b/documentation-support/src/test/resources/tech/picnic/errorprone/documentation/bugpattern-test-documentation-replacement.json @@ -0,0 +1,10 @@ +{ + "name": "IdentityConversion", + "identificationTests": [], + "replacementTests": [ + { + "inputLines": "import com.google.common.collect.ImmutableSet;\n\npublic final class A {\n public void m() {\n ImmutableSet set = ImmutableSet.copyOf(ImmutableSet.of());\n }\n}\n", + "outputLines": "import com.google.common.collect.ImmutableSet;\n\npublic final class A {\n public void m() {\n ImmutableSet set = ImmutableSet.of();\n }\n}\n" + } + ] +}