Java tutorial
/* * Copyright (C) 2017 CenturyLink, Inc. * * 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 com.centurylink.mdw.services.test; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintStream; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.text.ParseException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.TreeMap; import org.apache.commons.lang.StringEscapeUtils; import org.apache.xmlbeans.XmlException; import org.codehaus.groovy.control.CompilerConfiguration; import org.json.JSONException; import org.json.JSONObject; import com.centurylink.mdw.activity.types.AdapterActivity; import com.centurylink.mdw.app.ApplicationContext; import com.centurylink.mdw.bpm.MDWStatusMessageDocument; import com.centurylink.mdw.cache.impl.AssetCache; import com.centurylink.mdw.cache.impl.PackageCache; import com.centurylink.mdw.common.service.Query; import com.centurylink.mdw.common.service.ServiceException; import com.centurylink.mdw.config.PropertyManager; import com.centurylink.mdw.config.YamlBuilder; import com.centurylink.mdw.constant.OwnerType; import com.centurylink.mdw.dataaccess.DataAccessException; import com.centurylink.mdw.java.CompiledJavaCache; import com.centurylink.mdw.model.asset.Asset; import com.centurylink.mdw.model.event.AdapterStubRequest; import com.centurylink.mdw.model.event.AdapterStubResponse; import com.centurylink.mdw.model.listener.Listener; import com.centurylink.mdw.model.task.TaskInstance; import com.centurylink.mdw.model.task.UserTaskAction; import com.centurylink.mdw.model.variable.DocumentReference; import com.centurylink.mdw.model.variable.VariableInstance; import com.centurylink.mdw.model.workflow.Activity; import com.centurylink.mdw.model.workflow.ActivityInstance; import com.centurylink.mdw.model.workflow.ActivityRuntimeContext; import com.centurylink.mdw.model.workflow.ActivityStubRequest; import com.centurylink.mdw.model.workflow.ActivityStubResponse; import com.centurylink.mdw.model.workflow.Package; import com.centurylink.mdw.model.workflow.Process; import com.centurylink.mdw.model.workflow.ProcessInstance; import com.centurylink.mdw.model.workflow.WorkStatus; import com.centurylink.mdw.model.workflow.WorkStatuses; import com.centurylink.mdw.services.ServiceLocator; import com.centurylink.mdw.services.TaskServices; import com.centurylink.mdw.services.WorkflowServices; import com.centurylink.mdw.task.types.TaskList; import com.centurylink.mdw.test.PreFilter; import com.centurylink.mdw.test.TestCase; import com.centurylink.mdw.test.TestCase.Status; import com.centurylink.mdw.test.TestCaseActivityStub; import com.centurylink.mdw.test.TestCaseAdapterStub; import com.centurylink.mdw.test.TestCaseEvent; import com.centurylink.mdw.test.TestCaseFile; import com.centurylink.mdw.test.TestCaseHttp; import com.centurylink.mdw.test.TestCaseItem; import com.centurylink.mdw.test.TestCaseMessage; import com.centurylink.mdw.test.TestCaseProcess; import com.centurylink.mdw.test.TestCaseResponse; import com.centurylink.mdw.test.TestCaseTask; import com.centurylink.mdw.test.TestCompare; import com.centurylink.mdw.test.TestException; import com.centurylink.mdw.test.TestExecConfig; import com.centurylink.mdw.test.TestFailedException; import com.centurylink.mdw.util.HttpHelper; import com.centurylink.mdw.util.StringHelper; import com.centurylink.mdw.util.file.FileHelper; import groovy.lang.Binding; import groovy.lang.Closure; import groovy.lang.GroovyShell; import groovy.lang.Script; public class TestCaseRun implements Runnable { static final String NODE_PACKAGE = "com.centurylink.mdw.node"; private TestCase testCase; public TestCase getTestCase() { return testCase; } private String user; protected String getUser() { return user; } private File resultsDir; public File getResultsDir() { return resultsDir; } private int runNumber; public int getRunNumber() { return runNumber; } private String masterRequestId; public String getMasterRequestId() { return masterRequestId; } public void setMasterRequestId(String masterRequestId) { this.masterRequestId = masterRequestId; } private MasterRequestListener masterRequestListener; public void setMasterRequestListener(MasterRequestListener listener) { masterRequestListener = listener; } MasterRequestListener getMasterRequestListener() { return masterRequestListener; } private LogMessageMonitor monitor; protected LogMessageMonitor getMonitor() { return monitor; } private Map<String, Process> processCache; protected Map<String, Process> getProcessCache() { return processCache; } private TestExecConfig config; protected TestExecConfig getConfig() { return config; } public boolean isLoadTest() { return config.isLoadTest(); } public boolean isVerbose() { return config.isVerbose(); } public boolean isStubbing() { return config.isStubbing(); } public boolean isCreateReplace() { return config.isCreateReplace(); } private PrintStream log = System.out; public PrintStream getLog() { return log; } public void setLog(PrintStream outStream) { this.log = outStream; } private PreFilter preFilter = null; PreFilter getPreFilter() { return preFilter; } void setPreFilter(PreFilter preFilter) { this.preFilter = preFilter; } TestCaseProcess testCaseProcess; private List<ProcessInstance> processInstances; public List<ProcessInstance> getProcessInstances() { return processInstances; } private String message; private boolean oneThreadPerCase; private boolean passed; private WorkflowServices workflowServices; private TaskServices taskServices; public TestCaseRun(TestCase testCase, String user, File mainResultsDir, int run, String masterRequestId, LogMessageMonitor monitor, Map<String, Process> processCache, TestExecConfig config) throws IOException { this.testCase = testCase; this.user = user; this.resultsDir = new File(mainResultsDir + "/" + testCase.getPackage()); this.runNumber = run; this.masterRequestId = masterRequestId; this.monitor = monitor; this.processCache = processCache; this.config = config; this.oneThreadPerCase = !config.isLoadTest(); if (!resultsDir.exists()) { if (!resultsDir.mkdirs()) throw new IOException("Cannot create test results directory: " + resultsDir); } this.log = new PrintStream(resultsDir + "/" + testCase.getAsset().getRootName() + ".log"); if (isVerbose()) log.format("===== prepare case %s (id=%s)\r\n", testCase.getPath(), masterRequestId); this.workflowServices = ServiceLocator.getWorkflowServices(); this.taskServices = ServiceLocator.getTaskServices(); } public void startExecution() { passed = true; message = null; testCase.setStatus(TestCase.Status.InProgress); testCase.setStart(new Date()); if (testCase.getItems() != null) { for (TestCaseItem item : testCase.getItems()) { item.setStatus(TestCase.Status.InProgress); } } deleteResultsFile(); log.format("===== execute case %s\r\n", testCase.getPath()); } public void run() { startExecution(); try { if (testCase.getAsset().isFormat(Asset.POSTMAN)) { String runnerClass = NODE_PACKAGE + ".TestRunner"; Package pkg = PackageCache.getPackage(testCase.getPackage()); Class<?> testRunnerClass = CompiledJavaCache.getResourceClass(runnerClass, getClass().getClassLoader(), pkg); Object runner = testRunnerClass.newInstance(); Method runMethod = testRunnerClass.getMethod("run", TestCase.class); runMethod.invoke(runner, testCase); finishExecution(null); } else { String groovyScript = testCase.getText(); CompilerConfiguration compilerConfig = new CompilerConfiguration(); compilerConfig.setScriptBaseClass(TestCaseScript.class.getName()); Binding binding = new Binding(); binding.setVariable("testCaseRun", this); ClassLoader classLoader = this.getClass().getClassLoader(); Package testPkg = PackageCache.getPackage(testCase.getPackage()); if (testPkg != null) classLoader = testPkg.getCloudClassLoader(); GroovyShell shell = new GroovyShell(classLoader, binding, compilerConfig); Script gScript = shell.parse(groovyScript); gScript.setProperty("out", log); gScript.run(); finishExecution(null); } } catch (Throwable ex) { finishExecution(ex); } } void startProcess(TestCaseProcess process) throws TestException { if (isVerbose()) log.println("starting process " + process.getLabel() + "..."); this.testCaseProcess = process; try { Process proc = process.getProcess(); Map<String, String> params = process.getParams(); if (proc.isService()) workflowServices.invokeServiceProcess(proc, masterRequestId, OwnerType.TESTER, 0L, params); else workflowServices.launchProcess(proc, masterRequestId, OwnerType.TESTER, 0L, params); } catch (Exception ex) { throw new TestException("Failed to start " + process.getLabel(), ex); } } /** * Delete the (convention-based) result file if exists */ protected void deleteResultsFile() { String resFileName = getTestCase().getName(); if (resFileName.endsWith(".test")) { int dotTest = resFileName.lastIndexOf(".test"); resFileName = resFileName.substring(0, dotTest); } resFileName = resFileName + Asset.getFileExtension(Asset.YAML); File resFile = new File(resultsDir + "/" + resFileName); if (resFile.isFile()) resFile.delete(); } List<ProcessInstance> loadProcess(TestCaseProcess[] processes, Asset expectedResults) throws TestException { processInstances = null; if (isLoadTest()) throw new TestException("Not supported for load tests"); try { List<Process> processVos = new ArrayList<Process>(); if (isVerbose()) log.println("loading runtime data for processes:"); for (TestCaseProcess process : processes) { if (isVerbose()) log.println(" - " + process.getLabel()); processVos.add(process.getProcess()); } processInstances = loadResults(processVos, expectedResults, processes[0].isResultsById()); return processInstances; } catch (Exception ex) { String msg = ex.getMessage() == null ? "" : ex.getMessage().trim(); if (msg.startsWith("<")) { // xml message try { MDWStatusMessageDocument statusMsgDoc = MDWStatusMessageDocument.Factory.parse(msg); msg = statusMsgDoc.getMDWStatusMessage().getStatusMessage(); } catch (XmlException xmlEx) { // not an MDW status message -- just escape XML msg = StringEscapeUtils.escapeXml(msg); } } throw new TestException(msg, ex); } } protected List<ProcessInstance> loadResults(List<Process> processes, Asset expectedResults, boolean orderById) throws DataAccessException, IOException, ServiceException, JSONException, ParseException { List<ProcessInstance> mainProcessInsts = new ArrayList<ProcessInstance>(); Map<String, List<ProcessInstance>> fullProcessInsts = new TreeMap<String, List<ProcessInstance>>(); Map<String, String> fullActivityNameMap = new HashMap<String, String>(); for (Process proc : processes) { Query query = new Query(); query.setFilter("masterRequestId", masterRequestId); query.setFilter("processId", proc.getId().toString()); query.setDescending(true); List<ProcessInstance> procInstList = workflowServices.getProcesses(query).getProcesses(); Map<Long, String> activityNameMap = new HashMap<Long, String>(); for (Activity act : proc.getActivities()) { activityNameMap.put(act.getId(), act.getName()); fullActivityNameMap.put(proc.getId() + "-" + act.getId(), act.getName()); } if (proc.getSubprocesses() != null) { for (Process subproc : proc.getSubprocesses()) { for (Activity act : subproc.getActivities()) { activityNameMap.put(act.getId(), act.getName()); fullActivityNameMap.put(proc.getId() + "-" + act.getId(), act.getName()); } } } for (ProcessInstance procInst : procInstList) { procInst = workflowServices.getProcess(procInst.getId()); mainProcessInsts.add(procInst); List<ProcessInstance> procInsts = fullProcessInsts.get(proc.getName()); if (procInsts == null) procInsts = new ArrayList<ProcessInstance>(); procInsts.add(procInst); fullProcessInsts.put(proc.getName(), procInsts); if (proc.getSubprocesses() != null) { Query q = new Query(); q.setFilter("owner", OwnerType.MAIN_PROCESS_INSTANCE); q.setFilter("ownerId", procInst.getId().toString()); q.setFilter("processId", proc.getId().toString()); List<ProcessInstance> embeddedProcInstList = workflowServices.getProcesses(q).getProcesses(); for (ProcessInstance embeddedProcInst : embeddedProcInstList) { ProcessInstance fullChildInfo = workflowServices.getProcess(embeddedProcInst.getId()); String childProcName = "unknown_subproc_name"; for (Process subproc : proc.getSubprocesses()) { if (subproc.getId().toString().equals(embeddedProcInst.getComment())) { childProcName = subproc.getName(); if (!childProcName.startsWith(proc.getName())) childProcName = proc.getName() + " " + childProcName; break; } } List<ProcessInstance> pis = fullProcessInsts.get(childProcName); if (pis == null) pis = new ArrayList<ProcessInstance>(); pis.add(fullChildInfo); fullProcessInsts.put(childProcName, pis); } } } } String newLine = "\n"; if (!isCreateReplace()) { if (expectedResults == null || expectedResults.getRawFile() == null || !expectedResults.getRawFile().exists()) { throw new IOException("Expected results file not found for " + testCase.getPath()); } else { // try to determine newline chars from expectedResultsFile if (expectedResults.getStringContent().indexOf("\r\n") >= 0) newLine = "\r\n"; } } String yaml = translateToYaml(fullProcessInsts, fullActivityNameMap, orderById, newLine); if (isCreateReplace()) { log.println("creating expected results: " + expectedResults.getRawFile()); FileHelper.writeToFile(expectedResults.getRawFile().toString(), yaml, false); expectedResults.setStringContent(yaml); } String fileName = resultsDir + "/" + expectedResults.getName(); if (isVerbose()) log.println("creating actual results file: " + fileName); FileHelper.writeToFile(fileName, yaml, false); // set friendly statuses if (mainProcessInsts != null) { for (ProcessInstance pi : mainProcessInsts) pi.setStatus(WorkStatuses.getWorkStatuses().get(pi.getStatusCode())); } return mainProcessInsts; } protected String translateToYaml(Map<String, List<ProcessInstance>> processInstances, Map<String, String> activityNames, boolean orderById, String newLineChars) throws IOException, DataAccessException { YamlBuilder yaml = new YamlBuilder(newLineChars); for (String procName : processInstances.keySet()) { List<ProcessInstance> procInsts = processInstances.get(procName); for (int i = 0; i < procInsts.size(); i++) { ProcessInstance procInst = procInsts.get(i); yaml.append("process: # ").append(procInst.getId()).newLine(); yaml.append(" name: ").append(procName).newLine(); yaml.append(" instance: ").append((i + 1)).newLine(); LinkedList<ActivityInstance> orderedList = new LinkedList<ActivityInstance>(); for (ActivityInstance act : procInst.getActivities()) orderedList.add(act); if (orderById) { Collections.sort(orderedList, new Comparator<ActivityInstance>() { public int compare(ActivityInstance ai1, ActivityInstance ai2) { return (int) (ai1.getActivityId() - ai2.getActivityId()); } }); } for (ActivityInstance act : orderedList) { yaml.append(" activity: # ").append(act.getActivityId()).append(" \"") .append(StringHelper.dateToString(act.getStartDate())).append("\"").newLine(); String actNameKey = procInst.getProcessId() + "-" + act.getActivityId(); yaml.append(" name: ").appendMulti(" ", activityNames.get(actNameKey)).newLine(); yaml.append(" status: ").append(WorkStatuses.getWorkStatuses().get(act.getStatusCode())) .newLine(); if (act.getMessage() != null) { String msgLines[] = act.getMessage().split("\\r\\n|\\n|\\r"); String result = msgLines[0]; if (msgLines.length > 1) result += "..."; yaml.append(" result: ").append(result).newLine(); } } for (VariableInstance var : procInst.getVariables()) { yaml.append(" variable: # ").append(var.getInstanceId()).newLine(); yaml.append(" name: ").append(var.getName()).newLine(); yaml.append(" value: "); try { var.setProcessInstanceId(procInst.getId()); String val = getStringValue(var); if (var.isDocument()) procInst.getVariable().put(var.getName(), val); // pre-populate document values yaml.appendMulti(" ", val).newLine(); } catch (Throwable t) { log.println("Failed to translate variable instance: " + var.getInstanceId() + " to string with the following exception"); t.printStackTrace(log); yaml.append(" \"").append(var.getStringValue()).append("\"").newLine(); } } } } return yaml.toString(); } protected String getStringValue(VariableInstance var) throws TestException { String val = var.getStringValue(); if (var.isDocument()) { try { val = workflowServices.getDocumentStringValue(new DocumentReference(val).getDocumentId()); } catch (ServiceException ex) { throw new TestException(ex.getMessage(), ex); } } return val; } boolean verifyProcess(TestCaseProcess[] processes, Asset expectedResults) throws TestException { processInstances = null; if (isLoadTest()) return true; if (!passed) return false; try { List<Process> processVos = new ArrayList<Process>(); if (isVerbose()) log.println("loading runtime data for processes:"); for (TestCaseProcess process : processes) { if (isVerbose()) log.println(" - " + process.getLabel()); processVos.add(process.getProcess()); } processInstances = loadResults(processVos, expectedResults, processes[0].isResultsById()); if (processInstances.isEmpty()) throw new IllegalStateException( "No process instances found for masterRequestId: " + masterRequestId); return verifyProcesses(expectedResults); } catch (Exception ex) { String msg = ex.getMessage() == null ? "" : ex.getMessage().trim(); if (msg.startsWith("<")) { // xml message try { MDWStatusMessageDocument statusMsgDoc = MDWStatusMessageDocument.Factory.parse(msg); msg = statusMsgDoc.getMDWStatusMessage().getStatusMessage(); } catch (XmlException xmlEx) { // not an MDW status message -- just escape XML msg = StringEscapeUtils.escapeXml(msg); } } throw new TestException(msg, ex); } } protected boolean verifyProcesses(Asset resultsAsset) throws TestException, IOException, DataAccessException, ParseException { if (!resultsAsset.getRawFile().exists()) { message = "Expected results not found: " + resultsAsset; log.println("+++++ " + message); passed = false; } else { TestCaseFile actualResultsFile = new TestCaseFile(resultsDir + "/" + resultsAsset.getName()); if (isVerbose()) log.println("... compare " + resultsAsset + " with " + actualResultsFile + "\r\n"); if (!actualResultsFile.exists()) { message = "Actual results not found: " + actualResultsFile; log.println("+++++ " + message); passed = false; } else { if (isVerbose()) { log.println("expected:"); log.println(resultsAsset.getStringContent()); log.println("actual:"); log.println(actualResultsFile.getText()); } TestCompare testCompare = new TestCompare(getPreFilter()); int firstDiffLine = testCompare.doCompare(resultsAsset, actualResultsFile); if (firstDiffLine == 0) { passed = true; } else { passed = false; message = "+++++ " + resultsAsset.getName() + ": differs from line " + firstDiffLine; log.println(message); } } } return passed; } Process getProcess(String target) throws TestException { target = qualify(target); Process process = processCache.get(target); if (process == null) { try { Query query = getProcessQuery(target); process = workflowServices.getProcessDefinition(query.getPath(), query); if (process == null) throw new FileNotFoundException("Process: " + target + " not found"); processCache.put(target, process); } catch (Exception ex) { throw new TestException("Cannot load " + target, ex); } } return process; } Asset getAsset(String path) throws TestException { if (path.endsWith(".proc")) return getProcess(path); try { return AssetCache.getAsset(path.indexOf('/') > 0 ? path : getTestCase().getPackage() + '/' + path); } catch (Exception ex) { throw new TestException("Cannot load " + path, ex); } } TestCaseResponse sendMessage(TestCaseMessage message) throws TestException { if (isLoadTest()) // special subst for CSV message.setPayload(message.getPayload().replaceAll("#\\{", "#\\{\\$")); if (isVerbose()) { log.println("sending " + message.getProtocol() + " message..."); log.println("message payload:"); log.println(message.getPayload()); } try { String endpoint = "services"; if (Listener.METAINFO_PROTOCOL_SOAP.equals(message.getProtocol())) endpoint += "/SOAP"; HttpHelper httpHelper = getHttpHelper("POST", endpoint); httpHelper.setHeaders(getDefaultMessageHeaders(message.getPayload())); String actual = httpHelper.post(message.getPayload()); if (isVerbose()) { log.println("response:"); log.println(actual); } TestCaseResponse response = new TestCaseResponse(); response.setActual(actual); return response; } catch (Exception ex) { throw new TestException("Failed to send " + message.getProtocol() + " message", ex); } } TestCaseResponse http(TestCaseHttp http) throws TestException { if (isVerbose()) { log.println("http " + http.getMethod() + " request to " + http.getUri()); } try { HttpHelper helper; if (http.getMessage() != null && http.getMessage().getUser() != null) { helper = getHttpHelper(http.getMethod(), http.getUri(), http.getMessage().getUser(), http.getMessage().getPassword()); } else { helper = getHttpHelper(http.getMethod(), http.getUri()); } Map<String, String> headers = new HashMap<String, String>(); if (http.getMessage() != null) { if (http.getMessage().getPayload() != null && http.getMessage().getPayload().startsWith("{")) headers.put("Content-Type", "application/json"); if (http.getMessage().getHeaders() != null) headers.putAll(http.getMessage().getHeaders()); } if (!headers.isEmpty()) helper.setHeaders(headers); if (http.getConnectTimeout() > 0) helper.setConnectTimeout(http.getConnectTimeout()); if (http.getReadTimeout() > 0) helper.setReadTimeout(http.getReadTimeout()); String actual; if (http.getMethod().equalsIgnoreCase("get")) { try { actual = helper.get(); } catch (IOException ex) { actual = helper.getResponse(); } } else { if (http.getMessage() == null) { if (!http.getMethod().equalsIgnoreCase("delete")) throw new TestException("Missing payload for HTTP: " + http.getMethod()); } try { if (http.getMethod().equalsIgnoreCase("post")) actual = helper.post(http.getMessage().getPayload()); else if (http.getMethod().equalsIgnoreCase("put")) actual = helper.put(http.getMessage().getPayload()); else if (http.getMethod().equalsIgnoreCase("patch")) actual = helper.patch(http.getMessage().getPayload()); else if (http.getMethod().equalsIgnoreCase("delete")) actual = helper.delete(http.getMessage() == null ? null : http.getMessage().getPayload()); else throw new TestException("Unsupported http method: " + http.getMethod()); } catch (IOException ex) { actual = helper.getResponse(); if (helper.getResponseCode() <= 0) ex.printStackTrace(log); } } if (isVerbose()) { log.println("http response:"); log.println(actual); } TestCaseResponse response = new TestCaseResponse(); response.setHeaders(helper.getHeaders()); response.setCode(helper.getResponseCode()); response.setActual(actual); return response; } catch (Exception ex) { throw new TestException("Failed to send http " + http.getMethod() + " request to " + http.getUri(), ex); } } void wait(TestCaseProcess process) throws TestException { if (process.getTimeout() == 0) throw new TestException("Missing property 'timeout' for process wait command"); if (isVerbose()) log.println( "waiting for process: " + process.getLabel() + " (timeout=" + process.getTimeout() + "s)..."); String waitKey = getWaitKey(process.getProcess(), process.getActivityLogicalId(), process.getStatus()); performWait(waitKey, process.getTimeout()); } protected void performWait(String key, int timeout) throws TestException { try { synchronized (this) { monitor.register(this, key); this.wait(timeout * 1000); } Object stillthere = monitor.remove(key); if (stillthere != null) { log.println("wait command times out after: " + timeout + "s at: " + StringHelper.dateToString(new Date())); } else { Thread.sleep(2000); // to get around race condition if (isVerbose()) log.println("wait command satisfied: " + key); } } catch (InterruptedException e) { log.println("Wait gets interrupted"); testCase.setStatus(TestCase.Status.Errored); } } protected String getWaitKey(Process process, String activityLogicalId, String status) throws TestException { Long activityId = 0L; if (activityLogicalId != null) activityId = getActivityId(process, activityLogicalId); if (status == null) status = WorkStatus.STATUSNAME_COMPLETED; return monitor.createKey(masterRequestId, process.getId(), activityId, status); } private Long getActivityId(Process proc, String activityLogicalId) { for (Activity act : proc.getActivities()) { String lid = act.getLogicalId(); if (activityLogicalId.equals(lid)) { return act.getId(); } } return null; } boolean verifyResponse(TestCaseResponse response) throws TestException { if (isLoadTest()) return true; if (!passed) return false; try { if (isVerbose()) { log.println("expected response:"); log.println(response.getExpected()); log.println("actual response:"); log.println(response.getActual()); } return executeVerifyResponse(response.getExpected(), response.getActual()); } catch (Exception ex) { throw new TestException(ex.getMessage(), ex); } } protected boolean executeVerifyResponse(String expected, String actual) throws TestException, IOException, DataAccessException, ParseException { expected = expected.replaceAll("\r", ""); if (isVerbose()) log.println("comparing response..."); if (actual != null) { actual = actual.replaceAll("\r", ""); } if (!expected.equals(actual)) { int firstDiffLine = TestCompare.matchRegex(expected, actual); if (firstDiffLine != 0) { passed = false; message = "response differs from line: " + firstDiffLine; if (!isVerbose()) { // otherwise it was logged previously log.format("+++++ " + message + "\r\n"); log.format("Actual response: %s\r\n", actual); } throw new TestFailedException(message); } } return passed; } void performTaskAction(TestCaseTask task) throws TestException { Query query = getTaskQuery(task.getName()); try { TaskList taskList = taskServices.getTasks(query, user); List<TaskInstance> taskInstances = taskList.getTasks(); if (taskInstances.isEmpty()) throw new TestException("Cannot find task instances: " + query); TaskInstance taskInstance = taskInstances.get(0); // latest if (isVerbose()) log.println("performing: " + task.getOutcome() + " on task '" + task.getName() + "' (" + taskInstance.getId() + ")"); task.setId(taskInstance.getTaskInstanceId()); UserTaskAction taskAction = new UserTaskAction(); taskAction.setTaskAction(task.getOutcome()); taskAction.setUser(user); taskAction.setTaskInstanceId(task.getId()); if (task.getVariables() != null) workflowServices.setVariables(taskInstance.getOwnerId(), task.getVariables()); taskServices.performTaskAction(taskAction); } catch (TestException ex) { throw ex; } catch (Exception ex) { throw new TestException("Error actioning task: " + task.getId(), ex); } } void notifyEvent(TestCaseEvent event) throws TestException { if (isVerbose()) log.println("notifying event: '" + event.getId() + "'"); try { workflowServices.notify(event.getId(), event.getMessage(), 0); } catch (ServiceException ex) { throw new TestException("Unable to notifyEvent: " + ex.getMessage()); } } void sleep(int seconds) { if (isVerbose()) log.println("sleeping " + seconds + " seconds..."); if (oneThreadPerCase) { try { Thread.sleep(seconds * 1000); } catch (InterruptedException e) { log.println("Sleep interrupted"); } } // else do nothing - already slept } private List<TestCaseAdapterStub> adapterStubs = new ArrayList<TestCaseAdapterStub>(); void addAdapterStub(TestCaseAdapterStub adapterStub) { adapterStubs.add(adapterStub); } /** * Callback method invoked from stub server when notified from server. * Default implementation to substitute values from request as super.getStubResponse(); * Users can provide their own implementation as a Groovy closure. */ public ActivityStubResponse getStubResponse(ActivityStubRequest request) throws JSONException, TestException { // activity stubbing ActivityRuntimeContext activityRuntimeContext = request.getRuntimeContext(); if (testCaseProcess != null && testCaseProcess.getActivityStubs() != null) { for (TestCaseActivityStub activityStub : testCaseProcess.getActivityStubs()) { if (activityStub.getMatcher().call(activityRuntimeContext)) { Closure<String> completer = activityStub.getCompleter(); completer.setResolveStrategy(Closure.DELEGATE_FIRST); completer.setDelegate(activityStub); String resultCode = null; try { resultCode = completer.call(request.getJson().toString(2)); } catch (Throwable th) { th.printStackTrace(log); throw new TestException(th.getMessage(), th); } ActivityStubResponse activityStubResponse = new ActivityStubResponse(); activityStubResponse.setResultCode(resultCode); if (activityStub.getSleep() > 0) activityStubResponse.setDelay(activityStub.getSleep()); else if (activityStub.getDelay() > 0) activityStubResponse.setDelay(activityStub.getDelay()); if (activityStub.getVariables() != null) { Map<String, String> responseVariables = new HashMap<String, String>(); Map<String, Object> variables = activityStub.getVariables(); for (String name : variables.keySet()) { Object value = variables.get(name); // TODO: handle non-string objects responseVariables.put(name, value.toString()); } activityStubResponse.setVariables(responseVariables); } if (isVerbose()) log.println("Stubbing activity " + activityRuntimeContext.getProcess().getQualifiedName() + ":" + activityRuntimeContext.getActivityLogicalId() + " with result code: " + resultCode); return activityStubResponse; } } } ActivityStubResponse passthroughResponse = new ActivityStubResponse(); passthroughResponse.setPassthrough(true); return passthroughResponse; } /** * Callback method invoked from stub server when notified from server. * Default implementation to substitute values from request as super.getStubResponse(); * Users can provide their own implementation as a Groovy closure. */ public AdapterStubResponse getStubResponse(AdapterStubRequest request) throws JSONException, TestException { // adapter stubbing for (TestCaseAdapterStub adapterStub : adapterStubs) { boolean match = false; try { if (adapterStub.isEndpoint()) { match = adapterStub.getMatcher().call(request); } else { match = adapterStub.getMatcher().call(request.getContent()); } } catch (Throwable th) { th.printStackTrace(log); throw new TestException(th.getMessage(), th); } if (match) { try { String stubbedResponseContent = adapterStub.getResponder().call(request.getContent()); if (isVerbose()) { if (adapterStub.isEndpoint()) log.println( "Stubbing endpoint " + request.getUrl() + " with:\n" + stubbedResponseContent); else log.println("Stubbing response with: " + stubbedResponseContent); } int delay = 0; if (adapterStub.getDelay() > 0) delay = adapterStub.getDelay(); else if (adapterStub.getSleep() > 0) delay = adapterStub.getSleep(); AdapterStubResponse stubResponse = new AdapterStubResponse(stubbedResponseContent); stubResponse.setDelay(delay); stubResponse.setStatusCode(adapterStub.getStatusCode()); stubResponse.setStatusMessage(adapterStub.getStatusMessage()); return stubResponse; } catch (Throwable th) { th.printStackTrace(log); throw new TestException(th.getMessage(), th); } } } AdapterStubResponse passthroughResponse = new AdapterStubResponse(AdapterActivity.MAKE_ACTUAL_CALL); passthroughResponse.setPassthrough(true); return passthroughResponse; } public JSONObject submitItem(TestCase apiTestCase, TestCaseItem testItem) throws TestException { try { // close log stream and reopen after js runner to sync file access getLog().close(); String runnerClass = TestCaseRun.NODE_PACKAGE + ".TestRunner"; Package pkg = PackageCache.getPackage(apiTestCase.getPackage()); Class<?> testRunnerClass = CompiledJavaCache.getResourceClass(runnerClass, getClass().getClassLoader(), pkg); Object runner = testRunnerClass.newInstance(); Method runMethod = testRunnerClass.getMethod("run", TestCase.class); runMethod.invoke(runner, apiTestCase); setLog(new PrintStream(new FileOutputStream( getResultsDir() + "/" + getTestCase().getAsset().getRootName() + ".log", true))); return testItem.getResponse(); } catch (Exception ex) { throw new TestException(ex.getMessage(), ex); } } public boolean verifyItem(TestCase testCase, TestCaseItem item) throws TestException { // close log stream and reopen after js runner to sync file access getLog().close(); String runnerClass = TestCaseRun.NODE_PACKAGE + ".TestRunner"; Package pkg = PackageCache.getPackage(testCase.getPackage()); try { Class<?> testRunnerClass = CompiledJavaCache.getResourceClass(runnerClass, getClass().getClassLoader(), pkg); Object runner = testRunnerClass.newInstance(); Method runMethod = testRunnerClass.getMethod("run", TestCase.class); runMethod.invoke(runner, testCase); setLog(new PrintStream(new FileOutputStream( getResultsDir() + "/" + getTestCase().getAsset().getRootName() + ".log", true))); testCase.setStatus(item.getStatus()); testCase.setMessage(item.getMessage()); passed = testCase.getStatus() == Status.Passed; return passed; } catch (Exception ex) { throw new TestException(ex.getMessage(), ex); } } public void finishExecution(Throwable e) { if (isLoadTest()) { if (e != null) e.printStackTrace(); // otherwise won't see errors if (log != System.out && log != System.err) log.close(); return; } // function test only below if (e == null) { } else if (e instanceof TestException) { passed = false; message = firstLine(e.getMessage()); log.println(message); if (e.getCause() instanceof TestFailedException) { // find the script line number for (StackTraceElement el : e.getStackTrace()) { if (el.getFileName() != null && el.getFileName().endsWith(".groovy")) { log.println(" --> at " + getTestCase().getPath() + ":" + el.getLineNumber()); } } } else { e.printStackTrace(log); } } else if (e instanceof ParseException) { passed = false; message = "Command syntax error at line " + ((ParseException) e).getErrorOffset() + ": " + e.getMessage(); log.println(message); e.printStackTrace(log); } else { passed = false; message = firstLine(e.toString()); if ("Assertion failed: ".equals(message)) message += "See execution log for details."; log.println("Exception " + message); e.printStackTrace(log); } TestCase.Status status = testCase.getStatus(); Date endDate = new Date(); if (isVerbose()) { long seconds = (endDate.getTime() - testCase.getStart().getTime()) / 1000; if (status == TestCase.Status.Errored) log.println("===== case " + testCase.getPath() + " Errored after " + seconds + " seconds"); else if (status == TestCase.Status.Stopped) log.println("===== case " + testCase.getPath() + " Stopped after " + seconds + " seconds"); else log.println("===== case " + testCase.getPath() + (passed ? " Passed" : " Failed") + " after " + seconds + " seconds"); } if (log != System.out && log != System.err) log.close(); if (status != TestCase.Status.Errored && status != TestCase.Status.Stopped) { testCase.setEnd(endDate); testCase.setStatus(passed ? TestCase.Status.Passed : TestCase.Status.Failed); } if (testCase.getItems() != null) { if (e != null) e.printStackTrace(); // else would have to dig in testCase (not item) log for (TestCaseItem item : testCase.getItems()) { if (e != null) { // don't leave unfinished items if (item.getStatus() == Status.InProgress) { item.setStatus(testCase.getStatus()); item.setMessage(testCase.getMessage()); } } } } } protected HttpHelper getHttpHelper(String method, String endpoint) throws MalformedURLException { return getHttpHelper(method, endpoint, null, null); } protected HttpHelper getHttpHelper(String method, String endpoint, String user, String password) throws MalformedURLException { HttpHelper helper = HttpHelper.getHttpHelper(method, getUrl(endpoint)); if (user != null) { helper.getConnection().setUser(user); helper.getConnection().setPassword(password); } helper.getConnection().setHeader("Content-Type", "application/json"); return helper; } protected URL getUrl(String endpoint) throws MalformedURLException { String url = endpoint; if (!endpoint.startsWith("http://") && !endpoint.startsWith("https://")) { url = PropertyManager.getProperty("mdw.test.base.url"); if (url == null) url = ApplicationContext.getServicesUrl(); if (!endpoint.startsWith("/")) endpoint = "/" + endpoint; url += endpoint; } return new URL(url); } /** * Not for HTTP method but for general sendMessage(). */ protected Map<String, String> getDefaultMessageHeaders(String payload) { if (getMasterRequestId() != null) { Map<String, String> headers = new HashMap<String, String>(); headers.put("mdw-request-id", getMasterRequestId()); if (payload != null && payload.startsWith("{")) headers.put("Content-Type", "application/json"); return headers; } return null; } protected String qualify(String target) { if (target.indexOf('/') == -1 && getTestCase().getPackage() != null) return getTestCase().getPackage() + "/" + target; return target; } protected Query getProcessQuery(String target) { String procPath = target; int version = 0; // latest int spaceV = target.lastIndexOf(" v"); if (spaceV > 0) { try { version = Asset.parseVersionSpec(procPath.substring(spaceV + 2)); procPath = target.substring(0, spaceV); } catch (NumberFormatException ex) { // process name must contain space v } } Query query = new Query(procPath); query.setFilter("version", version); query.setFilter("app", "autotest"); return query; } protected Query getTaskQuery(String name) { Query query = new Query(""); query.setFilter("masterRequestId", masterRequestId); query.setFilter("name", name); query.setFilter("app", "autotest"); query.setDescending(true); return query; } /** * Makes sure the message is embeddable in an XML attribute for results parsing. */ private String firstLine(String msg) { if (msg == null) return msg; int newLine = msg.indexOf("\r\n"); if (newLine == -1) newLine = msg.indexOf("\n"); if (newLine == -1) return msg.replace("\"", "'"); else return msg.substring(0, newLine).replace("\"", "'"); } byte[] read(File file) throws IOException { FileInputStream fis = null; try { fis = new FileInputStream(file); byte[] bytes = new byte[(int) file.length()]; fis.read(bytes); return bytes; } finally { if (fis != null) fis.close(); } } }