    1   /*
    2    * Copyright 2002,2004 The Apache Software Foundation.
    3    *
    4    * Licensed under the Apache License, Version 2.0 (the "License");
    5    * you may not use this file except in compliance with the License.
    6    * You may obtain a copy of the License at
    7    *
    8    *      http://www.apache.org/licenses/LICENSE-2.0
    9    *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  package org.apache.commons.jelly.core;
    17
    18  import java.io.ByteArrayInputStream;
    19  import java.io.IOException;
    20  import java.io.InputStream;
    21  import java.net.URL;
    22
    23  import junit.framework.TestCase;
    24
    25  import org.apache.commons.jelly.JellyContext;
    26  import org.apache.commons.jelly.JellyException;
    27  import org.apache.commons.jelly.Script;
    28  import org.apache.commons.jelly.XMLOutput;
    29  import org.apache.commons.jelly.parser.XMLParser;
    30  import org.apache.commons.logging.Log;
    31  import org.apache.commons.logging.LogFactory;
    32  import org.xml.sax.InputSource;
    33  import org.xml.sax.SAXException;
    34
    35  /***
    36   * Automates the basic process of testing a tag library for a memory leak.
    37   * <p>
    38   * To use it, extend it. Use the {@link runScriptManyTimes(String, int)}
    39   * method in your unit tests.
    40   *
    41   * @author Hans Gilde
    42   *
    43   */
    44  public class BaseMemoryLeakTest extends TestCase {
    45      private final static Log log = LogFactory.getLog(BaseMemoryLeakTest.class);
    46
    47      /***
    48       * The JUnit constructor
    49       *
    50       * @param name
    51       */
    52      public BaseMemoryLeakTest(String name) {
    53          super(name);
    54      }
    55
    56      /*** Runs a script count times and reports the number of bytes "leaked".
    57       * Note that "leaked" means "not collected by the GC"
    58       * and can easily be different between JVM's. This is because all
    59       * freed references may not be available for GC in the short time
    60       * between their freeing and the completion of this test.
    61       * <p/>
    62       * However, running a
    63       * script 10,000 or 100,000 times should be a pretty good test
    64       * for a memory leak. If there's not too much memory "leaked",
    65       * you're probably OK.
    66       * @param scriptName The path to the script, from the classloader of the current class.
    67       * @param count The number of times to run the script.
    68       * @return The number of bytes "leaked"
    69       * @throws IOException
    70       * @throws SAXException
    71       * @throws JellyException
    72       */
    73      public long runScriptManyTimes(String scriptName, int count)
    74              throws IOException, SAXException, JellyException {
    75          Runtime rt = Runtime.getRuntime();
    76          JellyContext jc = new JellyContext();
    77          jc.setClassLoader(getClass().getClassLoader());
    78
    79          XMLOutput output = XMLOutput.createDummyXMLOutput();
    80
    81          URL url = this.getClass().getResource(scriptName);
    82
    83          String exturl = url.toExternalForm();
    84          int lastSlash = exturl.lastIndexOf("/");
    85          String extBase = exturl.substring(0,lastSlash+1);
    86          URL baseurl = new URL(extBase);
    87          jc.setCurrentURL(baseurl);
    88
    89          InputStream is = url.openStream();
    90          byte[] bytes = new byte[is.available()];
    91          is.read(bytes);
    92
    93          InputStream scriptIStream = new ByteArrayInputStream(bytes);
    94          InputSource scriptISource = new InputSource(scriptIStream);
    95
    96          is.close();
    97          is = null;
    98          bytes = null;
    99
    100         rt.runFinalization();
    101         rt.gc();
    102
    103         long start = rt.totalMemory() - rt.freeMemory();
    104         log.info("Starting memory test with used memory of " + start);
    105
    106         XMLParser parser;
    107         Script script;
    108
    109         int outputEveryXIterations = outputEveryXIterations();
    110
    111         for (int i = 0; i < count; i++) {
    112             scriptIStream.reset();
    113             parser = new XMLParser();
    114
    115             script = parser.parse(scriptISource);
    116             script.run(jc, output);
    117             // PL: I don't see why but removing the clear here
    118             //     does make the test fail!
    119             //     As if the WeakHashMap wasn't weak enough...
    120
    121             //Hans: The structure of the relationship
    122             //  between TagScript and Tag prevents WeakHashMap
    123             //  from working in this case, which is why I removed it.
    124             jc.clear();
    125
    126             if (outputEveryXIterations != 0 && i % outputEveryXIterations == 0) {
    127                 parser = null;
    128                 script = null;
    129
    130                 rt.runFinalization();
    131                 rt.gc();
    132                 long middle = rt.totalMemory() - rt.freeMemory();
    133                 log.info("Memory test after " + i + " runs: "
    134                         + (middle - start));
    135             }
    136         }
    137
    138         rt.gc();
    139
    140         jc = null;
    141         output = null;
    142         parser = null;
    143         script = null;
    144
    145         scriptIStream = null;
    146         scriptISource = null;
    147
    148         rt.runFinalization();
    149         rt.gc();
    150
    151         long nullsDone = rt.totalMemory() - rt.freeMemory();
    152         log.info("Memory test completed, memory \"leaked\": " + (nullsDone - start));
    153
    154         return nullsDone - start;
    155     }
    156
    157     protected int outputEveryXIterations() {
    158         return 1000;
    159     }
    160
    161 }