Package com.canoo.webtest.reporting

Source Code of com.canoo.webtest.reporting.StepExecutionListener

package com.canoo.webtest.reporting;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.apache.tools.ant.BuildEvent;
import org.apache.tools.ant.Task;

import com.canoo.webtest.ant.IPropertyExpansionListener;
import com.canoo.webtest.ant.TestStepSequence;
import com.canoo.webtest.engine.Context;
import com.canoo.webtest.engine.ContextHelper;
import com.canoo.webtest.engine.MimeMap;
import com.canoo.webtest.engine.NOPBuildListener;
import com.canoo.webtest.engine.WebClientContext;
import com.canoo.webtest.steps.HtmlParserMessage;
import com.canoo.webtest.util.ConversionUtil;
import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.ScriptException;
import com.gargoylesoftware.htmlunit.WebResponse;
import com.gargoylesoftware.htmlunit.html.DomChangeEvent;
import com.gargoylesoftware.htmlunit.html.DomChangeListener;
import com.gargoylesoftware.htmlunit.html.HtmlPage;

/**
* Listens for task execution to extract {@link StepResult}s to generate the report.
*
* @author Marc Guillemot
*/
public class StepExecutionListener extends NOPBuildListener implements
    IStepResultListener, IPropertyExpansionListener {
  private static final Logger LOG = Logger.getLogger(StepExecutionListener.class);
  private StepResult fCurrentResult;
  private final HtmlParserMessage.MessageCollector fHtmlParserMessageCollector;
  private final Context fContext;
  private Page fPreviousCurrentResponse;
    private WebClientContext.StoredResponses fPreviousResponses;
    private boolean domChangedInLastPage = false;
    private int resultIndex = 0;
    private final Thread owningThread;
   
  private final DomChangeListener domChangeListener = new DomChangeListener()
  {
    public void nodeAdded(final DomChangeEvent event)
    {
      domChangedInLastPage = true;
    }
    public void nodeDeleted(final DomChangeEvent event)
    {
      domChangedInLastPage = true;
    }
  };


  private RootStepResult fRootResult;
  private boolean fIgnoreCurrentTasks;

  public StepExecutionListener(final Context context) {
    owningThread = Thread.currentThread();
    fContext = context;
    if (context.getConfig().isShowHtmlParserOutput()) {
      fHtmlParserMessageCollector = (HtmlParserMessage.MessageCollector) context
          .getWebClient().getHTMLParserListener();
    } else {
      fHtmlParserMessageCollector = null;
    }
  }

  /**
   * Gets the html messages that have been catched since for the step beeing
   * executed
   *
   * @return a (possibly empty) list of {@link HtmlParserMessage}
   */
  protected List getLastHtmlParserMessages() {
    if (fHtmlParserMessageCollector == null) {
      return new ArrayList();
    }
    return fHtmlParserMessageCollector.popAll();
  }

  /**
   * @return the fRootResult.
   */
  public RootStepResult getRootResult() {
    return fRootResult;
  }

  /**
   * Indicates if report information should be captured for this task
   *
   * @param task the task
   * @return <code>true</code> if report information should be captured
   */
  protected boolean isInteresting(final Task task) {
    if (fIgnoreCurrentTasks) {
      LOG.debug("currently ignoring: " + task);
      return false;
    }
    if (isToIgnore(task)) {
      LOG.debug("toIgnore: " + task);
      fIgnoreCurrentTasks = true;
      return false;
    }
    return !"sequential".equals(task.getTaskName());
  }

  protected boolean isToIgnore(final Task task) {
    // antlib, macrodef or property that are situated directly in the project are called
    // when a <antcall /> is used within a webtest but should be ignored
    return "antlib".equals(task.getTaskName());
  }

  /**
   * @see com.canoo.webtest.ant.IPropertyExpansionListener#propertiesExpanded(java.lang.String,java.lang.String)
   */
  public void propertiesExpanded(final String originalValue, final String expanded) {
    if (!isEventForMe()) {
      return;
    }
    fCurrentResult.propertiesExpanded(originalValue, expanded);
  }

  /**
   * Called by {@link com.canoo.webtest.steps.Step} to notify computed values
   * resulting of the execution of a step.
   *
   * @see com.canoo.webtest.reporting.IStepResultListener#stepResults(java.util.Map)
   */
  public void stepResults(final Map results) {
    if (!isEventForMe()) {
      return;
    }
    fCurrentResult.addStepResults(results);
  }

  public void taskFinished(final BuildEvent event) {
    if (!isEventForMe()) {
      return;
    }
    final Task task = event.getTask();
    LOG.trace("taskFinished: " + task.getTaskName(), event.getException());

    if (isToIgnore(task)) { // current execution of taskdef implicitely generated by antlib used is finished
      fIgnoreCurrentTasks = false;
      return;
    }
    if (!isInteresting(task)) {
      return;
    }

    if (fCurrentResult == null) {
      throw new IllegalStateException("No current result");
    }

    final List liHtmlParserMessages = getLastHtmlParserMessages();

    if (!fCurrentResult.isSuccessful()) {
      final Throwable exception = event.getException();
      // TODO: once reporting stabilized, generalize HtmlParserMessage to Message and
      // add in exception message from inner steps into the reporting.
//            if (exception != null) {
//                final String message = exception.getMessage();
//                if (message != null) liHtmlParserMessages.add(
//                        new HtmlParserMessage(HtmlParserMessage.Type.WARNING, UrlBoundary.tryCreateUrl("http://dummy.url"), message, 0, 0));
//            }
      fRootResult.setLastFailingTaskResult(fCurrentResult, exception);
    }
    fCurrentResult.taskFinished(task, event.getException(), liHtmlParserMessages);
   
    saveCurrentResponseIfNeeded(event);
   
    if (event.getException() != null && fPreviousResponses != null)
    {
      fContext.restoreResponses(fPreviousResponses);
      fPreviousResponses = null;
    }
    fPreviousCurrentResponse = fContext.getCurrentResponse();

    fCurrentResult = fCurrentResult.getParent();
  }

  private void saveCurrentResponseIfNeeded(final BuildEvent event)
  {
    if (!isSaveResponse()) {
      return;
    }

    final String savePrefix = getSavePrefix();
    final File file;

    final WebResponse resp;
    // new current response
    if (isNewResponse(event))
    {
      if (isExceptionWithResponse(event))
      {
        final Throwable cause = event.getException().getCause();
        if (cause instanceof FailingHttpStatusCodeException)
        {
          resp = ((FailingHttpStatusCodeException) cause).getResponse();
        }
        else
        {
          resp = ((ScriptException) cause).getPage().getWebResponse();
        }
      }
      else
      {
        resp = fContext.getCurrentResponse().getWebResponse();
        if (fContext.getCurrentResponse() instanceof HtmlPage) {
          final HtmlPage page = (HtmlPage) fContext.getCurrentResponse();
          page.addDomChangeListener(domChangeListener);
        }
      }
     
      file = getResponseFile(resp, savePrefix, fCurrentResult.getTaskName());
      ContextHelper.writeResponseFile(resp, file);
    }
    else if (domChangedInLastPage && fContext.getCurrentResponse() instanceof HtmlPage)
    {
      final HtmlPage page = (HtmlPage) fContext.getCurrentResponse();
      resp = page.getWebResponse();
      file = getResponseFile("html", savePrefix, fCurrentResult.getTaskName());
      writeStringToFile(file, page.asXml());
    }
    else
    {
      // nothing to dump
      return;
    }

    final File infoFile = new File(file.getParentFile(), file.getName() + ".info");
    LOG.debug("Writing additional info to " + infoFile);
    final String data = "url=" + resp.getRequestUrl();
    writeStringToFile(infoFile, data);

    fCurrentResult.getAttributes().put("resultFilename", file.getName());
    domChangedInLastPage = false;
  }

  private void writeStringToFile(final File file, final String text) {
        try
    {
      FileUtils.writeStringToFile(file, text, "UTF-8");
    }
    catch (final IOException e)
    {
      LOG.error("Failed to write to file " + file, e);
      throw new RuntimeException("Failed to write reporting data", e);
    }
  }

  private String getSavePrefix()
  {
    String prefix = fCurrentResult.getAttribute("save");
    if (!StringUtils.isEmpty(prefix))
      return prefix;
   
    prefix = fCurrentResult.getAttribute("savePrefix");
    return StringUtils.defaultIfEmpty(prefix, fContext.getConfig().getSavePrefix());
  }

  /**
   * Gets the file in which the response should be written
   *
   * @param fileExtension the file extension
   * @param fileNamePrefix the file prefix to use
   * @return the file
   */
  File getResponseFile(final String fileExtension, final String fileNamePrefix, final String fileNameSuffix) {
    final int namespaceIndex = fileNameSuffix.indexOf(":");
    final File resultDir = fContext.getConfig().getWebTestResultDir();

    final String prefix = StringUtils.leftPad(String.valueOf(++resultIndex), 3, '0');
   
    final String filename = prefix + "_" + fileNamePrefix
        + "_" + fileNameSuffix.substring(namespaceIndex == -1 ? 0 : namespaceIndex + 1)
        + "." + fileExtension;
    return new File(resultDir, filename);
  }

  /**
   * Gets the file in which the response should be written
   *
   * @param response     the response to write
   * @param fileNamePrefix the file prefix to use
   * @return the file
   */
  File getResponseFile(final WebResponse response, final String fileNamePrefix, final String fileNameSuffix) {
    String contentType = response.getContentType();
    contentType = MimeMap.adjustMimeTypeIfNeeded(contentType, response.getRequestUrl().toString());
    final String extension = MimeMap.getExtension(contentType);

    return getResponseFile(extension, fileNamePrefix, fileNameSuffix);
  }

 
  private boolean isExceptionWithResponse(final BuildEvent event)
  {
    if (event.getException() == null)
      return false;
   
    final Throwable cause = event.getException().getCause();
    if (cause instanceof FailingHttpStatusCodeException)
      return true;
    else if (cause instanceof ScriptException)
    {
      final ScriptException se = (ScriptException) cause;
      return se.getPage() != null; // should probably always be the case
      // but a (now fixed) bug in HtmlUnit-1.14 causes an exception during init
      // of the JavaScriptEngine and no page is available
    }
    return false;
  }

  /**
   * Computes if actual current response as it has properties to be saved
   * (for instance when it has changed)
   */
  private boolean isNewResponse(final BuildEvent event)
  {
    LOG.debug("fPreviousCurrentResponse: " + fPreviousCurrentResponse);
    LOG.debug("fContext.getCurrentResponse(): " + fContext.getCurrentResponse());
    final boolean br = fPreviousCurrentResponse != fContext.getCurrentResponse();
    LOG.debug("isWorthSaving: " + br + ", " + isExceptionWithResponse(event));
    return (fContext.getCurrentResponse() != null
      && fPreviousCurrentResponse != fContext.getCurrentResponse())
      || isExceptionWithResponse(event);
  }

  private boolean isSaveResponse()
  {
    final String stepSave = fCurrentResult.getAttribute("save");
        if (!StringUtils.isEmpty(stepSave)) {
            return true;
        }
    final String stepSaveResponse = fCurrentResult.getAttribute("saveresponse");
        if (!StringUtils.isEmpty(stepSaveResponse)) {
            return ConversionUtil.convertToBoolean(stepSaveResponse, false);
        }
        return fContext.getConfig().isSaveResponse();
  }

  /**
   * Called by Ant when a task is started.
   * Captures the started task (in fact its wrapper) for report.
   *
   * @see org.apache.tools.ant.BuildListener#taskStarted(org.apache.tools.ant.BuildEvent)
   */
  public void taskStarted(final BuildEvent event) {
    if (!isEventForMe()) {
      return;
    }
    final Task task = event.getTask();
    if (!isInteresting(task)) {
      return;
    }

    final StepResult result = new StepResult(task);
    if (fCurrentResult != null) {
      fCurrentResult.addChild(result);
      fCurrentResult = result;
    }
    else {
      fRootResult = new RootStepResult((TestStepSequence) task);
      fCurrentResult = fRootResult;
    }
        fPreviousCurrentResponse = fContext.getCurrentResponse();
        fPreviousResponses = fContext.getResponses();
  }

  /**
   * Test if event is intended for this listener. When many threads run in parallel to execute <webtest>
   * of the same project, the listeners will "see" the events for the other <webtest> and they should ignore them
   */
  protected boolean isEventForMe() {
    return owningThread == Thread.currentThread();
  }

  /**
   * Called when the test execution finished. Here it does nothing, but it is intended for users extending this class.
   */
  public void webtestFinished() {
  }
}
TOP

Related Classes of com.canoo.webtest.reporting.StepExecutionListener

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.