/** * 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.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 com.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<TestsOnDisk> suites = gatherGeneratedTestsOnDisk(); info += "\nNew test suites: " + suites.size(); // identify for which CUTs we failed to generate tests Set<String> missingCUTs = new LinkedHashSet<String>(); 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<TestsOnDisk> gatherGeneratedTestsOnDisk(){ List<TestsOnDisk> list = new LinkedList<TestsOnDisk>(); List<File> generatedTests = FileIOUtils.getRecursivelyAllFilesInAllSubfolders(tmpTests.getAbsolutePath(), ".java"); List<File> generatedReports = FileIOUtils.getRecursivelyAllFilesInAllSubfolders(tmpReports.getAbsolutePath(), ".csv"); List<File> 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<String,CsvJUnitData> 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<String,File> 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: </br> * base = /some/where/in/file/system </br> * target = /some/where/in/file/system/com/name/of/a/package/AClass.java </br> * </br> * 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 <code>db</code>, 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<Coverage> coverageValues = new ArrayList<Coverage>(); 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<String[]> 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<String[]> rowCUT = new ArrayList<String[]>(); 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<CUT> 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; } }