org.evosuite.continuous.persistency.StorageManager Maven / Gradle / Ivy
/**
* Copyright (C) 2010-2016 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 .
*/
package org.evosuite.continuous.persistency;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.math.BigInteger;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.*;
import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.evosuite.Properties;
import org.evosuite.continuous.project.ProjectStaticData;
import org.evosuite.utils.ArrayUtil;
import org.evosuite.utils.FileIOUtils;
import org.evosuite.utils.LoggingUtils;
import org.evosuite.xsd.CUT;
import org.evosuite.xsd.CUTUtil;
import org.evosuite.xsd.Coverage;
import org.evosuite.xsd.Generation;
import org.evosuite.xsd.GenerationUtil;
import org.evosuite.xsd.Project;
import org.evosuite.xsd.ProjectUtil;
import org.evosuite.xsd.TestSuite;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import au.com.bytecode.opencsv.CSVReader;
/**
* Class used to store all CTG info on disk
*
* @author arcuri
*
*/
public class StorageManager {
private static Logger logger = LoggerFactory.getLogger(StorageManager.class);
private static final String TMP_PREFIX = "tmp_";
private File tmpLogs = null;
private File tmpReports = null;
private File tmpTests = null;
private File tmpPools = null;
private File tmpSeeds = null;
private boolean isStorageOk = false;
private DecimalFormat df = null;
public StorageManager() {
this.isStorageOk = this.openForWriting();
this.df = (DecimalFormat) NumberFormat.getNumberInstance(Locale.ENGLISH);
this.df.applyPattern("#0.00");
}
/**
* Open connection to Storage Manager
* Note: Here we just make sure we can write on disk
*
* @return
*/
private boolean openForWriting() {
File root = new File(Properties.CTG_DIR);
if(root.exists()){
if(root.isDirectory()){
if(!root.canWrite()){
logger.error("Cannot write in "+root.getAbsolutePath());
return false;
}
} else {
//it exists but not a folder...
boolean deleted = root.delete();
if(!deleted){
logger.error("Folder "+root+" is a file, and failed to delete it");
return false;
} else {
if(!root.mkdirs()){
logger.error("Failed to mkdir "+root.getAbsolutePath());
return false;
}
}
}
} else {
if(!root.mkdirs()){
logger.error("Failed to mkdir "+root.getAbsolutePath());
return false;
}
}
File testsFolder = getBestTestFolder();
if(!testsFolder.exists()){
if(!testsFolder.mkdirs()){
logger.error("Failed to mkdir "+testsFolder.getAbsolutePath());
return false;
}
}
File seedFolder = getSeedInFolder();
if(!seedFolder.exists()){
if(!seedFolder.mkdirs()){
logger.error("Failed to mkdir "+seedFolder.getAbsolutePath());
}
}
return true;
}
public static File getBestTestFolder(){
return getBestTestFolder(null);
}
public static File getBestTestFolder(File baseDir){
String base = "";
if(baseDir != null){
base = baseDir.getAbsolutePath() + File.separator;
}
return new File(base + Properties.CTG_DIR +
File.separator + Properties.CTG_BESTS_DIR_NAME);
}
public static File getSeedInFolder(){
return new File(new File(Properties.CTG_DIR),"evosuite-"+Properties.CTG_SEEDS_DIR_NAME);
}
/**
* Create a new tmp folder for this CTG session
*
* @return
*/
public boolean createNewTmpFolders() {
if (!this.isStorageOk) {
return false;
}
String time = DateFormatUtils.format(new Date(), "yyyy_MM_dd_HH_mm_ss", Locale.getDefault());
File tmp = null;
if (Properties.CTG_GENERATION_DIR_PREFIX == null)
tmp = new File(Properties.CTG_DIR + File.separator + TMP_PREFIX + time);
else
tmp = new File(Properties.CTG_DIR + File.separator + TMP_PREFIX + Properties.CTG_GENERATION_DIR_PREFIX + "_" + time);
if (!tmp.mkdirs())
return false;
// if we created the "tmp" folder or already exists, then it should be fine to create new folders in it
this.tmpLogs = new File(tmp.getAbsolutePath() + File.separator + Properties.CTG_TMP_LOGS_DIR_NAME);
if (!this.tmpLogs.exists() && !this.tmpLogs.mkdirs()) {
return false;
}
this.tmpReports = new File(tmp.getAbsolutePath() + File.separator + Properties.CTG_TMP_REPORTS_DIR_NAME);
if (!this.tmpReports.exists() && !this.tmpReports.mkdirs()) {
return false;
}
this.tmpTests = new File(tmp.getAbsolutePath() + File.separator + Properties.CTG_TMP_TESTS_DIR_NAME);
if (!this.tmpTests.exists() && !this.tmpTests.mkdirs()) {
return false;
}
this.tmpPools = new File(tmp.getAbsolutePath() + File.separator + Properties.CTG_TMP_POOLS_DIR_NAME);
if (!this.tmpPools.exists() && !this.tmpPools.mkdirs()) {
return false;
}
this.tmpSeeds = new File(tmp.getAbsolutePath() + File.separator + Properties.CTG_SEEDS_DIR_NAME);
if (!this.tmpSeeds.exists() && !this.tmpSeeds.mkdirs()) {
return false;
}
return true;
}
public void deleteAllOldTmpFolders(){
File root = new File(Properties.CTG_DIR);
for(File child : root.listFiles()){
if(!child.isDirectory()){
continue;
}
if(child.getName().startsWith(TMP_PREFIX)){
try {
FileUtils.deleteDirectory(child);
} catch (IOException e) {
logger.error("Failed to delete tmp folder "+child.getAbsolutePath());
}
}
}
}
/**
* Delete all CTG files
* @return
*/
public boolean clean(){
try {
FileUtils.deleteDirectory(new File(Properties.CTG_DIR));
} catch (IOException e) {
logger.error("Cannot delete folder "+Properties.CTG_DIR+": "+e,e);
return false;
}
return true;
}
public static class TestsOnDisk{
public final File testSuite;
public final String cut;
public final CsvJUnitData csvData;
public final File serializedSuite;
public TestsOnDisk(File testSuite, CsvJUnitData csvData, File serializedSuite) {
super();
this.testSuite = testSuite;
this.csvData = csvData;
this.cut = csvData.getTargetClass();
this.serializedSuite = serializedSuite; //this might be null
}
public boolean isValid(){
return testSuite!=null && testSuite.exists() &&
cut!=null && !cut.isEmpty() &&
csvData!=null &&
cut.equals(csvData.getTargetClass()) &&
(serializedSuite==null || serializedSuite.getName().endsWith(Properties.CTG_SEEDS_EXT))
;
}
}
/**
* Compare the results of this CTG run with what was in
* the database. Keep/update the best results.
*
* @param
* @return
*/
public String mergeAndCommitChanges(ProjectStaticData current, String[] cuts) throws NullPointerException{
if(current == null){
throw new NullPointerException("ProjectStaticData 'current' cannot be null");
}
Project db = StorageManager.getDatabaseProject();
String info = "\n\n=== CTG run results ===\n";
info += removeNoMoreExistentData(db, current);
List suites = gatherGeneratedTestsOnDisk();
info += "\nNew test suites: " + suites.size();
// identify for which CUTs we failed to generate tests
Set missingCUTs = new LinkedHashSet();
db.setTotalNumberOfTestableClasses(BigInteger.valueOf(current.getTotalNumberOfTestableCUTs()));
for (String cut : current.getClassNames()) {
if (!current.getClassInfo(cut).isTestable()) {
// if a class is not testable, we don't need to update any database
// of that class. and not even counting it as a missing class
continue ;
}
TestsOnDisk suite = suites.parallelStream().filter(s -> s.cut.equals(cut)).findFirst().orElse(null);
if (suite == null && current.getClassInfo(cut).isToTest()) {
missingCUTs.add(cut);
}
LoggingUtils.getEvoLogger().info("* Updating database to " + cut);
updateDatabase(cut, suite, db, current);
}
/*
* Print out what class(es) EvoSuite failed to generate
* test cases in this CTG run
*/
if (!missingCUTs.isEmpty()) {
if (missingCUTs.size() == 1) {
info += "\n\nWARN: failed to generate tests for " + missingCUTs.iterator().next();
} else {
info += "\n\nMissing classes:";
for (String missingCUT : missingCUTs) {
info += "\n" + missingCUT;
}
String summary = "\n\nWARN: failed to generate tests for " + missingCUTs.size() + " classes out of " + current.getTotalNumberOfTestableCUTs();
info += summary;
}
}
commitDatabase(db);
return info;
}
/**
* Not only we need the generated JUnit files, but also the statistics
* on their execution.
* Note: in theory we could re-execute the test cases to extract/recalculate
* those statistics, but it would be pretty inefficient
*
* @return a List containing all info regarding generated tests in the last CTG run
*/
public List gatherGeneratedTestsOnDisk(){
List list = new LinkedList();
List generatedTests = FileIOUtils.getRecursivelyAllFilesInAllSubfolders(tmpTests.getAbsolutePath(), ".java");
List generatedReports = FileIOUtils.getRecursivelyAllFilesInAllSubfolders(tmpReports.getAbsolutePath(), ".csv");
List generatedSerialized = FileIOUtils.getRecursivelyAllFilesInAllSubfolders(tmpSeeds.getAbsolutePath(), Properties.CTG_SEEDS_EXT);
/*
* Key -> name of CUT
* Value -> data extracted from CSV file
*
* We use a map, otherwise we could have 2 inner loops going potentially on thousands
* of classes, ie, O(n^2) complexity
*/
Map reports = new LinkedHashMap<>();
for(File file : generatedReports){
CsvJUnitData data = CsvJUnitData.openFile(file);
if(data==null){
logger.warn("Cannot process "+file.getAbsolutePath());
} else {
reports.put(data.getTargetClass(), data);
}
}
/*
* Key -> class name of CUT
* Value -> file location of serialized test suite
*/
Map seeds = new LinkedHashMap<>();
for(File file : generatedSerialized){
//this assumes that seed files are in the form cutName.seed
String cut = file.getName().substring(0 , file.getName().length() - (Properties.CTG_SEEDS_EXT.length() + 1));
seeds.put(cut,file);
}
/*
* Try to extract info for each generated JUnit test suite
*/
for(File test : generatedTests){
if (test.getAbsolutePath().contains(Properties.SCAFFOLDING_SUFFIX)) {
continue ;
}
String testName = extractClassName(tmpTests,test);
String cut = "";
for(String className : reports.keySet()){
/*
* This is tricky. We cannot be 100% what is going to be appended to the
* class name to form the test name, although the class name should still
* be a prefix. We need to check for the longest prefix as to avoid cases like
*
* org.Foo
* org.Foo2
*/
if(testName.startsWith(className) && className.length() > cut.length()){
cut = className;
}
}
//String cut = testName.substring(0, testName.indexOf(junitSuffix)); //This does not work, eg cases like _N_suffix
CsvJUnitData data = reports.get(cut);
if(data==null){
logger.warn("No CSV file for CUT "+cut+" with test suite at "+test.getAbsolutePath());
continue;
}
File seed = seeds.get(cut);
if(seed == null){
logger.warn("No '"+Properties.CTG_SEEDS_EXT+"' file was generated for CUT "+cut);
//do not skip, as this might happen if custom factory (ie no archive) was used for some experiments
}
TestsOnDisk info = new TestsOnDisk(test, data, seed);
if(info.isValid()){
list.add(info);
} else {
logger.warn("Invalid info for "+test.getAbsolutePath());
}
}
return list;
}
/**
* Example:
* base = /some/where/in/file/system
* target = /some/where/in/file/system/com/name/of/a/package/AClass.java
*
* We want "com.name.of.a.package.AClass" as a result
*
*/
protected String extractClassName(File base, File target){
int len = base.getAbsolutePath().length();
String path = target.getAbsolutePath();
String name = path.substring(len+1,path.length()-".java".length());
/*
* Using File.separator seems to give problems in Windows, because "\\" is treated specially
* by the replaceAll method
*/
name = name.replaceAll("/",".");
if(name.contains("\\")){
name = name.replaceAll("\\\\",".");
}
return name;
}
private void commitDatabase(Project db) {
StringWriter writer = null;
try{
writer = new StringWriter();
JAXBContext context = JAXBContext.newInstance(Project.class);
Marshaller m = context.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); // TODO remove me!
m.marshal(db, writer);
} catch(Exception e){
logger.error("Failed to create XML representation: "+e.getMessage(),e);
}
/*
* TODO: to be safe, we should first write to tmp file, delete original, and then
* rename the tmp
*/
File current = getProjectInfoFile();
current.delete();
try {
FileUtils.write(current, writer.toString());
} catch (IOException e) {
logger.error("Failed to write to database: "+e.getMessage(),e);
}
}
private static File getProjectInfoFile(){
return new File(Properties.CTG_DIR + File.separator + Properties.CTG_PROJECT_INFO);
}
/**
* Not only modify the state of db
, but
* also copy/replace new test cases on file disk
*
* @param ondisk
* @param db
*/
private void updateDatabase(String targetClass, TestsOnDisk ondisk, Project db, ProjectStaticData current) {
String testName = targetClass + Properties.JUNIT_SUFFIX; //extractClassName(tmpTests, ondisk.testSuite);
// CUT data
CUT cut = ProjectUtil.getCUT(db, targetClass);
if (cut == null) {
// first generation
cut = new CUT();
cut.setFullNameOfTargetClass(targetClass);
cut.setFullNameOfTestSuite(testName);
db.getCut().add(cut);
}
// Generation data
Generation generation = new Generation();
generation.setId(BigInteger.valueOf(cut.getGeneration().size()));
generation.setFailed(false); // by default
generation.setModified(current.getClassInfo(targetClass).hasChanged());
generation.setTimeBudgetInSeconds(BigInteger.valueOf(current.getClassInfo(targetClass).getTimeBudgetInSeconds()));
generation.setMemoryInMB(BigInteger.valueOf(current.getClassInfo(targetClass).getMemoryInMB()));
if (!current.getClassInfo(targetClass).isToTest()) {
// if a class was not considered for testing purpose,
// we still want to keep some information about it.
// that information will be crucial to, for example,
// determine how much time EvoSuite spent over all classes
cut.getGeneration().add(generation);
return ; // we do not have more information, so return
}
File std_err_CLIENT = new File(this.tmpLogs + File.separator + targetClass
+ File.separator + "std_err_CLIENT.log");
assert std_err_CLIENT.exists();
File std_out_CLIENT = new File(this.tmpLogs + File.separator + targetClass
+ File.separator + "std_out_CLIENT.log");
assert std_out_CLIENT.exists();
File std_err_MASTER = new File(this.tmpLogs + File.separator + targetClass
+ File.separator + "std_err_MASTER.log");
assert std_err_MASTER.exists();
File std_out_MASTER = new File(this.tmpLogs + File.separator + targetClass
+ File.separator + "std_out_MASTER.log");
assert std_out_MASTER.exists();
generation.setStdErrCLIENT(std_err_CLIENT.getAbsolutePath());
generation.setStdOutCLIENT(std_out_CLIENT.getAbsolutePath());
generation.setStdErrMASTER(std_err_MASTER.getAbsolutePath());
generation.setStdOutMASTER(std_out_MASTER.getAbsolutePath());
cut.getGeneration().add(generation);
if (ondisk == null) {
// EvoSuite failed to generate any test case for 'targetClass'.
// was it supposed to happen?
if (current.getClassInfo(targetClass).isToTest()) {
// it should have generated test cases
generation.setFailed(true);
/*
* TODO to properly update failure data, we will first need
* to change how we output such info in EvoSuite (likely
* we will need something more than statistics.csv)
*/
}
return;
}
assert ondisk.isValid();
CsvJUnitData csv = ondisk.csvData;
if (!isBetterThanAnyExistingTestSuite(db, current, ondisk)) {
// if the new test suite is not better than any other
// test suite (manually written or generated), we don't
// accept the new test suite and we just keep information
// about EvoSuite execution.
return;
}
// Test Suite data
TestSuite suite = new TestSuite();
suite.setFullPathOfTestSuite(ondisk.testSuite.getAbsolutePath());
suite.setNumberOfTests(BigInteger.valueOf(csv.getNumberOfTests()));
suite.setTotalNumberOfStatements(BigInteger.valueOf(csv.getTotalNumberOfStatements()));
suite.setTotalEffortInSeconds(BigInteger.valueOf(csv.getDurationInSeconds()));
List coverageValues = new ArrayList();
for (String criterion : csv.getCoverageVariables()) {
Coverage coverage = new Coverage();
coverage.setCriterion(criterion);
coverage.setCoverageValue(Double.parseDouble(this.df.format(csv.getCoverage(criterion))));
coverage.setCoverageBitString(csv.getCoverageBitString(criterion));
coverageValues.add(coverage);
}
suite.getCoverage().addAll(coverageValues);
generation.setSuite(suite);
/*
* So far we have modified only the content of db.
* Need also to update the actual test cases
*/
removeBestTestSuite(testName);
addBestTestSuite(ondisk.testSuite);
File scaffolding = getScaffoldingIfExists(ondisk.testSuite);
if (scaffolding != null) {
addBestTestSuite(scaffolding);
}
if (ondisk.serializedSuite != null) {
File target = new File(getSeedInFolder(), ondisk.serializedSuite.getName());
target.delete();
try {
FileUtils.copyFile(ondisk.serializedSuite, target);
} catch (IOException e) {
logger.error("Failed to copy over a new generated serialized test suite: "+e.getMessage(),e);
}
}
}
private File getScaffoldingIfExists(File testSuite) throws IllegalArgumentException{
String java = ".java";
if(testSuite==null || !testSuite.exists() || !testSuite.getName().endsWith(java)){
throw new IllegalArgumentException("Invalid test suite: "+testSuite);
}
String name = testSuite.getName();
String scaffoldingName = name.substring(0, name.length() - java.length()); //remove .java at the end
scaffoldingName += "_"+Properties.SCAFFOLDING_SUFFIX;
scaffoldingName += java;
File scaffolding = new File(testSuite.getParentFile().getAbsolutePath() + File.separator + scaffoldingName);
if(scaffolding.exists()){
return scaffolding;
} else {
return null;
}
}
/**
* From the test suites generated in the last CTG run, add the given
* one to the current best set
*
* @param newlyGeneratedTestSuite
*/
private void addBestTestSuite(File newlyGeneratedTestSuite) {
String testName = extractClassName(tmpTests,newlyGeneratedTestSuite);
String path = testName.replace(".", File.separator) + ".java";
File file = new File(getBestTestFolder() + File.separator + path);
file.delete(); //the following copy does not overwrite
try {
FileUtils.copyFile(newlyGeneratedTestSuite, file);
} catch (IOException e) {
logger.error("Failed to copy new generated test suite into the current best set: "+e.getMessage(),e);
}
}
/**
* Before accepting a new generated test suite, this function
* checks if it improves coverage of any existing test suite.
* The coverage of any existing test suite can be obtained
* using mvn evosuite:coverage, which creates a evosuite-report/statistics.csv
* file with code coverage. This function first verifies whether
* it a new class (or a class that has been modified). Note that
* by default and to be compatible with all Schedules, we consider
* that a class has always been modified, unless HistorySchedule
* says different. Then it checks if the new generated test suite
* has better coverage (or if it covers different goals). If yes,
* it returns true (and the generated test suite is accepted),
* false otherwise.
*
* @param db
* @param current
* @param suite
* @return true is the generated test suite is better (in terms of
* coverage) than any existing test suite, false otherwise
*/
private boolean isBetterThanAnyExistingTestSuite(Project db, ProjectStaticData current, TestsOnDisk suite) {
if (suite.csvData == null) {
// no data available
return false;
}
// first check if the class under test has been changed or if
// is a new class. if yes, accept the generated TestSuite
// (even without checking if the coverage has decreased)
// note: by default a class has been changed
if (current.getClassInfo(suite.cut).hasChanged()) {
return true;
}
// load evosuite-report/statistics.csv which contains
// the coverage of each existing test suite
String statistics = Properties.REPORT_DIR + File.separator + "statistics.csv";
File statistics_file = new File(statistics);
if (!statistics_file.exists()) {
// this could happen if file was manually removed
// or if is a project without test cases. before giving
// up, let's check if it's better than any previous generated
// test suite
return isBetterThanPreviousGeneration(db, current, suite);
}
List rows = null;
try {
CSVReader reader = new CSVReader(new FileReader(statistics_file));
rows = reader.readAll();
reader.close();
} catch (IOException e) {
logger.error(e.getMessage());
return true;
}
// select the row of the Class Under Test
List rowCUT = new ArrayList();
rowCUT.add(rows.get(0)); // add header (i.e., column names)
for (String[] row : rows) {
if (ArrayUtil.contains(row, suite.cut)) {
rowCUT.add(row);
break ;
}
}
if (rowCUT.size() == 1) {
// this could happen if the data of the Class Under
// Test was manually removed, or if during the execution
// of measureCoverage option something wrong happened.
// if so, try to compare with a previous generated one
return isBetterThanPreviousGeneration(db, current, suite);
}
// is the OverallCoverage higher?
double existingOverallCoverage = 0.0;
double generatedOverallCoverage = 0.0;
for (String variable : suite.csvData.getCoverageVariables()) {
String coverageVariable = CsvJUnitData.getValue(rowCUT, variable);
if (coverageVariable == null) {
continue ;
}
generatedOverallCoverage += suite.csvData.getCoverage(variable);
existingOverallCoverage += Double.valueOf(coverageVariable);
}
// average
generatedOverallCoverage /= suite.csvData.getNumberOfCoverageValues();
existingOverallCoverage /= suite.csvData.getNumberOfCoverageValues();
double covDif = generatedOverallCoverage - existingOverallCoverage;
// this check is to avoid issues with double truncation
if (covDif > 0.0001) {
return true;
}
// coverage seems to be either the same or lower. does the generated
// test suite cover different goals? we accept the generate TestSuite
// if it covers at least one goal not covered by the previous test suite
for (String variable : suite.csvData.getCoverageBitStringVariables()) {
String existingCoverage = CsvJUnitData.getValue(rowCUT, variable);
if (existingCoverage == null) {
continue ;
}
String generatedCoverage = suite.csvData.getCoverageBitString(variable);
if (generatedCoverage.length() != existingCoverage.length()) {
// accept the new suite, as we can't compare both BitStrings
return true;
}
for (int i = 0; i < generatedCoverage.length(); i++) {
if (existingCoverage.charAt(i) == '0' && generatedCoverage.charAt(i) == '1') {
return true;
}
}
}
return false;
}
/**
* Before accepting the new test suite this function verifies
* whether it is better (in terms of coverage) than a previous
* test generation. It first checks whether it is a class that
* has been modified. By default we consider that a class has
* always been changed. Only HistorySchedule will change that
* behavior. So, for all Schedules except History we accept
* the new generated test suite. For HistorySchedule, and if a
* class has not been changed it then checks if the new test
* suite improves the coverage of the previous one.
*
* @param db
* @param current
* @param suite
* @return true if the generated test suite is better (in terms of
* coverage) than a previous generated test suite, false otherwise
*/
private boolean isBetterThanPreviousGeneration(Project db, ProjectStaticData current, TestsOnDisk suite) {
if (suite.csvData == null) {
// no data available
return false;
}
// first check if the class under test has been changed or if
// is a new class. if yes, accept the generated TestSuite
// (even without checking if the coverage has increased/decreased)
// note: by default we consider that a class has been changed,
// only HistorySchedule change this behavior
if (current.getClassInfo(suite.cut).hasChanged()) {
return true;
}
CUT cut = ProjectUtil.getCUT(db, suite.cut);
Generation latestSuccessfulGeneration = CUTUtil.getLatestSuccessfulGeneration(cut);
if (latestSuccessfulGeneration == null) {
return true;
}
TestSuite previousTestSuite = latestSuccessfulGeneration.getSuite();
File oldFile = getFileForTargetBestTest(cut.getFullNameOfTestSuite());
if (!oldFile.exists()) {
// this could happen if file was manually removed
return true;
}
// is the OverallCoverage higher?
double previousOverallCoverage = GenerationUtil.getOverallCoverage(latestSuccessfulGeneration);
double generatedOverallCoverage = 0.0;
// first, check if the coverage of at least one criterion is better
for (Coverage coverage : previousTestSuite.getCoverage()) {
if (!suite.csvData.hasCoverage(coverage.getCriterion())) {
continue ;
}
generatedOverallCoverage += suite.csvData.getCoverage(coverage.getCriterion());
}
generatedOverallCoverage /= suite.csvData.getNumberOfCoverageValues();
double covDif = generatedOverallCoverage - previousOverallCoverage;
if (covDif > 0.01) {
// this check is to avoid issues with double truncation
// by default, the coverage values in the project_info.xml
// just has two decimal digits
return true;
}
// seems we got the same coverage or lower, what about goals covered?
// if the new test generation is covering other goals, accept it, as
// developers could be interested on that particular goal(s)
for (Coverage coverage : previousTestSuite.getCoverage()) {
if (!suite.csvData.hasCoverage(coverage.getCriterion())) {
continue ;
}
String generatedCoverage = suite.csvData.getCoverageBitString(coverage.getCriterion());
String previousCoverage = coverage.getCoverageBitString();
if (generatedCoverage.length() != previousCoverage.length()) {
// accept the new suite, as we can't compare both BitStrings
return true;
}
for (int i = 0; i < generatedCoverage.length(); i++) {
if (previousCoverage.charAt(i) == '0' && generatedCoverage.charAt(i) == '1') {
return true;
}
}
}
if (covDif < 0.0) {
// a negative difference means that the previous coverage
// was higher, therefore discard the new test suite
return false;
}
// if we got same coverage, look at size
int oldSize = previousTestSuite.getTotalNumberOfStatements().intValue();
int newSize = suite.csvData.getTotalNumberOfStatements();
if (newSize != oldSize) {
return newSize < oldSize;
}
// same number of statements, look the number of test cases
int oldNumTests = previousTestSuite.getNumberOfTests().intValue();
int newNumTests = suite.csvData.getNumberOfTests();
return newNumTests < oldNumTests;
}
/**
* Some classes could had been removed/renamed.
* So just delete all info regarding them
*
* @param
*/
private String removeNoMoreExistentData(Project db,
ProjectStaticData current) {
int removed = 0;
Iterator iter = db.getCut().iterator();
while(iter.hasNext()){
CUT cut = iter.next();
String cutName = cut.getFullNameOfTargetClass();
if(! current.containsClass(cutName)){
iter.remove();
removeBestTestSuite(cut.getFullNameOfTestSuite());
removed++;
}
}
return "Removed test suites: "+removed;
}
/**
* Remove the given test suite
*
* @param
*/
private void removeBestTestSuite(String testName) {
File file = getFileForTargetBestTest(testName);
if(!file.exists()){
logger.debug("Nothing to delete, as following file does not exist: "+file.getAbsolutePath());
} else {
boolean deleted = file.delete();
if(!deleted){
logger.warn("Failed to delete "+file.getAbsolutePath());
}
}
}
private File getFileForTargetBestTest(String testName) {
String path = testName.replace(".", File.separator);
path += ".java";
return new File(getBestTestFolder() + File.separator + path);
}
/**
* Get current representation of the test cases in the database
*
* @return
*/
public static Project getDatabaseProject() {
File current = getProjectInfoFile();
InputStream stream = null;
if(!current.exists()){
stream = getDefaultXmlStream();
return getProject(current, stream);
} else {
try {
stream = getCurrentXmlStream(current);
return getProject(current, stream);
} catch(Exception e){
//this could happen if it was an old file, and EvoSuite did not have a proper backward compatibility
stream = getDefaultXmlStream();
return getProject(current, stream);
}
}
}
private static InputStream getCurrentXmlStream(File current) {
InputStream stream;
try {
stream = new FileInputStream(current);
} catch (FileNotFoundException e) {
assert false; // this should never happen
throw new RuntimeException("Bug in EvoSuite framework: "+e.getMessage());
}
return stream;
}
private static InputStream getDefaultXmlStream() {
InputStream stream;/*
* this will happen the first time CTG is run
*/
String empty = "/xsd/ctg_project_report_empty.xml";
try {
stream = StorageManager.class.getResourceAsStream(empty);
} catch (Exception e) {
throw new RuntimeException("Failed to read resource "+empty+" , "+e.getMessage());
}
return stream;
}
private static Project getProject(File current, InputStream stream) {
try{
JAXBContext jaxbContext = JAXBContext.newInstance(Project.class);
SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
Schema schema = factory.newSchema(new StreamSource(StorageManager.class.getResourceAsStream("/xsd/ctg_project_report.xsd")));
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
jaxbUnmarshaller.setSchema(schema);
return (Project) jaxbUnmarshaller.unmarshal(stream);
} catch(Exception e){
String msg = "Error in reading "+current.getAbsolutePath()+" , "+e;
logger.error(msg,e);
throw new RuntimeException(msg);
}
}
public File getTmpLogs() {
return tmpLogs;
}
public File getTmpReports() {
return tmpReports;
}
public File getTmpTests() {
return tmpTests;
}
public File getTmpPools() {
return tmpPools;
}
public File getTmpSeeds() {
return tmpSeeds;
}
public boolean isStorageOk() {
return this.isStorageOk;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy