package net.sourceforge.cobertura.thread.test;
/*
* 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/>.
*/
import groovy.util.AntBuilder;
import groovy.util.Node;
import net.sourceforge.cobertura.ant.InstrumentTask;
import net.sourceforge.cobertura.ant.ReportTask;
import net.sourceforge.cobertura.test.util.TestUtils;
import org.apache.commons.io.FileUtils;
import org.apache.tools.ant.taskdefs.Echo;
import org.apache.tools.ant.taskdefs.Java;
import org.apache.tools.ant.taskdefs.Javac;
import org.apache.tools.ant.types.DirSet;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.types.Path;
import org.codehaus.groovy.ant.Groovyc;
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 static org.junit.Assert.assertEquals;
/**
* This tests the thread safety of Cobertura.
*
* Multiple threads are kicked off that will execute the same switch or
* if statement. The switch statement would like like this:
*
* switch(counter)
* {
* case 0: counter++; break;
* case 1: counter++; break;
* etc.
* default: counter = 0;
* }
* The if statement looks like this:
* if (counter==0||counter==1||counter==2 ... )
* {
* counter++;
* }
*
* Notice that the counter is incremented so the cases or conditionals are
* hit in order with each call.
*
* Multiple threads will process the switch or if statement at the same time.
* Since the case and conditional information is handled with arrays that grow
* dynamically larger as each case or conditional is hit, you will get index
* out of bounds exceptions if Cobertura is not thread safe. One thread
* will increase the array while another is trying to do the same. Only one
* thread's change is saved, and the other thread is likely to throw an
* out of bounds exception.
*/
public class ThreadedFunctionalTest {
AntBuilder ant = TestUtils.getCoberturaAntBuilder(TestUtils
.getCoberturaClassDir());
private static int numberOfCalls = 300; //this number can't be too high or you will get a stack overflow (due to the inject below)
private static int numberOfThreads = 2;
private static int numberOfRetries = 10;
boolean branchOrSwitchCode;
/*
* A big switch statement with number of cases equal to numberOfCalls:
*
* switch(counter)
* {
* case 0: counter++; break;
* case 1: counter++; break;
* etc.
* default: counter = 0;
* }
*/
private static String SWITCH_CODE;
/*
* A big if statement with number of conditionals equal to numberOfCalls:
*
* if (counter==0||counter==1||counter==2 ... )
* {
* counter++;
* }
*/
private static String IF_STATEMENT_CODE;
static {
StringBuilder tempBuilder = new StringBuilder();
tempBuilder.append("\nswitch(counter)");
tempBuilder.append("\n{");
for (int i = 0; i < numberOfCalls; i++) {
tempBuilder.append("\ncase " + i + ": counter++; break;");
}
tempBuilder.append("\ndefault: counter = 0; break;}");
SWITCH_CODE = tempBuilder.toString();
tempBuilder = new StringBuilder();
tempBuilder.append("if (");
for (int i = 0; i < numberOfCalls; i++) {
tempBuilder.append("counter==" + i);
if (i < numberOfCalls - 1) { // want to add || to everything except the last one
tempBuilder.append("||");
}
}
tempBuilder.append(") {\n counter++;\n }");
IF_STATEMENT_CODE = tempBuilder.toString();
}
private String getThreadedCode(String branchOrSwitchCode) {
/*
* This code will kick off a number of threads equal to numberOfThreads.
* Each thread will do a number of calls equal to numberOfCalls.
* Each call will be a call to the method acall(). The body of this
* method is passed in as branchOrSwithcCode and is either a switch
* statement with a large number of cases or an if statement with
* a large number of conditionals.
*/
return "\npackage mypackage;" + "\n" + "\nimport java.util.ArrayList;"
+ "\n" + "\nclass MyThreads extends Thread" + "\n{"
+ "\n int counter = 0;" + "\n" + "\n void acall()" + "\n {"
+ "\n "
+ branchOrSwitchCode
+ "\n }"
+ "\n"
+ "\n public void run()"
+ "\n {"
+ "\n try"
+ "\n {"
+ "\n for (int i=0; i< "
+ numberOfCalls
+ "; i++)"
+ "\n {"
+ "\n yield();"
+ "\n acall();"
+ "\n }"
+ "\n }"
+ "\n catch (Throwable t)"
+ "\n {"
+ "\n t.printStackTrace();"
+ "\n System.exit(1);"
+ "\n }"
+ "\n }"
+ "\n"
+ "\n public static void main(String[] args)"
+ "\n {"
+ "\n ArrayList threads = new ArrayList();"
+ "\n for (int i=0; i<"
+ numberOfThreads
+ "; i++)"
+ "\n {"
+ "\n threads.add(new MyThreads());"
+ "\n }"
+ "\n for (int i=0; i<"
+ numberOfThreads
+ "; i++)"
+ "\n {"
+ "\n ((Thread) threads.get(i)).start();"
+ "\n }" + "\n }" + "\n}";
}
@Test
public void simpleThreadTest() throws Exception {
Echo echo = new Echo();
echo.setMessage("Running threaded test with switch statement.");
echo.execute();
runTest(SWITCH_CODE);
}
@Test
public void simpleThreadTestWithIfStatement() throws Exception {
Echo echo = new Echo();
echo.setMessage("Running threaded test with if statement.");
echo.execute();
runTest(IF_STATEMENT_CODE);
}
@After
public void tearDown() throws IOException {
FileUtils.deleteDirectory(new File("/tmp/src"));
}
@Before
public void setUp() throws IOException {
FileUtils.deleteDirectory(new File("/tmp/src"));
}
private void runTest(String code) throws Exception {
/*
* Use a temporary directory and create a MyThreads.java source file
* that creates multiple threads which do repetitive calls into
* a method. The method contains either a switch with a large number
* of cases or an if statement with a large number of conditionals depending
* on the code passed into this method.
*/
File tempDir = TestUtils.getTempDir();
final File srcDir = new File(tempDir, "src");
File sourceFile = new File(srcDir, "mypackage/MyThreads.java");
final File datafile = new File(srcDir, "cobertura.ser");
sourceFile.getParentFile().mkdirs();
BufferedWriter bw = null;
try {
bw = new BufferedWriter(new FileWriter(sourceFile));
bw.write(getThreadedCode(code));
} finally {
bw.close();
}
compileSource(srcDir);
instrumentClasses(srcDir, datafile);
/*
* This test does not seem to fail all the time, so we will need to try several times.
*/
Path p = new Path(TestUtils.project);
DirSet dirSet = new DirSet();
FileSet fileSet = new FileSet();
dirSet.setDir(srcDir);
fileSet.setDir(new File("src/test/resources/antLibrary/common/groovy"));
fileSet.setIncludes("*.jar");
p.addFileset(fileSet);
p.addDirset(dirSet);
p.setProject(TestUtils.project);
p.addDirset(TestUtils.getCoberturaClassDirSet());
for (int i = 0; i < numberOfRetries; i++) {
System.out.println("Executing build: " + i);
Java java = new Java();
java.setClassname("mypackage.MyThreads");
java.setDir(srcDir);
java.setFork(true);
java.setProject(TestUtils.project);
java.setFailonerror(true);
java.setClasspath(p);
java.execute();
}
System.out.println("Starting reporting task.");
ReportTask reportTask = new ReportTask();
reportTask.setProject(TestUtils.project);
reportTask.setDataFile(datafile.getAbsolutePath());
reportTask.setFormat("xml");
reportTask.setDestDir(srcDir);
reportTask.execute();
System.out.println("Finish reporting task.");
Node dom = TestUtils.getXMLReportDOM(srcDir.getAbsolutePath()
+ "/coverage.xml");
int hitCount = TestUtils.getHitCount(dom, "mypackage.MyThreads",
"acall");
assertEquals("hit count incorrect", numberOfRetries * numberOfThreads
* numberOfCalls, hitCount);
}
public void compileSource(final File srcDir) {
System.out.println("Invoking groovyC command on "
+ srcDir.getAbsolutePath());
Javac javac = new Javac();
javac.setDebug(true);
javac.setProject(TestUtils.project);
Groovyc groovyc = new Groovyc();
groovyc.setProject(TestUtils.project);
groovyc
.setSrcdir(new Path(TestUtils.project, srcDir.getAbsolutePath()));
groovyc.setDestdir(srcDir);
groovyc.addConfiguredJavac(javac);
groovyc.execute();
System.out.println("Finish invoking groovyC command.");
}
public void instrumentClasses(File srcDir, File datafile) {
System.out.println("Start instrumenting classes.");
FileSet fileset = new FileSet();
fileset.setDir(srcDir);
fileset.setIncludes("**/*.class");
InstrumentTask instrumentTask = new InstrumentTask();
instrumentTask.setProject(TestUtils.project);
instrumentTask.setDataFile(datafile.getAbsolutePath());
instrumentTask.setThreadsafeRigorous(true);
instrumentTask.addFileset(fileset);
instrumentTask.execute();
System.out.println("Finish instrumenting classes.");
}
}