]> source.dussan.org Git - poi.git/commitdiff
Add MemoryVerifier to enable memory leak checking in unit-tests and add
authorDominik Stadler <centic@apache.org>
Sun, 15 Dec 2019 14:52:37 +0000 (14:52 +0000)
committerDominik Stadler <centic@apache.org>
Sun, 15 Dec 2019 14:52:37 +0000 (14:52 +0000)
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

src/ooxml/testcases/org/apache/poi/xssf/XSSFMemoryLeakTests.java [new file with mode: 0644]
src/testcases/org/apache/poi/util/MemoryLeakVerifier.java [new file with mode: 0644]

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 (file)
index 0000000..958e545
--- /dev/null
@@ -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 (file)
index 0000000..c69baa7
--- /dev/null
@@ -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());
+       }
+}