Java tutorial
/* * Copyright 2011 Oliver B. Fischer * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package de.fischer.thotti.core.runner; import de.fischer.thotti.core.misc.StopWatch; import de.fischer.thotti.core.resources.ndresult.NonDistributedTestResultType; import de.fischer.thotti.core.resources.ndresult.TestSuiteResult; import de.fischer.thotti.core.resources.ndtest.NonDistributedTestType; import de.fischer.thotti.core.resources.ndtest.NonDistributedTestsType; import de.fischer.thotti.core.resources.ndtest.TestInstanceType; import de.fischer.thotti.core.resources.ndtest.TestSuite; import de.fischer.thotti.ec2.core.ThottiRuntimeException; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.GnuParser; import org.apache.commons.cli.Option; import org.apache.commons.cli.OptionBuilder; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.apache.commons.exec.CommandLine; import org.apache.commons.exec.DefaultExecutor; import org.apache.commons.exec.ExecuteException; import org.apache.commons.lang3.StringUtils; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; import javax.xml.datatype.DatatypeConfigurationException; import javax.xml.datatype.DatatypeFactory; import javax.xml.datatype.Duration; import javax.xml.datatype.XMLGregorianCalendar; import java.io.File; import java.io.IOException; import java.math.BigInteger; import java.net.InetAddress; import java.net.URL; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import java.util.LinkedList; import java.util.List; import java.util.UUID; /** * The nondistributed test runner executes a given test configuration. * <p/> * <h2>Implementation Notes</h2> * * @author Oliver Fischer */ public class NDRunner { /** * Return code of the {@link NDRunner} if the execution was successfull * without any internal errors. This return code indicates only the * successfull execution of the runner itself. */ public static final int SUCCESS = 0; /** * Return code of the {@link NDRunner} if the execution was not * successfull. This return code indicates an internal error * like a misconfiguration. */ public static final int ERROR = 1; /** * Return code of the {@link NDRunner} in slave mode if * a test run failed because the test class was't found. */ public static final int TEST_CLASS_NOT_FOUND = 5; /** * Return code of the {@link NDRunner} in slave mode if * a test run failed because the test method wasn't found. */ public static final int TEST_METHOD_NOT_FOUND = 6; /** * Return code of the {@link NDRunner} in slave mode if * the test method is overloaded and the name * of the test method is not unique. */ public static final int TEST_METHOD_AMBIGOUS = 7; /** * Return code of the {@link NDRunner} in slave mode if * the signature of the test method is not compatible * with the parameter specification in the test configuration. */ public static final int TEST_METHOD_SIGNATURE_MISMATCH = 8; /** * The file name of the standard Thotti configuration file. */ public final String THOTTI_STANDARD_TEST_CONFIGURATION = "thotti-test-config.xml"; protected StopWatch stopWatch = new StopWatch(); protected URL configResource; protected TestSuite testSuite; public static final String THOTTI_RESULT_FILE = "thotti-result.xml"; /** * Main method of the {@link NDRunner}. */ public static void main(String[] args) throws IOException { try { // @todo Evaluate --jopt-simple Options options = createCommandLineOption(); CommandLineParser parser = new GnuParser(); try { // parse the command line arguments org.apache.commons.cli.CommandLine line = parser.parse(options, args); if (line.hasOption("slave")) { if (!line.hasOption("executionid")) { throw new ParseException("Execution id is missing."); } String eid = line.getOptionValue("executionid"); int eidInt = Integer.valueOf(eid); new NDRunner().runSlaveMode(eidInt); } else { new NDRunner().runMasterMode(); } } catch (ParseException exp) { System.err.println("Parsing failed. Reason: " + exp.getMessage()); } } catch (RunnerException e) { if (e.getCause() != null) { e.printStackTrace(System.err); System.err.println(); } System.err.println(e.getMessage()); System.exit(ERROR); } } private static Options createCommandLineOption() { Options options = new Options(); Option version = new Option("slave", "Run in slave mode"); Option executionid = OptionBuilder.withArgName("id").hasArg() .withDescription("Run the test with this execution id.").create("executionid"); options.addOption(version); options.addOption(executionid); return options; } /** * Starts the test runner in slave mode. * <p/> * In the slave mode the runner will only execute * the test instance with the given execution ID. * * @param executionID The execution id of the test * to execute. * @see #runMasterMode() */ private void runSlaveMode(int executionID) throws RunnerException, IOException { configResource = findTestConfiguration(); testSuite = loadTestConfiguration(); FindResult found = findInstanceByID(testSuite, executionID); executeSingleTest(found.test, found.instance); } protected FindResult findInstanceByID(TestSuite testSuite, int executionID) { if (testSuite == null) throw new NullPointerException(); // Remember, the execution id is unique within a given test suite // but only for he given suite. Generating the same tests suite // from the same annotations might result in different IDs TestInstanceType instance = null; NonDistributedTestType testOfInstance = null; NonDistributedTestsType tests = testSuite.getNonDistributedTests(); for (NonDistributedTestType test : tests.getNonDistributedTests()) { for (TestInstanceType current : test.getTestInstances()) { BigInteger id = current.getExecutionID(); if (id.intValue() == executionID) { instance = current; testOfInstance = test; break; } } } if (instance == null) { throw new ThottiRuntimeException( "Internal error while running in " + "slave mode: No test instace " + executionID + " found."); } return new FindResult(testOfInstance, instance); } /** * Starts the test runner in the master mode. */ private void runMasterMode() throws RunnerException, IOException { configResource = findTestConfiguration(); testSuite = loadTestConfiguration(); assert testSuite.getNonDistributedTests() != null; List<NDTestResult> resultList = new LinkedList<NDTestResult>(); NonDistributedTestsType ndTest = testSuite.getNonDistributedTests(); List<NonDistributedTestType> tests = ndTest.getNonDistributedTests(); for (NonDistributedTestType test : tests) { for (TestInstanceType instance : test.getTestInstances()) { NDTestResult result = forkNDTestRunnerInSlaveMode(test, instance); resultList.add(result); } } TestSuiteResult resultType = generatedTestResult(resultList); saveTestResult(resultType); } private TestSuiteResult generatedTestResult(List<NDTestResult> resultList) throws RunnerException { if (null == resultList) { throw new NullPointerException(); } DatatypeFactory datatypeFac = null; try { datatypeFac = DatatypeFactory.newInstance(); } catch (DatatypeConfigurationException e) { throw new RunnerException("Failed to instanciate datatype factory.", e); } de.fischer.thotti.core.resources.ndtest.ObjectFactory factory = new de.fischer.thotti.core.resources.ndtest.ObjectFactory(); de.fischer.thotti.core.resources.ndresult.ObjectFactory factory2 = new de.fischer.thotti.core.resources.ndresult.ObjectFactory(); TestSuiteResult suiteResult = factory2.createTestSuiteResult(); for (NDTestResult result : resultList) { NonDistributedTestResultType ndResult = factory2.createNonDistributedTestResultType(); Duration execTime = datatypeFac.newDuration(result.getExecutionTime()); XMLGregorianCalendar startTime = datatypeFac.newXMLGregorianCalendar(result.getStartTime()); ndResult.setTestID(result.getTestId()); ndResult.setExitCode(BigInteger.valueOf(result.getExitCode())); ndResult.setJvmArgsID(result.getJVMArgsID()); ndResult.setJvmArgs(result.getJvmArgs()); ndResult.setExecutionTimeMS(execTime); ndResult.setStartedAt(startTime); ndResult.setParamGrpID(result.getParamGrpID()); try { ndResult.setMahoutVersion(org.apache.mahout.Version.versionFromResource()); } catch (IOException e) { e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. } suiteResult.getNonDistributedTestResults().add(ndResult); } suiteResult.setUuid(generateResultUUID()); return suiteResult; } protected void saveTestResult(TestSuiteResult result) { Package pkg = TestSuiteResult.class.getPackage(); File resultFile = generateResultFileName(); JAXBContext jaxbContext = null; try { jaxbContext = JAXBContext.newInstance(pkg.getName()); Marshaller marshaller = jaxbContext.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); marshaller.marshal(result, resultFile); } catch (JAXBException e) { // @todo? } } private NDTestResult forkNDTestRunnerInSlaveMode(NonDistributedTestType test, TestInstanceType instance) throws IOException { NDTestResult result = new NDTestResult(); GregorianCalendar startTime; long startTimeMS; long endTimeMS; int exitCode = -9999; result.setTestId(test.getId()); result.setJVMArgs(instance.getJvmArguments()); result.setJVMArgsID(instance.getJvmArgsID()); result.setParamGrpID(instance.getName()); CommandLine cmdLine = new CommandLine("java"); if (false == StringUtils.isEmpty(instance.getJvmArguments())) { cmdLine.addArguments(instance.getJvmArguments()); } cmdLine.addArgument("-cp").addArgument(System.getProperty("java.class.path")) .addArgument(NDRunner.class.getName()).addArgument("--slave").addArgument("--executionid") .addArgument(instance.getExecutionID().toString()); System.gc(); DefaultExecutor executor = new DefaultExecutor(); startTime = (GregorianCalendar) Calendar.getInstance(); startTimeMS = startTime.getTimeInMillis(); try { exitCode = executor.execute(cmdLine); result.setSuccessfulTermination(true); result.setExitCode(exitCode); } catch (ExecuteException e) { result.setSuccessfulTermination(false); result.setExitCode(e.getExitValue()); } finally { endTimeMS = System.currentTimeMillis(); } result.setExecutionTime(endTimeMS - startTimeMS); result.setStartTime(startTime); System.out.println(result); return result; } protected TestSuite loadTestConfiguration() throws InvalidTestXMLConfigurationException { Package pkg = TestSuite.class.getPackage(); TestSuite suite = null; try { JAXBContext jaxbContext = JAXBContext.newInstance(pkg.getName()); Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); suite = (TestSuite) unmarshaller.unmarshal(configResource); } catch (JAXBException e) { throw new InvalidTestXMLConfigurationException( "Error while " + "reading the test configuration file " + configResource.toExternalForm(), e); } return suite; } /** * Searches the classpath for the standard test configuration * file. * * @see #THOTTI_STANDARD_TEST_CONFIGURATION */ protected URL findTestConfiguration() throws NoTestConfigurationException { ClassLoader myLoader = getClass().getClassLoader(); URL config = myLoader.getResource(THOTTI_STANDARD_TEST_CONFIGURATION); if (null == config) { throw new NoTestConfigurationException( "Standard configuration file " + THOTTI_STANDARD_TEST_CONFIGURATION + " " + "not found as resource with the current " + "classpath settings."); } return config; } void executeSingleTest(NonDistributedTestType test, TestInstanceType instance) throws IOException, TestMethodSignatureMismatchException, NoSuchTestClassException, AmbiguousTestMethodException, NoSuchTestMethodException { SingleTestExecution ste = new SingleTestExecution().withClass(test.getClazz()).withMethod(test.getMethod()) .withInstance(instance); try { ste.run(); } catch (NoSuchTestClassException e) { System.err.println(e.getMessage()); System.exit(TEST_CLASS_NOT_FOUND); } catch (NoSuchTestMethodException e) { System.err.println(e.getMessage()); System.exit(TEST_METHOD_NOT_FOUND); } catch (AmbiguousTestMethodException re) { System.err.println(re.getMessage()); System.exit(TEST_METHOD_AMBIGOUS); } catch (TestMethodSignatureMismatchException e) { System.err.println(e.getMessage()); System.exit(TEST_METHOD_SIGNATURE_MISMATCH); } } public File generateResultFileName() { return new File(THOTTI_RESULT_FILE); } /** * Internal factory method to create a fresh instance * of {@link SingleTestExecution}. * <p/> * This method exists only to support mocking in unit tests * better. * * @return new instance of {@link SingleTestExecution}. */ SingleTestExecution createSingleTestExecution() { return new SingleTestExecution(); } public String getHostname() { try { InetAddress localMachine = InetAddress.getLocalHost(); return localMachine.getHostName(); } catch (java.net.UnknownHostException uhe) { return null; } } public String getGenerationDateAndTime() { Date now = new Date(); SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssz"); return formatter.format(now); } private String generateResultUUID() { String uuid = UUID.randomUUID().toString(); return uuid; } private class FindResult extends TestInstanceType { NonDistributedTestType test; TestInstanceType instance; public FindResult(NonDistributedTestType t, TestInstanceType i) { super(); test = t; instance = i; } } }