package org.mutabilitydetector.unittesting; /* * #%L * MutabilityDetector * %% * Copyright (C) 2008 - 2014 Graham Allan * %% * 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. * #L% */ import com.google.classpath.ClassPath; import com.google.classpath.ClassPathFactory; import com.google.common.collect.Lists; import org.hamcrest.Matcher; import org.mutabilitydetector.AnalysisResult; import org.mutabilitydetector.AnalysisSession; import org.mutabilitydetector.Configuration; import org.mutabilitydetector.ConfigurationBuilder; import org.mutabilitydetector.Configurations; import org.mutabilitydetector.MutableReasonDetail; import org.mutabilitydetector.asmoverride.AsmVerifierFactory; import org.mutabilitydetector.asmoverride.AsmVerifierFactory.ClassloadingOption; import org.mutabilitydetector.asmoverride.ClassLoadingVerifierFactory; import org.mutabilitydetector.asmoverride.NonClassLoadingVerifierFactory; import org.mutabilitydetector.checkers.ClassPathBasedCheckerRunnerFactory; import org.mutabilitydetector.checkers.MutabilityCheckerFactory; import org.mutabilitydetector.checkers.MutabilityCheckerFactory.ReassignedFieldAnalysisChoice; import org.mutabilitydetector.classloading.CachingAnalysisClassLoader; import org.mutabilitydetector.classloading.ClassForNameWrapper; import org.mutabilitydetector.locations.Dotted; import org.mutabilitydetector.unittesting.internal.AssertionReporter; import org.mutabilitydetector.unittesting.matchers.reasons.WithAllowedReasonsMatcher; import java.util.ArrayList; import java.util.List; import static java.util.Arrays.asList; import static org.mutabilitydetector.DefaultCachingAnalysisSession.createWithGivenClassPath; import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable; import static org.mutabilitydetector.unittesting.matchers.reasons.WithAllowedReasonsMatcher.withAllowedReasons; import static org.mutabilitydetector.unittesting.matchers.reasons.WithAllowedReasonsMatcher.withNoAllowedReasons; /** * Performs Mutability Detector's analysis and produces unit-test-friendly * {@link AssertionError} if the result is not as expected. * <p> * Instances of this class provide the methods accessed by * {@link MutabilityAssert}. More detailed documentation can be found there. */ public class MutabilityAsserter { private final AssertionReporter reporter; private final AnalysisSession analysisSession; private MutabilityAsserter(AssertionReporter reporter, AnalysisSession analysisSession) { this.reporter = reporter; this.analysisSession = analysisSession; } /** * Create a new asserter with an existing {@link Configuration}. * <p> * Example: * <pre><code> * MutabilityAsserter.configured(MyConfigurations.DEFAULT_CONFIGURATIONS); * </code></pre> * @see Configurations * @see Configurations#JDK_CONFIGURATION * @see Configurations#NO_CONFIGURATION * @see Configurations#OUT_OF_THE_BOX_CONFIGURATION */ public static MutabilityAsserter configured(Configuration configuration) { ClassPath classpath = new ClassPathFactory().createFromJVM(); AsmVerifierFactory verifierFactory = configuration.classloadingOption() == ClassloadingOption.ENABLED ? new ClassLoadingVerifierFactory(new CachingAnalysisClassLoader(new ClassForNameWrapper())) : new NonClassLoadingVerifierFactory(classpath); AnalysisSession analysisSession = createWithGivenClassPath(classpath, new ClassPathBasedCheckerRunnerFactory(classpath, configuration.exceptionPolicy()), new MutabilityCheckerFactory(ReassignedFieldAnalysisChoice.NAIVE_PUT_FIELD_ANALYSIS, configuration.immutableContainerClasses()), verifierFactory, configuration); return new MutabilityAsserter(new AssertionReporter(), analysisSession); } /** * Create a new asserter with a {@link Configuration} as built by the given * {@link ConfigurationBuilder}. * <p> * Use this method when you want to build a one-time Configuration inline.. * <p> * Example: * * <pre> * <code> * MutabilityAsserter.configured(new ConfigurationBuilder() { * @Override public void configure() { * hardcodeAsDefinitelyImmutable(ActuallyImmutable.class); * } * }); * </code> * </pre> */ public static MutabilityAsserter configured(ConfigurationBuilder configuration) { return configured(configuration.build()); } /** * @see MutabilityAssert#assertImmutable(Class) */ public void assertImmutable(Class<?> expectedImmutableClass) { reporter.assertThat(getResultFor(expectedImmutableClass), withNoAllowedReasons(areImmutable())); } /** * @see MutabilityAssert#assertInstancesOf(Class, Matcher) */ public void assertInstancesOf(Class<?> clazz, Matcher<AnalysisResult> mutabilityMatcher) { reporter.assertThat(getResultFor(clazz), withNoAllowedReasons(mutabilityMatcher)); } /** * @see MutabilityAssert#assertInstancesOf(Class, Matcher, Matcher) */ @SuppressWarnings("unchecked") public void assertInstancesOf(Class<?> clazz, Matcher<AnalysisResult> mutabilityMatcher, Matcher<MutableReasonDetail> allowing) { WithAllowedReasonsMatcher areImmutable_withReasons = withAllowedReasons(mutabilityMatcher, asList((allowing))); reporter.assertThat(getResultFor(clazz), areImmutable_withReasons); } /** * @see MutabilityAssert#assertInstancesOf(Class, Matcher, Matcher, Matcher) */ @SuppressWarnings("unchecked") public void assertInstancesOf(Class<?> clazz, Matcher<AnalysisResult> mutabilityMatcher, Matcher<MutableReasonDetail> allowingFirst, Matcher<MutableReasonDetail> allowingSecond) { WithAllowedReasonsMatcher areImmutable_withReasons = withAllowedReasons(mutabilityMatcher, asList(allowingFirst, allowingSecond)); reporter.assertThat(getResultFor(clazz), areImmutable_withReasons); } /** * @see MutabilityAssert#assertInstancesOf(Class, Matcher, Matcher, Matcher, Matcher) */ @SuppressWarnings("unchecked") public void assertInstancesOf(Class<?> clazz, Matcher<AnalysisResult> mutabilityMatcher, Matcher<MutableReasonDetail> allowingFirst, Matcher<MutableReasonDetail> allowingSecond, Matcher<MutableReasonDetail> allowingThird) { WithAllowedReasonsMatcher areImmutable_withReasons = withAllowedReasons(mutabilityMatcher, asList(allowingFirst, allowingSecond, allowingThird)); reporter.assertThat(getResultFor(clazz), areImmutable_withReasons); } /** * @see MutabilityAssert#assertInstancesOf(Class, Matcher, Matcher, Matcher, Matcher, Matcher...) */ public void assertInstancesOf(Class<?> clazz, Matcher<AnalysisResult> mutabilityMatcher, Matcher<MutableReasonDetail> allowingFirst, Matcher<MutableReasonDetail> allowingSecond, Matcher<MutableReasonDetail> allowingThird, Matcher<MutableReasonDetail>... allowingRest) { List<Matcher<MutableReasonDetail>> allowedReasonMatchers = new ArrayList<Matcher<MutableReasonDetail>>(); allowedReasonMatchers.add(allowingFirst); allowedReasonMatchers.add(allowingSecond); allowedReasonMatchers.add(allowingThird); allowedReasonMatchers.addAll(asList(allowingRest)); WithAllowedReasonsMatcher areImmutable_withReasons = withAllowedReasons(mutabilityMatcher, allowedReasonMatchers); reporter.assertThat(getResultFor(clazz), areImmutable_withReasons); } /** * @see MutabilityAssert#assertInstancesOf(Class, Matcher, Iterable) */ public void assertInstancesOf(Class<?> clazz, Matcher<AnalysisResult> mutabilityMatcher, Iterable<Matcher<MutableReasonDetail>> allowingAll) { Iterable<Matcher<MutableReasonDetail>> allowedReasonMatchers = Lists.newArrayList(allowingAll); WithAllowedReasonsMatcher areImmutable_withReasons = withAllowedReasons(mutabilityMatcher, allowedReasonMatchers); reporter.assertThat(getResultFor(clazz), areImmutable_withReasons); } private AnalysisResult getResultFor(Class<?> clazz) { return analysisSession.resultFor(Dotted.fromClass(clazz)); } }