package at.ac.tuwien.iter.services.impl; import java.io.File; import java.io.IOException; import java.lang.Thread.UncaughtExceptionHandler; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Hashtable; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import javax.xml.bind.JAXBException; import org.apache.tapestry5.ioc.services.RegistryShutdownHub; import org.apache.tapestry5.ioc.services.TypeCoercer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import at.ac.tuwien.iter.data.Test; import at.ac.tuwien.iter.data.TestResult; import at.ac.tuwien.iter.data.TestResultsCollector; import at.ac.tuwien.iter.exceptions.TestExecutionException; import at.ac.tuwien.iter.executors.BasicRunner; import at.ac.tuwien.iter.executors.ConfigurationManager; import at.ac.tuwien.iter.services.AssertionService; import at.ac.tuwien.iter.services.DataCollectionService; import at.ac.tuwien.iter.services.Iter; import at.ac.tuwien.iter.services.LoadGenerator; import at.ac.tuwien.iter.services.LoadGeneratorSource; import at.ac.tuwien.iter.services.TestSuiteEvolver; /** * This is the main class that manages the test suite generation guided by the * model. Each test suite generation process is (and must be independent!) from * the others. * * * @author alessiogambi * */ public class IterImpl implements Iter { // TODO To run the tests -> Check if Tapestry already provide one or we can // just // add to it private ExecutorService executor; private Logger logger; // Inputs private String customerName; private String serviceName; // This must be injected private LoadGenerator loadGenerator; // This is the result ! private Set<Test> testSuite; private Map<Test, Integer> hitCount; private TestResultsCollector testResultsCollector; private List<Test> experimentAgenda; // / VERY BAD ! private ConfigurationManager configurationManager; private TypeCoercer typeCoercer; private AssertionService assertionService; private DataCollectionService dataCollectionService; // Test Execution private int nParallelTests; private int nInitialTests; private long experimentTimeout; private boolean bootstrap; private File bootstrapFile; private File testResultFile; private TestSuiteEvolver testSuiteEvolver; private LoadGeneratorSource loadGeneratorSource; public IterImpl( // Resources Logger logger, // User inputs - Do we really need this here ? Those are used only // because AUToCLES need that, maybe we can move them there - TODO // Move this into Test execution framework String customerName, String serviceName, // Test Execution - TODO Move this into Test execution framework int nParallelTests, // Test Suite Initialization. int nInitialTests, // Input-output final File testResultFile, final File bootstrapFile, // Experimental Environment - TODO Move this into Test execution // framework URL autoclesURL, // Experiment setup - Pre/Post conditions ? long experimentTimeout, boolean bootstrap, // Other services - Why we need this here ? LoadGenerator loadGenerator, // This should be avoided, but @PostInjection does not work fine // apparently... RegistryShutdownHub registryShutdownHub, // TypeCoercer typeCoercer, // Infrastructure service AssertionService assertionService, // OK DataCollectionService dataCollectionService, // Not sure TestSuiteEvolver testSuiteEvolver, // OK LoadGeneratorSource loadGeneratorSource// Not sure ) { this.bootstrap = bootstrap; this.logger = logger; this.customerName = customerName; this.serviceName = serviceName; this.loadGenerator = loadGenerator; this.assertionService = assertionService; this.dataCollectionService = dataCollectionService; this.loadGeneratorSource = loadGeneratorSource; this.nParallelTests = nParallelTests; this.nInitialTests = nInitialTests; this.experimentTimeout = experimentTimeout; this.testResultsCollector = new TestResultsCollector(); this.testSuite = new HashSet<Test>(); this.hitCount = new Hashtable<Test, Integer>(); this.testResultFile = testResultFile; this.bootstrapFile = bootstrapFile; // this.mathEngineDao = mathEngineDao; this.typeCoercer = typeCoercer; this.testSuiteEvolver = testSuiteEvolver; // TODO This must be removed, use a service instead and provide symbols // !!! this.configurationManager = new ConfigurationManager( autoclesURL.toString(), this.customerName, this.serviceName, this.loadGenerator); // TODO EXECUTOR SERVICE MUST BE ACCESSED/INJECTED AS DEPENDENCY // Register hook for shutdown // Use the @PostInjection annotation, see the ref manual registryShutdownHub.addRegistryShutdownListener(new Runnable() { public void run() { if (executor != null) { executor.shutdown(); } } }); // TODO IS IT REALLY WORKING ?! // Register hook for shutdown: STORE ALL THE RESULTS ! // Use the @PostInjection annotation, see the ref manual registryShutdownHub.addRegistryShutdownListener(new Runnable() { public void run() { if (testResultsCollector != null) { try { TestResultsCollector.saveToFile( testResultFile.getAbsolutePath(), testResultsCollector); } catch (JAXBException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } } }); } /** * Try to bootstrap from the given file. If something goes wrong the * bootstrap may add some (previously) test to the Agenda */ void bootstrap() throws TestExecutionException { // Stats int total = 0; int failed = 0; int rescheduled = 0; String bFile = bootstrapFile.getAbsolutePath(); long startTime = System.currentTimeMillis(); logger.info("Iter.bootstrapAndStart() BootStraping from " + bootstrapFile.getAbsolutePath()); if (bootstrapFile.exists()) { // Try Load all the cached executions if the file exists try { testResultsCollector = TestResultsCollector .loadFromFile(bootstrapFile.getAbsolutePath()); } catch (Throwable e) { logger.error( String.format( "Error. Cannot load the boostrap file %s. Skip the bootstrap process.", bootstrapFile.getAbsolutePath()), e); throw new TestExecutionException( "Cannot load the bootstrap file"); } // Collect some data total = testResultsCollector.getTestResults().size(); try { // Run the assertion again for (TestResult testResult : testResultsCollector) { logger.info("Processing testResult: " + testResult); Test newTest = loadGeneratorSource.getLoadGenerator( testResult.getLoadGeneratorID()).generateTest( testResult.getParametersAsNumbers()); try { logger.info("Registering test in testsuite: " + newTest); testSuite.add(newTest); } catch (Throwable e) { logger.warn( "Cannot store the test in the test suite file. Run again test " + newTest, e); // TODO Check if this will eventually override the one // stored in the boostraped file/ experimentAgenda.add(newTest); rescheduled++; failed++; continue; } try { logger.info("Checking Assertions for test: " + newTest); assertionService.check(testResult); } catch (Throwable e) { logger.error( "Cannot assert test results in bootstraping file. Skip !", e); failed++; continue; } } } catch (Throwable e) { // TODO Not sure if really needed anymore logger.error("Cannot complete the bootstrap!", e); throw new TestExecutionException( "Cannot complete the bootstrap"); } } else { logger.warn(String .format("The specified boostraping file %s does not exists. Continue with no bootstrap.", bootstrapFile.getAbsolutePath())); } long endTime = System.currentTimeMillis(); /* * Print Bootstrap statistics */ StringBuffer sb = new StringBuffer(); sb.append("\n\n").append("=======================================\n") .append("\tBootstrap summary\n") .append("=======================================\n"); sb.append(" Elaborated ").append(total).append(" test results\n"); sb.append(" Input file ").append(bFile).append("\n"); sb.append(" Result:\n"); sb.append(" - Ok: ").append((total - failed)).append("\n"); sb.append(" - Failed: ").append(failed).append("\n"); sb.append(" - Rescheduled: ").append(rescheduled).append("\n"); sb.append(" Elaboration time was ") .append(String.format("%.2f", (double) ((endTime - startTime) / 1000l))) .append(" secs\n"); sb.append("=======================================\n"); logger.info(sb.toString()); } private List<Test> createRandomTests() { List<Test> randomExperiments = new ArrayList<Test>(); for (int experiment = 0; experiment < nInitialTests; experiment++) { randomExperiments.add(loadGenerator.generateRandomCase()); } return randomExperiments; } // Mainly for unit testing protected Collection<TestResult> getTestResults() { return testResultsCollector.getTestResults(); } protected Collection<Test> getTestSuite() { return testSuite; } /* * Schedule the experiments over the set of executors and then blocks until * all the experiments ran. Experiments that run fine are removed from the * list, if there is some failures in the execution the test must be * repeated * * @param experiments */ private void scheduleAndRunExperiments(final List<Test> experiments) throws InterruptedException { final AtomicBoolean stopTest = new AtomicBoolean(false); final UncaughtExceptionHandler uncaughtExceptionHandler = new UncaughtExceptionHandler() { public void uncaughtException(Thread thread, Throwable throwable) { logger.error(" uncaughtException " + throwable.getMessage() + " from Thread " + thread); if (throwable.getMessage().contains("STOP THE TEST")) { stopTest.set(true); } } }; // TODO Try to exploit the parallel executor provided by the framework // itself. Note that we need to capture any exception generated by the // worker threads ! final ThreadFactory factory = new ThreadFactory() { public Thread newThread(Runnable runnable) { final Thread thread = new Thread(runnable); // Force our generated Handler here thread.setUncaughtExceptionHandler(uncaughtExceptionHandler); return thread; } }; executor = Executors.newFixedThreadPool(nParallelTests, factory); for (final Test test : experiments) { // Should I USED FUTURE<?> here ? /* * Something like: ExecutorService executor = * Executors.newSingleThreadExecutor(); Runnable task = new * Runnable() { public void run() { throw new * RuntimeException("foo"); } }; * * Future<?> future = executor.submit(task); try { future.get(); } * catch (ExecutionException e) { Exception rootException = * e.getCause(); } */ executor.execute(new Runnable() { private void saveResultsToFile() throws JAXBException, IOException { // Store the result String fileName = testResultFile.getAbsolutePath(); logger.debug("Basic Runner: Storing results to " + fileName); TestResultsCollector.saveToFile(fileName, testResultsCollector); } public void run() { Logger _logger = LoggerFactory.getLogger(logger.getName() + "-Test-" + test.getId()); BasicRunner runner = new BasicRunner(_logger, configurationManager, typeCoercer, dataCollectionService, experimentTimeout); TestResult testResult = null; try { testResult = runner.executeTest(test); } catch (TestExecutionException e) { e.printStackTrace(); if (e.getMessage().contains("STOP THE TEST")) { logger.error("STOP THE TEST SUITE !"); throw new RuntimeException(e); } } catch (Exception e) { logger.error("Error while executing the test"); throw new RuntimeException(e); } try { // Store the execution in the TestResultCollectorFile - // Only if there are not exceptions testResultsCollector.addTestResult(testResult); } catch (Exception e) { logger.warn("Error while add test result to collector", e); } // TODO Not sure this belongs HERE... maybe it's part of the // "main" process. // Increase our testsuite synchronized (testSuite) { testSuite.add(test); // This is specific for our iter search !! hitCount.put(test, 0); } logger.info("Added the test " + test.getId() + "to the test suite file"); // Run all the assertions: What if multiple threads run // this at the same time ? MathDao should be synch and // thread safe... try { assertionService.check(testResult); } catch (Exception e) { logger.warn( "Failed to store assertion for " + test.getId() + "in the XML file !", e); } /* * TODO: This fails always if there are no plastiicty check * at all. For the moment we simply comment the code out ! * TODO Shall we capture this some how in a configurable way * ? Maybe in the future we may implement somthing like: if * fails the check then repeat */ // try { // // if (testResult.getTestReport("plasticity") != null // && !testResult.getTestReport("plasticity") // .isFailed()) { // If the experiment was fine remove form the agenda synchronized (experiments) { logger.info("Removing " + test + " from the list of experiments to run"); if (!experiments.remove(test)) { logger.warn("IterImpl.scheduleExperiments() ERROR while removing " + test); } } // // } else { // logger.info("Plasticity check failed, so we keep the experiment for another round !"); // } // } catch (Exception e) { // logger.warn("Error while checink test reports", e); // } try { saveResultsToFile(); } catch (Exception e) { logger.warn( "Failed to store result for " + test.getId() + "in the XML file !", e); e.printStackTrace(); } } }); } // Follow the common pattern for temporary thread pools and wait // the end of all the experiments executor.shutdown(); try { // Wait until this is over or the process gets interrupted executor.awaitTermination(Long.MAX_VALUE, TimeUnit.MINUTES); } catch (InterruptedException e) { e.printStackTrace(); logger.warn(" Timeout on Wait for termination. Stop the test"); throw new RuntimeException("STOP THE TEST", e); } logger.info("Experiments ROUND finished !"); } public void start() { // State Variables // Search status boolean running = true; // the Agenda contains the list of experiments to run. // This is a "global" var in the class experimentAgenda = new ArrayList<Test>(); if (bootstrap) { // Shall we move exc inside the private method ? try { bootstrap(); } catch (TestExecutionException e) { logger.warn("Problems during the bootstrap", e); } } // If the r option is zero or not specified, we will not create random // tests experimentAgenda.addAll(createRandomTests()); while (running) { try { logger.info("IterImpl.start() Scheduling : " + experimentAgenda.size() + " experiments to run."); // Schedule all the experiments over the N executors // This can be esily become a service as well. // This will block until all the experiments ran scheduleAndRunExperiments(experimentAgenda); // If for some reasons some experiment failed or must be // repeated in the next round, we will keep it inside the agenda } catch (InterruptedException e) { logger.warn("Interrupted execution. Exit"); running = false; break; } // Maybe the assertions should be here !? logger.info("IterImpl.start() Experiments that remains to run or must be repeated: " + experimentAgenda.size()); /* * - Make this a configurable setting - */ Collection<Test> newExperiments = null; try { // Evolve the test suite starting from the current one, plus the // results obtained from it newExperiments = testSuiteEvolver.evolveTestSuite(testSuite, testResultsCollector.getTestResults()); } catch (Exception e) { logger.warn("Error during test suite evolution. Continue", e); } // THIS IS SPECIFIC FOR OUR TEST SUITE. CAN BE A CONFIGURABLE // SERVICE IN CHAIN/PIPELINE with constraints (on the number of test // for example). no need for update result one we have everything // inside the testSuiteObject ! experimentAgenda.addAll(newExperiments); // Check for termination. No more experiments means we are done ! if (experimentAgenda.size() == 0) { logger.info("There are no more tests to run !"); running = false; } } // Store to file try { TestResultsCollector.saveToFile(testResultFile.getAbsolutePath(), testResultsCollector); logger.info("Results stored to " + testResultFile.getAbsolutePath()); } catch (Exception e) { logger.error("Results cannot be stored to " + testResultFile.getAbsolutePath()); e.printStackTrace(); } } }