Skip to content

Commit 58a4292

Browse files
ajozkarczews
authored andcommitted
Add static mutable fields check. Handling JaCoCo static mutable fields in classes. (#5)
1 parent ffa2359 commit 58a4292

File tree

6 files changed

+167
-10
lines changed

6 files changed

+167
-10
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.github.karczews.utilsverifier;
2+
3+
/**
4+
* Internal tool that contains convenience methods to work with arrays.
5+
*/
6+
final class Arrays2 {
7+
private Arrays2() {
8+
}
9+
10+
/**
11+
* Performs a check if the specified item is contained in the specified
12+
* array.
13+
*
14+
* @param item will be checked if it's in the array
15+
* @param array checked if contains the specified item
16+
* @param <T> type of the array elements
17+
* @return true if the array contains specified item, false otherwise
18+
* @throws NullPointerException if the passed array is null
19+
*/
20+
public static <T> boolean contains(final T item, final T[] array) {
21+
for (int index = 0; index < array.length; index++) {
22+
if (array[index] == item)
23+
return true;
24+
}
25+
return false;
26+
}
27+
}

src/main/java/com/github/karczews/utilsverifier/UtilsVerifier.java

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,20 +27,26 @@
2727
* <li> is final
2828
* <li> has only one private constructor
2929
* <li> has no instance fields
30+
* <li> has no mutable static fields
3031
* </ul>
3132
*
3233
* @param <T> class under test
3334
*/
34-
public class UtilsVerifier<T> {
35+
public final class UtilsVerifier<T> {
3536

3637
private final Class<T> classUnderTest;
3738

3839
private Class<? extends Throwable> expectedConstructorException;
40+
3941
private boolean suppressFinalClassCheck = false;
4042
private boolean suppressOnlyOneConstructorCheck = false;
4143
private boolean suppressPrivateConstructorCheck = false;
4244
private boolean suppressInstanceFieldCheck = false;
4345
private boolean suppressInstanceMethodCheck = false;
46+
private boolean suppressMutableStaticFieldsCheck = false;
47+
48+
// list of mutable static fields often added by outside tools like JaCoCo
49+
private final String[] allowedMutableStaticFields = {"$jacocoData"};
4450

4551
private UtilsVerifier(final Class<T> type) {
4652
classUnderTest = type;
@@ -65,6 +71,7 @@ public void verify() {
6571
verifyPrivateConstructor();
6672
hasNoInstanceFields();
6773
hasNoInstanceMethods();
74+
hasNoMutableStaticFields();
6875
}
6976

7077
/**
@@ -131,6 +138,17 @@ public UtilsVerifier<T> suppressInstanceMethodCheck(final boolean suppressCheck)
131138
return this;
132139
}
133140

141+
/**
142+
* Suppress static mutable fields verification.
143+
* Use if util class is allowed to have mutable static fields.
144+
*
145+
* @param suppressCheck true if check should be suppressed, false otherwise
146+
*/
147+
public UtilsVerifier<T> suppressMutableStaticFieldsCheck(final boolean suppressCheck) {
148+
suppressMutableStaticFieldsCheck = suppressCheck;
149+
return this;
150+
}
151+
134152
private void checkIfClassIsFinal() {
135153
if (suppressFinalClassCheck) return;
136154
if (!Modifier.isFinal(classUnderTest.getModifiers())) {
@@ -195,5 +213,22 @@ private void hasNoInstanceMethods() {
195213
}
196214
}
197215
}
198-
//TODO: add check for mutable static fields
216+
217+
private void hasNoMutableStaticFields() {
218+
if (suppressMutableStaticFieldsCheck) return;
219+
final Field[] fields = classUnderTest.getDeclaredFields();
220+
221+
fields:
222+
for (int index = 0; index < fields.length; index++) {
223+
final Field field = fields[index];
224+
final int modifiers = field.getModifiers();
225+
if (Arrays2.contains(field.getName(), allowedMutableStaticFields)) {
226+
continue fields;
227+
}
228+
if (Modifier.isStatic(modifiers) && !Modifier.isFinal(modifiers)) {
229+
throw new AssertionError(classUnderTest.getName()
230+
+ " contains static mutable field " + field.getName());
231+
}
232+
}
233+
}
199234
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package com.github.karczews.utilsverifier;
2+
3+
import org.junit.Assert;
4+
import org.junit.Rule;
5+
import org.junit.Test;
6+
import org.junit.rules.ExpectedException;
7+
8+
public class Arrays2Test {
9+
@Rule
10+
public ExpectedException expectedException = ExpectedException.none();
11+
12+
@Test
13+
public void shouldThrowNullPointerExceptionForNullArray() {
14+
expectedException.expect(NullPointerException.class);
15+
16+
final String[] array = null;
17+
final String item = "";
18+
19+
Arrays2.contains(item, array);
20+
}
21+
22+
@Test
23+
public void shouldReturnFalseForEmptyArray() {
24+
final String[] array = {};
25+
final String item = "";
26+
27+
Assert.assertFalse(Arrays2.contains(item, array));
28+
}
29+
30+
@Test
31+
public void shouldReturnFalse() {
32+
final String[] array = {"item1", "item2"};
33+
final String item = "";
34+
35+
Assert.assertFalse(Arrays2.contains(item, array));
36+
}
37+
38+
@Test
39+
public void shouldReturnFalseForNullItem() {
40+
final String[] array = {"item1", "item2"};
41+
final String item = null;
42+
43+
Assert.assertFalse(Arrays2.contains(item, array));
44+
}
45+
46+
@Test
47+
public void shouldReturnTrue() {
48+
final String[] array = {"item1", "item2"};
49+
final String item = "item1";
50+
51+
Assert.assertTrue(Arrays2.contains(item, array));
52+
}
53+
54+
@Test
55+
public void verifyArraysTool() {
56+
UtilsVerifier.forClass(Arrays2.class).verify();
57+
}
58+
}

src/test/java/com/github/karczews/utilsverifier/UtilsVerifierTest.java

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,29 +15,49 @@
1515

1616
import com.github.karczews.utilsverifier.subjects.AbstractClass;
1717
import com.github.karczews.utilsverifier.subjects.DefaultConstructor;
18+
import com.github.karczews.utilsverifier.subjects.ImmutableStaticFields;
1819
import com.github.karczews.utilsverifier.subjects.InstanceFields;
1920
import com.github.karczews.utilsverifier.subjects.InstanceMethods;
2021
import com.github.karczews.utilsverifier.subjects.MultipleConstructors;
22+
import com.github.karczews.utilsverifier.subjects.MutableStaticFields;
2123
import com.github.karczews.utilsverifier.subjects.NonFinalClass;
2224
import com.github.karczews.utilsverifier.subjects.NonPrivateConstructor;
2325
import com.github.karczews.utilsverifier.subjects.ThrowingConstructor;
2426
import com.github.karczews.utilsverifier.subjects.WellFormed;
25-
27+
import static org.hamcrest.core.AllOf.allOf;
28+
import static org.hamcrest.core.StringContains.containsString;
2629
import org.junit.Rule;
2730
import org.junit.Test;
2831
import org.junit.rules.ExpectedException;
29-
import org.mockito.Mockito;
30-
31-
import static org.hamcrest.core.AllOf.allOf;
32-
import static org.hamcrest.core.StringContains.containsString;
33-
import static org.mockito.Mockito.mock;
34-
import static org.mockito.Mockito.spy;
3532

3633
public class UtilsVerifierTest {
3734

3835
@Rule
3936
public ExpectedException expectedException = ExpectedException.none();
4037

38+
@Test
39+
public void shouldFailOnMutableStaticFields() {
40+
expectedException.expect(AssertionError.class);
41+
expectedException.expectMessage(containsString("staticInt"));
42+
43+
suppressedVerifier(MutableStaticFields.class)
44+
.suppressMutableStaticFieldsCheck(false)
45+
.verify();
46+
}
47+
48+
@Test
49+
public void shouldPassOnMutableStaticFields() {
50+
suppressedVerifier(ImmutableStaticFields.class)
51+
.suppressMutableStaticFieldsCheck(false)
52+
.verify();
53+
54+
// due to complex condition two asserts in one test
55+
// to not create confusion with strange test name
56+
suppressedVerifier(InstanceFields.class)
57+
.suppressMutableStaticFieldsCheck(false)
58+
.verify();
59+
}
60+
4161
@Test
4262
public void shouldFailOnNonFinalClassVerification() {
4363
expectedException.expect(AssertionError.class);
@@ -152,6 +172,7 @@ private static <T> UtilsVerifier<T> suppressedVerifier(final Class<T> type) {
152172
.suppressOnlyOneConstructorCheck(true)
153173
.suppressPrivateConstructorCheck(true)
154174
.suppressInstanceFieldCheck(true)
155-
.suppressInstanceMethodCheck(true);
175+
.suppressInstanceMethodCheck(true)
176+
.suppressMutableStaticFieldsCheck(true);
156177
}
157178
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.github.karczews.utilsverifier.subjects;
2+
3+
public final class ImmutableStaticFields {
4+
private static final int staticInt = 0;
5+
6+
private ImmutableStaticFields(){
7+
}
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.github.karczews.utilsverifier.subjects;
2+
3+
public final class MutableStaticFields {
4+
private static int staticInt;
5+
6+
private MutableStaticFields() {
7+
}
8+
}

0 commit comments

Comments
 (0)