package org.mutabilitydetector.unittesting.clientusage;
/*
* #%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.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.mutabilitydetector.unittesting.AllowedReason.allowingForSubclassing;
import static org.mutabilitydetector.unittesting.AllowedReason.allowingNonFinalFields;
import static org.mutabilitydetector.unittesting.AllowedReason.provided;
import static org.mutabilitydetector.unittesting.MutabilityAssert.assertImmutable;
import static org.mutabilitydetector.unittesting.MutabilityAssert.assertInstancesOf;
import static org.mutabilitydetector.unittesting.MutabilityMatchers.areEffectivelyImmutable;
import static org.mutabilitydetector.unittesting.MutabilityMatchers.areImmutable;
import static org.mutabilitydetector.unittesting.MutabilityMatchers.areNotImmutable;
import org.junit.Rule;
import org.junit.Test;
import org.mutabilitydetector.benchmarks.ImmutableExample;
import org.mutabilitydetector.benchmarks.ImmutableProvidedOtherClassIsImmutable;
import org.mutabilitydetector.benchmarks.ImmutableProvidedOtherClassIsImmutable.ThisHasToBeImmutable;
import org.mutabilitydetector.benchmarks.MutableByHavingPublicNonFinalField;
import org.mutabilitydetector.benchmarks.mutabletofield.DependsOnManyTypesBeingImmutable;
import org.mutabilitydetector.benchmarks.mutabletofield.HasDateField;
import org.mutabilitydetector.benchmarks.mutabletofield.MutatesAsInternalCaching;
import org.mutabilitydetector.benchmarks.mutabletofield.generic.HasFieldOfGenericType;
import org.mutabilitydetector.benchmarks.mutabletofield.generic.HasFieldUsingGenericTypeOfClass;
import org.mutabilitydetector.benchmarks.mutabletofield.jdktypefields.HasAStringField;
import org.mutabilitydetector.benchmarks.mutabletofield.jdktypefields.HasCollectionField;
import org.mutabilitydetector.benchmarks.sealed.IsSubclassableAndDependsOnParameterBeingImmutable;
import org.mutabilitydetector.benchmarks.sealed.MutableByNotBeingFinalClass;
import org.mutabilitydetector.benchmarks.settermethod.MutableByHavingSetterMethod;
import org.mutabilitydetector.benchmarks.types.AbstractType;
import org.mutabilitydetector.benchmarks.types.InterfaceType;
import org.mutabilitydetector.benchmarks.visibility.AlmostEffectivelyImmutable;
import org.mutabilitydetector.junit.FalsePositive;
import org.mutabilitydetector.junit.IncorrectAnalysisRule;
import org.mutabilitydetector.unittesting.AllowedReason;
import org.mutabilitydetector.unittesting.MutabilityAssertionError;
import com.google.common.collect.Lists;
public class MutabilityAssertTest {
private final Class<?> immutableClass = ImmutableExample.class;
private final Class<?> mutableClass = MutableByHavingPublicNonFinalField.class;
private final String expectedError = String.format("%n" +
"Expected: org.mutabilitydetector.benchmarks.MutableByHavingPublicNonFinalField to be IMMUTABLE%n" +
" but: org.mutabilitydetector.benchmarks.MutableByHavingPublicNonFinalField is actually NOT_IMMUTABLE%n" +
" Reasons:%n" +
" Can be subclassed, therefore parameters declared to be this type could be mutable subclasses at runtime. [Class: org.mutabilitydetector.benchmarks.MutableByHavingPublicNonFinalField]%n" +
" Field is visible outwith this class, and is not declared final. [Field: name, Class: org.mutabilitydetector.benchmarks.MutableByHavingPublicNonFinalField]%n" +
" Field is not final, if shared across threads the Java Memory Model will not guarantee it is initialised before it is read. [Field: name, Class: org.mutabilitydetector.benchmarks.MutableByHavingPublicNonFinalField]%n" +
" Allowed reasons:%n" +
" None.");
@Test
public void assertImmutableWithImmutableClassDoesNotThrowAssertionError() throws Exception {
assertImmutable(immutableClass);
}
@Test(expected = MutabilityAssertionError.class)
public void assertImmutableWithMutableClassThrowsAssertionError() throws Exception {
assertImmutable(mutableClass);
}
@Test
public void whenAssertImmutableFailsReasonsArePrintedWithAssertionFailure() throws Exception {
try {
assertImmutable(mutableClass);
fail("Assertion should have failed.");
} catch (final AssertionError ae) {
assertEquals("", expectedError, ae.getMessage());
}
}
@Test
public void whenAssertInstancesOfFailsReasonsArePrintedWithAssertionFailure() throws Exception {
try {
assertInstancesOf(mutableClass, areImmutable());
fail("Assertion should have failed.");
} catch (final AssertionError ae) {
assertThat(ae.getMessage(), equalTo(expectedError));
}
}
@Test
public void assertInstancesOfClassAreImmutableDoesNotFailForImmutableClass() throws Exception {
assertInstancesOf(ImmutableExample.class, areImmutable());
}
@Test(expected = MutabilityAssertionError.class)
public void assertThatIsImmutableFailsForMutableClass() throws Exception {
assertInstancesOf(MutableByHavingPublicNonFinalField.class, areImmutable());
}
@Test
public void failedMatchMessageFromAssertThatIsDescriptive() throws Exception {
try {
assertInstancesOf(mutableClass, areImmutable());
} catch (AssertionError ae) {
assertThat(ae.getMessage(), equalTo(expectedError));
}
}
@Test public void canSpecifyMultipleAllowedReasons() {
assertInstancesOf(IsSubclassableAndDependsOnParameterBeingImmutable.class,
areImmutable(),
allowingForSubclassing(),
provided(ThisHasToBeImmutable.class).isAlsoImmutable());
}
@SuppressWarnings("unchecked")
@Test public void varArgsArgumentsCompilesAndExecutes() {
assertInstancesOf(IsSubclassableAndDependsOnParameterBeingImmutable.class,
areImmutable(),
allowingForSubclassing(),
provided(ThisHasToBeImmutable.class).isAlsoImmutable(),
allowingForSubclassing(),
allowingForSubclassing(),
allowingForSubclassing());
}
@SuppressWarnings("unchecked")
@Test public void iterableArgumentCompilesAndExecutes() {
assertInstancesOf(IsSubclassableAndDependsOnParameterBeingImmutable.class,
areImmutable(),
Lists.newArrayList(allowingForSubclassing(),
provided(ThisHasToBeImmutable.class).isAlsoImmutable(),
allowingForSubclassing(),
allowingForSubclassing(),
allowingForSubclassing()));
}
@Test
public void canSpecifyIsImmutableAsLongAsOtherClassIsImmutable() throws Exception {
assertInstancesOf(ImmutableProvidedOtherClassIsImmutable.class,
areImmutable(),
provided(ThisHasToBeImmutable.class).isAlsoImmutable());
}
@Test
public void canSpecifyIsImmutableAsLongAsGenericTypeIsImmutable() throws Exception {
assertInstancesOf(HasFieldOfGenericType.class,
areImmutable(),
provided("T").isAlsoImmutable(),
provided("N").isAlsoImmutable());
}
@Test
public void canSpecifyIsImmutableAsLongAsGenericTypeUsedByFieldIsImmutable() throws Exception {
assertInstancesOf(HasFieldUsingGenericTypeOfClass.class, areNotImmutable());
assertInstancesOf(HasFieldUsingGenericTypeOfClass.class, areImmutable(), provided("MY_TYPE").isAlsoImmutable());
}
@Test(expected = MutabilityAssertionError.class)
public void failsWhenAllowingReasonWhichIsNotTheCauseOfMutability() {
assertInstancesOf(MutableByHavingSetterMethod.class,
areImmutable(),
provided(ThisHasToBeImmutable.class).isAlsoImmutable());
}
@Test
public void providesUsefulFailureMessageWhenAssertionFails() {
try {
assertInstancesOf(MutableByHavingSetterMethod.class,
areImmutable(),
provided(ThisHasToBeImmutable.class).isAlsoImmutable());
} catch (AssertionError e) {
assertThat(e.getMessage(), containsString("can be reassigned"));
}
}
@Test
public void canAllowSubclassingForNonFinalClasses() throws Exception {
assertInstancesOf(MutableByNotBeingFinalClass.class,
areImmutable(),
allowingForSubclassing());
}
@Test(expected = MutabilityAssertionError.class)
public void allowSubclassingFailsWhenReasonIsDifferent() throws Exception {
assertInstancesOf(MutableByHavingSetterMethod.class,
areImmutable(),
allowingForSubclassing());
}
@Test
public void canMatchEffectivelyImmutableAllowingAnotherReason() throws Exception {
assertInstancesOf(AlmostEffectivelyImmutable.class,
areEffectivelyImmutable(),
allowingNonFinalFields(),
allowingForSubclassing());
}
@Test
public void canMatchImmutableAllowingAnotherReason() throws Exception {
assertInstancesOf(AlmostEffectivelyImmutable.class,
areImmutable(),
allowingNonFinalFields(),
allowingForSubclassing());
}
@Rule public final IncorrectAnalysisRule incorrectAnalysisRule = new IncorrectAnalysisRule();
/**
* @see #canMatchEffectivelyImmutableAllowingAnotherReason() for a workaround
*/
@FalsePositive("Issue 21: can't think of an elegant solution to this,")
@Test
public void canMatchEffectivelyImmutableAllowingAnotherReasonWithoutExplicitlyAllowingNonFinalFields() throws Exception {
assertInstancesOf(AlmostEffectivelyImmutable.class,
areEffectivelyImmutable(),
allowingForSubclassing());
}
@Test
public void havingStringFieldsDoesNotCauseFalsePositivesInTheDefaultConfiguration() throws Exception {
assertImmutable(HasAStringField.class);
}
@Test
public void allowSpecifyingThatMultipleTypesMustAlsoBeImmutable() throws Exception {
assertInstancesOf(DependsOnManyTypesBeingImmutable.class,
areImmutable(),
provided(AbstractType.class, InterfaceType.class).isAlsoImmutable());
}
@FalsePositive("Does not work when specifying two different ProvidedOtherClass reasons.")
@Test
public void allowSpecifyingThatMultipleTypesMustAlsoBeImmutable_does_not_work_with_separate_provided_calls() throws Exception {
assertInstancesOf(DependsOnManyTypesBeingImmutable.class,
areImmutable(),
provided(AbstractType.class).isAlsoImmutable(),
provided(InterfaceType.class).isAlsoImmutable());
}
@Test
public void canAllowCollectionFieldsDeemedToHaveBeenSafelyCopiedAndWrappedInUnmodifiable() throws Exception {
assertInstancesOf(HasCollectionField.class,
areImmutable(),
AllowedReason.assumingFields("myStrings").areSafelyCopiedUnmodifiableCollectionsWithImmutableElements());
}
@Test
public void canAllowAMutableFieldWhicIsNotMutatedAndDoesNotEscape() throws Exception {
assertInstancesOf(HasDateField.class,
areImmutable(),
AllowedReason.assumingFields("myDate").areNotModifiedAndDoNotEscape());
}
@Test
public void canAllowInternalCachingWhichCausesUnobservableMutation() throws Exception {
assertInstancesOf(MutatesAsInternalCaching.class,
areImmutable(),
AllowedReason.assumingFields("lengthWhenConcatenated").areModifiedAsPartOfAnUnobservableCachingStrategy());
}
}