You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@poi.apache.org by ce...@apache.org on 2019/12/15 14:52:37 UTC

svn commit: r1871588 - in /poi/trunk/src: ooxml/testcases/org/apache/poi/xssf/XSSFMemoryLeakTests.java testcases/org/apache/poi/util/MemoryLeakVerifier.java

Author: centic
Date: Sun Dec 15 14:52:37 2019
New Revision: 1871588

URL: http://svn.apache.org/viewvc?rev=1871588&view=rev
Log:
Add MemoryVerifier to enable memory leak checking in unit-tests and add
an initial test which verifies some things for XSSF

Added:
    poi/trunk/src/ooxml/testcases/org/apache/poi/xssf/XSSFMemoryLeakTests.java
    poi/trunk/src/testcases/org/apache/poi/util/MemoryLeakVerifier.java

Added: poi/trunk/src/ooxml/testcases/org/apache/poi/xssf/XSSFMemoryLeakTests.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/ooxml/testcases/org/apache/poi/xssf/XSSFMemoryLeakTests.java?rev=1871588&view=auto
==============================================================================
--- poi/trunk/src/ooxml/testcases/org/apache/poi/xssf/XSSFMemoryLeakTests.java (added)
+++ poi/trunk/src/ooxml/testcases/org/apache/poi/xssf/XSSFMemoryLeakTests.java Sun Dec 15 14:52:37 2019
@@ -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();
+    }
+}

Added: poi/trunk/src/testcases/org/apache/poi/util/MemoryLeakVerifier.java
URL: http://svn.apache.org/viewvc/poi/trunk/src/testcases/org/apache/poi/util/MemoryLeakVerifier.java?rev=1871588&view=auto
==============================================================================
--- poi/trunk/src/testcases/org/apache/poi/util/MemoryLeakVerifier.java (added)
+++ poi/trunk/src/testcases/org/apache/poi/util/MemoryLeakVerifier.java Sun Dec 15 14:52:37 2019
@@ -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());
+	}
+}



---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@poi.apache.org
For additional commands, e-mail: commits-help@poi.apache.org