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 uri = testNode.valueOf("uri");
570 String fullURL = Tools.glue(protoHostPort, "/", uri);
572 String authIDForTest = testNode.valueOf("@auth");
573 String currentAuthForTest = authsMap.map.get(authIDForTest);
574 if (Tools.notEmpty(currentAuthForTest)){
575 authForTest = currentAuthForTest; //else just run with current from last loop;
577 if (Tools.isEmpty(authForTest)){
578 authForTest = defaultAuths.getDefaultAuth();
581 if (uri.indexOf("$")>-1){
582 uri = evalStruct.eval(uri, serviceResultsMap, null, jexl, jc);
584 fullURL = fixupFullURL(fullURL, protoHostPort, uri);
586 List<Integer> expectedCodes = new ArrayList<Integer>();
587 String expectedCodesStr = testNode.valueOf("expectedCodes");
588 if (Tools.notEmpty(expectedCodesStr)){
589 String[] codesArray = expectedCodesStr.split(",");
590 for (String code : codesArray){
591 expectedCodes.add(new Integer(code.trim()));
595 Node responseNode = testNode.selectSingleNode("response");
596 PartsStruct expectedResponseParts = null;
597 if (responseNode!=null){
598 expectedResponseParts = PartsStruct.readParts(responseNode, testID, xmlReplayBaseDir);
599 //System.out.println("reponse parts: >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"+expectedResponseParts);
602 ServiceResult serviceResult;
603 boolean isPOST = method.equalsIgnoreCase("POST");
604 boolean isPUT = method.equalsIgnoreCase("PUT");
605 if ( isPOST || isPUT ) {
606 PartsStruct parts = PartsStruct.readParts(testNode, testID, xmlReplayBaseDir);
607 if (Tools.notEmpty(parts.overrideTestID)) {
608 testID = parts.overrideTestID;
611 String csid = CSIDfromTestID(testNode, serviceResultsMap);
612 if (Tools.notEmpty(csid)) uri = Tools.glue(uri, "/", csid+"/items/");
614 uri = fromTestID(uri, testNode, serviceResultsMap);
616 //vars only make sense in two contexts: POST/PUT, because you are submitting another file with internal expressions,
617 // and in <response> nodes. For GET, DELETE, there is no payload, so all the URLs with potential expressions are right there in the testNode.
618 Map<String,String> vars = null;
619 if (parts.varsList.size()>0){
620 vars = parts.varsList.get(0);
622 serviceResult = XmlReplayTransport.doPOST_PUTFromXML(parts.responseFilename, vars, protoHostPort, uri, method, XmlReplayTransport.APPLICATION_XML, evalStruct, authForTest, testIDLabel);
624 serviceResult.addVars(vars);
626 results.add(serviceResult);
628 serviceResultsMap.put(testID, serviceResult); //PUTs do not return a Location, so don't add PUTs to serviceResultsMap.
630 fullURL = fixupFullURL(fullURL, protoHostPort, uri);
631 } else if (method.equalsIgnoreCase("DELETE")){
632 String fromTestID = testNode.valueOf("fromTestID");
633 ServiceResult pr = serviceResultsMap.get(fromTestID);
635 serviceResult = XmlReplayTransport.doDELETE(pr.deleteURL, authForTest, testIDLabel, fromTestID);
636 serviceResult.fromTestID = fromTestID;
637 if (expectedCodes.size()>0){
638 serviceResult.expectedCodes = expectedCodes;
640 results.add(serviceResult);
641 if (serviceResult.codeInSuccessRange(serviceResult.responseCode)){ //gotExpectedResult depends on serviceResult.expectedCodes.
642 serviceResultsMap.remove(fromTestID);
645 if (Tools.notEmpty(fromTestID)){
646 serviceResult = new ServiceResult();
647 serviceResult.responseCode = 0;
648 serviceResult.error = "ID not found in element fromTestID: "+fromTestID;
649 System.err.println("****\r\nServiceResult: "+serviceResult.error+". SKIPPING TEST. Full URL: "+fullURL);
651 serviceResult = XmlReplayTransport.doDELETE(fullURL, authForTest, testID, fromTestID);
653 serviceResult.fromTestID = fromTestID;
654 results.add(serviceResult);
656 } else if (method.equalsIgnoreCase("GET")){
657 fullURL = fromTestID(fullURL, testNode, serviceResultsMap);
658 serviceResult = XmlReplayTransport.doGET(fullURL, authForTest, testIDLabel);
659 results.add(serviceResult);
660 serviceResultsMap.put(testID, serviceResult);
661 } else if (method.equalsIgnoreCase("LIST")){
662 fullURL = fixupFullURL(fullURL, protoHostPort, uri);
663 String listQueryParams = ""; //TODO: empty for now, later may pick up from XML control file.
664 serviceResult = XmlReplayTransport.doLIST(fullURL, listQueryParams, authForTest, testIDLabel);
665 results.add(serviceResult);
666 serviceResultsMap.put(testID, serviceResult);
668 throw new Exception("HTTP method not supported by XmlReplay: "+method);
671 serviceResult.testID = testID;
672 serviceResult.fullURL = fullURL;
673 serviceResult.auth = authForTest;
674 serviceResult.method = method;
675 if (expectedCodes.size()>0){
676 serviceResult.expectedCodes = expectedCodes;
678 if (Tools.isEmpty(serviceResult.testID)) serviceResult.testID = testIDLabel;
679 if (Tools.isEmpty(serviceResult.testGroupID)) serviceResult.testGroupID = testGroupID;
681 Node expectedLevel = testNode.selectSingleNode("response/expected");
682 if (expectedLevel!=null){
683 String level = expectedLevel.valueOf("@level");
684 serviceResult.payloadStrictness = level;
686 //=====================================================
687 // ALL VALIDATION FOR ALL REQUESTS IS DONE HERE:
688 //=====================================================
689 boolean hasError = false;
690 String vError = validateResponse(serviceResult, serviceResultsMap, expectedResponseParts, evalStruct);
691 if (Tools.notEmpty(vError)){
692 serviceResult.error = vError;
693 serviceResult.failureReason = " : VALIDATION ERROR; ";
696 if (hasError == false){
697 hasError = ! serviceResult.gotExpectedResult();
700 boolean doingAuto = (dump.dumpServiceResult == ServiceResult.DUMP_OPTIONS.auto);
701 String serviceResultRow = serviceResult.dump(dump.dumpServiceResult, hasError)+"; time:"+(System.currentTimeMillis()-startTime);
702 String leader = (dump.dumpServiceResult == ServiceResult.DUMP_OPTIONS.detailed) ? "XmlReplay:"+testIDLabel+": ": "";
704 report.addTestResult(serviceResult);
706 if ( (dump.dumpServiceResult == ServiceResult.DUMP_OPTIONS.detailed)
707 || (dump.dumpServiceResult == ServiceResult.DUMP_OPTIONS.full) ){
708 System.out.println("\r\n#---------------------#");
710 System.out.println(timeString()+" "+leader+serviceResultRow+"\r\n");
711 if (dump.payloads || (doingAuto&&hasError) ) {
712 if (Tools.notBlank(serviceResult.requestPayload)){
713 System.out.println("\r\n========== request payload ===============");
714 System.out.println(serviceResult.requestPayload);
715 System.out.println("==========================================\r\n");
718 if (dump.payloads || (doingAuto&&hasError)) {
719 if (Tools.notBlank(serviceResult.result)){
720 System.out.println("\r\n========== response payload ==============");
721 System.out.println(serviceResult.result);
722 System.out.println("==========================================\r\n");
725 } catch (Throwable t) {
726 String msg = "ERROR: XmlReplay experienced an error in a test node: "+testNode+" Throwable: "+t;
727 System.out.println(msg);
728 System.out.println(Tools.getStackTrace(t));
729 ServiceResult serviceResult = new ServiceResult();
730 serviceResult.error = msg;
731 serviceResult.failureReason = " : SYSTEM ERROR; ";
732 results.add(serviceResult);
735 if (Tools.isTrue(autoDeletePOSTS) && param_autoDeletePOSTS){
736 autoDelete(serviceResultsMap, "default", 0);
740 //=== Now spit out the HTML report file ===
741 File m = new File(controlFileName);
742 String localName = m.getName();//don't instantiate, just use File to extract file name without directory.
743 String reportName = localName+'-'+testGroupID+".html";
745 File resultFile = report.saveReport(xmlReplayBaseDir, reportsDir, reportName);
746 if (resultFile!=null) {
747 String toc = report.getTOC(reportName);
748 reportsList.add(toc);
750 //================================
755 private static String timeString() {
756 java.util.Date date= new java.util.Date();
757 java.sql.Timestamp ts = new java.sql.Timestamp(date.getTime());
758 return ts.toString();
762 //======================== MAIN ===================================================================
764 private static Options createOptions() {
765 Options options = new Options();
766 options.addOption("xmlReplayBaseDir", true, "default/basedir");
770 public static String usage(){
771 String result = "org.collectionspace.services.IntegrationTests.xmlreplay.XmlReplay {args}\r\n"
772 +" -xmlReplayBaseDir <dir> \r\n"
773 +" You may also override these with system args, e.g.: \r\n"
774 +" -DxmlReplayBaseDir=/path/to/dir \r\n"
775 +" These may also be passed in via the POM.\r\n"
776 +" You can also set these system args, e.g.: \r\n"
777 +" -DtestGroupID=<oneID> \r\n"
778 +" -DtestID=<one TestGroup ID>"
779 +" -DautoDeletePOSTS=<true|false> \r\n"
780 +" (note: -DautoDeletePOSTS won't force deletion if set to false in control file.";
784 private static String opt(CommandLine line, String option){
786 String fromProps = System.getProperty(option);
787 if (Tools.notEmpty(fromProps)){
793 result = line.getOptionValue(option);
800 public static void main(String[]args) throws Exception {
801 Options options = createOptions();
802 //System.out.println("System CLASSPATH: "+prop.getProperty("java.class.path", null));
803 CommandLineParser parser = new GnuParser();
805 // parse the command line arguments
806 CommandLine line = parser.parse(options, args);
808 String xmlReplayBaseDir = opt(line, "xmlReplayBaseDir");
809 String reportsDir = opt(line, "reportsDir");
810 String testGroupID = opt(line, "testGroupID");
811 String testID = opt(line, "testID");
812 String autoDeletePOSTS = opt(line, "autoDeletePOSTS");
813 String dumpResults = opt(line, "dumpResults");
814 String controlFilename = opt(line, "controlFilename");
815 String xmlReplayMaster = opt(line, "xmlReplayMaster");
817 if (Tools.isBlank(reportsDir)){
818 reportsDir = xmlReplayBaseDir + XmlReplayTest.REPORTS_DIRNAME;
820 reportsDir = Tools.fixFilename(reportsDir);
821 xmlReplayBaseDir = Tools.fixFilename(xmlReplayBaseDir);
822 controlFilename = Tools.fixFilename(controlFilename);
824 boolean bAutoDeletePOSTS = true;
825 if (Tools.notEmpty(autoDeletePOSTS)) {
826 bAutoDeletePOSTS = Tools.isTrue(autoDeletePOSTS);
828 boolean bDumpResults = false;
829 if (Tools.notEmpty(dumpResults)) {
830 bDumpResults = Tools.isTrue(autoDeletePOSTS);
832 if (Tools.isEmpty(xmlReplayBaseDir)){
833 System.err.println("ERROR: xmlReplayBaseDir was not specified.");
836 File f = new File(Tools.glue(xmlReplayBaseDir, "/", controlFilename));
837 if (Tools.isEmpty(xmlReplayMaster) && !f.exists()){
838 System.err.println("Control file not found: "+f.getCanonicalPath());
841 File fMaster = new File(Tools.glue(xmlReplayBaseDir, "/", xmlReplayMaster));
842 if (Tools.notEmpty(xmlReplayMaster) && !fMaster.exists()){
843 System.err.println("Master file not found: "+fMaster.getCanonicalPath());
847 String xmlReplayBaseDirResolved = (new File(xmlReplayBaseDir)).getCanonicalPath();
848 System.out.println("XmlReplay ::"
849 + "\r\n xmlReplayBaseDir: "+xmlReplayBaseDir
850 + "\r\n xmlReplayBaseDir(resolved): "+xmlReplayBaseDirResolved
851 + "\r\n controlFilename: "+controlFilename
852 + "\r\n xmlReplayMaster: "+xmlReplayMaster
853 + "\r\n testGroupID: "+testGroupID
854 + "\r\n testID: "+testID
855 + "\r\n autoDeletePOSTS: "+bAutoDeletePOSTS
856 + (Tools.notEmpty(xmlReplayMaster)
857 ? ("\r\n will use master file: "+fMaster.getCanonicalPath())
858 : ("\r\n will use control file: "+f.getCanonicalPath()) )
861 if (Tools.notEmpty(xmlReplayMaster)){
862 if (Tools.notEmpty(controlFilename)){
863 System.out.println("WARN: controlFilename: "+controlFilename+" will not be used because master was specified. Running master: "+xmlReplayMaster);
865 XmlReplay replay = new XmlReplay(xmlReplayBaseDirResolved, reportsDir);
866 replay.readOptionsFromMasterConfigFile(xmlReplayMaster);
867 replay.setAutoDeletePOSTS(bAutoDeletePOSTS);
868 Dump dumpFromMaster = replay.getDump();
869 dumpFromMaster.payloads = Tools.isTrue(dumpResults);
870 replay.setDump(dumpFromMaster);
871 replay.runMaster(xmlReplayMaster, false); //false, because we already just read the options, and override a few.
873 Dump dump = getDumpConfig();
874 dump.payloads = Tools.isTrue(dumpResults);
875 List<String> reportsList = new ArrayList<String>();
876 runXmlReplayFile(xmlReplayBaseDirResolved, controlFilename, testGroupID, testID, createResultsMap(), bAutoDeletePOSTS, dump, "", null, reportsList, reportsDir);
877 System.out.println("DEPRECATED: reportsList is generated, but not dumped: "+reportsList.toString());
879 } catch (ParseException exp) {
880 // oops, something went wrong
881 System.err.println("Cmd-line parsing failed. Reason: " + exp.getMessage());
882 System.err.println(usage());
883 } catch (Exception e) {
884 System.out.println("Error : " + e.getMessage());