blob: 3bcd1c04a32c14bb6a3945bd8c8310c3ee48261e [file] [log] [blame]
/*
* Copyright 2002,2004 The Apache Software Foundation.
*
* Licensed 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.commons.jelly.core;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import junit.framework.TestCase;
import org.apache.commons.jelly.JellyContext;
import org.apache.commons.jelly.JellyException;
import org.apache.commons.jelly.Script;
import org.apache.commons.jelly.XMLOutput;
import org.apache.commons.jelly.parser.XMLParser;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
* Automates the basic process of testing a tag library for a memory leak.
* <p>
* To use it, extend it. Use the {@link runScriptManyTimes(String, int)}
* method in your unit tests.
*
* @author Hans Gilde
*
*/
public class BaseMemoryLeakTest extends TestCase {
private final static Log log = LogFactory.getLog(BaseMemoryLeakTest.class);
/**
* The JUnit constructor
*
* @param name
*/
public BaseMemoryLeakTest(String name) {
super(name);
}
/** Runs a script count times and reports the number of bytes "leaked".
* Note that "leaked" means "not collected by the GC"
* and can easily be different between JVM's. This is because all
* freed references may not be available for GC in the short time
* between their freeing and the completion of this test.
* <p/>
* However, running a
* script 10,000 or 100,000 times should be a pretty good test
* for a memory leak. If there's not too much memory "leaked",
* you're probably OK.
* @param scriptName The path to the script, from the classloader of the current class.
* @param count The number of times to run the script.
* @return The number of bytes "leaked"
* @throws IOException
* @throws SAXException
* @throws JellyException
*/
public long runScriptManyTimes(String scriptName, int count)
throws IOException, SAXException, JellyException {
Runtime rt = Runtime.getRuntime();
JellyContext jc = new JellyContext();
jc.setClassLoader(getClass().getClassLoader());
XMLOutput output = XMLOutput.createDummyXMLOutput();
URL url = this.getClass().getResource(scriptName);
String exturl = url.toExternalForm();
int lastSlash = exturl.lastIndexOf("/");
String extBase = exturl.substring(0,lastSlash+1);
URL baseurl = new URL(extBase);
jc.setCurrentURL(baseurl);
InputStream is = url.openStream();
byte[] bytes = new byte[is.available()];
is.read(bytes);
InputStream scriptIStream = new ByteArrayInputStream(bytes);
InputSource scriptISource = new InputSource(scriptIStream);
is.close();
is = null;
bytes = null;
rt.runFinalization();
rt.gc();
long start = rt.totalMemory() - rt.freeMemory();
log.info("Starting memory test with used memory of " + start);
XMLParser parser;
Script script;
int outputEveryXIterations = outputEveryXIterations();
for (int i = 0; i < count; i++) {
scriptIStream.reset();
parser = new XMLParser();
script = parser.parse(scriptISource);
script.run(jc, output);
// PL: I don't see why but removing the clear here
// does make the test fail!
// As if the WeakHashMap wasn't weak enough...
//Hans: The structure of the relationship
// between TagScript and Tag prevents WeakHashMap
// from working in this case, which is why I removed it.
jc.clear();
if (outputEveryXIterations != 0 && i % outputEveryXIterations == 0) {
parser = null;
script = null;
rt.runFinalization();
rt.gc();
long middle = rt.totalMemory() - rt.freeMemory();
log.info("TagHolderMap has " + jc.getThreadScriptDataMap().size() + " entries.");
log.info("Memory test after " + i + " runs: "
+ (middle - start));
}
}
rt.gc();
jc = null;
output = null;
parser = null;
script = null;
scriptIStream = null;
scriptISource = null;
rt.runFinalization();
rt.gc();
long nullsDone = rt.totalMemory() - rt.freeMemory();
log.info("Memory test completed, memory \"leaked\": " + (nullsDone - start));
return nullsDone - start;
}
protected int outputEveryXIterations() {
return 1000;
}
}