/*
* The Apache Software License, Version 1.1
*
* Copyright (C) 2000-2002 The Apache Software Foundation. All rights
* reserved.
* Copyright (C) 2009 John Lewis
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution, if
* any, must include the following acknowlegement:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowlegement may appear in the software itself,
* if and wherever such third-party acknowlegements normally appear.
*
* 4. The names "Ant" and "Apache Software
* Foundation" must not be used to endorse or promote products derived
* from this software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache"
* nor may "Apache" appear in their names without prior written
* permission of the Apache Group.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*/
package net.sourceforge.cobertura.thread.test;
import groovy.util.AntBuilder;
import groovy.util.Node;
import net.sourceforge.cobertura.ant.ReportTask;
import net.sourceforge.cobertura.test.util.TestUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.tools.ant.taskdefs.Echo;
import org.apache.tools.ant.taskdefs.Java;
import org.apache.tools.ant.types.DirSet;
import org.apache.tools.ant.types.Path;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class MultipleClassloaderFunctionalTest {
/*
* This tests the case where there are multiple classloaders that each
* load the Cobertura classes. Cobertura uses static fields to
* hold the data. When there are multiple classloaders, each classloader
* will keep track of the line counts for the classes that it loads.
*
* The static initializers for the Cobertura classes are also called for
* each classloader. So, there is one shutdown hook for each classloader.
* So, when the JVM exits, each shutdown hook will try to write the
* data it has kept to the datafile. They will do this at the same
* time. Before Java 6, this seemed to work fine, but with Java 6, there
* seems to have been a change with how file locks are implemented. So,
* care has to be taken to make sure only one thread locks a file at a time.
*
*/
AntBuilder ant = TestUtils.getCoberturaAntBuilder(TestUtils
.getCoberturaClassDir());
TestUtils testUtil = new TestUtils();
private static String CALLED_CODE = "\n package mypackage;" + "\n "
+ "\n public class Called {" + "\n public static void callThis()"
+ "\n {" + "\n }" + "\n }";
@Before
public void setUp() throws IOException {
FileUtils.deleteDirectory(new File("/tmp/src"));
}
@After
public void tearDown() throws IOException {
FileUtils.deleteDirectory(new File("/tmp/src"));
}
@Test
public void multipleClassloadersTest() throws Exception {
Echo echo = new Echo();
echo.setMessage("Running multiple classloader test.");
echo.execute();
runTest();
}
/*
* This code creates a Java class that has a main method that will create two
* classloaders. Each classloader will then load the mypackage.Called class
* defined above. Then, the mypackage.Called.callThis() method is called once.
*/
private String getMainCode(File instrumentDir) {
return "\n package mypackage;"
+ "\n "
+ "\n import java.net.URLClassLoader;"
+ "\n import java.net.URL;"
+ "\n import java.io.File;"
+ "\n import java.lang.reflect.Method;"
+ "\n "
+ "\n public class Main {"
+ "\n public static void main(String[] args) throws Throwable"
+ "\n {"
+ "\n createClassloaderAndCallMethod();"
+ "\n createClassloaderAndCallMethod();"
+ "\n }"
+ "\n "
+ "\n /*"
+ "\n * Create a classloader that loads the instrumented code and the cobertura classes."
+ "\n * Then, call the mypackage.Called.callThis() static method."
+ "\n */"
+ "\n public static void createClassloaderAndCallMethod() throws Throwable"
+ "\n {"
+ "\n File instrumentDir = new File(\""
+ instrumentDir.getAbsolutePath()
+ "\");"
+ "\n File coberturaClassDir = new File(\""
+ TestUtils.getCoberturaClassDir().getAbsolutePath()
+ "\");"
+ "\n "
+ "\n /*"
+ "\n * Create a classloader with a null parent classloader to ensure that this"
+ "\n * classloader loads the Cobertura classes itself."
+ "\n */"
+ "\n URLClassLoader loader = new URLClassLoader("
+ "\n new URL[] {instrumentDir.toURL(), coberturaClassDir.toURL()}, null);"
+ "\n "
+ "\n // use reflection to call mypackage.Called.callThis() once."
+ "\n Class calledClass = loader.loadClass(\"mypackage.Called\");"
+ "\n Method method = calledClass.getMethod(\"callThis\", null);"
+ "\n " + "\n method.invoke(null, null);" + "\n }"
+ "\n }";
}
private void runTest() throws Exception {
/*
* Use a temporary directory and create a Main.java source file
* that creates multiple classloaders. Also, create a Called.java
* file that defines the mypackage.Called.callThis() method that
* will be called by mypackage.Main.main().
*/
File tempDir = TestUtils.getTempDir();
File srcDir = new File(tempDir, "src");
File instrumentDir = new File(tempDir, "instrument");
File mainSourceFile = new File(srcDir, "mypackage/Main.java");
File datafile = new File(srcDir, "cobertura.ser");
mainSourceFile.getParentFile().mkdirs();
BufferedWriter bw = null;
try {
bw = new BufferedWriter(new FileWriter(mainSourceFile));
bw.write(getMainCode(instrumentDir));
} catch (IOException e) {
e.printStackTrace();
fail();
} finally {
IOUtils.closeQuietly(bw);
}
File calledSourceFile = new File(srcDir, "mypackage/Called.java");
try {
bw = new BufferedWriter(new FileWriter(calledSourceFile));
bw.write(CALLED_CODE);
} catch (IOException e) {
e.printStackTrace();
fail();
} finally {
IOUtils.closeQuietly(bw);
}
TestUtils.compileSource(ant, srcDir);
TestUtils.instrumentClasses(ant, srcDir, datafile, instrumentDir);
/*
* Kick off the Main class. I'll use the non-instrumented classes, but
* I think you could use the instrumented ones as well.
*/
DirSet dirSet = new DirSet();
dirSet.setDir(srcDir);
dirSet.setProject(TestUtils.project);
Path classpath = new Path(TestUtils.project);
classpath.addDirset(dirSet);
classpath.addDirset(TestUtils.getCoberturaClassDirSet());
Java java = new Java();
java.setProject(TestUtils.project);
java.setClassname("mypackage.Main");
java.setDir(srcDir);
java.setFork(true);
java.setFailonerror(true);
java.setClasspath(classpath);
java.execute();
/*
* Now create a cobertura xml file and make sure the correct counts are in it.
*/
ReportTask reportTask = new ReportTask();
reportTask.setProject(TestUtils.project);
reportTask.setDataFile(datafile.getAbsolutePath());
reportTask.setFormat("xml");
reportTask.setDestDir(srcDir);
reportTask.execute();
Node dom = TestUtils.getXMLReportDOM(srcDir.getAbsolutePath()
+ "/coverage.xml");
List<Node> lines = TestUtils.getLineCounts(dom, "mypackage.Called",
"callThis", null);
// the callThis() method is empty, so the only line is the ending brace.
assertEquals(1, lines.size());
for (int i = 0; i < lines.size(); i++) {
assertEquals("hit count incorrect", "2", lines.get(i).attribute(
"hits"));
}
}
}