All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.evosuite.junit.JUnitAnalyzer Maven / Gradle / Ivy

The newest version!
/**
 * Copyright (C) 2010-2018 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.junit;

import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.*;

import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

import org.apache.commons.io.FileUtils;
import org.evosuite.Properties;
import org.evosuite.TestGenerationContext;
import org.evosuite.TimeController;
import org.evosuite.classpath.ClassPathHandler;
import org.evosuite.instrumentation.NonInstrumentingClassLoader;
import org.evosuite.junit.writer.TestSuiteWriter;
import org.evosuite.junit.writer.TestSuiteWriterUtils;
import org.evosuite.runtime.classhandling.JDKClassResetter;
import org.evosuite.runtime.sandbox.Sandbox;
import org.evosuite.runtime.util.JarPathing;
import org.evosuite.testcase.TestCase;
import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This class is used to check if a set of test cases are valid for JUnit: ie,
 * if they can be compiled, they do not fail, and if running them a second time
 * produces same result (ie not fail).
 * 
 * @author arcuri
 * 
 */
public class JUnitAnalyzer {

	private static Logger logger = LoggerFactory.getLogger(JUnitAnalyzer.class);

	private static int dirCounter = 0;

	private static final String JAVA = ".java";
	private static final String CLASS = ".class";

	
	private static NonInstrumentingClassLoader loader = new NonInstrumentingClassLoader();
	
	/**
	 * Try to compile each test separately, and remove the ones that cannot be
	 * compiled
	 * 
	 * @param tests
	 */
	public static void removeTestsThatDoNotCompile(List tests) {

		logger.info("Going to execute: removeTestsThatDoNotCompile");

		if (tests == null || tests.isEmpty()) { //nothing to do
			return;
		}

		Iterator iter = tests.iterator();

		while (iter.hasNext()) {
			if(!TimeController.getInstance().hasTimeToExecuteATestCase()) {
				break;
			}

			TestCase test = iter.next();

			File dir = createNewTmpDir();
			if (dir == null) {
				logger.warn("Failed to create tmp dir");
				return;
			}
			logger.debug("Created tmp folder: " + dir.getAbsolutePath());

			try {
				List singleList = new ArrayList();
				singleList.add(test);
				List generated = compileTests(singleList, dir);
				if (generated == null) {
					iter.remove();
					String code = test.toCode();
					logger.error("Failed to compile test case:\n" + code);
				}
			} finally {
				//let's be sure we clean up all what we wrote on disk
				if (dir != null) {
					try {
						FileUtils.deleteDirectory(dir);
						logger.debug("Deleted tmp folder: " + dir.getAbsolutePath());
					} catch (Exception e) {
						logger.error("Cannot delete tmp dir: " + dir.getAbsolutePath(), e);
					}
				}
			}

		} // end of while
	}

