Java tutorial
/** * Copyright (C) 2014 John Berlin * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see http://www.gnu.org/licenses/ */ package edu.odu.cs.cs350.yellow1.jar; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.security.MessageDigest; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Locale; import java.util.Queue; import java.util.concurrent.Callable; import org.apache.commons.exec.CommandLine; import org.apache.commons.exec.DefaultExecutor; import org.apache.commons.exec.ExecuteException; import org.apache.commons.exec.ExecuteWatchdog; import org.apache.commons.exec.Executor; import org.apache.commons.exec.PumpStreamHandler; import org.apache.commons.io.FileUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; /** * Fork a jvm process for each jar to be run and kill the process when it exits * <br>To allow for the mutation analysis phase to go by quickly run all mutants in parallel(limited to the number of available cpus) * <br>Implements the {@link Callable} interface to allow for this concurrency * <br>Takes a mutant jar and runs all tests in the suit on it * <br>Captures the output created by executing the mutant on a test * <br>Compares the output created by a mutant when a successful jvm exit occurs * Considered a one shot class also how do you do a unit test for Callable * * @author jberlin * */ public class ExecuteJar implements Callable<ExecutionResults> { private final boolean isWindows = System.getProperty("os.name").toLowerCase().contains("windows"); private final String javaExecutable = isWindows ? "java.exe" : "java"; private final String pathToJVM = System.getProperty("java.home") + File.separator + "bin" + File.separator + javaExecutable; private static final Logger logger = LogManager.getLogger(ExecuteJar.class); private File jar; private Executor executor; private ExecuteWatchdog wDog; private PumpStreamHandler streamHandler; private FileOutputStream log; private FileOutputStream err; private Queue<File> tests; private List<File> didNotExecute; private List<File> outFiles; private List<File> errFiles; private File killedMutant = null; private String pathToOutputDir; private String jarName; private String pathToGold; private int numOfSucesses = 0; private int numOfFailurs = 0; private int testNumKilledME = 0; private byte[] goldHash; private boolean success; private boolean testMore = true; private boolean killed = false; /** * Construct a new instance of ExecuteJar * @param jar the file object representing the mutant jar to run tests on * @param pathToGold the absolutepath to the gold output * @param pathToOutputDir the path to output directory * @param tests List of file objects reprenting the tests to run on the mutant jar */ public ExecuteJar(File jar, String pathToGold, String pathToOutputDir, List<File> tests) { this.jar = jar; this.pathToOutputDir = pathToOutputDir; this.pathToGold = pathToGold; this.tests = new ArrayDeque<>(tests); this.jarName = jar.getName().substring(0, jar.getName().indexOf(".")); this.didNotExecute = new ArrayList<>(); this.errFiles = new ArrayList<>(); this.outFiles = new ArrayList<>(); } /** * * {@inheritDoc} * <br>Run all tests in the test suit on the mutant * capturing the output created and if the execution of the mutant with a test exits successfully compare the standard output generated by<br> * the mutant if different stop running tests and return {@link ExecutionResults} * <br> Treats exiting the jvm with error as was not killed continue to run more tests * @return {@link ExecutionResults} */ @Override public ExecutionResults call() throws Exception { //create new Executor for monitoring mutation running executor = new DefaultExecutor(); //get a MessageDigest Instance for use in comparing outputs MessageDigest mDigest = MessageDigest.getInstance("MD5"); //get file object for gold file File f = new File(pathToGold); //get the hash value for the gold file goldHash = mDigest.digest(FileUtils.readFileToByteArray(f)); //reset the MessageDigest mDigest.reset(); int testCount = 0; //Create a new ExecuteWatchdog with timeout at 10 seconds wDog = new ExecuteWatchdog(10000); executor.setWatchdog(wDog); //loop through the tests till empty while (!tests.isEmpty()) { //get the next test File test = tests.poll();//poll removes the test from the queue //prepair captured output files String testName = test.getName(); testName = testName.toUpperCase(Locale.getDefault()).substring(0, testName.indexOf(".")); String outName = jarName + "_" + testName + "_out.txt"; String errOutName = jarName + "_" + testName + "_err.txt"; //create file objects to be written to File standardOut = new File(pathToOutputDir + File.separator + outName); File standardErr = new File(pathToOutputDir + File.separator + errOutName); //file streams create the files for me try { log = new FileOutputStream(standardOut); err = new FileOutputStream(standardErr); } catch (FileNotFoundException e1) { logger.error("log or err file not found for jar " + jarName, e1.getMessage()); } //create new stream handler for each execution streamHandler = new PumpStreamHandler(/* standard out */log, /* error out */err); executor.setStreamHandler(streamHandler); //construct the executable command CommandLine args = new CommandLine(pathToJVM); args.addArgument("-jar"); args.addArgument(jar.getAbsolutePath()); args.addArgument(test.getAbsolutePath()); //new process destroyer per execution ShutDownSpawnedJVMProcess killJVM = new ShutDownSpawnedJVMProcess("java -jar " + jarName, 10000); killJVM.setWaitOnShutdown(true); executor.setProcessDestroyer(killJVM); success = false; try { streamHandler.start(); int result = executor.execute(args); logger.info(jarName + " Sucess with val=[" + result + "] for test[" + testName + "]"); success = true; } catch (ExecuteException ee) { logger.error(jarName + " Execute exception " + ee.getMessage() + " with val=[" + ee.getExitValue() + "] for test[" + testName + "]"); } catch (IOException e) { logger.error(jarName + " IOExecption " + e.getMessage()); } finally { //PumpStreamHandler does not guarantee the closing of stream 100% so to release the locks held by the filestreams //on the created output files so close manually //if the streamhandler was able to close then this will through exception which we ignore try { streamHandler.stop(); //log.flush(); log.close(); //err.flush(); err.close(); } catch (IOException e) { logger.error(e.getMessage()); //ignore nothing I can do } } //if the spawned process exited with success value //check the hash of the output file and delete the empty error file //if the hash is different the mutant was killed otherwise test more //if the spawned process exited with an error value delete empty standard out file and test more if (success) { ++numOfSucesses; standardErr.delete(); outFiles.add(standardOut); if (!Arrays.equals(goldHash, mDigest.digest(FileUtils.readFileToByteArray(standardOut)))) { testMore = false; logger.debug("Different hashes for jar [" + jarName + "] for test [" + testName + "]"); } else { logger.debug("Same hashes for jar [" + jarName + "] for test [" + testName + "]"); } mDigest.reset(); } else { ++numOfFailurs; standardOut.delete(); errFiles.add(standardErr); this.didNotExecute.add(test); } ++testCount; //the mutant was killed so stop testing if (!testMore) { testMore = false; killed = true; testNumKilledME = testCount; break; } } jar.delete(); return new ExecutionResults(numOfSucesses, numOfFailurs, testNumKilledME, jarName, killed, killedMutant, outFiles, errFiles); } }