package detective.core.runner; import geb.Browser; import groovy.lang.Closure; import groovyx.gpars.dataflow.Promise; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.codehaus.groovy.runtime.GroovyCategorySupport; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.ImmutableMap; import com.typesafe.config.ConfigException; import detective.core.Parameters; import detective.core.Scenario; import detective.core.Scenario.Step; import detective.core.Detective; import detective.core.Story; import detective.core.StoryRunner; import detective.core.TestTask; import detective.core.distribute.JobRunResult.JobRunResultSteps; import detective.core.distribute.JobToRun; import detective.core.distribute.scenario.ScenarioRunContext; import detective.core.dsl.DslException; import detective.core.dsl.ParametersImpl; import detective.core.dsl.SharedDataPlaceHolder; import detective.core.dsl.builder.DslBuilder; import detective.core.dsl.table.Row; import detective.core.exception.ScenarioFailException; import detective.core.exception.StoryFailException; import detective.core.filter.RunnerFilterChain; import detective.core.geb.GebSession; import detective.core.matcher.SubsetAssertError; import detective.core.services.DetectiveFactory; import detective.utils.StringUtils; import detective.utils.Utils; public class SimpleStoryRunner implements StoryRunner{ private static final Logger logger = LoggerFactory.getLogger(SimpleStoryRunner.class); public SimpleStoryRunner(){ } public void run(final Story story, final JobToRun job) { int scenarioIndexConfig = getScenarioIndexConfig(); Map<Scenario, Promise<Object>> promises = new HashMap<Scenario, Promise<Object>>(); int currentIndex = 0; for (final Scenario scenario : story.getScenarios()){ try { if (scenarioIndexConfig == -1 || scenarioIndexConfig == currentIndex){ Promise<Object> p = DetectiveFactory.INSTANCE.getThreadGroup().task(new Runnable(){ @Override public void run() { try { runScenario(scenario, new ParametersImpl()); } catch (Throwable e) { throw new StoryFailException(story, e.getMessage(), e); } }}); promises.put(scenario, p); }else{ scenario.setIgnored(true); } currentIndex ++; } catch (Throwable e) { makeScenarioFail(scenario, e); } } for (Scenario s : promises.keySet()){ Promise<Object> promise = promises.get(s); try { promise.join(); if (promise.isError()){ makeScenarioFail(s, promise.getError()); } } catch (Throwable e) { makeScenarioFail(s, e); } } } private int getScenarioIndexConfig() throws ConfigException.WrongType{ int scenarioIndex = -1; try { scenarioIndex = Detective.getConfig().getNumber("detective.runner.scenario.index").intValue(); } catch (ConfigException.Missing e1) { } return scenarioIndex; } private void makeScenarioFail(Scenario s, Throwable e) { s.setSuccessed(false); s.setError(e); throw new StoryFailException(s.getStory(), e.getMessage(), e); //logger.error("Scenario [" + s.getTitle() + "] in story [" + s.getStory().getTitle() + "] fail, " + e.getMessage(), e); } //Handle Scenario Role public void runScenario(final Scenario scenario, Parameters config){ Parameters datain = new ParametersImpl(config); List<Row> datatable = scenario.getScenarioTable(); if (datatable != null && datatable.size() >= 1){ String[] headers = checkGetHeader(datatable); List<Promise<Object>> promises = new ArrayList<Promise<Object>>(); for (int i = 0; i < datatable.size(); i++){ Row row = datatable.get(i); prepareDataIn(datain, headers, row); final Parameters datainWithRow = datain.clone(); Promise<Object> p = DetectiveFactory.INSTANCE.getThreadGroup().task(new Runnable(){ public void run(){ try { runScenarioNew(scenario, datainWithRow); } catch (Throwable e) { throw new ScenarioFailException(scenario, 0, e.getMessage(), e); } } }); promises.add(p); } for (Promise<Object> p : promises){ try { p.join(); if (p.isError()){ makeScenarioFail(scenario, p.getError()); }else scenario.setSuccessed(true); } catch (InterruptedException e) { makeScenarioFail(scenario, e); } } }else{ datain = datain.clone(); try { runScenarioNew(scenario, datain); scenario.setSuccessed(true); } catch (Throwable e) { makeScenarioFail(scenario, e); } } } /** * Run Scenario in current thread */ private void runScenarioNew(final Scenario scenario, Parameters datain) throws Throwable { //Shared data need join into the running user code so that they can change it Parameters parameterForWholeScenario = new ParametersImpl(scenario.getStory().getSharedDataMap()); parameterForWholeScenario.putAll(datain); GebSession.setParameters(parameterForWholeScenario); try { try { addAllStepInfo(scenario, datain); int i = 0; for (Step step : scenario.getSteps()){ boolean stepSuccessed = false; try { if (step.getExpectClosure() != null){ Closure<?> expectClosure = (Closure)step.getExpectClosure().clone(); runAsNormal(scenario, parameterForWholeScenario, step, expectClosure); }else{ throw new DslException("There is no \"then\" section in DSL scenario part. \n " + scenario.toString()); } stepSuccessed = true; } finally { logStepInfo(step, i, stepSuccessed, parameterForWholeScenario); i++; } } }catch (Throwable e){ if (GebSession.isBrowserAvailable() && !"disable".equals(Detective.getConfig().getString("browser.report"))){ GebSession.getBrowser().report("Fail_" + scenario.getStory().getTitle() + "_" + scenario.getTitle()); } throw e; } } finally { GebSession.cleanBrowser(); GebSession.cleanParameters(); } } private void addAllStepInfo(final Scenario scenario, Parameters datain){ ScenarioRunContext context = (ScenarioRunContext)datain.get("_scenarioContext"); if (context != null){ for (int i = 0; i < scenario.getSteps().size(); i++){ Step step = scenario.getSteps().get(i); JobRunResultSteps stepResult = new JobRunResultSteps(); stepResult.setStepName(step.getTitle()); stepResult.setSuccessed(false); context.addJobRunResultSteps(stepResult); } } } private void logStepInfo(Step step, int stepIndex, boolean successed, Parameters datain){ //TODO Need Redesign for this as it requires distribute package. This implementation is really bad ScenarioRunContext context = (ScenarioRunContext)datain.get("_scenarioContext"); if (context != null && context.getSteps().size() > stepIndex){ JobRunResultSteps stepResult = context.getSteps().get(stepIndex); stepResult.setSuccessed(successed); List<String> msgs = Detective.getUserMessage(datain); if (msgs != null){ stepResult.getAdditionalMsgs().addAll(msgs); } } //We always clear this information as it only exists in current step if (Detective.getUserMessage(datain) != null){ Detective.getUserMessage(datain).clear(); } } private void runAsNormal(final Scenario scenario, Parameters parameterForWholeScenario, Step step, Closure<?> expectClosure) { Parameters dataToPassIntoExpectClosure = parameterForWholeScenario; ExpectClosureDelegate delegate = new ExpectClosureDelegate(dataToPassIntoExpectClosure); expectClosure.setDelegate(delegate); //expectClosure.setResolveStrategy(Closure.DELEGATE_ONLY); expectClosure.setResolveStrategy(Closure.DELEGATE_FIRST); try { //GroovyCategorySupport.use(ExpectObjectWrapper.class, scenario.getOutcomes().getExpectClosure()); expectClosure.call(); } catch (WrongPropertyNameInDslException e) { StringBuilder sb = new StringBuilder(e.getPropertyName()); sb.append(" not able to found in properties list, do you mean : ") .append(StringUtils.getBestMatch(e.getPropertyName(), dataToPassIntoExpectClosure.keySet()).or("notAbleToFoundBestMatchProperties")) .append("\nthe avaiable properties we got:") .append(dataToPassIntoExpectClosure.keySet()) .append("\nHere is the properties and the values for your reference:\n") .append(dataToPassIntoExpectClosure.toString()); throw new DslException(sb.toString(), e); } catch (java.lang.AssertionError e){ SubsetAssertError subsetError = Utils.findCauseBy(e, SubsetAssertError.class); if (subsetError != null){ //TODO We shoudn't check this here, consider to refactor into a filter. Detective.logUserMessage(parameterForWholeScenario, subsetError.getMessage()); Detective.logUserMessage(parameterForWholeScenario, subsetError.getFullTableStrVersion()); Detective.logUserMessage(parameterForWholeScenario, subsetError.getSubsetTableStrVersion()); } throw new detective.core.AssertionError(scenario.getStory(), scenario, step, e); } catch (groovy.lang.MissingPropertyException e){ throw new DslException(e.getMessage() + ". Please note we have a know ambiguousness for parent child relationship, for example login.username is a valid identifier for us, but when you add login.username.lastname, we have no idea it is going to access a property from identifier login.username or it is a new identifier.", e); } //this.updateSharedData(scenario, dataToPassIntoExpectClosure); } private Parameters prepareDataIn(Parameters datain, String[] headers, Row row) { Object[] values = row.asArray(); for (int i = 0; i < headers.length; i++){ datain.put(headers[i], values[i]); } return datain; } private String[] checkGetHeader(List<Row> datatable){ return datatable.get(0).getHeaderAsStrings(); } private void updateSharedData(Scenario scenario, Parameters dataout) { Story story = scenario.getStory(); Set<String> sharedDataKeys = story.getSharedDataMap().keySet(); Set<String> unbindShareVarKeys = dataout.getUnbindShareVarKeys(); for (String key : sharedDataKeys){ if (!unbindShareVarKeys.contains(key) && dataout.containsKey(key)) story.putSharedData(key, dataout.get(key)); } } /** * Dataout will override data in if have same key */ private Parameters combineSharedAndInAndOut(Parameters shared, Parameters datain, Parameters dataout){ Parameters p = new ParametersImpl(shared); p.putAllUnwrappered(datain); p.putAllUnwrappered(dataout); return p; } }