package fr.inria.diversify.dspot;
import fr.inria.diversify.buildSystem.maven.MavenBuilder;
import fr.inria.diversify.diversification.InputProgram;
import fr.inria.diversify.logger.branch.CoverageReader;
import fr.inria.diversify.logger.branch.TestCoverage;
import fr.inria.diversify.processor.test.*;
import fr.inria.diversify.util.Log;
import fr.inria.diversify.util.LoggerUtils;
import org.apache.commons.io.FileUtils;
import spoon.reflect.declaration.*;
import spoon.reflect.reference.CtTypeReference;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
/**
* User: Simon
* Date: 28/04/15
* Time: 16:00
*/
public class TestAmplification {
//current amp test
//Todo rename
protected List<CtMethod> ampTests;
//original test + good amp test
protected Set<CtMethod> goodTest;
protected List<TestCoverage> ampCoverage;
protected MavenBuilder builder;
protected InputProgram inputProgram;
protected String outputDirectory;
protected boolean guavaTestlib = false;
protected String logger;
public TestAmplification(InputProgram inputProgram, MavenBuilder builder, String outputDirectory) {
this.inputProgram = inputProgram;
this.builder = builder;
this.outputDirectory = outputDirectory;
logger = "fr.inria.diversify.logger.logger";
}
protected void init() {
goodTest = new HashSet<>();
ampTests = new ArrayList<>();
}
public void amplification(CtClass classTest, int maxIteration) throws IOException, InterruptedException {
init();
int nbTest = getAllTest(classTest).size();
if(nbTest == 0) {
return;
}
Log.info("amplification of {} ({} test)", classTest.getQualifiedName(), nbTest);
int nbAmpTest = 0;
run(classTest);
deleteLogFile();
List<CtMethod> instruTests = initAmpTest(classTest);
for (int i = 0; i < maxIteration; i++) {
Log.info("iteration {}:", i);
Log.info("{} new tests generated", instruTests.size() - nbTest);
int status = run(classTest);
if(status < -1) {
break;
}
ampCoverage = loadCoverageInfo();
Collection<CtMethod> testToAmp = selectTestToAmp(nbTest);
Log.info("{} tests selected", testToAmp.size());
nbAmpTest += testToAmp.size();
if (testToAmp.isEmpty()) {
break;
}
removeTests(instruTests);
instruTests = makeAndWriteAmpTest(testToAmp);
}
Log.info("{} tests amplified", nbAmpTest);
removeTests(instruTests);
makeDSpotClassTest();
}
protected int run(CtClass classTest) throws InterruptedException {
String goals = "test -Dmaven.compiler.useIncrementalCompilation=false -Dmaven.main.skip=true -Dtest=";
if(classTest.getModifiers().contains(ModifierKind.ABSTRACT)) {
goals += inputProgram.getAllElement(CtClass.class).stream()
.map(elem -> (CtClass) elem)
.filter(cl -> !cl.getModifiers().contains(ModifierKind.ABSTRACT))
.filter(cl -> {
CtTypeReference superClass = cl.getSuperclass();
while (superClass != null && superClass.getDeclaration() != null) {
if (superClass.getDeclaration() == classTest) {
return true;
}
superClass = superClass.getSuperclass();
}
return false;
})
.map(CtClass::getQualifiedName)
.collect(Collectors.joining(","));
} else {
goals += classTest.getQualifiedName();
}
builder.runBuilder(new String[]{goals});
return builder.getStatus();
}
protected void removeTests(Collection<CtMethod> tests) {
tests.stream()
.forEach(test -> {
CtClass cl = (CtClass) test.getDeclaringType();
cl.removeMethod(test);
});
}
protected Collection<CtMethod> selectTestToAmp(int reduceThreshold) {
Map<CtMethod, Set<String>> select = new HashMap<>();
for (CtMethod ampTest : ampTests) {
for (TestCoverage tc : getTestCoverageFor(ampTest)) {
TestCoverage parentTc = getParentTestCoverageFor(tc);
if (parentTc != null && tc.containsAllBranch(parentTc) && !parentTc.containsAllBranch(tc)) {
Set<String> branches = tc.getCoveredBranch();
branches.removeAll(parentTc.getCoveredBranch());
select.put(ampTest, branches);
goodTest.add(ampTest);
break;
}
}
}
if(select.size() > reduceThreshold) {
return reduceSelectedTest(select);
} else {
return select.keySet();
}
}
protected Collection<CtMethod> reduceSelectedTest(Map<CtMethod, Set<String>> selected) {
Map<Set<String>, List<CtMethod>> map = selected.keySet().stream()
.collect(Collectors.groupingBy(mth -> selected.get(mth)));
List<Set<String>> sortedKey = map.keySet().stream()
.sorted((l1, l2) -> Integer.compare(l2.size(), l1.size()))
.collect(Collectors.toList());
Set<String> branches = sortedKey.stream()
.flatMap(list -> list.stream())
.collect(Collectors.toSet());
List<CtMethod> methods = new ArrayList<>();
while(!branches.isEmpty()) {
Set<String> key = sortedKey.remove(0);
branches.removeAll(key);
methods.add(map.get(key).stream().findAny().get());
}
return methods;
}
protected List<TestCoverage> getTestCoverageFor(CtMethod ampTest) {
String testName = ampTest.getSimpleName();
return ampCoverage.stream()
.filter(c -> c.getTestName().endsWith(testName))
.collect(Collectors.toList());
}
protected TestCoverage getParentTestCoverageFor(TestCoverage tc) {
String parentName = tc.getTestName().substring(0, tc.getTestName().lastIndexOf("_"));
return ampCoverage.stream()
.filter(c -> c.getTestName().endsWith(parentName))
.findFirst()
.orElse(null);
}
protected List<TestCoverage> loadCoverageInfo() throws IOException {
CoverageReader reader = new CoverageReader(outputDirectory+ "/log");
List<TestCoverage> result = reader.loadTest();
deleteLogFile();
return result;
}
protected void deleteLogFile() throws IOException {
File dir = new File(outputDirectory+ "/log");
for(File file : dir.listFiles()) {
if(!file.getName().equals("info")) {
FileUtils.forceDelete(file);
}
}
}
protected List<CtMethod> initAmpTest(CtClass classTest) {
goodTest = getAllTest(classTest);
return makeAndWriteAmpTest(goodTest);
}
protected List<CtMethod> makeAndWriteAmpTest(Collection<CtMethod> tests) {
ampTests.clear();
removeTests(goodTest);
File out = new File(outputDirectory + "/" + inputProgram.getRelativeTestSourceCodeDir());
List<CtMethod> currentAmpTests = tests.stream()
.flatMap(test -> ampTests(test).stream())
.collect(Collectors.toList());
ampTests.addAll(currentAmpTests);
currentAmpTests.addAll(goodTest);
currentAmpTests.stream()
.forEach(test -> test.getDeclaringType().removeMethod(test));
List<CtClass> classesInstru = currentAmpTests.stream()
.map(ampTest -> instruMethod(ampTest))
.map(instruTest -> {
CtClass cl = (CtClass) instruTest.getDeclaringType();
cl.addMethod(instruTest);
return cl;
})
.distinct()
.collect(Collectors.toList());
classesInstru.stream()
.forEach(cl -> {
try {
LoggerUtils.printJavaFile(out, cl);
} catch (Exception e) {
}
}
);
return currentAmpTests;
}
protected void makeDSpotClassTest() {
File out = new File(outputDirectory + "/" + inputProgram.getRelativeTestSourceCodeDir());
goodTest.stream()
// .map(ampTest -> instruMethod(ampTest))
.forEach(test -> test.getDeclaringType().addMethod(test));
goodTest.stream()
.map(test -> test.getDeclaringType())
.distinct()
.forEach(cl -> {
try {
LoggerUtils.printJavaFile(out, cl);
} catch (Exception e) {
}
}
);
}
protected List<CtMethod> ampTests(CtMethod test) {
List<CtMethod> methods = new ArrayList<>();
TestDataMutator dataMutator = new TestDataMutator();
dataMutator.setFactory(inputProgram.getFactory());
methods.addAll(dataMutator.apply(test));
TestMethodCallAdder methodAdd = new TestMethodCallAdder();
methodAdd.setFactory(inputProgram.getFactory());
methods.addAll(methodAdd.apply(test));
TestMethodCallRemover methodRemove = new TestMethodCallRemover();
methodRemove.setFactory(inputProgram.getFactory());
methods.addAll(methodRemove.apply(test));
return methods;
}
protected CtMethod instruMethod(CtMethod method) {
CtMethod clone = cloneMethod(method);
TestCaseProcessor testCase = new TestCaseProcessor(inputProgram.getAbsoluteTestSourceCodeDir(), false);
testCase.setLogger(logger + ".Logger");
testCase.setFactory(inputProgram.getFactory());
testCase.process(clone);
TestLoggingInstrumenter logging = new TestLoggingInstrumenter();
logging.setLogger(logger + ".Logger");
logging.setFactory(inputProgram.getFactory());
logging.process(clone);
return clone;
}
protected CtMethod cloneMethod(CtMethod method) {
CtMethod cloned_method = method.getFactory().Core().clone(method);
cloned_method.setParent(method.getParent());
CtAnnotation toRemove = cloned_method.getAnnotations().stream()
.filter(annotation -> annotation.toString().contains("Override"))
.findFirst().orElse(null);
if(toRemove != null) {
cloned_method.removeAnnotation(toRemove);
}
return cloned_method;
}
protected Set<CtMethod> getAllTest(CtClass classTest) {
return new ArrayList<CtMethod>(classTest.getMethods()).stream()
.filter(mth -> isTest(mth))
.collect(Collectors.toSet());
}
protected boolean isTest(CtMethod candidate) {
if(candidate.isImplicit()
|| !candidate.getModifiers().contains(ModifierKind.PUBLIC)
|| candidate.getBody() == null
|| candidate.getBody().getStatements().size() == 0) {
return false;
}
if(!candidate.getPosition().getFile().toString().contains(inputProgram.getRelativeTestSourceCodeDir())) {
return false;
}
if(!guavaTestlib) {
return candidate.getSimpleName().contains("test")
|| candidate.getAnnotations().stream()
.map(annotation -> annotation.toString())
.anyMatch(annotation -> annotation.startsWith("@org.junit.Test"));
} else {
return subClassOfAbstractTester((CtClass) candidate.getDeclaringType())
&& (candidate.getSimpleName().contains("test")
|| candidate.getAnnotations().stream()
.map(annotation -> annotation.toString())
.anyMatch(annotation -> annotation.startsWith("@org.junit.Test")));
}
}
protected boolean subClassOfAbstractTester(CtClass cl) {
try {
if (cl.getSimpleName().equals("AbstractTester")) {
return true;
} else {
if (cl.getSuperclass().getDeclaration() != null || !cl.getSuperclass().getSimpleName().equals("Object")) {
return subClassOfAbstractTester((CtClass) cl.getSuperclass().getDeclaration());
}
}
} catch (Exception e) {}
return false;
}
}