	/**
	 * Compile and run all the test cases, and mark as "unstable" all the ones
	 * that fail during execution (ie, unstable assertions).
	 * 
	 * 

* If a test fail due to an exception not related to a JUnit assertion, then * remove such test from the input list * * @param tests * @return the number of unstable tests */ public static int handleTestsThatAreUnstable(List tests) { int numUnstable = 0; logger.info("Going to execute: handleTestsThatAreUnstable"); if (tests == null || tests.isEmpty()) { //nothing to do return numUnstable; } File dir = createNewTmpDir(); if (dir == null) { logger.error("Failed to create tmp dir"); return numUnstable; } logger.debug("Created tmp folder: " + dir.getAbsolutePath()); try { List generated = compileTests(tests, dir); if (generated == null) { /* * Note: in theory this shouldn't really happen, as check for compilation * is done before calling this method */ logger.warn("Failed to compile the test cases "); return numUnstable; } if(!TimeController.getInstance().hasTimeToExecuteATestCase()) { logger.error("Ran out of time while checking tests"); return numUnstable; } // Create a new classloader so that each test gets freshly loaded classes loader = new NonInstrumentingClassLoader(); Class[] testClasses = loadTests(generated); if (testClasses == null) { logger.error("Found no classes for compiled tests"); return numUnstable; } JUnitResult result = runTests(testClasses, dir); if (result.wasSuccessful()) { return numUnstable; //everything is OK } failure_loop: for (JUnitFailure failure : result.getFailures()) { String testName = failure.getDescriptionMethodName();//TODO check if correct for (int i = 0; i < tests.size(); i++) { if (TestSuiteWriterUtils.getNameOfTest(tests, i).equals(testName)) { if (tests.get(i).isFailing()) { logger.info("Failure is expected, continuing..."); continue failure_loop; } } } if(testName == null){ /* * this can happen if there is a failure in the scaffolding (eg @AfterClass/@BeforeClass). * in such case, everything need to be deleted */ StringBuilder sb = new StringBuilder(); sb.append("Issue in scaffolding of the test suite: "+failure.getMessage()+"\n"); sb.append("Stack trace:\n"); for (String elem : failure.getExceptionStackTrace()) { sb.append(elem+"\n"); } logger.error(sb.toString()); numUnstable = tests.size(); tests.clear(); return numUnstable; } // On the Sheffield cluster, the "well-known fle is not secure" issue is impossible to understand, // so it might be best to ignore it for now. if(testName.equals("initializationError") && failure.getMessage().contains("Failed to attach Java Agent")) { logger.warn("Likely error with EvoSuite instrumentation, ignoring failure in test execution"); continue failure_loop; } logger.warn("Found unstable test named " + testName + " -> " + failure.getExceptionClassName() + ": " + failure.getMessage()); for (String elem : failure.getExceptionStackTrace()) { logger.info(elem); } boolean toRemove = !(failure.isAssertionError()); for (int i = 0; i < tests.size(); i++) { if (TestSuiteWriterUtils.getNameOfTest(tests, i).equals(testName)) { logger.warn("Failing test:\n " + tests.get(i).toCode()); numUnstable++; /* * we have a match. should we remove it or mark as unstable? * When we have an Assert.* failing, we can just comment out * all the assertions in the test case. If it is an "assert" * in the SUT that fails, we do want to have the JUnit test fail. * On the other hand, if a test fail due to an uncaught exception, * we should delete it, as it would either represent a bug in EvoSuite * or something we cannot (easily) fix here */ if (!toRemove) { logger.debug("Going to mark test as unstable: " + testName); tests.get(i).setUnstable(true); } else { logger.debug("Going to remove unstable test: " + testName); tests.remove(i); } break; } } } } catch (Exception e) { logger.error("" + e, e); return numUnstable; } finally { //let's be sure we clean up all what we wrote on disk if (dir != null) { try { FileUtils.deleteDirectory(dir); } catch (Exception e) { logger.warn("Cannot delete tmp dir: " + dir.getName(), e); } } } //if we arrive here, then it means at least one test was unstable return numUnstable; } private static JUnitResult runTests(Class[] testClasses, File testClassDir) throws JUnitExecutionException { return runJUnitOnCurrentProcess(testClasses); } private static JUnitResult runJUnitOnCurrentProcess(Class[] testClasses) { JUnitCore runner = new JUnitCore(); /* * Why deactivating the sandbox? This is pretty tricky. * The JUnitCore runner will execute the test cases on a new * thread, which might not be privileged. If the test cases need * the JavaAgent, then they will fail due to the sandbox :( * Note: if the test cases need a sandbox, they will have code * to do that by their self. When they do it, the initialization * will be after the agent is already loaded. */ boolean wasSandboxOn = Sandbox.isSecurityManagerInitialized(); Set privileged = null; if(wasSandboxOn){ privileged = Sandbox.resetDefaultSecurityManager(); } Result result = null; ClassLoader currentLoader = Thread.currentThread().getContextClassLoader(); try { TestGenerationContext.getInstance().goingToExecuteSUTCode(); Thread.currentThread().setContextClassLoader(testClasses[0].getClassLoader()); JDKClassResetter.reset(); //be sure we reset it here, otherwise "init" in the test case would take current changed state result = runner.run(testClasses); } finally { Thread.currentThread().setContextClassLoader(currentLoader); TestGenerationContext.getInstance().doneWithExecutingSUTCode(); } if(wasSandboxOn){ //only activate Sandbox if it was already active before if(!Sandbox.isSecurityManagerInitialized()) Sandbox.initializeSecurityManagerForSUT(privileged); } else { if(Sandbox.isSecurityManagerInitialized()){ logger.warn("EvoSuite problem: tests set up a security manager, but they do not remove it after execution"); Sandbox.resetDefaultSecurityManager(); } } JUnitResultBuilder builder = new JUnitResultBuilder(); JUnitResult junitResult = builder.build(result); return junitResult; } /** * Check if it is possible to use the Java compiler. * * @return */ public static boolean isJavaCompilerAvailable() { JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); return compiler != null; } // We have to have a unique name for this test suite as it is loaded by the // EvoSuite classloader, and thus cannot easily be re-loaded private static int NUM = 0; private static List compileTests(List tests, File dir) { TestSuiteWriter suite = new TestSuiteWriter(); suite.insertAllTests(tests); //to get name, remove all package before last '.' int beginIndex = Properties.TARGET_CLASS.lastIndexOf(".") + 1; String name = Properties.TARGET_CLASS.substring(beginIndex); name += "_" +(NUM++) + "_tmp_" + Properties.JUNIT_SUFFIX ; //postfix try { //now generate the JUnit test case List generated = suite.writeTestSuite(name, dir.getAbsolutePath(), Collections.EMPTY_LIST); for (File file : generated) { if (!file.exists()) { logger.error("Supposed to generate " + file + " but it does not exist"); return null; } } //try to compile the test cases JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); if (compiler == null) { logger.error("No Java compiler is available"); return null; } DiagnosticCollector diagnostics = new DiagnosticCollector(); Locale locale = Locale.getDefault(); Charset charset = Charset.forName("UTF-8"); StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, locale, charset); Iterable compilationUnits = fileManager.getJavaFileObjectsFromFiles(generated); List optionList = new ArrayList<>(); String evosuiteCP = ClassPathHandler.getInstance().getEvoSuiteClassPath(); if(JarPathing.containsAPathingJar(evosuiteCP)){ evosuiteCP = JarPathing.expandPathingJars(evosuiteCP); } String targetProjectCP = ClassPathHandler.getInstance().getTargetProjectClasspath(); if(JarPathing.containsAPathingJar(targetProjectCP)){ targetProjectCP = JarPathing.expandPathingJars(targetProjectCP); } String classpath = targetProjectCP + File.pathSeparator + evosuiteCP; optionList.addAll(Arrays.asList("-classpath", classpath)); CompilationTask task = compiler.getTask(null, fileManager, diagnostics, optionList, null, compilationUnits); boolean compiled = task.call(); fileManager.close(); if (!compiled) { logger.error("Compilation failed on compilation units: "+ compilationUnits); logger.error("Classpath: "+classpath); //TODO remove logger.error("evosuiteCP: "+evosuiteCP); for (Diagnostic diagnostic : diagnostics.getDiagnostics()) { if (diagnostic.getMessage(null).startsWith("error while writing")) { logger.error("Error is due to file permissions, ignoring..."); return generated; } logger.error("Diagnostic: " + diagnostic.getMessage(null) + ": " + diagnostic.getLineNumber()); } StringBuffer buffer = new StringBuffer(); for (JavaFileObject sourceFile : compilationUnits) { List lines = FileUtils.readLines(new File(sourceFile.toUri().getPath())); buffer.append(compilationUnits.iterator().next().toString()+"\n"); for (int i = 0; i < lines.size(); i++) { buffer.append((i + 1) + ": " + lines.get(i) +"\n"); } } logger.error(buffer.toString()); return null; } return generated; } catch (IOException e) { logger.error("" + e, e); return null; } } protected static File createNewTmpDir() { File dir = null; String dirName = FileUtils.getTempDirectoryPath() + File.separator + "EvoSuite_" + (dirCounter++) + "_" + +System.currentTimeMillis(); //first create a tmp folder dir = new File(dirName); if (!dir.mkdirs()) { logger.error("Cannot create tmp dir: " + dirName); return null; } if (!dir.exists()) { logger.error("Weird behavior: we created folder, but Java cannot determine if it exists? Folder: " + dirName); return null; } return dir; } private static Class[] loadTests(List tests) { /* * Ideally, when we run a generated test case, it * will automatically use JavaAgent to instrument the CUT. * But here we have already loaded the CUT by now, so that * mechanism will not work. * * A simple option is to just use an instrumenting class loader, * as it does exactly the same type of instrumentation. * But a better idea would be to use a new * non-instrumenting classloader to re-load the CUT, and so see * if the JavaAgent works properly. */ Class[] testClasses = getClassesFromFiles(tests); List otherClasses = listOnlyFiles(tests); /* * this is important to force the loading of all files generated * in the target folder. * If we do not do that, then we will miss all the anonymous classes */ getClassesFromFiles(otherClasses); return testClasses; } private static List listOnlyFiles(List tests) throws IllegalArgumentException{ if(tests==null || tests.isEmpty()){ return null; } Set classNames = new LinkedHashSet<>(); File parentFolder = tests.get(0).getParentFile(); for(File file : tests){ if(!file.getParentFile().equals(parentFolder)){ throw new IllegalArgumentException("Tests file are not in the same folder"); } classNames.add(removeFileExtension(file.getName())); } /* * if we already loaded a CUT due to its .java, do not want * to re-loaded it for a .class file that is in the same folder */ List otherClasses = new LinkedList<>(); for(File file : parentFolder.listFiles()){ String name = removeFileExtension(file.getName()); if(classNames.contains(name)){ continue; } classNames.add(name); otherClasses.add(file); } return otherClasses; } private static String removeFileExtension(String str) { if (str == null) { return null; } int pos = str.lastIndexOf("."); if (pos == -1) { return str; } return str.substring(0, pos); } /** *

* The output of EvoSuite is a set of test cases. For debugging and * experiment, we usually would not write any JUnit to file. But we still * want to see if test cases can compile and execute properly. As EvoSuite * is supposed to only capture the current behavior of the SUT, all * generated test cases should pass. *

* *

* Here we compile to a tmp folder, load and execute the test cases, and * then clean up (ie delete all generated files). *

* * @param tests * @return * @deprecated not used anymore, as check are done in different methods now, and old "assert" was not really valid */ public static boolean verifyCompilationAndExecution(List tests) { if (tests == null || tests.isEmpty()) { //nothing to compile or run return true; } File dir = createNewTmpDir(); if (dir == null) { logger.warn("Failed to create tmp dir"); return false; } try { List generated = compileTests(tests, dir); if (generated == null) { logger.warn("Failed to compile the test cases "); return false; } //as last step, execute the generated/compiled test cases Class[] testClasses = loadTests(generated); if (testClasses == null) { logger.error("Found no classes for compiled tests"); return false; } JUnitResult result = runTests(testClasses, dir); if (!result.wasSuccessful()) { logger.error("" + result.getFailureCount() + " test cases failed"); for (JUnitFailure failure : result.getFailures()) { logger.error("Failure " + failure.getExceptionClassName() + ": " + failure.getMessage() + "\n" + failure.getTrace()); } return false; } else { /* * OK, it was successful, but was there any test case at all? * * Here we just log (and not return false), as it might be that EvoSuite is just not able to generate * any test case for this SUT */ if (result.getRunCount() == 0) { logger.warn("There was no test to run"); } } } catch (Exception e) { logger.error("" + e, e); return false; } finally { //let's be sure we clean up all what we wrote on disk if (dir != null) { try { FileUtils.deleteDirectory(dir); } catch (IOException e) { logger.warn("Cannot delete tmp dir: " + dir.getName(), e); } } } logger.debug("Successfully compiled and run test cases generated for " + Properties.TARGET_CLASS); return true; } /** * Given a list of files representing .java/.class classes, load them (it * assumes the classpath to be correctly set) * * @param files * @return */ private static Class[] getClassesFromFiles(Collection files) { /* * first load only the scaffolding files */ for (File file : files) { if(!isScaffolding(file)){ continue; } loadClass(file); } List> classes = new ArrayList<>(); /* * once the scaffoldings are loaded, we can load the tests that * depend on them */ for (File file : files) { if(isScaffolding(file)){ continue; } Class clazz = loadClass(file); if(clazz != null){ classes.add(clazz); } } return classes.toArray(new Class[classes.size()]); } private static boolean isScaffolding(File file){ String name = file.getName(); return name.endsWith("_"+Properties.SCAFFOLDING_SUFFIX+JAVA) || name.endsWith("_"+Properties.SCAFFOLDING_SUFFIX+CLASS); } private static Class loadClass(File file){ if (!file.isFile()) { return null; } String packagePrefix = Properties.CLASS_PREFIX; if (!packagePrefix.isEmpty() && !packagePrefix.endsWith(".")) { packagePrefix += "."; } String name = file.getName(); if (!name.endsWith(JAVA) && !name.endsWith(CLASS)) { /* * this could happen when we scan a folder for all src/compiled * files */ return null; } String fileName = file.getAbsolutePath(); if (name.endsWith(JAVA)) { name = name.substring(0, name.length() - JAVA.length()); fileName = fileName.substring(0, fileName.length() - JAVA.length()) + ".class"; } else { assert name.endsWith(CLASS); name = name.substring(0, name.length() - CLASS.length()); } String className = packagePrefix + name; Class testClass = null; try { logger.info("Loading class " + className); //testClass = ((InstrumentingClassLoader) TestGenerationContext.getInstance().getClassLoaderForSUT()).loadClassFromFile(className, testClass = loader.loadClassFromFile(className,fileName); } catch (ClassNotFoundException e) { logger.error("Failed to load test case " + className + " from file " + file.getAbsolutePath() + " , error " + e, e); } return testClass; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy