Skip to content

Commit b62fc00

Browse files
committed
Add some tests that use reflection
Still need to find a way to check for memory retention beyond what AbstractLangTest.after() does
1 parent daa63aa commit b62fc00

File tree

6 files changed

+108
-4
lines changed

6 files changed

+108
-4
lines changed

src/main/java/org/apache/commons/lang3/builder/ReflectionDiffBuilder.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,6 @@ public DiffResult<T> build() {
212212
if (getLeft().equals(getRight())) {
213213
return diffBuilder.build();
214214
}
215-
216215
appendFields(getLeft().getClass());
217216
return diffBuilder.build();
218217
}

src/test/java/org/apache/commons/lang3/AbstractLangTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ public class AbstractLangTest {
3232
@AfterEach
3333
public void after() {
3434
assertTrue(ToStringStyle.getRegistry().isEmpty(), "Expected null, actual: " + ToStringStyle.getRegistry());
35+
// TODO Do more to make sure memory is not retained, maybe like Log4j checks for it.
3536
}
3637

3738
}

src/test/java/org/apache/commons/lang3/builder/EqualsBuilderReflectJreImplementationTest.java

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,13 @@
4242
import java.time.temporal.TemporalAmount;
4343
import java.time.temporal.TemporalField;
4444
import java.time.temporal.TemporalUnit;
45+
import java.util.ArrayList;
4546
import java.util.Arrays;
4647
import java.util.List;
48+
import java.util.function.Supplier;
4749

4850
import org.apache.commons.lang3.AbstractLangTest;
51+
import org.apache.commons.lang3.stream.IntStreams;
4952
import org.junit.jupiter.api.Test;
5053

