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

determineDifferences method of RecursiveComparisonDifferenceCalculator not able to compare the list of 100 objects #2979

Closed
amitnarula opened this issue Mar 11, 2023 · 5 comments
Labels
theme: recursive comparison An issue related to the recursive comparison
Milestone

Comments

@amitnarula
Copy link

amitnarula commented Mar 11, 2023

Trying to work with assertj-core recursive comparison APIs for deep comparison between the objects as follows. But it appears that it doesn't work when we pass collections of custom objects for deep comparison when they are large in number for example even for 50 or 100, the comparison takes forever.

Let me share you the code chunks to look at.

  • assertj core version: 3.24.2
  • java version: 11
  • test framework version: junit 4.13.1
  • os (if relevant): Windows 10 64 bit

Minimal/simplified code reproducing the bug

Assignment class

class Assignment
{
   String assignmentName;
   String assignmentDescription;
   
   Assignment(String assignmentName, String assignmentDescription)
   {
     this.assignmentName = assignmentName;
     this.assignmentDescription = assignmentDescription;
   }
}

Owner class

class Owner
{
   String id;
   String name;
   ArrayList<Assignment> assignments;

   Owner(String id,String name, ArrayList<Assignment> assignments)
   {
      this.id = id;
      this.name = name;
      this.assignments = assignments;
   }
}

Code to generate a single Assignment object

public static Assignment GenerateAssignment()
{
    return new Assignment("111", "Assignment 1");
}

Code to generate different assignment object

public static Assignment GenerateDifferentAssignment()
{
    return new Assignment("222", "Assignment 2");
}

Code to generate list of Assignment objects

public static ArrayList<Assignment> GenerateListOfAssignments(Integer size)
{
    ArrayList<Assignment> lst = new ArrayList<Assignment>();
    for(Integer i=0 ; i<size; i++)
    {
          lst.add(GenerateAssignment());
    }
    return lst;
}

Code to generate different assignment objects

public static ArrayList<Assignment> GenerateDifferentListOfAssignments(Integer size)
{
    ArrayList<Assignment> lst = new ArrayList<Assignment>();
    for(Integer i=0 ; i<size; i++)
    {
          lst.add(GenerateDifferentAssignment());
    }
   return lst;
}

Code to generate an Owner object

public static Owner GenerateOwner(Integer numberOfAssignments)
{
     ArrayList<Assignment> lstAssignments = GenerateListOfAssignments(numberOfAssignments);
     return new Owner("owner1", "TestOwner 1", lstAssignments);
}

Code to generate a different Owner object

public static Owner GenerateDifferentOwner(Integer numberOfAssignments)
{
     ArrayList<Assignment> lstAssignments = GenerateDifferentListOfAssignments(numberOfAssignments);
     return new Owner("owner2", "TestOwner 2", lstAssignments);
}

Code to generate list of Owners

public static ArrayList<Owner> GenerateListOfOwners(Integer noOfOwners,  noOfAssignments)
{
    ArrayList<Owner> lstOwner = new ArrayList<Owner>();
    for(Integer i=0 ; i<noOfOwners; i++)
    {
          lst.add(GenerateOwner(noOfAssignments));
    }
    return lstOwner;
}

Code to generate a different list of Owners

public static ArrayList<Owner> GenerateDifferentListOwners(Integer noOfOwners,  noOfAssignments)
{
    ArrayList<Owner> lstOwner = new ArrayList<Owner>();
    for(Integer i=0 ; i<noOfOwners; i++)
    {
          lst.add(GenerateDifferentOwner(noOfAssignments));
    }
    return lstOwner;
}

JUnit test

@Test
public static void DeepCompareCollections()
{

  //100 owners with 50 assignments each
   ArrayList<Owner> actual = GenerateListOfOwners(100, 50);

  //100 different owners with 50 different assignments each
  ArrayList<Owner> expected = GeneratedDifferentListOfOwners(100, 50);

   //Recursive comparison logic
    var config = new RecursiveComparisonConfiguration();
    config.ignoreCollectionOrder(true);
    config.ignoreCollectionOrderInFields();

   List<ComparisonDifference> difference = new RecursiveComparisonDifferenceCalculator().determineDifference(actual, expected, 
config);

}

Once you debug the test : DeepCompareCollections test as shown above , you will observe that the recursive comparison takes a lot of time to determine the difference between the two collections. We can adjust the number of owners and their corresponding assignments to observe the behavior further.

@joel-costigliola joel-costigliola added this to the 3.25.0 milestone Mar 12, 2023
@joel-costigliola
Copy link
Member

Thanks for reporting the issue with details @amitnarula

@scordio scordio added the theme: recursive comparison An issue related to the recursive comparison label Mar 12, 2023
@amitnarula
Copy link
Author

Thanks for reporting the issue with details @amitnarula

No worries @joel-costigliola

@joel-costigliola
Copy link
Member

joel-costigliola commented Mar 21, 2023

Alright, so the issue is that the recursive comparison does brute introspection leading to introspect over and over again the same class fields instead of caching the result.

Moreover the default comparison strategy introspects first property getters and then fields, so paying the price of a double introspection since your classes don't have getters.

I'm adding caches and should have much better performance but the thing that can't be optimized is reading the field values for a given instance.

@joel-costigliola
Copy link
Member

Note that since 3.24.0 one can set a comparison strategy on fields only (no property introspection), see https://assertj.github.io/doc/#assertj-core-recursive-comparison-introspection-strategy

@joel-costigliola
Copy link
Member

joel-costigliola commented Mar 21, 2023

another super important factor is ignoring the collection order, comparing unordered collections mean comparing each element of the actual collection to each element of the expected one, that's O(n2). I'm not sure if there is something we could do about it, I'm open to suggestions 😉

I think it is the main factor here, running you test without it took ~2s.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
theme: recursive comparison An issue related to the recursive comparison
Projects
None yet
Development

No branches or pull requests

3 participants