/**
* Copyright (C) 2010-2017 Gordon Fraser, Andrea Arcuri and EvoSuite
* contributors
*
* This file is part of EvoSuite.
*
* EvoSuite is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3.0 of the License, or
* (at your option) any later version.
*
* EvoSuite is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with EvoSuite. If not, see <http://www.gnu.org/licenses/>.
*/
package org.evosuite.testcarver.codegen;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.tools.*;
import org.eclipse.jdt.internal.compiler.tool.EclipseCompiler;
import org.evosuite.testcarver.capture.CaptureLog;
import org.evosuite.testcarver.configuration.Configuration;
import org.evosuite.testcarver.exception.CapturerException;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.InitializationError;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import gnu.trove.list.array.TIntArrayList;
public final class PostProcessor {
private static final Logger logger = LoggerFactory.getLogger(PostProcessor.class);
private static TIntArrayList failedRecords;
private static int recentLogRecNo;
private PostProcessor() {}
public static void init()
{
failedRecords = new TIntArrayList();
recentLogRecNo = -1;
}
public static void notifyRecentlyProcessedLogRecNo(final int logRecNo)
{
recentLogRecNo = logRecNo;
}
public static void captureException(final int logRecNo)
{
failedRecords.add(logRecNo);
}
private static void writeTest(final CaptureLog log, final String packageName, final String testName, final Class<?>[] observedClasses, final File targetFile, final boolean postprocessing)
{
// TODO parallelize
FileOutputStream fout = null;
try
{
fout = new FileOutputStream(targetFile);
// final CodeGenerator generator = new CodeGenerator(log);
final ICaptureLogAnalyzer analyzer = new CaptureLogAnalyzer();
final JUnitCodeGenerator codeGen = new JUnitCodeGenerator(testName, packageName);
final String code;
if(postprocessing)
{
codeGen.enablePostProcessingCodeGeneration();
analyzer.analyze(log, codeGen, observedClasses);
code = codeGen.getCode().toString();
// code = generator.generateCodeForPostProcessing(testName, packageName, observedClasses).toString();
}
else
{
// if recent log recno is contained in failed records,
// remove it as the very last statement has to throw an exception, if it threw one in
// the original program run
failedRecords.remove(recentLogRecNo)
;
codeGen.disablePostProcessingCodeGeneration(failedRecords);
analyzer.analyze(log, codeGen, observedClasses);
code = codeGen.getCode().toString();
// code = generator.generateFinalCode(testName, packageName, failedRecords, observedClasses).toString();
}
// TODO not nice but how can blank line be inserted with JDT?
fout.write(code.replaceAll("\\s+;", "\n").getBytes());
}
catch(final Exception e)
{
throw new CapturerException(e);
}
finally
{
try
{
if(fout != null)
{
fout.close();
}
}
catch (final IOException e)
{
throw new CapturerException(e);
}
}
}
/**
*
* @param logs logs of captured interaction
* @param packages package names associated with logs. Mapping logs.get(i) belongs to packages.get(i)
* @throws IOException
*/
public static void process(final List<CaptureLog> logs, final List<String> packages, final List<Class<?>[]> observedClasses) throws IOException
{
if(logs == null)
{
throw new NullPointerException("list of CaptureLogs must not be null");
}
if(packages == null)
{
throw new NullPointerException("list of package names associated with logs must not be null");
}
if(observedClasses == null)
{
throw new NullPointerException("list of classes to be observed must not be null");
}
final int size = logs.size();
if(packages.size() != size || observedClasses.size() != size)
{
throw new IllegalArgumentException("given lists must have same size");
}
// create post-processing sources in os specific temp folder
final File tempDir = new File(System.getProperty("java.io.tmpdir"),
"postprocessing_" + System.currentTimeMillis());
tempDir.mkdir();
CaptureLog log;
String packageName;
Class<?>[] classes;
String targetFolder;
File targetFile;
final StringBuilder testClassNameBuilder = new StringBuilder();
String testClassName;
for(int i = 0; i < size; i++)
{
//=============== prepare carved test for post-processing ================================================
packageName = packages.get(i);
targetFolder = packageName.replace(".", File.separator);
classes = observedClasses.get(i);
if(classes.length == 0)
{
throw new IllegalArgumentException("there must be at least one class to be observed");
}
// for(int j = 0; j < classes.length; j++)
// {
// testClassNameBuilder.append(classes[j].getSimpleName());
// if(testClassNameBuilder.length() >= 10)
// {
// break;
// }
// }
testClassNameBuilder.append("CarvedTest");
log = logs.get(i);
long s = System.currentTimeMillis();
logger.debug(">>>> (postprocess) start test create ");
targetFile = new File(tempDir, targetFolder);
targetFile.mkdirs();
testClassName = getFreeClassName(targetFile, testClassNameBuilder.toString());
targetFile = new File(targetFile, testClassName + ".java");
// write out test files containing post-processing statements
writeTest(log, packageName, testClassName, classes, targetFile, true);
logger.debug(">>>> (postprocess) end test creation -> " + (System.currentTimeMillis() - s) / 1000);
//=============== compile generated post-processing test ================================================
final JavaCompiler compiler = new EclipseCompiler();
// final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
final StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjectsFromFiles(Arrays.asList(new File[]{targetFile}));
//--- add modified bins (see Transformer and ClassPreparer.createPreparedBin()) to class path
String classPath = Configuration.INSTANCE.getProperty(Configuration.MODIFIED_BIN_LOC);
classPath += File.pathSeparator + System.getProperty("java.class.path");
final Boolean wasCompilationSuccess = compiler.getTask(null, fileManager, null, Arrays.asList(new String[]{"-cp", classPath}), null, compilationUnits).call();
if(! wasCompilationSuccess)
{
logger.error("Compilation was not not successful for " + targetFile);
fileManager.close();
continue;
}
fileManager.close();
//=============== execute + observe post-processing test run ================================================
final PostProcessorClassLoader cl = new PostProcessorClassLoader(tempDir);
final Class<?> testClass = cl.findClass(packageName + '.' + testClassName);
BlockJUnit4ClassRunner testRunner;
try
{
testRunner = new BlockJUnit4ClassRunner(testClass);
testRunner.run(new RunNotifier());
}
catch (InitializationError e)
{
logger.error(""+e,e);
}
//============== generate final test file ================================================================
final String targetDir = Configuration.INSTANCE.getProperty(Configuration.GEN_TESTS_LOC);
targetFile = new File(new File(targetDir), targetFolder);
targetFile.mkdirs();
testClassName = getFreeClassName(targetFile, testClassNameBuilder.toString());
targetFile = new File(targetFile, testClassName + ".java");
writeTest(log, packageName, testClassName, classes, targetFile, false);
// recycle StringBuilder for testClassName
testClassNameBuilder.setLength(0);
}
// clean up post-processing stuff
tempDir.delete();
}
private static String getFreeClassName(final File targetDir, final String className)
{
final String[] similarFileNames = targetDir.list(new FilenameFilter()
{
final Pattern PATTERN = Pattern.compile(className + "\\d*\\.java");
@Override
public boolean accept(final File dir, final String name)
{
final Matcher m = PATTERN.matcher(name);
return m.matches();
}
});
if(similarFileNames.length > 0)
{
return className + similarFileNames.length;
}
return className;
}
private static final class PostProcessorClassLoader extends ClassLoader
{
private final File baseDir;
public PostProcessorClassLoader(final File baseDir)
{
this.baseDir = baseDir;
}
@Override
public Class<?> findClass(final String name)
{
final String fileName = name.replace(".", File.separator);
final File targetFile = new File(this.baseDir, fileName + ".class");
try
{
if(! targetFile.exists())
{
return super.findClass(fileName);
}
final FileInputStream fin = new FileInputStream(targetFile);
final byte[] content = new byte[(int) targetFile.length()];
fin.read(content);
fin.close();
return super.defineClass(name, content, 0, content.length);
}
catch(Exception e)
{
throw new RuntimeException(e);
}
}
}
}