private void helpTestCopyOnRead(String queryString, int expectedResultsSize, int numObjects,
      int objectsAndResultsMultiplier, boolean hasIndex, boolean isPR, boolean containsInnerQuery)
      throws Exception {
    int expectedResultsSizeMultiplied = expectedResultsSize * objectsAndResultsMultiplier;
    int numInstances = numObjects * objectsAndResultsMultiplier;

    // We are a 1 VM test, replicated regions would put the actual domain object into the cache
    if (hasIndex && isPR) {
      // If we are PR we serialize the values
      // BUT if we have an index, we deserialize the values, so our instance count is double of our
      // put amount so far
      numInstances += numObjects * objectsAndResultsMultiplier;
    }
    assertEquals("Unexpected number of Portfolio instances", numInstances,
        Portfolio.instanceCount.get());

    // execute query
    QueryService qs = utils.getCache().getQueryService();
    Query query = qs.newQuery(queryString);
    SelectResults results = (SelectResults) query.execute();
    assertEquals("Results did not match expected count for query" + queryString,
        expectedResultsSizeMultiplied, results.size());
    for (Object o : results) {
      if (o instanceof Portfolio) {
        Portfolio p = (Portfolio) o;
        p.status = "discardStatus";
      } else {
        Struct struct = (Struct) o;
        Portfolio p = (Portfolio) struct.getFieldValues()[0];
        p.status = "discardStatus";
      }
    }

    if (!hasIndex && isPR) {
      // We are PR and we do not have an index, so we must deserialize all the values in the cache
      // at this point
      numInstances += (numObjects * objectsAndResultsMultiplier);
      if (containsInnerQuery) {
        // if we have an inner query, it would deserialize all objects for the inner query as well
        numInstances += numObjects * objectsAndResultsMultiplier;
      }
    }
    // So with all the deserialized objects we must also combine the query results of the query due
    // to copy on read
    assertEquals("Unexpected number of Portfolio instances for query " + query,
        numInstances + expectedResultsSizeMultiplied, Portfolio.instanceCount.get());


    results = (SelectResults) query.execute();
    assertEquals("No results were found", expectedResultsSizeMultiplied, results.size());
    for (Object o : results) {
      if (o instanceof Portfolio) {
        Portfolio p = (Portfolio) o;
        assertEquals("status should not have been changed", "testStatus", p.status);
      } else {
        Struct struct = (Struct) o;
        Portfolio p = (Portfolio) struct.getFieldValues()[0];
        assertEquals("status should not have been changed", "testStatus", p.status);
      }
    }
    if (!hasIndex && isPR) {
      // Again, because we have no index, we must deserialize the values in the region
      numInstances += (numObjects * objectsAndResultsMultiplier);
      if (containsInnerQuery) {
        // If we have an inner query, we must also deserialize the values in the region for this
        // query
        // We have some interesting logic in LocalRegion when deserializing. Based on these flags
        // we do not store the deserialized instance back in the cache
        numInstances += numObjects * objectsAndResultsMultiplier;
      }
    }
    // So with all the deserialized objects we must also combine the query results of two queries at
    // this point. These results themselves would have been copied
    assertEquals("Unexpected number of Portfolio instances",
        numInstances + expectedResultsSizeMultiplied * 2, Portfolio.instanceCount.get());

  }
@param objectsAndResultsMultiplier number of similar objects to put into the cache so that
       results from queries will be satisfied by the multiple
geode-core/src/integrationTest/java/org/apache/geode/cache/query/internal/index/CopyOnReadIndexJUnitTest.java