/** Created by: Steve Neal Date: 29/09/11 */ @SuppressWarnings("unchecked") public class ChorusInterpreter { private ChorusLog log = ChorusLogFactory.getLog(ChorusInterpreter.class); private static final ScheduledExecutorService timeoutExcecutor = NamedExecutors.newSingleThreadScheduledExecutor("TimeoutExecutor"); private long scenarioTimeoutMillis = 360000; private List<String> basePackages = Collections.emptyList(); private ExecutionListenerSupport executionListenerSupport; private HandlerClassDiscovery handlerClassDiscovery = new HandlerClassDiscovery(); private SpringContextSupport springContextSupport = new SpringContextSupport(); private ScheduledFuture scenarioTimeoutInterrupt; private ScheduledFuture scenarioTimeoutStopThread; private ScheduledFuture scenarioTimeoutKill; private StepProcessor stepProcessor; private SubsystemManager subsystemManager; private HashMap<String, Class> allHandlerClasses; public ChorusInterpreter(ExecutionListenerSupport executionListenerSupport) { this.executionListenerSupport = executionListenerSupport; stepProcessor = new StepProcessor(executionListenerSupport); } public void initialize() { // load all available handler classes allHandlerClasses = handlerClassDiscovery.discoverHandlerClasses(basePackages); } public void runFeatures(ExecutionToken executionToken, List<FeatureToken> features) { // RUN EACH FEATURE for (FeatureToken feature : features) { try { runFeature(executionToken, feature); } catch (Throwable t) { log.error("Exception while running feature " + feature, t); executionToken.incrementFeaturesFailed(); } } } private void runFeature(ExecutionToken executionToken, FeatureToken feature) { executionListenerSupport.notifyFeatureStarted(executionToken, feature); // notify we started, even if there are missing handlers // (but nothing will be done) // this is still important so execution listeners at least see the feature (but will show as // 'unimplemented') String config = feature.isConfiguration() ? " in config " + feature.getConfigurationName() : ""; log.info("Running feature from file: " + feature.getFeatureFile() + config); // check that the required handler classes are all available and list them in order of // precedence List<Class> orderedHandlerClasses = new ArrayList<>(); StringBuilder unavailableHandlersMessage = handlerClassDiscovery.findHandlerClasses(allHandlerClasses, feature, orderedHandlerClasses); boolean foundAllHandlerClasses = unavailableHandlersMessage.length() == 0; // run the scenarios in the feature if (foundAllHandlerClasses) { log.debug("The following handlers will be used " + orderedHandlerClasses); List<ScenarioToken> scenarios = feature.getScenarios(); try { HandlerManager handlerManager = new HandlerManager( feature, orderedHandlerClasses, springContextSupport, subsystemManager, executionToken.getProfile()); handlerManager.createFeatureScopedHandlers(); handlerManager.processStartOfFeature(); runScenarios(executionToken, feature, scenarios, handlerManager); handlerManager.processEndOfFeature(); } catch (Exception e) { log.error("Exception while running feature " + feature.getName(), e); } String description = feature.getEndState() == EndState.PASSED ? " passed! " : feature.getEndState() == EndState.PENDING ? " pending! " : " failed! "; log.trace("The feature " + description); if (feature.getEndState() == EndState.PASSED) { executionToken.incrementFeaturesPassed(); } else if (feature.getEndState() == EndState.PENDING) { executionToken.incrementFeaturesPending(); } else { executionToken.incrementFeaturesFailed(); } } else { log.warn( "The following handlers were not available, failing feature " + feature.getName() + " " + unavailableHandlersMessage); feature.setUnavailableHandlersMessage(unavailableHandlersMessage.toString()); executionToken.incrementUnavailableHandlers(); executionToken.incrementFeaturesFailed(); } executionListenerSupport.notifyFeatureCompleted(executionToken, feature); } private void runScenarios( ExecutionToken executionToken, FeatureToken feature, List<ScenarioToken> scenarios, HandlerManager handlerManager) throws Exception { log.debug("Now running scenarios " + scenarios + " for feature " + feature); for (Iterator<ScenarioToken> iterator = scenarios.iterator(); iterator.hasNext(); ) { ScenarioToken scenario = iterator.next(); // if the feature start scenario exists and failed we skip all but feature end scenario boolean skip = !scenario.isFeatureStartScenario() && !scenario.isFeatureEndScenario() && feature.isFeatureStartScenarioFailed(); if (skip) { log.warn( "Skipping scenario " + scenario + " since " + KeyWord.FEATURE_START_SCENARIO_NAME + " failed"); } runScenario(executionToken, handlerManager, scenario, skip); } } private void runScenario( ExecutionToken executionToken, HandlerManager handlerManager, ScenarioToken scenario, boolean skip) throws Exception { executionListenerSupport.notifyScenarioStarted(executionToken, scenario); log.info(String.format("Processing scenario: %s", scenario.getName())); // reset the ChorusContext for the scenario ChorusContext.destroy(); handlerManager.setCurrentScenario(scenario); List<Object> handlerInstances = handlerManager.getOrCreateHandlersForScenario(); handlerManager.processStartOfScope(Scope.SCENARIO, handlerInstances); createTimeoutTasks( Thread .currentThread()); // will interrupt or eventually kill thread / interpreter if blocked log.debug("Running scenario steps for Scenario " + scenario); StepInvokerProvider p = getStepInvokers(handlerInstances); stepProcessor.runSteps(executionToken, p, scenario.getSteps(), skip); stopTimeoutTasks(); // the special start or end scenarios don't count in the execution stats if (!scenario.isStartOrEndScenario()) { updateExecutionStats(executionToken, scenario); } handlerManager.processEndOfScope(Scope.SCENARIO, handlerInstances); executionListenerSupport.notifyScenarioCompleted(executionToken, scenario); } private StepInvokerProvider getStepInvokers(List<Object> handlerInstances) { CompositeStepInvokerProvider stepInvokerProvider = new CompositeStepInvokerProvider(); for (Object handler : handlerInstances) { HandlerClassInvokerFactory f = new HandlerClassInvokerFactory(handler); stepInvokerProvider.addChild(f); } stepInvokerProvider.addChild((StepInvokerProvider) subsystemManager.getRemotingManager()); return stepInvokerProvider; } private void updateExecutionStats(ExecutionToken executionToken, ScenarioToken scenario) { if (scenario.getEndState() == EndState.PASSED) { executionToken.incrementScenariosPassed(); } else if (scenario.getEndState() == EndState.PENDING) { executionToken.incrementScenariosPending(); } else { executionToken.incrementScenariosFailed(); } } private void createTimeoutTasks(final Thread t) { scenarioTimeoutInterrupt = timeoutExcecutor.schedule( new Runnable() { public void run() { timeoutIfStillRunning(t); } }, scenarioTimeoutMillis, TimeUnit.MILLISECONDS); scenarioTimeoutStopThread = timeoutExcecutor.schedule( new Runnable() { public void run() { stopThreadIfStillRunning(t); } }, scenarioTimeoutMillis * 2, TimeUnit.MILLISECONDS); scenarioTimeoutKill = timeoutExcecutor.schedule( new Runnable() { public void run() { killInterpreterIfStillRunning(t); } }, scenarioTimeoutMillis * 3, TimeUnit.MILLISECONDS); } private void stopTimeoutTasks() { scenarioTimeoutInterrupt.cancel(true); scenarioTimeoutStopThread.cancel(true); scenarioTimeoutKill.cancel(true); } private void timeoutIfStillRunning(Thread t) { if (t.isAlive()) { log.warn("Scenario timed out after " + scenarioTimeoutMillis + " millis, will interrupt"); stepProcessor.setInterruptingOnTimeout(true); t.interrupt(); // first try to interrupt to see if this can unblock/fail the scenario } } private void stopThreadIfStillRunning(Thread t) { if (t.isAlive()) { log.error( "Scenario did not respond to interrupt after timeout, " + "will stop the interpreter thread and fail the tests"); t.stop(); // this will trigger a ThreadDeath exception which we should allow to propagate // and will terminate the interpreter } } private void killInterpreterIfStillRunning(Thread t) { if (t.isAlive()) { log.error( "Scenario did not respond to thread.kill() after timeout, will now kill the interpreter"); System.exit(1); } } public void setBasePackages(List<String> basePackages) { this.basePackages = basePackages; } public void setDryRun(boolean dryRun) { this.stepProcessor.setDryRun(dryRun); } public void setScenarioTimeoutMillis(long scenarioTimeoutMillis) { this.scenarioTimeoutMillis = scenarioTimeoutMillis; } public void setSubsystemManager(SubsystemManager subsystemManager) { this.subsystemManager = subsystemManager; } }
/** * Created with IntelliJ IDEA. User: nick Date: 16/05/12 Time: 17:36 To change this template use * File | Settings | File Templates. */ public class DynamicProxyMBeanCreator { private ChorusLog log = ChorusLogFactory.getLog(DynamicProxyMBeanCreator.class); private JMXConnector jmxConnector; protected MBeanServerConnection mBeanServerConnection; private final String host; private final int jmxPort; public DynamicProxyMBeanCreator(String host, int jmxPort) { this.host = host; this.jmxPort = jmxPort; } public MBeanServerConnection connect() { MBeanServerConnection result; try { String serviceURL = String.format("service:jmx:rmi:///jndi/rmi://%s:%d/jmxrmi", host, jmxPort); jmxConnector = JMXConnectorFactory.connect(new JMXServiceURL(serviceURL), null); log.debug("Connecting to JMX service URL: " + serviceURL); result = jmxConnector.getMBeanServerConnection(); } catch (Exception e) { String msg = String.format("Failed to connect to mBean server on (%s:%s)", host, jmxPort); if (log.isDebugEnabled()) { log.debug(msg, e); } else { log.warn(msg); } throw new ChorusException(msg, e); } mBeanServerConnection = result; return result; } public <T> T createMBeanProxy(final String mxbeanName, Class<T> mxbeanInterface) throws IOException { final InvocationHandler handler = new InvocationHandler() { public Object invoke(Object proxy, final Method method, final Object[] args) throws Throwable { Class<?>[] paramTypes = method.getParameterTypes(); if (method.getName().equals("equals") && paramTypes.length == 1 && paramTypes[0] == Object.class) { // this is the equals method being called on the proxy, we need to handle it locally return args[0] == proxy; } else { return mBeanServerConnection.invoke( new ObjectName(mxbeanName), method.getName(), args, getClassNameArray(method.getParameterTypes())); } } private String[] getClassNameArray(Class[] args) { String[] classNames = new String[args.length]; int pos = 0; for (Class a : args) { classNames[pos++] = a.getName(); } return classNames; } }; return (T) Proxy.newProxyInstance( mxbeanInterface.getClassLoader(), new Class[] {mxbeanInterface}, handler); } // @TODO arrange to close the connection? At what point? public void dispose() { try { jmxConnector.close(); } catch (IOException e) { // safe to ignore this exception - may get here if server process dies before JMX connection // is destroyed } } }