aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDominik Stadler <centic@apache.org>2019-12-15 14:52:37 +0000
committerDominik Stadler <centic@apache.org>2019-12-15 14:52:37 +0000
commit57e5e87e58c8eabfc2fd1f3b14808814d668a83f (patch)
treefb12dd1ad753062df6fd6f086ee309847428e558
parent3fb5e794904652908f122af210db4ca2003d5ac2 (diff)
downloadpoi-57e5e87e58c8eabfc2fd1f3b14808814d668a83f.tar.gz
poi-57e5e87e58c8eabfc2fd1f3b14808814d668a83f.zip
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
-rw-r--r--src/ooxml/testcases/org/apache/poi/xssf/XSSFMemoryLeakTests.java144
-rw-r--r--src/testcases/org/apache/poi/util/MemoryLeakVerifier.java106
2 files changed, 250 insertions, 0 deletions
diff --git a/src/ooxml/testcases/org/apache/poi/xssf/XSSFMemoryLeakTests.java b/src/ooxml/testcases/org/apache/poi/xssf/XSSFMemoryLeakTests.java
new file mode 100644
index 0000000000..958e54554c
--- /dev/null
+++ b/src/ooxml/testcases/org/apache/poi/xssf/XSSFMemoryLeakTests.java
@@ -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();
+ }
+}
diff --git a/src/testcases/org/apache/poi/util/MemoryLeakVerifier.java b/src/testcases/org/apache/poi/util/MemoryLeakVerifier.java
new file mode 100644
index 0000000000..c69baa7900
--- /dev/null
+++ b/src/testcases/org/apache/poi/util/MemoryLeakVerifier.java
@@ -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());
+ }
+}