package org.mutabilitydetector.unittesting.matchers.reasons;
/*
* #%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 static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.mutabilitydetector.Configurations.OUT_OF_THE_BOX_CONFIGURATION;
import static org.mutabilitydetector.IsImmutable.NOT_IMMUTABLE;
import static org.mutabilitydetector.TestUtil.analysisDatabase;
import static org.mutabilitydetector.TestUtil.testAnalysisSession;
import static org.mutabilitydetector.TestUtil.testingVerifierFactory;
import static org.mutabilitydetector.checkers.info.AnalysisDatabase.TYPE_STRUCTURE;
import static org.mutabilitydetector.unittesting.AllowedReason.assumingFields;
import static org.mutabilitydetector.unittesting.MutabilityAssert.assertImmutable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.common.collect.ImmutableSet;
import org.hamcrest.Matchers;
import org.junit.Test;
import org.mutabilitydetector.AnalysisResult;
import org.mutabilitydetector.IsImmutable;
import org.mutabilitydetector.MutableReasonDetail;
import org.mutabilitydetector.TestUtil;
import org.mutabilitydetector.checkers.AsmMutabilityChecker;
import org.mutabilitydetector.checkers.CanSubclassChecker;
import org.mutabilitydetector.checkers.CollectionWithMutableElementTypeToFieldChecker;
import org.mutabilitydetector.checkers.MutableTypeToFieldChecker;
import org.mutabilitydetector.checkers.NonFinalFieldChecker;
import org.mutabilitydetector.checkers.PublishedNonFinalFieldChecker;
import org.mutabilitydetector.checkers.info.AnalysisDatabase;
import org.mutabilitydetector.checkers.info.AnalysisInProgress;
import org.mutabilitydetector.checkers.info.CyclicReferences;
import org.mutabilitydetector.checkers.info.MutableTypeInformation;
import org.mutabilitydetector.checkers.info.PrivateMethodInvocationInformation;
import org.mutabilitydetector.checkers.info.TypeStructureInformation;
import org.mutabilitydetector.checkers.settermethod.SetterMethodChecker;
import org.mutabilitydetector.locations.Dotted;
@SuppressWarnings("unused")
public class FieldAssumptionsTest {
private final MutableTypeInformation mutableTypeInfo = new MutableTypeInformation(
testAnalysisSession(),
OUT_OF_THE_BOX_CONFIGURATION,
CyclicReferences.newEmptyMutableInstance());
private final TypeStructureInformation typeStructureInfo = analysisDatabase().requestInformation(TYPE_STRUCTURE);
private final Set<Dotted> immutableContainerClasses = Collections.emptySet();
private final AnalysisInProgress analysisInProgress = AnalysisInProgress.noAnalysisUnderway();
private final AsmMutabilityChecker mutableTypeToFieldChecker = new MutableTypeToFieldChecker(
typeStructureInfo,
mutableTypeInfo,
testingVerifierFactory(),
immutableContainerClasses,
analysisInProgress);
private final AsmMutabilityChecker mutableElementTypeChecker = new CollectionWithMutableElementTypeToFieldChecker(
mutableTypeInfo,
testingVerifierFactory(),
ImmutableSet.<Dotted>of(),
analysisInProgress);
private final PrivateMethodInvocationInformation privateMethodInvocationInfo = TestUtil.analysisDatabase().requestInformation(AnalysisDatabase.PRIVATE_METHOD_INVOCATION);
private final AsmMutabilityChecker setterMethodChecker = SetterMethodChecker.newInstance(privateMethodInvocationInfo);
private final AsmMutabilityChecker nonFinalFieldChecker = new NonFinalFieldChecker();
@Test
public void matchesWhenGivenFieldNameIsLinkedToMutableTypeToFieldReason() throws Exception {
MutableReasonDetail reason = getOnlyReasonFromRunningChecker(mutableTypeToFieldChecker, MutableFieldUsedSafely.class);
assertThat(reason, assumingFields("myPrivateMap").areNotModifiedAndDoNotEscape());
}
@Test
public void doesNotMatchWhenGivenIncorrectFieldName() throws Exception {
MutableReasonDetail reason = getOnlyReasonFromRunningChecker(mutableTypeToFieldChecker, MutableFieldUsedSafely.class);
assertThat(reason, not(assumingFields("myPrivateMapNOTCALLEDTHIS").areNotModifiedAndDoNotEscape()));
}
@Test
public void matchesWhenFieldIsACollectionTypeWithAMutableElementType() throws Exception {
MutableReasonDetail reason = getOnlyReasonFromRunningChecker(mutableElementTypeChecker, UsesMutableElementOfCollectionSafely.class);
assertThat(reason, assumingFields("dates").areNotModifiedAndDoNotEscape());
}
@Test
public void doesNotMatchForReasonWhereUnsafeToAssumeNotModifyingTheFieldLocally() throws Exception {
MutableReasonDetail reason = getOnlyReasonFromRunningChecker(new PublishedNonFinalFieldChecker(), MutableForIrrelevantReason.class);
assertThat(reason, not(assumingFields("reassignMe").areNotModifiedAndDoNotEscape()));
}
@Test
public void doesNotMatchForReasonWhichDoesNotOriginateFromAField() throws Exception {
MutableReasonDetail reason = getOnlyReasonFromRunningChecker(new CanSubclassChecker(), CanSubclass.class);
assertThat(reason, not(assumingFields("mutabilityIsNothingToDoWithThisField").areNotModifiedAndDoNotEscape()));
}
@Test
public void allowsReassigningFieldsAsPartOfInternalCachingStrategy() {
MutableReasonDetail reason = getOnlyReasonFromRunningChecker(setterMethodChecker, ReassignsForCaching.class);
assertThat(reason, assumingFields("first3Chars").areModifiedAsPartOfAnUnobservableCachingStrategy());
}
@Test
public void allowsNonFinalFieldsAsPartOfInternalCachingStrategy() throws Exception {
AnalysisResult result = TestUtil.runChecker(nonFinalFieldChecker, ReassignsForCaching.class);
assertThat(result.isImmutable, is(IsImmutable.EFFECTIVELY_IMMUTABLE));
assertThat(result.reasons, Matchers.hasSize(1));
MutableReasonDetail reason = result.reasons.iterator().next();
assertThat(reason, assumingFields("first3Chars").areModifiedAsPartOfAnUnobservableCachingStrategy());
}
@Test
public void isImmutable() throws Exception {
assertImmutable(FieldAssumptions.class);
}
private MutableReasonDetail getOnlyReasonFromRunningChecker(AsmMutabilityChecker mutabilityChecker, Class<?> toAnalyse) {
AnalysisResult result = TestUtil.runChecker(mutabilityChecker, toAnalyse);
assertThat(result.isImmutable, is(NOT_IMMUTABLE));
assertThat(result.reasons, Matchers.hasSize(1));
return result.reasons.iterator().next();
}
private static final class MutableFieldUsedSafely {
private final Map<String, Long> myPrivateMap;
public MutableFieldUsedSafely() {
this.myPrivateMap = new HashMap<String, Long>();
myPrivateMap.put("a", 1L);
myPrivateMap.put("b", 2L);
myPrivateMap.put("c", 3L);
}
public Long getA() {
return myPrivateMap.get("a");
}
}
private static final class UsesMutableElementOfCollectionSafely {
private final List<Date> dates;
public UsesMutableElementOfCollectionSafely(List<Date> dates) {
this.dates = Collections.unmodifiableList(new ArrayList<Date>(dates));
}
public Long getFirstTime() {
return dates.get(0).getTime();
}
}
private static final class MutableForIrrelevantReason {
public String reassignMe;
}
protected static class CanSubclass {
private final String mutabilityIsNothingToDoWithThisField = "unchangeable";
}
private static final class ReassignsForCaching {
public final String someString;
private String first3Chars;
public ReassignsForCaching(String someString, String first3Chars) {
this.someString = someString;
}
public String getFirst3Chars() {
if (first3Chars == null) {
first3Chars = someString.substring(0, 3);
}
return first3Chars;
}
}
}