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 abstract 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 }