5154
/**
@@ -90,13 +93,14 @@ public String toString() {
9093
}
9194
}
9295

93-
static class MyClass {
96+
static class MyClass implements Cloneable {
9497

9598
private final MyCharSequence charSequence;
9699
private final MyTemporal temporal;
97100
private final MyTemporalAccessor temporalAccessor;
98101
private final MyTemporalAmount temporalAmount;
99102
private final Object[] objects;
103+
private final List<Supplier<?>> list = new ArrayList<>();
100104

101105
MyClass(final MyCharSequence charSequence, final MyTemporal temporal, final MyTemporalAccessor temporalAccessor,
102106
final MyTemporalAmount temporalAmount) {
@@ -117,6 +121,7 @@ static class MyClass {
117121
localDate, HijrahDate.from(localDate), JapaneseDate.from(localDate), MinguoDate.from(localDate), ThaiBuddhistDate.from(localDate),
118122
localDate, localTime, localDateTime, offsetDateTime, OffsetTime.of(localTime, zoneOffset), Year.of(value), YearMonth.of(value, value),
119123
ZonedDateTime.of(localDateTime, zoneOffset), zoneOffset, ZoneId.of(zoneOffset.getId()) };
124+
IntStreams.range(100).forEach(i -> list.add(() -> charSequence));
120125
}
121126

122127
@Override
@@ -178,6 +183,7 @@ public Temporal with(final TemporalField field, final long newValue) {
178183
}
179184

180185
}
186+
181187
static class MyTemporalAccessor implements TemporalAccessor {
182188

183189
private final String string;
@@ -256,9 +262,9 @@ public String toString() {
256262

257263
@Test
258264
public void testRecursive() {
259-
final MyClass o1 = new MyClass(new MyCharSequence("1"), new MyTemporal("2"), new MyTemporalAccessor("3"), new MyTemporalAmount("4"));
265+
final MyClass o1 = new MyClass(new MyCharSequence("1"), new MyTemporal("2"), new MyTemporalAccessor("3"), new MyTemporalAmount("4"));
260266
// This gives you different instances of MyTemporalAccessor for 1 (and 2) that should be equals by reflection.
261-
final MyClass o1Bis = new MyClass(new MyCharSequence("1"), new MyTemporal("2"), new MyTemporalAccessor("3"), new MyTemporalAmount("4"));
267+
final MyClass o1Bis = new MyClass(new MyCharSequence("1"), new MyTemporal("2"), new MyTemporalAccessor("3"), new MyTemporalAmount("4"));
262268
final MyClass o2 = new MyClass(new MyCharSequence("5"), new MyTemporal("6"), new MyTemporalAccessor("7"), new MyTemporalAmount("8"));
263269
final MyClass o2Bis = new MyClass(new MyCharSequence("5"), new MyTemporal("6"), new MyTemporalAccessor("7"), new MyTemporalAmount("8"));
264270
// MyTemporal
@@ -276,4 +282,14 @@ public void testRecursive() {
276282
assertFalse(new EqualsBuilder().setTestRecursive(true).append(o2, o1).isEquals());
277283
}
278284

285+
@Test
286+
public void testRetention() throws Exception {
287+
// The following should not retain memory.
288+
for (int i = 0; i < Integer.getInteger("testRetention", 10_000); i++) {
289+
final Class<?> clazz = TestClassBuilder.defineSimpleClass(getClass().getPackage().getName(), i);
290+
assertTrue(new EqualsBuilder().setTestRecursive(true).append(clazz.newInstance(), clazz.newInstance()).isEquals());
291+
}
292+
// some retention is checked in super's after().
293+
}
294+
279295
}

src/test/java/org/apache/commons/lang3/builder/HashCodeBuilderAndEqualsBuilderTest.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ private void testFixture(final boolean testTransients) {
110110
new SubAllTransientFixture(2, 'c', "Test", (short) 2, "Same"),
111111
new SubAllTransientFixture(2, 'c', "Test", (short) 2, "Same"),
112112
testTransients);
113+
113114
}
114115

115116
@Test
@@ -133,4 +134,13 @@ public void testIntegerWithTransients() {
133134
testInteger(true);
134135
}
135136

137+
@Test
138+
public void testRetention() throws Exception {
139+
// The following should not retain memory.
140+
for (int i = 0; i < Integer.getInteger("testRecursive", 10_000); i++) {
141+
final Class<?> clazz = TestClassBuilder.defineSimpleClass(getClass().getPackage().getName(), i);
142+
assertEqualsAndHashCodeContract(clazz.newInstance(), clazz.newInstance(), false);
143+
}
144+
}
145+
136146
}

src/test/java/org/apache/commons/lang3/builder/ReflectionDiffBuilderTest.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ private static final class TypeTestChildClass extends TypeTestClass {
3737

3838
@SuppressWarnings("unused")
3939
private static class TypeTestClass implements Diffable<TypeTestClass> {
40+
4041
private static int staticField;
4142
private final ToStringStyle style = SHORT_STYLE;
4243
private final boolean booleanField = true;
@@ -159,6 +160,7 @@ public void testGetExcludeFieldNamesWithNullExcludedFieldNames() {
159160
final String[] excludeFieldNames = reflectionDiffBuilder.getExcludeFieldNames();
160161
assertNotNull(excludeFieldNames);
161162
assertEquals(0, excludeFieldNames.length);
163+
assertNotNull(reflectionDiffBuilder.build());
162164
}
163165

164166
@Test
@@ -171,6 +173,7 @@ public void testGetExcludeFieldNamesWithNullExcludedFieldNamesCtor() {
171173
final String[] excludeFieldNames = reflectionDiffBuilder.getExcludeFieldNames();
172174
assertNotNull(excludeFieldNames);
173175
assertEquals(0, excludeFieldNames.length);
176+
assertNotNull(reflectionDiffBuilder.build());
174177
}
175178

176179
@Test
@@ -189,6 +192,7 @@ public void testGetExcludeFieldNamesWithNullValuesInExcludedFieldNames() {
189192
assertNotNull(excludeFieldNames);
190193
assertEquals(1, excludeFieldNames.length);
191194
assertEquals("charField", excludeFieldNames[0]);
195+
assertNotNull(reflectionDiffBuilder.build());
192196
}
193197

194198
@Test
@@ -202,6 +206,7 @@ public void testGetExcludeFieldNamesWithNullValuesInExcludedFieldNamesCtor() {
202206
assertNotNull(excludeFieldNames);
203207
assertEquals(1, excludeFieldNames.length);
204208
assertEquals("charField", excludeFieldNames[0]);
209+
assertNotNull(reflectionDiffBuilder.build());
205210
}
206211

207212
@Test
@@ -212,6 +217,19 @@ public void testNoDifferences() {
212217
assertEquals(0, firstObject.diffDeprecated(secondObject).getNumberOfDiffs());
213218
}
214219

220+
@Test
221+
public void testRetention() throws Exception {
222+
// The following should not retain memory.
223+
for (int i = 0; i < Integer.getInteger("testRecursive", 10_000); i++) {
224+
final Class<?> clazz = TestClassBuilder.defineSimpleClass(getClass().getPackage().getName(), i);
225+
final Object firstObject = clazz.newInstance();
226+
final Object secondObject = clazz.newInstance();
227+
final ReflectionDiffBuilder<Object> reflectionDiffBuilder = new ReflectionDiffBuilder<>(firstObject, secondObject, SHORT_STYLE);
228+
assertNotNull(reflectionDiffBuilder.build());
229+
}
230+
}
231+
232+
215233
@Test
216234
public void testNoDifferencesDiffExcludeAnnotatedField() {
217235
final TypeTestClass firstObject = new TypeTestClass();
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.commons.lang3.builder;
19+
20+
import org.objectweb.asm.ClassWriter;
21+
import org.objectweb.asm.MethodVisitor;
22+
import org.objectweb.asm.Opcodes;
23+
24+
/**
25+
* Builds classes dynamically for tests.
26+
*/
27+
public class TestClassBuilder {
28+
29+
/**
30+
* Extends {@link ClassLoader} to make {@link ClassLoader#defineClass(String, byte[])} public.
31+
*/
32+
static final class DynamicClassLoader extends ClassLoader {
33+
public Class<?> defineClass(final String name, final byte[] b) {
34+
return defineClass(name, b, 0, b.length);
35+
}
36+
}
37+
38+
/**
39+
* Defines the simplest possible class.
40+
*
41+
* @param name The class name.
42+
* @return The new class.
43+
*/
44+
static Class<?> defineSimpleClass(final String name) {
45+
final ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
46+
classWriter.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, name, null, "java/lang/Object", new String[] {});
47+
final MethodVisitor ctor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
48+
ctor.visitCode();
49+
ctor.visitVarInsn(Opcodes.ALOAD, 0);
50+
ctor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
51+
ctor.visitInsn(Opcodes.RETURN);
52+
ctor.visitMaxs(1, 1);
53+
return new DynamicClassLoader().defineClass(name.replace('/', '.'), classWriter.toByteArray());
54+
}
55+
56+
static Class<?> defineSimpleClass(final String packageName, final int i) {
57+
return defineSimpleClass(packageName.replace('.', '/') + "/C" + i);
58+
}
59+
60+
}

0 commit comments

Comments
 (0)