Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ConcurrentModificationException: Unable to verify subclasses of ArrayList collection #341

Closed
Alexis2004 opened this issue Aug 28, 2020 · 5 comments

Comments

@Alexis2004
Copy link

What steps will reproduce the problem?

Verify any class that extends ArrayList collection.

What is the code that triggers this problem?

import nl.jqno.equalsverifier.EqualsVerifier;
import nl.jqno.equalsverifier.Warning;
import org.junit.Test;

import java.util.ArrayList;

public class ArrayListProblemTest {

	public static class TestDTO extends ArrayList<Integer> {
	}

	@Test
	public void testDto() {
		EqualsVerifier
				.forClass(TestDTO.class)
				.suppress(Warning.NULL_FIELDS)
				.verify();
	}

}

What error message or stack trace does EqualsVerifier give?

java.lang.AssertionError: EqualsVerifier found a problem in class ArrayListProblemTest$TestDTO.
-> null

For more information, go to: http://www.jqno.nl/equalsverifier/errormessages

	at nl.jqno.equalsverifier.api.SingleTypeEqualsVerifierApi.verify(SingleTypeEqualsVerifierApi.java:271)
	at ArrayListProblemTest.testDto(ArrayListProblemTest.java:17)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
	at org.junit.vintage.engine.execution.RunnerExecutor.execute(RunnerExecutor.java:40)
	at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183)
	at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195)
	at java.base/java.util.Iterator.forEachRemaining(Iterator.java:133)
	at java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1801)
	at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
	at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:150)
	at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:173)
	at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:497)
	at org.junit.vintage.engine.VintageTestEngine.executeAllChildren(VintageTestEngine.java:80)
	at org.junit.vintage.engine.VintageTestEngine.execute(VintageTestEngine.java:71)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:229)
	at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$6(DefaultLauncher.java:197)
	at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:211)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:191)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:128)
	at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:71)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:220)
	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:53)
Caused by: java.util.ConcurrentModificationException
	at java.base/java.util.ArrayList.hashCodeRange(ArrayList.java:621)
	at java.base/java.util.ArrayList.hashCode(ArrayList.java:613)
	at nl.jqno.equalsverifier.internal.util.CachedHashCodeInitializer.getInitializedHashCode(CachedHashCodeInitializer.java:69)
	at nl.jqno.equalsverifier.internal.checkers.ExamplesChecker.checkHashCode(ExamplesChecker.java:134)
	at nl.jqno.equalsverifier.internal.checkers.ExamplesChecker.checkSingle(ExamplesChecker.java:79)
	at nl.jqno.equalsverifier.internal.checkers.ExamplesChecker.check(ExamplesChecker.java:45)
	at nl.jqno.equalsverifier.api.SingleTypeEqualsVerifierApi.verifyWithExamples(SingleTypeEqualsVerifierApi.java:364)
	at nl.jqno.equalsverifier.api.SingleTypeEqualsVerifierApi.performVerification(SingleTypeEqualsVerifierApi.java:321)
	at nl.jqno.equalsverifier.api.SingleTypeEqualsVerifierApi.verify(SingleTypeEqualsVerifierApi.java:267)
	... 42 more

What did you expect?

I expect that my DTO can be verified by EqualsVerifier.

Which version of EqualsVerifier are you using?

Latest version 3.4.2

Please provide any additional information below.

It seems that EqualsVerifier library is trying to duplicate object in some strange way that break ArrayList internals integrity.

@jqno
Copy link
Owner

jqno commented Aug 29, 2020

AbstractList has a very specialized and optimized implementation that is extremely hard to verify directly. The same applies to any of its subclasses. I've tried several times to make it work, but it's just not feasible, unfortunately.

As a workaround you could change your DTO to contain ArrayList instead of overriding it. Comosition over inheritance, right? 😉

I'll see if I can make EqualsVerifier emit a more helpful error message in this situation.

@Alexis2004
Copy link
Author

It's a good idea to use composition instead of inheritance in new code. Unfortunately, I'm trying to introduce unit testing for some legacy DTOs in the project and that DTO is part of the Hibernate entity that have a lot of saved data in production databases. So I will have to implement additional type adapters for each entity to save the current data format, and this doesn't look like an easy task.

So I'm wondering if it is possible to implement special behavior for cloning successors of ArrayList collection? For example, we can try to instantiate a new instance of TestDTO using a parameterless constructor and then adding to it duplicates of all items from source collection using the addAll method. If it's impossible to implement it automatically it would be great to give a manual handle to control the instantiation of problematic classes like this (something similar to withPrefabValues configuration option).

@jqno
Copy link
Owner

jqno commented Aug 31, 2020

Ah, I see.

Instantiating objects isn't the problem, EqualsVerifier can do that just fine. The issue is that EqualsVerifier will use reflection to change fields and see what happens. This is fine for POJO's, but AbstractList has complicated internal invariants that get screwed up this way. Unfortunately, this is how EqualsVerifier works. There are other ways of course, but those would probably require an (almost) complete re-write of EqualsVerifier. That requires an amount of time that I simply do not have, unfortunately.

@jqno jqno closed this as completed Aug 31, 2020
@jqno jqno reopened this Aug 31, 2020
@jqno
Copy link
Owner

jqno commented Aug 31, 2020

Oops, accidentally closed this. I still want to implement a better error message.

@jqno
Copy link
Owner

jqno commented Sep 8, 2020

I've released version 3.4.3, which contains a more helpful error message.

@jqno jqno closed this as completed Sep 8, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants