001: // Copyright © 2002-2007 Canoo Engineering AG, Switzerland.
002: package com.canoo.webtest.reporting;
003:
004: import java.io.PrintWriter;
005: import java.io.StringWriter;
006: import java.util.ArrayList;
007: import java.util.Iterator;
008: import java.util.List;
009: import java.util.Map;
010: import java.util.TreeMap;
011:
012: import org.apache.commons.lang.StringUtils;
013: import org.apache.tools.ant.BuildException;
014: import org.apache.tools.ant.Location;
015: import org.w3c.dom.Document;
016: import org.w3c.dom.Element;
017: import org.w3c.dom.Node;
018: import org.w3c.dom.NodeList;
019:
020: import com.canoo.webtest.boundary.PackageBoundary;
021: import com.canoo.webtest.engine.NameValuePair;
022: import com.canoo.webtest.engine.WebTestException;
023: import com.canoo.webtest.steps.HtmlParserMessage;
024:
025: /**
026: * The class converts a given {@link com.canoo.webtest.reporting.StepResult} into its XML representation.
027: * It is used to decouple report file handling ({@link XmlReporter}) and report content generation (this class).
028: *
029: * @author Marc Guillemot
030: */
031: public class XmlResultConverter {
032:
033: // Constants for all used XML elements
034: public static final String ROOT_ELEMENT = "summary";
035: public static final String TESTRESULT_ELEMENT = "testresult";
036: public static final String RESULTS_ELEMENT = "results";
037: public static final String CONFIG_ELEMENT = "config";
038: public static final String PARAMETER_ELEMENT = "parameter";
039: public static final String STEP_ELEMENT = "step";
040: public static final String FAILURE_ELEMENT = "failure";
041: public static final String ERROR_ELEMENT = "error";
042: public static final String STEP_RESULT_ATTRIBUTE = "result";
043: public static final String STEP_COMPLETED_ELEMENT = "completed";
044: public static final String STEP_FAILED_ELEMENT = "failed";
045: public static final String STEP_NOTEXECUTED_ELEMENT = "notexecuted";
046:
047: private RootStepResult fResult;
048:
049: public XmlResultConverter(final RootStepResult result) {
050: fResult = result;
051: }
052:
053: /**
054: * This method actually adds the XML representation of the test result
055: * to the provided XML document. If the document is new (empty), it
056: * creates the required root element. Then it appends a new
057: * child to the root containing this result representation.
058: *
059: * @param doc An XML document into which the report for the current
060: * test result shall be added
061: * @throws ReportCreationException Signals an inconsistency in the
062: * XML document
063: */
064: public void addToDocument(final Document doc)
065: throws ReportCreationException {
066: final NodeList nodeList = doc
067: .getElementsByTagName(ROOT_ELEMENT);
068: final Element rootElement;
069: if (nodeList.getLength() == 0) {
070: if (doc.getChildNodes().getLength() > 0) {
071: throw new ReportCreationException(
072: "Another root already exists!");
073: }
074: rootElement = doc.createElement(ROOT_ELEMENT);
075: doc.appendChild(rootElement);
076: addSummaryAttribute(rootElement);
077: } else {
078: rootElement = (Element) nodeList.item(0);
079: }
080: final Element testResult = addNewElement(rootElement,
081: TESTRESULT_ELEMENT);
082: addResultAttributes(testResult);
083: final String description = fResult.getWebtestDescription();
084: if (!StringUtils.isEmpty(description)) {
085: final Element descriptionElt = addNewElement(testResult,
086: "description");
087: addText(descriptionElt, description); // as node with text because extensions will come
088: }
089: addConfig(testResult);
090: addResults(testResult);
091: }
092:
093: private void addSummaryAttribute(final Element rootElement) {
094: addNewAttribute(rootElement, "Implementation-Title",
095: PackageBoundary.getImplementationTitle());
096: addNewAttribute(rootElement, "Implementation-Version",
097: PackageBoundary.getImplementationVersion());
098: }
099:
100: private void addResults(final Element parent) {
101: final Element resultsElement = addNewElement(parent,
102: RESULTS_ELEMENT);
103:
104: addSteps(resultsElement, fResult.getChildren());
105: addFailureIfNeeded(resultsElement);
106: addErrorIfNeeded(resultsElement);
107: }
108:
109: private void addErrorIfNeeded(final Element parent) {
110: if (fResult.isError()) {
111: final Element errorElement = addNewElement(parent,
112: ERROR_ELEMENT);
113: final Throwable error = fResult.getException();
114: addNewAttribute(errorElement, "exception", error.getClass()
115: .getName());
116: addFailureDetails(errorElement);
117:
118: // stacktrace
119: final Element stackTrace = addNewElement(errorElement,
120: "stacktrace");
121: final StringWriter s = new StringWriter();
122: error.printStackTrace(new PrintWriter(s));
123: addText(stackTrace, s.toString());
124: }
125: }
126:
127: private void addFailureIfNeeded(final Element parent) {
128: if (fResult.isFailure()) {
129: final Element failureElement = addNewElement(parent,
130: FAILURE_ELEMENT);
131: addFailureDetails(failureElement);
132: }
133: }
134:
135: private void addFailureDetails(final Element failure) {
136: final Throwable throwable = fResult.getException();
137:
138: final List locations = new ArrayList();
139: Throwable rootCause = throwable;
140: Throwable ex = throwable;
141: while (ex != null) {
142: rootCause = ex;
143: if (rootCause instanceof BuildException)
144: locations.add(((BuildException) rootCause)
145: .getLocation());
146: ex = ex.getCause();
147: }
148:
149: if (!locations.isEmpty()) {
150: // call stack of ant calls (if any)
151: final Element antCallStack = addNewElement(failure,
152: "antStack");
153: for (int i = locations.size() - 1; i >= 0; --i) {
154: final Location loc = (Location) locations.get(i);
155: final Element antCall = addNewElement(antCallStack,
156: "call");
157: antCall.setAttribute("filename", loc.getFileName());
158: antCall.setAttribute("line", String.valueOf(loc
159: .getLineNumber()));
160: }
161: }
162: if (rootCause instanceof WebTestException) {
163: final WebTestException e = (WebTestException) rootCause;
164:
165: addNewAttribute(failure, "message", e.getShortMessage());
166:
167: for (final Iterator iter = e.getDetails().iterator(); iter
168: .hasNext();) {
169: final NameValuePair detail = (NameValuePair) iter
170: .next();
171: final Element detailElt = addNewElement(failure,
172: "detail");
173: addNewAttribute(detailElt, "name", detail.getName());
174: addText(detailElt, detail.getValue());
175: }
176: } else {
177: addNewAttribute(failure, "message", throwable.getMessage());
178: }
179: }
180:
181: private void addSteps(final Element parent, final List steps) {
182: for (final Iterator iter = steps.iterator(); iter.hasNext();) {
183: addSingleTask(parent, (StepResult) iter.next());
184: }
185: }
186:
187: private void addSingleTask(final Element parent,
188: final StepResult stepResult) {
189: final Element stepElement = addNewElement(parent, STEP_ELEMENT);
190: stepElement.setAttribute("taskName", stepResult.getTaskName());
191: if (stepResult.getTaskDescription() != null) {
192: stepElement.setAttribute("description", stepResult
193: .getTaskDescription());
194: }
195:
196: addStepResult(stepElement, stepResult);
197:
198: final Map attributes = new TreeMap(stepResult.getAttributes());
199:
200: attributes.remove("description");
201: final String resultFile = (String) attributes
202: .remove("resultFilename");
203: addParameters(stepElement, attributes); // add attributes bevor resultFile
204: if (resultFile != null) {
205: final Element resultFileElement = addNewElement(
206: stepElement, "resultFile");
207: resultFileElement.setAttribute("name", resultFile);
208: }
209:
210: // html errors and warnings
211: if (!stepResult.getHtmlParserMessages().isEmpty()) {
212: final Element htmlParser = addNewElement(stepElement,
213: "htmlparser");
214: addParserMessages(htmlParser, stepResult
215: .getHtmlParserMessages());
216: }
217: addSteps(stepElement, stepResult.getChildren());
218: }
219:
220: /**
221: * creates the subnodes for the parser messages
222: *
223: * @param htmlParser the parent node
224: * @param messages the list of {@link HtmlParserMessage}
225: */
226: private static void addParserMessages(final Element htmlParser,
227: final List messages) {
228: for (Iterator iter = messages.iterator(); iter.hasNext();) {
229: final HtmlParserMessage msg = (HtmlParserMessage) iter
230: .next();
231: final Element elt = addNewElement(htmlParser, msg.getType()
232: .toString());
233: elt.setAttribute("url", msg.getURL().toString());
234: elt.setAttribute("line", String.valueOf(msg.getLine()));
235: elt.setAttribute("col", String.valueOf(msg.getColumn()));
236: elt.appendChild(htmlParser.getOwnerDocument()
237: .createTextNode(msg.getMessage()));
238: }
239: }
240:
241: private static void addStepResult(final Element parent,
242: final StepResult step) {
243: final long duration;
244: final String result;
245: if (step.isCompleted()) {
246: result = step.isSuccessful() ? STEP_COMPLETED_ELEMENT
247: : STEP_FAILED_ELEMENT;
248: duration = step.getDuration();
249: } else {
250: result = STEP_NOTEXECUTED_ELEMENT;
251: duration = 0;
252: }
253: parent.setAttribute(STEP_RESULT_ATTRIBUTE, result);
254: parent.setAttribute("duration", Long.toString(duration));
255: }
256:
257: private void addConfig(final Element resultElement) {
258: final Element configElement = addNewElement(resultElement,
259: CONFIG_ELEMENT);
260: addParameters(configElement, fResult.getConfig()
261: .getParameterDictionary());
262: }
263:
264: private void addParameters(final Element parent,
265: final Map parameterDictionary) {
266: for (final Iterator iter = parameterDictionary.entrySet()
267: .iterator(); iter.hasNext();) {
268: final Map.Entry parameter = (Map.Entry) iter.next();
269: final Element parameterElement = addNewElement(parent,
270: PARAMETER_ELEMENT);
271: addNewAttribute(parameterElement, "name", parameter
272: .getKey().toString());
273: final String value = String.valueOf(parameter.getValue());
274: addNewAttribute(parameterElement, "value", value);
275: }
276: }
277:
278: /**
279: * PRE: there must be at least one user test step.
280: * This is asserted in {@link com.canoo.webtest.ant.TestStepSequence#doExecute()}.
281: */
282: private void addResultAttributes(final Element resultElement) {
283: addNewAttribute(resultElement, "testspecname", fResult
284: .getWebtestName());
285: addNewAttribute(resultElement, "location", fResult
286: .getWebtestLocation().toString());
287:
288: addNewAttribute(resultElement, "starttime", fResult
289: .getStartDate().toString());
290: if (fResult.isCompleted()) {
291: addNewAttribute(resultElement, "endtime", fResult
292: .getEndDate().toString());
293: }
294: addNewAttribute(resultElement, "successful", fResult
295: .isSuccessful() ? "yes" : "no");
296: }
297:
298: private static void addNewAttribute(final Element parent,
299: final String name, final String value) {
300: parent.setAttribute(name, value);
301: }
302:
303: /**
304: * Creates an element with the given name and adds it to the parent
305: * @param parent the parent node
306: * @param name the name of the element to create
307: * @return the newly created element
308: */
309: private static Element addNewElement(final Node parent,
310: final String name) {
311: final Element e = parent.getOwnerDocument().createElement(name);
312: parent.appendChild(e);
313: return e;
314: }
315:
316: /**
317: * Creates a CDATA section with the provided text and adds it to the parent node
318: * @param parent the parent node
319: * @param text the text to add
320: */
321: private static void addText(final Node parent, final String text) {
322: parent.appendChild(parent.getOwnerDocument()
323: .createCDATASection(text));
324: }
325: }
|