Add MemoryVerifier to enable memory leak checking in unit-tests and add

an initial test which verifies some things for XSSF

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1871588 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Dominik Stadler 2019-12-15 14:52:37 +00:00
parent 3fb5e79490
commit 57e5e87e58
2 changed files with 250 additions and 0 deletions

View File

@ -0,0 +1,144 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.xssf;
import org.apache.poi.util.MemoryLeakVerifier;
import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.junit.After;
import org.junit.Test;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTCell;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.assertSame;
/**
* A test which uses {@link MemoryLeakVerifier} to ensure that certain
* objects are not left over in memory after the test.
*
* E.g. verifies that objects are freed when stuff is removed from sheets or rows
*/
public class XSSFMemoryLeakTests {
private final MemoryLeakVerifier verifier = new MemoryLeakVerifier();
// keep some items in memory, so checks in tearDown() actually
// verify that they do not keep certain objects in memory,
// e.g. nested CT... objects which should be released
@SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
private List<Object> references = new ArrayList<>();
@After
public void tearDown() {
verifier.assertGarbageCollected();
}
@Test
public void testWriteRow() throws IOException {
final XSSFWorkbook wb = new XSSFWorkbook();
final XSSFSheet sheet1 = wb.createSheet("Sheet1");
final XSSFRow row = sheet1.createRow(0);
final XSSFCell cell = row.createCell(0);
cell.setCellValue("hello");
// Cannot check the CTCell here as it is reused now and thus
// not freed until we free up the Cell itself
//verifier.addObject(ctCell);
try (OutputStream out = new ByteArrayOutputStream(8192)) {
wb.write(out);
}
CTCell ctCell = cell.getCTCell();
assertSame("The CTCell should not be replaced",
cell.getCTCell(), ctCell);
assertSame("The CTCell in the row should not be replaced",
row.getCTRow().getCArray(0), ctCell);
wb.close();
}
@Test
public void testRemoveCellFromRow() throws IOException {
final XSSFWorkbook wb = new XSSFWorkbook();
final XSSFSheet sheet1 = wb.createSheet("Sheet1");
final XSSFRow rowToCheck = sheet1.createRow(0);
references.add(rowToCheck);
XSSFCell cell = rowToCheck.createCell(0);
cell.setCellValue("hello");
// previously the CTCell was still referenced in the CTRow, verify that it is freed
verifier.addObject(cell);
verifier.addObject(cell.getCTCell());
rowToCheck.removeCell(cell);
wb.close();
}
@Test
public void testRemove2CellsFromRow() throws IOException {
final XSSFWorkbook wb = new XSSFWorkbook();
final XSSFSheet sheet1 = wb.createSheet("Sheet1");
final XSSFRow rowToCheck = sheet1.createRow(0);
references.add(rowToCheck);
XSSFCell cell1 = rowToCheck.createCell(0);
cell1.setCellValue("hello");
XSSFCell cell2 = rowToCheck.createCell(1);
cell2.setCellValue("world");
// previously the CTCell was still referenced in the CTRow, verify that it is freed
verifier.addObject(cell1);
verifier.addObject(cell1.getCTCell());
verifier.addObject(cell2);
verifier.addObject(cell2.getCTCell());
rowToCheck.removeCell(cell2);
rowToCheck.removeCell(cell1);
wb.close();
}
@Test
public void testRemoveRowFromSheet() throws IOException {
final XSSFWorkbook wb1 = new XSSFWorkbook();
final XSSFSheet sheetToCheck = wb1.createSheet("Sheet1");
references.add(sheetToCheck);
final XSSFRow row = sheetToCheck.createRow(0);
final XSSFCell cell = row.createCell(0);
cell.setCellValue(1);
// ensure that the row-data is not kept somewhere in another member
verifier.addObject(row.getCTRow());
verifier.addObject(row);
verifier.addObject(cell.getCTCell());
verifier.addObject(cell);
sheetToCheck.removeRow(row);
wb1.close();
}
}

View File

@ -0,0 +1,106 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.util;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.assertNull;
/**
* A simple utility class that can verify that objects have been successfully garbage collected.
*
* Usage is something like
*
* private final MemoryLeakVerifier verifier = new MemoryLeakVerifier();
{@literal}After
public void tearDown() {
verifier.assertGarbageCollected();
}
{@literal}Test
public void someTest() {
...
verifier.addObject(object);
}
*
* This will verify at the end of the test if the object is actually removed by the
* garbage collector or if it lingers in memory for some reason.
*
* Idea taken from http://stackoverflow.com/a/7410460/411846
*/
public class MemoryLeakVerifier {
private static final int MAX_GC_ITERATIONS = 50;
private static final int GC_SLEEP_TIME = 100;
private final List<WeakReference<Object>> references = new ArrayList<>();
public MemoryLeakVerifier() {
}
public void addObject(Object object) {
references.add(new WeakReference<>(object));
}
/**
* Attempts to perform a full garbage collection so that all weak references will be removed. Usually only
* a single GC is required, but there have been situations where some unused memory is not cleared up on the
* first pass. This method performs a full garbage collection and then validates that the weak reference
* now has been cleared. If it hasn't then the thread will sleep for 100 milliseconds and then retry up to
* 50 more times. If after this the object still has not been collected then the assertion will fail.
*
* Based upon the method described in: http://www.javaworld.com/javaworld/javatips/jw-javatip130.html
*/
public void assertGarbageCollected() {
assertGarbageCollected(MAX_GC_ITERATIONS);
}
/**
* Used only for testing the class itself where we would like to fail faster than 5 seconds
* @param maxIterations The number of times a GC will be invoked until a possible memory leak is reported
*/
void assertGarbageCollected(int maxIterations) {
try {
for(WeakReference<Object> ref : references) {
assertGarbageCollected(ref, maxIterations);
}
} catch (InterruptedException e) {
// just ensure that we quickly return when the thread is interrupted
}
}
private static void assertGarbageCollected(WeakReference<Object> ref, int maxIterations) throws InterruptedException {
Runtime runtime = Runtime.getRuntime();
for (int i = 0; i < maxIterations; i++) {
runtime.runFinalization();
runtime.gc();
if (ref.get() == null)
break;
// Pause for a while and then go back around the loop to try again...
//EventQueue.invokeAndWait(Procedure.NoOp); // Wait for the AWT event queue to have completed processing
Thread.sleep(GC_SLEEP_TIME);
}
assertNull("Object should not exist after " + MAX_GC_ITERATIONS + " collections, but still had: " + ref.get(),
ref.get());
}
}