/* * Copyright 2014 Google Inc. All Rights Reserved. * * 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.errorprone.scanner; import static com.google.common.truth.Truth.assertAbout; import static com.google.common.truth.Truth.assertThat; import static com.google.errorprone.scanner.BuiltInCheckerSuppliers.getSuppliers; import static org.junit.Assert.expectThrows; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.truth.FailureStrategy; import com.google.common.truth.Subject; import com.google.common.truth.SubjectFactory; import com.google.errorprone.BugCheckerInfo; import com.google.errorprone.BugPattern.SeverityLevel; import com.google.errorprone.ErrorProneJavaCompilerTest; import com.google.errorprone.ErrorProneJavaCompilerTest.UnsuppressibleArrayEquals; import com.google.errorprone.ErrorProneOptions; import com.google.errorprone.InvalidCommandLineOptionException; import com.google.errorprone.bugpatterns.ArrayEquals; import com.google.errorprone.bugpatterns.BadShiftAmount; import com.google.errorprone.bugpatterns.BugChecker; import com.google.errorprone.bugpatterns.ChainingConstructorIgnoresParameter; import com.google.errorprone.bugpatterns.DepAnn; import com.google.errorprone.bugpatterns.DivZero; import com.google.errorprone.bugpatterns.EqualsIncompatibleType; import com.google.errorprone.bugpatterns.LongLiteralLowerCaseSuffix; import com.google.errorprone.bugpatterns.PreconditionsCheckNotNull; import com.google.errorprone.bugpatterns.RestrictedApiChecker; import com.google.errorprone.bugpatterns.StaticQualifiedUsingExpression; import com.google.errorprone.bugpatterns.StringEquality; import java.util.Collections; import java.util.Map; import java.util.function.Supplier; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** * Tests for {@link ScannerSupplier}. */ @RunWith(JUnit4.class) public class ScannerSupplierTest { @Test public void fromBugCheckerClassesWorks() { ScannerSupplier ss = ScannerSupplier.fromBugCheckerClasses( ArrayEquals.class, StaticQualifiedUsingExpression.class); assertScanner(ss).hasEnabledChecks(ArrayEquals.class, StaticQualifiedUsingExpression.class); } @Test public void fromBugCheckersWorks() { ScannerSupplier ss = ScannerSupplier.fromBugCheckerInfos( ImmutableList.of( BugCheckerInfo.create(ArrayEquals.class), BugCheckerInfo.create(StaticQualifiedUsingExpression.class))); assertScanner(ss).hasEnabledChecks(ArrayEquals.class, StaticQualifiedUsingExpression.class); } @Test public void plusWorks() { ScannerSupplier ss1 = ScannerSupplier.fromBugCheckerClasses( ArrayEquals.class, StaticQualifiedUsingExpression.class); ScannerSupplier ss2 = ScannerSupplier.fromBugCheckerClasses( BadShiftAmount.class, PreconditionsCheckNotNull.class); assertScanner(ss1.plus(ss2)) .hasEnabledChecks( ArrayEquals.class, StaticQualifiedUsingExpression.class, BadShiftAmount.class, PreconditionsCheckNotNull.class); } @Test public void plusDoesntAllowDuplicateChecks() { ScannerSupplier ss1 = ScannerSupplier.fromBugCheckerClasses( ArrayEquals.class, StaticQualifiedUsingExpression.class); ScannerSupplier ss2 = ScannerSupplier.fromBugCheckerClasses(ArrayEquals.class); IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> ss1.plus(ss2)); assertThat(expected.getMessage()).contains("ArrayEquals"); } @Test public void filterWorks() { ScannerSupplier ss = ScannerSupplier.fromBugCheckerClasses( ArrayEquals.class, BadShiftAmount.class, StaticQualifiedUsingExpression.class); assertScanner(ss.filter(input -> input.canonicalName().equals("BadShiftAmount"))) .hasEnabledChecks(BadShiftAmount.class); } @Test @SuppressWarnings("unchecked") public void applyOverridesWorksOnEmptySeverityMap() throws Exception { ScannerSupplier ss = ScannerSupplier.fromBugCheckerClasses( ChainingConstructorIgnoresParameter.class, DepAnn.class, LongLiteralLowerCaseSuffix.class); ErrorProneOptions epOptions = ErrorProneOptions.processArgs(Collections.emptyList()); assertScanner(ss.applyOverrides(epOptions)) .hasEnabledChecks( ChainingConstructorIgnoresParameter.class, DepAnn.class, LongLiteralLowerCaseSuffix.class); } @Test public void applyOverridesEnablesCheck() throws Exception { ScannerSupplier ss = ScannerSupplier.fromBugCheckerClasses( ArrayEquals.class, BadShiftAmount.class, StaticQualifiedUsingExpression.class) .filter(Predicates.alwaysFalse()); // disables all checks ErrorProneOptions epOptions = ErrorProneOptions.processArgs( ImmutableList.of("-Xep:ArrayEquals", "-Xep:BadShiftAmount")); assertScanner(ss.applyOverrides(epOptions)) .hasEnabledChecks(ArrayEquals.class, BadShiftAmount.class); } @Test public void applyOverridesEnableAllChecks() throws Exception { ScannerSupplier ss = ScannerSupplier.fromBugCheckerClasses( ArrayEquals.class, BadShiftAmount.class, StaticQualifiedUsingExpression.class) .filter(Predicates.alwaysFalse()); // disables all checks assertScanner(ss).hasEnabledChecks(); // assert empty scanner has no enabled checks ErrorProneOptions epOptions = ErrorProneOptions.processArgs(ImmutableList.of("-XepAllDisabledChecksAsWarnings")); assertScanner(ss.applyOverrides(epOptions)) .hasEnabledChecks( ArrayEquals.class, BadShiftAmount.class, StaticQualifiedUsingExpression.class); epOptions = ErrorProneOptions.processArgs( ImmutableList.of("-XepAllDisabledChecksAsWarnings", "-Xep:ArrayEquals:OFF")); assertScanner(ss.applyOverrides(epOptions)) .hasEnabledChecks(BadShiftAmount.class, StaticQualifiedUsingExpression.class); // The 'AllDisabledChecksAsWarnings' flag doesn't populate through to additional plugins assertScanner( ss.applyOverrides(epOptions) .plus(ScannerSupplier.fromBugCheckerClasses(DivZero.class).filter(t -> false))) .hasEnabledChecks(BadShiftAmount.class, StaticQualifiedUsingExpression.class); } @Test public void applyOverridesDisableAllChecks() throws Exception { // Create new scanner. ScannerSupplier ss = ScannerSupplier.fromBugCheckerClasses( ArrayEquals.class, BadShiftAmount.class, StaticQualifiedUsingExpression.class); // Make sure all checks in the scanner start enabled. assertScanner(ss) .hasEnabledChecks( ArrayEquals.class, BadShiftAmount.class, StaticQualifiedUsingExpression.class); // Apply the 'DisableAllChecks' flag, make sure all checks are disabled. ErrorProneOptions epOptions = ErrorProneOptions.processArgs(ImmutableList.of("-XepDisableAllChecks")); assertScanner(ss.applyOverrides(epOptions)).hasEnabledChecks(); // Don't suppress unsuppressible checks. ss = ss.plus(ScannerSupplier.fromBugCheckerClasses(RestrictedApiChecker.class)); assertScanner(ss.applyOverrides(epOptions)).hasEnabledChecks(RestrictedApiChecker.class); // Apply 'DisableAllChecks' flag with another -Xep flag epOptions = ErrorProneOptions.processArgs( ImmutableList.of("-XepDisableAllChecks", "-Xep:ArrayEquals:ERROR")); // Make sure the severity flag overrides the global disable flag. assertScanner(ss.applyOverrides(epOptions)) .hasEnabledChecks(RestrictedApiChecker.class, ArrayEquals.class); // Order matters. The DisableAllChecks flag should override all severities that come before it. epOptions = ErrorProneOptions.processArgs( ImmutableList.of("-Xep:ArrayEquals:ERROR", "-XepDisableAllChecks")); // Make sure the global disable flag overrides flags that come before it. assertScanner(ss.applyOverrides(epOptions)).hasEnabledChecks(RestrictedApiChecker.class); // The 'DisableAllChecks' flag doesn't populate through to additional plugins. assertScanner( ss.applyOverrides(epOptions).plus(ScannerSupplier.fromBugCheckerClasses(DivZero.class))) .hasEnabledChecks(RestrictedApiChecker.class, DivZero.class); } @Test public void applyOverridesDisableErrors() { // BadShiftAmount (error), ArrayEquals (unsuppressible error), StringEquality (warning) ScannerSupplier ss = ScannerSupplier.fromBugCheckerClasses( BadShiftAmount.class, UnsuppressibleArrayEquals.class, StringEquality.class); ErrorProneOptions epOptions = ErrorProneOptions.processArgs(ImmutableList.of("-XepAllErrorsAsWarnings")); assertScanner(ss.applyOverrides(epOptions)) .hasSeverities( ImmutableMap.of( "ArrayEquals", SeverityLevel.ERROR, // Unsuppressible, not demoted "BadShiftAmount", SeverityLevel.WARNING, // Demoted from error to warning "StringEquality", SeverityLevel.WARNING)); // Already warning, unaffected // Flags after AllErrorsAsWarnings flag should override it. epOptions = ErrorProneOptions.processArgs( ImmutableList.of("-XepAllErrorsAsWarnings", "-Xep:StringEquality:ERROR")); assertScanner(ss.applyOverrides(epOptions)) .hasSeverities( ImmutableMap.of( "ArrayEquals", SeverityLevel.ERROR, "BadShiftAmount", SeverityLevel.WARNING, "StringEquality", SeverityLevel.ERROR)); // AllErrorsAsWarnings flag should override all error-level severity flags that come before it. epOptions = ErrorProneOptions.processArgs( ImmutableList.of("-Xep:StringEquality:ERROR", "-XepAllErrorsAsWarnings")); assertScanner(ss.applyOverrides(epOptions)) .hasSeverities( ImmutableMap.of( "ArrayEquals", SeverityLevel.ERROR, "BadShiftAmount", SeverityLevel.WARNING, "StringEquality", SeverityLevel.WARNING)); // AllErrorsAsWarnings only overrides error-level severity flags. // That is, checks disabled before the flag are left disabled, not promoted to warnings. epOptions = ErrorProneOptions.processArgs( ImmutableList.of("-Xep:BadShiftAmount:OFF", "-XepAllErrorsAsWarnings")); assertScanner(ss.applyOverrides(epOptions)) .hasSeverities( ImmutableMap.of( "ArrayEquals", SeverityLevel.ERROR, "StringEquality", SeverityLevel.WARNING)); assertScanner(ss.applyOverrides(epOptions)) .hasEnabledChecks(UnsuppressibleArrayEquals.class, StringEquality.class); } @Test public void applyOverridesDisableErrorsOnlyForEnabledChecks() { Supplier<ScannerSupplier> filteredScanner = () -> ScannerSupplier.fromBugCheckerClasses( BadShiftAmount.class, UnsuppressibleArrayEquals.class, EqualsIncompatibleType.class) .filter(p -> !p.checkerClass().equals(EqualsIncompatibleType.class)); ErrorProneOptions epOptions = ErrorProneOptions.processArgs(ImmutableList.of("-XepAllErrorsAsWarnings")); assertScanner(filteredScanner.get().applyOverrides(epOptions)) .hasEnabledChecks(UnsuppressibleArrayEquals.class, BadShiftAmount.class); epOptions = ErrorProneOptions.processArgs( ImmutableList.of("-XepAllErrorsAsWarnings", "-Xep:BadShiftAmount:OFF")); assertScanner(filteredScanner.get().applyOverrides(epOptions)) .hasEnabledChecks(UnsuppressibleArrayEquals.class); } @Test public void applyOverridesDisablesChecks() throws Exception { ScannerSupplier ss = ScannerSupplier.fromBugCheckerClasses( ChainingConstructorIgnoresParameter.class, DepAnn.class, LongLiteralLowerCaseSuffix.class); ErrorProneOptions epOptions = ErrorProneOptions.processArgs( ImmutableList.of( "-Xep:LongLiteralLowerCaseSuffix:OFF", "-Xep:ChainingConstructorIgnoresParameter:OFF")); assertScanner(ss.applyOverrides(epOptions)).hasEnabledChecks(DepAnn.class); } @Test public void applyOverridesThrowsExceptionWhenDisablingNonDisablableCheck() throws Exception { ScannerSupplier ss = ScannerSupplier.fromBugCheckerClasses( ErrorProneJavaCompilerTest.UnsuppressibleArrayEquals.class); ErrorProneOptions epOptions = ErrorProneOptions.processArgs( ImmutableList.of("-Xep:ArrayEquals:OFF")); InvalidCommandLineOptionException exception = expectThrows(InvalidCommandLineOptionException.class, () -> ss.applyOverrides(epOptions)); assertThat(exception.getMessage()).contains("may not be disabled"); } @Test public void applyOverridesThrowsExceptionWhenDemotingNonDisablableCheck() throws Exception { ScannerSupplier ss = ScannerSupplier.fromBugCheckerClasses( ErrorProneJavaCompilerTest.UnsuppressibleArrayEquals.class); ErrorProneOptions epOptions = ErrorProneOptions.processArgs( ImmutableList.of("-Xep:ArrayEquals:WARN")); InvalidCommandLineOptionException exception = expectThrows(InvalidCommandLineOptionException.class, () -> ss.applyOverrides(epOptions)); assertThat(exception.getMessage()).contains("may not be demoted to a warning"); } @Test public void applyOverridesSucceedsWhenDisablingUnknownCheckAndIgnoreUnknownCheckNamesIsSet() throws Exception { ScannerSupplier ss = ScannerSupplier.fromBugCheckerClasses(ArrayEquals.class); ErrorProneOptions epOptions = ErrorProneOptions.processArgs( ImmutableList.of("-XepIgnoreUnknownCheckNames", "-Xep:foo:OFF")); assertScanner(ss.applyOverrides(epOptions)).hasEnabledChecks(ArrayEquals.class); } @Test public void applyOverridesSetsSeverity() throws Exception { ScannerSupplier ss = ScannerSupplier.fromBugCheckerClasses( BadShiftAmount.class, ChainingConstructorIgnoresParameter.class, StringEquality.class); ErrorProneOptions epOptions = ErrorProneOptions.processArgs( ImmutableList.of( "-Xep:ChainingConstructorIgnoresParameter:WARN", "-Xep:StringEquality:ERROR")); ScannerSupplier overriddenScannerSupplier = ss.applyOverrides(epOptions); Map<String, SeverityLevel> expected = ImmutableMap.of( "BadShiftAmount", SeverityLevel.ERROR, "ChainingConstructorIgnoresParameter", SeverityLevel.WARNING, "StringEquality", SeverityLevel.ERROR); assertScanner(overriddenScannerSupplier).hasSeverities(expected); } @Test public void allChecksAsWarningsWorks() throws Exception { ScannerSupplier ss = ScannerSupplier.fromBugCheckerClasses( BadShiftAmount.class, ChainingConstructorIgnoresParameter.class, StringEquality.class) .filter(Predicates.alwaysFalse()); assertScanner(ss).hasEnabledChecks(); // Everything's off ErrorProneOptions epOptions = ErrorProneOptions.processArgs( ImmutableList.of("-Xep:StringEquality:OFF", "-XepAllDisabledChecksAsWarnings")); ScannerSupplier withOverrides = ss.applyOverrides(epOptions); assertScanner(withOverrides) .hasEnabledChecks( BadShiftAmount.class, ChainingConstructorIgnoresParameter.class, StringEquality.class); ImmutableMap<String, SeverityLevel> expectedSeverities = ImmutableMap.of( "BadShiftAmount", SeverityLevel.WARNING, "ChainingConstructorIgnoresParameter", SeverityLevel.WARNING, "StringEquality", SeverityLevel.WARNING); assertScanner(withOverrides).hasSeverities(expectedSeverities); epOptions = ErrorProneOptions.processArgs( ImmutableList.of( "-Xep:StringEquality:OFF", "-XepAllDisabledChecksAsWarnings", "-Xep:StringEquality:OFF")); withOverrides = ss.applyOverrides(epOptions); assertScanner(withOverrides) .hasEnabledChecks(BadShiftAmount.class, ChainingConstructorIgnoresParameter.class); expectedSeverities = ImmutableMap.of( "BadShiftAmount", SeverityLevel.WARNING, "ChainingConstructorIgnoresParameter", SeverityLevel.WARNING); assertScanner(withOverrides).hasSeverities(expectedSeverities); } private static class ScannerSupplierSubject extends Subject<ScannerSupplierSubject, ScannerSupplier> { ScannerSupplierSubject(FailureStrategy failureStrategy, ScannerSupplier scannerSupplier) { super(failureStrategy, scannerSupplier); } final void hasSeverities(Map<String, SeverityLevel> severities) { check().that(getSubject().severities()).containsExactlyEntriesIn(severities); } @SafeVarargs final void hasEnabledChecks(Class<? extends BugChecker>... bugCheckers) { check() .that(getSubject().getEnabledChecks()) .containsExactlyElementsIn(getSuppliers(bugCheckers)); } } private ScannerSupplierSubject assertScanner(ScannerSupplier scannerSupplier) { return assertAbout(SCANNER_SUBJECT_FACTORY).that(scannerSupplier); } private static final SubjectFactory<ScannerSupplierSubject, ScannerSupplier> SCANNER_SUBJECT_FACTORY = new SubjectFactory<ScannerSupplierSubject, ScannerSupplier>() { @Override public ScannerSupplierSubject getSubject(FailureStrategy fs, ScannerSupplier t) { return new ScannerSupplierSubject(fs, t); } }; }