1 package org.collectionspace.services.IntegrationTests.xmlreplay;
3 import org.apache.commons.cli.*;
5 import org.apache.commons.io.FileUtils;
6 import org.apache.commons.jexl2.JexlEngine;
7 import org.collectionspace.services.common.api.Tools;
9 import org.dom4j.io.SAXReader;
14 /** This class is used to replay a request to the Services layer, by sending the XML payload
15 * in an appropriate Multipart request.
16 * See example usage in calling class XmlReplayTest in services/IntegrationTests, and also in main() in this class.
17 * @author Laramie Crocker
19 public class XmlReplay {
21 public XmlReplay(String basedir, String reportsDir){
22 this.basedir = basedir;
23 this.serviceResultsMap = createResultsMap();
24 this.reportsList = new ArrayList<String>();
25 this.reportsDir = reportsDir;
28 public static final String DEFAULT_CONTROL = "xml-replay-control.xml";
29 public static final String DEFAULT_MASTER_CONTROL = "xml-replay-master.xml";
30 public static final String DEFAULT_DEV_MASTER_CONTROL = "dev-master.xml";
31 private static final int MAX_REATTEMPTS = 10;
32 private static final String REATTEMPT_KEY = "REATTEMPT_KEY";
34 private String reportsDir = "";
35 public String getReportsDir(){
38 private String basedir = "."; //set from constructor.
39 public String getBaseDir(){
43 private String controlFileName = DEFAULT_CONTROL;
44 public String getControlFileName() {
45 return controlFileName;
47 public void setControlFileName(String controlFileName) {
48 this.controlFileName = controlFileName;
51 private String protoHostPort = "";
52 public String getProtoHostPort() {
55 public void setProtoHostPort(String protoHostPort) {
56 this.protoHostPort = protoHostPort;
59 private boolean autoDeletePOSTS = true;
60 public boolean isAutoDeletePOSTS() {
61 return autoDeletePOSTS;
63 public void setAutoDeletePOSTS(boolean autoDeletePOSTS) {
64 this.autoDeletePOSTS = autoDeletePOSTS;
68 public Dump getDump() {
71 public void setDump(Dump dump) {
75 AuthsMap defaultAuthsMap;
76 public AuthsMap getDefaultAuthsMap(){
77 return defaultAuthsMap;
79 public void setDefaultAuthsMap(AuthsMap authsMap){
80 defaultAuthsMap = authsMap;
83 private Map<String, ServiceResult> serviceResultsMap;
84 public Map<String, ServiceResult> getServiceResultsMap(){
85 return serviceResultsMap;
87 public static Map<String, ServiceResult> createResultsMap(){
88 return new HashMap<String, ServiceResult>();
91 private List<String> reportsList;
92 public List<String> getReportsList(){
96 public String toString(){
97 return "XmlReplay{"+this.basedir+", "+this.defaultAuthsMap+", "+this.dump+", "+this.reportsDir+'}';
100 // ============== METHODS ===========================================================
102 /** Optional information method: call this method after instantiating this class using the constructor XmlReplay(String), which sets the basedir. Then you
103 * pass in your relative masterFilename to that basedir to this method, which will return true if the file is readable, valid xml, etc.
104 * Do this in preference to just seeing if File.exists(), because there are rules to finding the file relative to the maven test dir, yada, yada.
105 * This method makes it easy to have a development test file that you don't check in, so that dev tests can be missing gracefully, etc.
107 public boolean masterConfigFileExists(String masterFilename){
109 org.dom4j.Document doc = openMasterConfigFile(masterFilename);
114 } catch (Throwable t){
119 public org.dom4j.Document openMasterConfigFile(String masterFilename) throws FileNotFoundException {
120 String fullPath = Tools.glue(basedir, "/", masterFilename);
121 File f = new File(fullPath);
125 org.dom4j.Document document = getDocument(fullPath); //will check full path first, then checks relative to PWD.
126 if (document == null){
127 throw new FileNotFoundException("XmlReplay master control file ("+masterFilename+") not found in basedir: "+basedir+". Exiting test.");
132 /** specify the master config file, relative to getBaseDir(), but ignore any tests or testGroups in the master.
133 * @return a Document object, which you don't need to use: all options will be stored in XmlReplay instance.
135 public org.dom4j.Document readOptionsFromMasterConfigFile(String masterFilename) throws FileNotFoundException {
136 org.dom4j.Document document = openMasterConfigFile(masterFilename);
137 if (document == null){
138 throw new FileNotFoundException(masterFilename);
140 protoHostPort = document.selectSingleNode("/xmlReplayMaster/protoHostPort").getText().trim();
141 AuthsMap authsMap = readAuths(document);
142 setDefaultAuthsMap(authsMap);
143 Dump dump = XmlReplay.readDumpOptions(document);
148 public List<List<ServiceResult>> runMaster(String masterFilename) throws Exception {
149 return runMaster(masterFilename, true);
152 /** Creates new instances of XmlReplay, one for each controlFile specified in the master,
153 * and setting defaults from this instance, but not sharing ServiceResult objects or maps. */
154 public List<List<ServiceResult>> runMaster(String masterFilename, boolean readOptionsFromMaster) throws Exception {
155 List<List<ServiceResult>> list = new ArrayList<List<ServiceResult>>();
156 org.dom4j.Document document;
157 if (readOptionsFromMaster){
158 document = readOptionsFromMasterConfigFile(masterFilename);
160 document = openMasterConfigFile(masterFilename);
163 throw new FileNotFoundException(masterFilename);
165 String controlFile, testGroup, test;
167 runNodes = document.selectNodes("/xmlReplayMaster/run");
168 for (Node runNode : runNodes) {
169 controlFile = runNode.valueOf("@controlFile");
170 testGroup = runNode.valueOf("@testGroup");
171 test = runNode.valueOf("@test"); //may be empty
173 //Create a new instance and clone only config values, not any results maps.
174 XmlReplay replay = new XmlReplay(basedir, this.reportsDir);
175 replay.setControlFileName(controlFile);
176 replay.setProtoHostPort(protoHostPort);
177 replay.setAutoDeletePOSTS(isAutoDeletePOSTS());
178 replay.setDump(dump);
179 replay.setDefaultAuthsMap(getDefaultAuthsMap());
181 //Now run *that* instance.
182 List<ServiceResult> results = replay.runTests(testGroup, test);
184 this.reportsList.addAll(replay.getReportsList()); //Add all the reports from the inner replay, to our master replay's reportsList, to generate the index.html file.
186 XmlReplayReport.saveIndexForMaster(basedir, reportsDir, masterFilename, this.reportsList);
190 /** Use this if you wish to run named tests within a testGroup, otherwise call runTestGroup(). */
191 public List<ServiceResult> runTests(String testGroupID, String testID) throws Exception {
192 List<ServiceResult> result = runXmlReplayFile(this.basedir,
193 this.controlFileName,
196 this.serviceResultsMap,
197 this.autoDeletePOSTS,
200 this.defaultAuthsMap,
206 /** Use this if you wish to specify just ONE test to run within a testGroup, otherwise call runTestGroup(). */
207 public ServiceResult runTest(String testGroupID, String testID) throws Exception {
208 List<ServiceResult> result = runXmlReplayFile(this.basedir,
209 this.controlFileName,
212 this.serviceResultsMap,
213 this.autoDeletePOSTS,
216 this.defaultAuthsMap,
219 if (result.size()>1){
220 throw new IndexOutOfBoundsException("Multiple ("+result.size()+") tests with ID='"+testID+"' were found within test group '"+testGroupID+"', but there should only be one test per ID attribute.");
222 return result.get(0);
225 /** Use this if you wish to run all tests within a testGroup.*/
226 public List<ServiceResult> runTestGroup(String testGroupID) throws Exception {
227 //NOTE: calling runTest with empty testID runs all tests in a test group, but don't expose this fact.
228 // Expose this method (runTestGroup) instead.
229 return runTests(testGroupID, "");
232 public List<ServiceResult> autoDelete(String logName){
233 return autoDelete(this.serviceResultsMap, logName, 0);
236 /** Use this method to clean up resources created on the server that returned CSIDs, if you have
237 * specified autoDeletePOSTS==false, which means you are managing the cleanup yourself.
238 * @param serviceResultsMap a Map of ServiceResult objects, which will contain ServiceResult.deleteURL.
239 * @return a List<String> of debug info about which URLs could not be deleted.
241 private static List<ServiceResult> autoDelete(Map<String, ServiceResult> serviceResultsMap, String logName, int reattempt) {
242 List<ServiceResult> results = new ArrayList<ServiceResult>();
243 HashMap<String, ServiceResult> reattemptList = new HashMap<String, ServiceResult>();
244 int deleteFailures = 0;
245 for (ServiceResult pr : serviceResultsMap.values()) {
247 if (Tools.notEmpty(pr.deleteURL)){
248 ServiceResult deleteResult = XmlReplayTransport.doDELETE(pr.deleteURL, pr.auth, pr.testID, "[autodelete:"+logName+"]");
249 if (deleteResult.gotExpectedResult() == false || deleteResult.responseCode != 200) {
250 reattemptList.put(REATTEMPT_KEY + deleteFailures++, pr); // We need to try again after our dependents have been deleted. cow()
252 results.add(deleteResult);
254 ServiceResult errorResult = new ServiceResult();
255 errorResult.fullURL = pr.fullURL;
256 errorResult.testGroupID = pr.testGroupID;
257 errorResult.fromTestID = pr.fromTestID;
258 errorResult.overrideGotExpectedResult();
259 results.add(errorResult);
261 } catch (Throwable t){
262 String s = (pr!=null) ? "ERROR while cleaning up ServiceResult map: "+pr+" for "+pr.deleteURL+" :: "+t
263 : "ERROR while cleaning up ServiceResult map (null ServiceResult): "+t;
264 System.err.println(s);
265 ServiceResult errorResult = new ServiceResult();
266 errorResult.fullURL = pr.fullURL;
267 errorResult.testGroupID = pr.testGroupID;
268 errorResult.fromTestID = pr.fromTestID;
269 errorResult.error = s;
270 results.add(errorResult);
274 // If there were things we had trouble deleting, it might have been because they had dependents that
275 // needed to be deleted first. Therefore, we're going to try again and again (recursively) up until we reach
276 // our MAX_REATTEMPTS limit.
278 if (reattemptList.size() > 0 && reattempt < MAX_REATTEMPTS) {
279 return autoDelete(reattemptList, logName, ++reattempt); // recursive call
285 public static class AuthsMap {
286 Map<String,String> map;
288 public String getDefaultAuth(){
289 return map.get(defaultID);
291 public String toString(){
292 return "AuthsMap: {default='"+defaultID+"'; "+map.keySet()+'}';
296 public static AuthsMap readAuths(org.dom4j.Document document){
297 Map<String, String> map = new HashMap<String, String>();
298 List<Node> authNodes = document.selectNodes("//auths/auth");
299 for (Node auth : authNodes) {
300 map.put(auth.valueOf("@ID"), auth.getStringValue());
302 AuthsMap authsMap = new AuthsMap();
303 Node auths = document.selectSingleNode("//auths");
304 String defaultID = "";
306 defaultID = auths.valueOf("@default");
309 authsMap.defaultID = defaultID;
313 public static class Dump {
314 public boolean payloads = false;
315 //public static final ServiceResult.DUMP_OPTIONS dumpServiceResultOptions = ServiceResult.DUMP_OPTIONS;
316 public ServiceResult.DUMP_OPTIONS dumpServiceResult = ServiceResult.DUMP_OPTIONS.minimal;
317 public String toString(){
318 return "payloads: "+payloads+" dumpServiceResult: "+dumpServiceResult;
322 public static Dump getDumpConfig(){
326 public static Dump readDumpOptions(org.dom4j.Document document){
327 Dump dump = getDumpConfig();
328 Node dumpNode = document.selectSingleNode("//dump");
329 if (dumpNode != null){
330 dump.payloads = Tools.isTrue(dumpNode.valueOf("@payloads"));
331 String dumpServiceResultStr = dumpNode.valueOf("@dumpServiceResult");
332 if (Tools.notEmpty(dumpServiceResultStr)){
333 dump.dumpServiceResult = ServiceResult.DUMP_OPTIONS.valueOf(dumpServiceResultStr);
339 private static class PartsStruct {
340 public List<Map<String,String>> varsList = new ArrayList<Map<String,String>>();
341 String responseFilename = "";
342 String overrideTestID = "";
343 String startElement = "";
346 public static PartsStruct readParts(Node testNode, final String testID, String xmlReplayBaseDir){
347 PartsStruct resultPartsStruct = new PartsStruct();
348 resultPartsStruct.responseFilename = testNode.valueOf("filename");
349 resultPartsStruct.startElement = testNode.valueOf("startElement");
350 resultPartsStruct.label = testNode.valueOf("label");
351 String responseFilename = testNode.valueOf("filename");
352 if (Tools.notEmpty(responseFilename)){
353 resultPartsStruct.responseFilename = xmlReplayBaseDir + '/' + responseFilename;
354 List<Node> varNodes = testNode.selectNodes("vars/var");
355 readVars(testNode, varNodes, resultPartsStruct);
357 return resultPartsStruct;
360 private static void readVars(Node testNode, List<Node> varNodes, PartsStruct resultPartsStruct){
361 Map<String,String> vars = new HashMap<String,String>();
362 resultPartsStruct.varsList.add(vars);
363 //System.out.println("### vars: "+vars.size()+" ########");
364 for (Node var: varNodes){
365 String ID = var.valueOf("@ID");
366 String value = var.getText();
367 //System.out.println("ID: "+ID+" value: "+value);
368 vars.put(ID, value); //vars is already part of resultPartsStruct.varsList
370 //System.out.println("### end-vars ########");
376 private static String fixupFullURL(String fullURL, String protoHostPort, String uri){
377 if ( ! uri.startsWith(protoHostPort)){
378 fullURL = Tools.glue(protoHostPort, "/", uri);
385 private static String fromTestID(String fullURL, Node testNode, Map<String, ServiceResult> serviceResultsMap){
386 String fromTestID = testNode.valueOf("fromTestID");
387 if (Tools.notEmpty(fromTestID)){
388 ServiceResult getPR = serviceResultsMap.get(fromTestID);
390 fullURL = Tools.glue(fullURL, "/", getPR.location);
396 private static String CSIDfromTestID(Node testNode, Map<String, ServiceResult> serviceResultsMap){
398 String fromTestID = testNode.valueOf("fromTestID");
399 if (Tools.notEmpty(fromTestID)){
400 ServiceResult getPR = serviceResultsMap.get(fromTestID);
402 result = getPR.location;
408 public static org.dom4j.Document getDocument(String xmlFileName) {
409 org.dom4j.Document document = null;
410 SAXReader reader = new SAXReader();
412 document = reader.read(xmlFileName);
413 } catch (DocumentException e) {
414 System.out.println("ERROR reading document: "+e);
415 //e.printStackTrace();
420 protected static String validateResponseSinglePayload(ServiceResult serviceResult,
421 Map<String, ServiceResult> serviceResultsMap,
422 PartsStruct expectedResponseParts,
423 XmlReplayEval evalStruct)
426 byte[] b = FileUtils.readFileToByteArray(new File(expectedResponseParts.responseFilename));
427 String expectedPartContent = new String(b);
428 Map<String,String> vars = expectedResponseParts.varsList.get(0); //just one part, so just one varsList. Must be there, even if empty.
429 expectedPartContent = evalStruct.eval(expectedPartContent, serviceResultsMap, vars, evalStruct.jexl, evalStruct.jc);
430 serviceResult.expectedContentExpanded = expectedPartContent;
431 String label = "NOLABEL";
432 String leftID = "{from expected part, label:"+label+" filename: "+expectedResponseParts.responseFilename+"}";
433 String rightID = "{from server, label:"+label
434 +" fromTestID: "+serviceResult.fromTestID
435 +" URL: "+serviceResult.fullURL
437 String startElement = expectedResponseParts.startElement;
438 String partLabel = expectedResponseParts.label;
439 if (Tools.isBlank(startElement)){
440 if (Tools.notBlank(partLabel))
441 startElement = "/document/*[local-name()='"+partLabel+"']";
443 TreeWalkResults.MatchSpec matchSpec = TreeWalkResults.MatchSpec.createDefault();
444 TreeWalkResults list =
445 XmlCompareJdom.compareParts(expectedPartContent,
447 serviceResult.result,
451 serviceResult.addPartSummary(label, list);
455 protected static String validateResponse(ServiceResult serviceResult,
456 Map<String, ServiceResult> serviceResultsMap,
457 PartsStruct expectedResponseParts,
458 XmlReplayEval evalStruct){
460 if (expectedResponseParts == null) return OK;
461 if (serviceResult == null) return OK;
462 if (serviceResult.result.length() == 0) return OK;
464 return validateResponseSinglePayload(serviceResult, serviceResultsMap, expectedResponseParts, evalStruct);
465 } catch (Exception e){
466 String err = "ERROR in XmlReplay.validateResponse() : "+e;
471 //================= runXmlReplayFile ======================================================
473 public static List<ServiceResult> runXmlReplayFile(String xmlReplayBaseDir,
474 String controlFileName,
477 Map<String, ServiceResult> serviceResultsMap,
478 boolean param_autoDeletePOSTS,
480 String protoHostPortParam,
481 AuthsMap defaultAuths,
482 List<String> reportsList,
485 //Internally, we maintain two collections of ServiceResult:
486 // the first is the return value of this method.
487 // the second is the serviceResultsMap, which is used for keeping track of CSIDs created by POSTs, for later reference by DELETE, etc.
488 List<ServiceResult> results = new ArrayList<ServiceResult>();
490 XmlReplayReport report = new XmlReplayReport(reportsDir);
492 String controlFile = Tools.glue(xmlReplayBaseDir, "/", controlFileName);
493 org.dom4j.Document document;
494 document = getDocument(controlFile); //will check full path first, then checks relative to PWD.
496 throw new FileNotFoundException("XmlReplay control file ("+controlFileName+") not found in basedir: "+xmlReplayBaseDir+" Exiting test.");
498 String protoHostPort;
499 if (Tools.isEmpty(protoHostPortParam)){
500 protoHostPort = document.selectSingleNode("/xmlReplay/protoHostPort").getText().trim();
501 System.out.println("DEPRECATED: Using protoHostPort ('"+protoHostPort+"') from xmlReplay file ('"+controlFile+"'), not master.");
503 protoHostPort = protoHostPortParam;
505 if (Tools.isEmpty(protoHostPort)){
506 throw new Exception("XmlReplay control file must have a protoHostPort element");
510 AuthsMap authsMap = readAuths(document);
511 if (authsMap.map.size()==0){
512 authsMap = defaultAuths;
513 authsMapINFO = "Using defaultAuths from master file: "+defaultAuths;
515 authsMapINFO = "Using AuthsMap from control file: "+authsMap;
518 report.addTestGroup(testGroupID, controlFileName); //controlFileName is just the short name, without the full path.
519 String xmlReplayHeader = "========================================================================"
520 +"\r\nXmlReplay running:"
521 +"\r\n controlFile: "+ (new File(controlFile).getCanonicalPath())
522 +"\r\n protoHostPort: "+protoHostPort
523 +"\r\n testGroup: "+testGroupID
524 + (Tools.notEmpty(oneTestID) ? "\r\n oneTestID: "+oneTestID : "")
525 +"\r\n AuthsMap: "+authsMapINFO
526 +"\r\n param_autoDeletePOSTS: "+param_autoDeletePOSTS
527 +"\r\n Dump info: "+dump
528 +"\r\n========================================================================"
530 report.addRunInfo(xmlReplayHeader);
532 System.out.println(xmlReplayHeader);
534 String autoDeletePOSTS = "";
535 List<Node> testgroupNodes;
536 if (Tools.notEmpty(testGroupID)){
537 testgroupNodes = document.selectNodes("//testGroup[@ID='"+testGroupID+"']");
539 testgroupNodes = document.selectNodes("//testGroup");
542 JexlEngine jexl = new JexlEngine(); // Used for expression language expansion from uri field.
543 XmlReplayEval evalStruct = new XmlReplayEval();
544 evalStruct.serviceResultsMap = serviceResultsMap;
545 evalStruct.jexl = jexl;
547 for (Node testgroup : testgroupNodes) {
549 XmlReplayEval.MapContextWKeys jc = new XmlReplayEval.MapContextWKeys();//MapContext(); //Get a new JexlContext for each test group.
552 autoDeletePOSTS = testgroup.valueOf("@autoDeletePOSTS");
554 if (Tools.notEmpty(oneTestID)){
555 tests = testgroup.selectNodes("test[@ID='"+oneTestID+"']");
557 tests = testgroup.selectNodes("test");
559 String authForTest = "";
560 int testElementIndex = -1;
562 for (Node testNode : tests) {
563 long startTime = System.currentTimeMillis();
566 String testID = testNode.valueOf("@ID");
567 String testIDLabel = Tools.notEmpty(testID) ? (testGroupID+'.'+testID) : (testGroupID+'.'+testElementIndex);
568 String method = testNode.valueOf("method");
569 String contentType = testNode.valueOf("contentType");
570 String uri = testNode.valueOf("uri");
571 String fullURL = Tools.glue(protoHostPort, "/", uri);
573 if (contentType == null || contentType.equals("")) {
574 contentType = XmlReplayTransport.APPLICATION_XML;
577 String currentAuthForTest = null;
578 String authIDForTest = testNode.valueOf("@auth");
580 if (Tools.notEmpty(authIDForTest)){
581 currentAuthForTest = authsMap.map.get(authIDForTest);
584 String tokenAuthExpression = testNode.valueOf("@tokenauth");
586 if (Tools.notEmpty(tokenAuthExpression)){
587 currentAuthForTest = "Bearer " + evalStruct.eval(tokenAuthExpression, serviceResultsMap, null, jexl, jc);
591 if (Tools.notEmpty(currentAuthForTest)){
592 authForTest = currentAuthForTest; //else just run with current from last loop;
594 if (Tools.isEmpty(authForTest)){
595 authForTest = defaultAuths.getDefaultAuth();
598 if (uri.indexOf("$")>-1){
599 uri = evalStruct.eval(uri, serviceResultsMap, null, jexl, jc);
601 fullURL = fixupFullURL(fullURL, protoHostPort, uri);
603 List<Integer> expectedCodes = new ArrayList<Integer>();
604 String expectedCodesStr = testNode.valueOf("expectedCodes");
605 if (Tools.notEmpty(expectedCodesStr)){
606 String[] codesArray = expectedCodesStr.split(",");
607 for (String code : codesArray){
608 expectedCodes.add(new Integer(code.trim()));
612 Node responseNode = testNode.selectSingleNode("response");
613 PartsStruct expectedResponseParts = null;
614 if (responseNode!=null){
615 expectedResponseParts = PartsStruct.readParts(responseNode, testID, xmlReplayBaseDir);
616 //System.out.println("reponse parts: >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"+expectedResponseParts);
619 ServiceResult serviceResult;
620 boolean isPOST = method.equalsIgnoreCase("POST");
621 boolean isPUT = method.equalsIgnoreCase("PUT");
622 if ( isPOST || isPUT ) {
623 PartsStruct parts = PartsStruct.readParts(testNode, testID, xmlReplayBaseDir);
624 if (Tools.notEmpty(parts.overrideTestID)) {
625 testID = parts.overrideTestID;
628 String csid = CSIDfromTestID(testNode, serviceResultsMap);
629 if (Tools.notEmpty(csid)) uri = Tools.glue(uri, "/", csid+"/items/");
631 uri = fromTestID(uri, testNode, serviceResultsMap);
633 //vars only make sense in two contexts: POST/PUT, because you are submitting another file with internal expressions,
634 // and in <response> nodes. For GET, DELETE, there is no payload, so all the URLs with potential expressions are right there in the testNode.
635 Map<String,String> vars = null;
636 if (parts.varsList.size()>0){
637 vars = parts.varsList.get(0);
639 serviceResult = XmlReplayTransport.doPOST_PUTFromXML(parts.responseFilename, vars, protoHostPort, uri, method, contentType, evalStruct, authForTest, testIDLabel);
641 serviceResult.addVars(vars);
643 results.add(serviceResult);
645 serviceResultsMap.put(testID, serviceResult); //PUTs do not return a Location, so don't add PUTs to serviceResultsMap.
647 fullURL = fixupFullURL(fullURL, protoHostPort, uri);
648 } else if (method.equalsIgnoreCase("DELETE")){
649 String fromTestID = testNode.valueOf("fromTestID");
650 ServiceResult pr = serviceResultsMap.get(fromTestID);
652 serviceResult = XmlReplayTransport.doDELETE(pr.deleteURL, authForTest, testIDLabel, fromTestID);
653 serviceResult.fromTestID = fromTestID;
654 if (expectedCodes.size()>0){
655 serviceResult.expectedCodes = expectedCodes;
657 results.add(serviceResult);
658 if (serviceResult.codeInSuccessRange(serviceResult.responseCode)){ //gotExpectedResult depends on serviceResult.expectedCodes.
659 serviceResultsMap.remove(fromTestID);
662 if (Tools.notEmpty(fromTestID)){
663 serviceResult = new ServiceResult();
664 serviceResult.responseCode = 0;
665 serviceResult.error = "ID not found in element fromTestID: "+fromTestID;
666 System.err.println("****\r\nServiceResult: "+serviceResult.error+". SKIPPING TEST. Full URL: "+fullURL);
668 serviceResult = XmlReplayTransport.doDELETE(fullURL, authForTest, testID, fromTestID);
670 serviceResult.fromTestID = fromTestID;
671 results.add(serviceResult);
673 } else if (method.equalsIgnoreCase("GET")){
674 fullURL = fromTestID(fullURL, testNode, serviceResultsMap);
675 serviceResult = XmlReplayTransport.doGET(fullURL, authForTest, testIDLabel);
676 results.add(serviceResult);
677 serviceResultsMap.put(testID, serviceResult);
678 } else if (method.equalsIgnoreCase("LIST")){
679 fullURL = fixupFullURL(fullURL, protoHostPort, uri);
680 String listQueryParams = ""; //TODO: empty for now, later may pick up from XML control file.
681 serviceResult = XmlReplayTransport.doLIST(fullURL, listQueryParams, authForTest, testIDLabel);
682 results.add(serviceResult);
683 serviceResultsMap.put(testID, serviceResult);
685 throw new Exception("HTTP method not supported by XmlReplay: "+method);
688 serviceResult.testID = testID;
689 serviceResult.fullURL = fullURL;
690 serviceResult.auth = authForTest;
691 serviceResult.method = method;
692 if (expectedCodes.size()>0){
693 serviceResult.expectedCodes = expectedCodes;
695 if (Tools.isEmpty(serviceResult.testID)) serviceResult.testID = testIDLabel;
696 if (Tools.isEmpty(serviceResult.testGroupID)) serviceResult.testGroupID = testGroupID;
698 Node expectedLevel = testNode.selectSingleNode("response/expected");
699 if (expectedLevel!=null){
700 String level = expectedLevel.valueOf("@level");
701 serviceResult.payloadStrictness = level;
703 //=====================================================
704 // ALL VALIDATION FOR ALL REQUESTS IS DONE HERE:
705 //=====================================================
706 boolean hasError = false;
707 String vError = validateResponse(serviceResult, serviceResultsMap, expectedResponseParts, evalStruct);
708 if (Tools.notEmpty(vError)){
709 serviceResult.error = vError;
710 serviceResult.failureReason = " : VALIDATION ERROR; ";
713 if (hasError == false){
714 hasError = ! serviceResult.gotExpectedResult();
717 boolean doingAuto = (dump.dumpServiceResult == ServiceResult.DUMP_OPTIONS.auto);
718 String serviceResultRow = serviceResult.dump(dump.dumpServiceResult, hasError)+"; time:"+(System.currentTimeMillis()-startTime);
719 String leader = (dump.dumpServiceResult == ServiceResult.DUMP_OPTIONS.detailed) ? "XmlReplay:"+testIDLabel+": ": "";
721 report.addTestResult(serviceResult);
723 if ( (dump.dumpServiceResult == ServiceResult.DUMP_OPTIONS.detailed)
724 || (dump.dumpServiceResult == ServiceResult.DUMP_OPTIONS.full) ){
725 System.out.println("\r\n#---------------------#");
727 System.out.println(timeString()+" "+leader+serviceResultRow+"\r\n");
728 if (dump.payloads || (doingAuto&&hasError) ) {
729 if (Tools.notBlank(serviceResult.requestPayload)){
730 System.out.println("\r\n========== request payload ===============");
731 System.out.println(serviceResult.requestPayload);
732 System.out.println("==========================================\r\n");
735 if (dump.payloads || (doingAuto&&hasError)) {
736 if (Tools.notBlank(serviceResult.result)){
737 System.out.println("\r\n========== response payload ==============");
738 System.out.println(serviceResult.result);
739 System.out.println("==========================================\r\n");
742 } catch (Throwable t) {
743 String msg = "ERROR: XmlReplay experienced an error in a test node: "+testNode+" Throwable: "+t;
744 System.out.println(msg);
745 System.out.println(Tools.getStackTrace(t));
746 ServiceResult serviceResult = new ServiceResult();
747 serviceResult.error = msg;
748 serviceResult.failureReason = " : SYSTEM ERROR; ";
749 results.add(serviceResult);
752 if (Tools.isTrue(autoDeletePOSTS) && param_autoDeletePOSTS){
753 autoDelete(serviceResultsMap, "default", 0);
757 //=== Now spit out the HTML report file ===
758 File m = new File(controlFileName);
759 String localName = m.getName();//don't instantiate, just use File to extract file name without directory.
760 String reportName = localName+'-'+testGroupID+".html";
762 File resultFile = report.saveReport(xmlReplayBaseDir, reportsDir, reportName);
763 if (resultFile!=null) {
764 String toc = report.getTOC(reportName);
765 reportsList.add(toc);
767 //================================
772 private static String timeString() {
773 java.util.Date date= new java.util.Date();
774 java.sql.Timestamp ts = new java.sql.Timestamp(date.getTime());
775 return ts.toString();
779 //======================== MAIN ===================================================================
781 private static Options createOptions() {
782 Options options = new Options();
783 options.addOption("xmlReplayBaseDir", true, "default/basedir");
787 public static String usage(){
788 String result = "org.collectionspace.services.IntegrationTests.xmlreplay.XmlReplay {args}\r\n"
789 +" -xmlReplayBaseDir <dir> \r\n"
790 +" You may also override these with system args, e.g.: \r\n"
791 +" -DxmlReplayBaseDir=/path/to/dir \r\n"
792 +" These may also be passed in via the POM.\r\n"
793 +" You can also set these system args, e.g.: \r\n"
794 +" -DtestGroupID=<oneID> \r\n"
795 +" -DtestID=<one TestGroup ID>"
796 +" -DautoDeletePOSTS=<true|false> \r\n"
797 +" (note: -DautoDeletePOSTS won't force deletion if set to false in control file.";
801 private static String opt(CommandLine line, String option){
803 String fromProps = System.getProperty(option);
804 if (Tools.notEmpty(fromProps)){
810 result = line.getOptionValue(option);
817 public static void main(String[]args) throws Exception {
818 Options options = createOptions();
819 //System.out.println("System CLASSPATH: "+prop.getProperty("java.class.path", null));
820 CommandLineParser parser = new GnuParser();
822 // parse the command line arguments
823 CommandLine line = parser.parse(options, args);
825 String xmlReplayBaseDir = opt(line, "xmlReplayBaseDir");
826 String reportsDir = opt(line, "reportsDir");
827 String testGroupID = opt(line, "testGroupID");
828 String testID = opt(line, "testID");
829 String autoDeletePOSTS = opt(line, "autoDeletePOSTS");
830 String dumpResults = opt(line, "dumpResults");
831 String controlFilename = opt(line, "controlFilename");
832 String xmlReplayMaster = opt(line, "xmlReplayMaster");
834 if (Tools.isBlank(reportsDir)){
835 reportsDir = xmlReplayBaseDir + XmlReplayTest.REPORTS_DIRNAME;
837 reportsDir = Tools.fixFilename(reportsDir);
838 xmlReplayBaseDir = Tools.fixFilename(xmlReplayBaseDir);
839 controlFilename = Tools.fixFilename(controlFilename);
841 boolean bAutoDeletePOSTS = true;
842 if (Tools.notEmpty(autoDeletePOSTS)) {
843 bAutoDeletePOSTS = Tools.isTrue(autoDeletePOSTS);
845 boolean bDumpResults = false;
846 if (Tools.notEmpty(dumpResults)) {
847 bDumpResults = Tools.isTrue(autoDeletePOSTS);
849 if (Tools.isEmpty(xmlReplayBaseDir)){
850 System.err.println("ERROR: xmlReplayBaseDir was not specified.");
853 File f = new File(Tools.glue(xmlReplayBaseDir, "/", controlFilename));
854 if (Tools.isEmpty(xmlReplayMaster) && !f.exists()){
855 System.err.println("Control file not found: "+f.getCanonicalPath());
858 File fMaster = new File(Tools.glue(xmlReplayBaseDir, "/", xmlReplayMaster));
859 if (Tools.notEmpty(xmlReplayMaster) && !fMaster.exists()){
860 System.err.println("Master file not found: "+fMaster.getCanonicalPath());
864 String xmlReplayBaseDirResolved = (new File(xmlReplayBaseDir)).getCanonicalPath();
865 System.out.println("XmlReplay ::"
866 + "\r\n xmlReplayBaseDir: "+xmlReplayBaseDir
867 + "\r\n xmlReplayBaseDir(resolved): "+xmlReplayBaseDirResolved
868 + "\r\n controlFilename: "+controlFilename
869 + "\r\n xmlReplayMaster: "+xmlReplayMaster
870 + "\r\n testGroupID: "+testGroupID
871 + "\r\n testID: "+testID
872 + "\r\n autoDeletePOSTS: "+bAutoDeletePOSTS
873 + (Tools.notEmpty(xmlReplayMaster)
874 ? ("\r\n will use master file: "+fMaster.getCanonicalPath())
875 : ("\r\n will use control file: "+f.getCanonicalPath()) )
878 if (Tools.notEmpty(xmlReplayMaster)){
879 if (Tools.notEmpty(controlFilename)){
880 System.out.println("WARN: controlFilename: "+controlFilename+" will not be used because master was specified. Running master: "+xmlReplayMaster);
882 XmlReplay replay = new XmlReplay(xmlReplayBaseDirResolved, reportsDir);
883 replay.readOptionsFromMasterConfigFile(xmlReplayMaster);
884 replay.setAutoDeletePOSTS(bAutoDeletePOSTS);
885 Dump dumpFromMaster = replay.getDump();
886 dumpFromMaster.payloads = Tools.isTrue(dumpResults);
887 replay.setDump(dumpFromMaster);
888 replay.runMaster(xmlReplayMaster, false); //false, because we already just read the options, and override a few.
890 Dump dump = getDumpConfig();
891 dump.payloads = Tools.isTrue(dumpResults);
892 List<String> reportsList = new ArrayList<String>();
893 runXmlReplayFile(xmlReplayBaseDirResolved, controlFilename, testGroupID, testID, createResultsMap(), bAutoDeletePOSTS, dump, "", null, reportsList, reportsDir);
894 System.out.println("DEPRECATED: reportsList is generated, but not dumped: "+reportsList.toString());
896 } catch (ParseException exp) {
897 // oops, something went wrong
898 System.err.println("Cmd-line parsing failed. Reason: " + exp.getMessage());
899 System.err.println(usage());
900 } catch (Exception e) {
901 System.out.println("Error : " + e.getMessage());