All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.evosuite.regression.RegressionExceptionHelper Maven / Gradle / Ivy

The newest version!
/**
 * Copyright (C) 2010-2018 Gordon Fraser, Andrea Arcuri and EvoSuite
 * contributors
 *
 * This file is part of EvoSuite.
 *
 * EvoSuite is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation, either version 3.0 of the License, or
 * (at your option) any later version.
 *
 * EvoSuite is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with EvoSuite. If not, see .
 */
package org.evosuite.regression;

import org.apache.commons.lang3.StringUtils;
import org.evosuite.PackageInfo;
import org.evosuite.runtime.mock.EvoSuiteMock;
import org.evosuite.testcase.statements.MethodStatement;
import org.evosuite.testcase.statements.Statement;

import java.lang.reflect.Modifier;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import dk.brics.automaton.RegExp;

public class RegressionExceptionHelper {


  private static final List> INVALID_EXCEPTIONS = Arrays.asList(new Class[]{
      StackOverflowError.class, // Might be thrown at different places
      AssertionError.class}     // Depends whether assertions are enabled or not
  );

  /**
   * Get a simple (and unique looking) exception name (exType or exThrowingMethodCall:exType)
   */
  public static String simpleExceptionName(RegressionTestChromosome test, Integer statementPos,
                                           Throwable ex) {
    if (ex == null) {
      return "";
    }
    String exception = ex.getClass().getSimpleName();
    if (test.getTheTest().getTestCase().hasStatement(statementPos)) {
      Statement exThrowingStatement = test.getTheTest().getTestCase().getStatement(statementPos);
      if (exThrowingStatement instanceof MethodStatement) {
        String exMethodcall = ((MethodStatement) exThrowingStatement).getMethod().getName();
        exception = exMethodcall + ":" + exception;
      }
    }
    return exception;
  }

  /**
   * Get signature based on the root cause from the stack trace
   * (uses: location of the error triggering line from CUT)
   * @param CUT the class to expect the exception to be thrown from
   * @return signature string
   */
  public static String getExceptionSignature(Throwable throwable, String CUT) {
    String signature = throwable.getClass().getSimpleName();

    StackTraceElement[] stackTrace = throwable.getStackTrace();
    for (StackTraceElement el : stackTrace) {
      String elClass = el.getClassName();
      if (!Objects.equals(elClass, CUT)) {
        continue;
      }

      String method = el.getMethodName();
      int line = el.getLineNumber();
      signature += ":" + method + "-" + line;
      break;
    }
    return signature;
  }

  /**
   * Calculate the number of different exceptions, given two sets of exceptions.
   */
  public static int compareExceptionDiffs(Map originalExceptionMapping,
                                          Map regressionExceptionMapping) {

    int exDiff = (int) Math
        .abs((originalExceptionMapping.size() - regressionExceptionMapping.size()));


    /*
     * If the number of exceptions is different, clearly the executions are
     * propagating to different results. Otherwise, we need to compare the
     * same assertions to make sure they're actually the same.
     */

    if (exDiff == 0) {
      // For all exceptions thrown original class
      for (Map.Entry origException : originalExceptionMapping.entrySet()) {
        boolean skip = false;

        // Skip if the exception or the message are null
        // Sometimes the getMesage may call the CUT's exception handler which may crash
        try {
          if (origException.getValue() == null || origException.getValue().getMessage() == null) {
            originalExceptionMapping.remove(origException.getKey());
            skip = true;
          }
        } catch (Throwable t) {
          continue;
        }

        // See if exception throwing classes differed
        try {
          Throwable x = origException.getValue();
          Class ex = getExceptionClassToUse(x);
          String sourceClass = getSourceClassName(x);
          if (sourceClass != null && isValidSource(sourceClass) && isExceptionToAssertValid(ex)) {
            // Get other exception throwing class and compare them
            Throwable otherX = regressionExceptionMapping.get(origException.getKey());
            String otherSourceClass = getSourceClassName(otherX);
            if (!sourceClass.equals(otherSourceClass)) {
              exDiff++;
              //logger.warn("Exception throwing classes differed: {} {}", sourceClass, otherSourceClass);
            }

          }
        } catch (Throwable t) {
          // ignore
        }

        // Skip if the exceptions are not comparable
        try {
          if (regressionExceptionMapping.containsKey(origException.getKey())
              && (regressionExceptionMapping.get(origException.getKey()) == null
              || regressionExceptionMapping.get(origException.getKey()).getMessage() == null)) {
            regressionExceptionMapping.remove(origException.getKey());
            skip = true;
          }
        } catch (Throwable t) {
          continue;
        }

        // If they start differing from @objectID skip this one
        if (!skip && regressionExceptionMapping.get(origException.getKey()) != null) {
          String origExceptionMessage = origException.getValue().getMessage();
          String regExceptionMessage = regressionExceptionMapping.get(origException.getKey())
              .getMessage();
          int diffIndex = StringUtils.indexOfDifference(origExceptionMessage, regExceptionMessage);
          if (diffIndex > 0) {
            if (origExceptionMessage.charAt(diffIndex - 1) == '@') {
              originalExceptionMapping.remove(origException.getKey());
              regressionExceptionMapping.remove(origException.getKey());
              skip = true;
            } else {
              // If @ is in the last 10 characters, it's likely an object pointer comparison issue
              int howFarBack = 10;
              if (diffIndex > howFarBack) {
                String last10 = origExceptionMessage.substring(diffIndex - howFarBack, diffIndex);
                if (last10.contains("@")) {
                  originalExceptionMapping.remove(origException.getKey());
                  regressionExceptionMapping.remove(origException.getKey());
                  skip = true;
                }
              }
            }

          }
        }

        // ignore security manager exceptions
        if (!skip && origException.getValue().getMessage().contains("Security manager blocks")) {
          originalExceptionMapping.remove(origException.getKey());
          regressionExceptionMapping.remove(origException.getKey());
          skip = true;
        }

        if (skip) {
          continue;
        }

        // do the comparison
        if (!regressionExceptionMapping.containsKey(origException.getKey())
            || (!regressionExceptionMapping
            .get(origException.getKey()).getMessage()
            .equals(origException.getValue().getMessage()))) {
          exDiff++;
        }
      }
      // For all exceptions in the regression class.
      // Any bad exceptions were removed from this object earlier
      for (Map.Entry regException : regressionExceptionMapping.entrySet()) {
        if (!originalExceptionMapping.containsKey(regException.getKey())) {
          exDiff++;
        }
      }
    }

    return exDiff;
  }

  /**
   * Add regression-diff comments for exception messages
   */
  public static void addExceptionAssertionComments(RegressionTestChromosome regressionTest,
                                                   Map originalExceptionMapping,
                                                   Map regressionExceptionMapping) {
    for (Map.Entry original : originalExceptionMapping.entrySet()) {
      int originalStatementPos = original.getKey();
      Throwable originalException = original.getValue();
      if (!regressionExceptionMapping.containsKey(originalStatementPos)) {

        if (testStatementCommentNotContains(regressionTest, originalStatementPos,
            "modified version")) {
          addExceptionDifferenceComment(regressionTest, originalException, originalStatementPos,
              "The modified version did not exhibit this exception", false);
          //FIXME: for some reason we're not adding this comment to the other version!
        }
      } else {
        if (originalException != null && originalException.getMessage() != null) {
          // compare the exception messages
          if (!originalException.getMessage()
              .equals(regressionExceptionMapping.get(originalStatementPos).getMessage())) {
            if (testStatementCommentNotContains(regressionTest, originalStatementPos,
                "EXCEPTION DIFF:")) {
              regressionTest.getTheTest().getTestCase().getStatement(originalStatementPos)
                  .addComment(
                      "EXCEPTION DIFF:\nDifferent Exceptions were thrown:\nOriginal Version:\n    "
                          + originalException.getClass().getName() + " : "
                          + originalException.getMessage() + "\nModified Version:\n    "
                          + regressionExceptionMapping.get(originalStatementPos).getClass()
                          .getName()
                          + " : "
                          + regressionExceptionMapping.get(originalStatementPos).getMessage()
                          + "\n");
            }
          } else {
            // Compare the classes throwing the exception
            Class ex = getExceptionClassToUse(originalException);
            String sourceClass = getSourceClassName(originalException);
            if (sourceClass != null && isValidSource(sourceClass) && isExceptionToAssertValid(ex)
                && regressionExceptionMapping.get(originalStatementPos) != null) {
              Throwable otherX = regressionExceptionMapping.get(originalStatementPos);
              String otherSourceClass = getSourceClassName(otherX);
              if (!sourceClass.equals(otherSourceClass)) {
                if (testStatementCommentNotContains(regressionTest, originalStatementPos,
                    "EXCEPTION DIFF:")) {
                  regressionTest.getTheTest().getTestCase().getStatement(originalStatementPos)
                      .addComment(
                          "EXCEPTION DIFF:\nExceptions thrown by different classes:\nOriginal Version:\n    "
                              + sourceClass + "\nModified Version:\n    "
                              + otherSourceClass + "\n");
                }
              }
            }
          }
        }

        // If both show the same error, pop the error from the
        // regression exception map, to get to a diff.
        regressionExceptionMapping.remove(originalStatementPos);
      }
    }
    for (Map.Entry regression : regressionExceptionMapping.entrySet()) {
      Throwable regressionException = regression.getValue();
      int regressionStatementPos = regression.getKey();
      if (testStatementCommentNotContains(regressionTest, regressionStatementPos,
          "original version")) {

        addExceptionDifferenceComment(regressionTest, regressionException, regressionStatementPos,
            "The original version did not exhibit this exception", true);
      }
    }
  }

  private static boolean testStatementCommentNotContains(RegressionTestChromosome test,
                                                         int statementPos, String compareComment) {
    return (test.getTheTest().getTestCase().hasStatement(statementPos)
        && !test.getTheTest().getTestCase().getStatement(statementPos)
        .getComment().contains(compareComment));
  }

  /**
   * Add exception difference comment to test case, at given position
   * @param test the test case to add the comment to
   * @param t throwable (Exception) which has occurred
   * @param statementPos the position to add the comment on
   * @param comment the comment string
   * @param addToTestForOtherClassLoader whether to add the same comment on the test on reg. CL
   */
  private static void addExceptionDifferenceComment(RegressionTestChromosome test, Throwable t,
                                                    int statementPos, String comment,
                                                    boolean addToTestForOtherClassLoader) {
    String exceptionDiffComment = "EXCEPTION DIFF:\n" + comment + ":\n    "
        + t.getClass().getName() + " : " + t.getMessage() + "\n\n";

    test.getTheTest().getTestCase().getStatement(statementPos)
        .addComment(exceptionDiffComment);
    if (addToTestForOtherClassLoader) {
      test.getTheSameTestForTheOtherClassLoader().getTestCase().getStatement(statementPos)
          .addComment(exceptionDiffComment);
    }
  }

  /**
   * This part is "temporarily" copied over from TestCodeVisitor.
   * Until they are made statically available to use in this class.
   */
  private static String getSourceClassName(Throwable exception) {
    if (exception.getStackTrace().length == 0) {
      return null;
    }
    return exception.getStackTrace()[0].getClassName();
  }

  private static boolean isValidSource(String sourceClass) {
    return (!sourceClass.startsWith(PackageInfo.getEvoSuitePackage() + ".") ||
        sourceClass.startsWith(PackageInfo.getEvoSuitePackage() + ".runtime.")) &&
        !sourceClass.equals(URLClassLoader.class.getName()) &&
        // Classloaders may differ, e.g. when running with ant
        !sourceClass.startsWith(RegExp.class.getPackage().getName()) &&
        !sourceClass.startsWith("java.lang.System") &&
        !sourceClass.startsWith("java.lang.String") &&
        !sourceClass.startsWith("sun.") &&
        !sourceClass.startsWith("com.sun.") &&
        !sourceClass.startsWith("jdk.internal.");
  }

  private static boolean isExceptionToAssertValid(Class exceptionClass) {
    return !INVALID_EXCEPTIONS.contains(exceptionClass);
  }

  private static Class getExceptionClassToUse(Throwable exception) {
        /*
            we can only catch a public class.
            for "readability" of tests, it shouldn't be a mock one either
          */
    Class ex = exception.getClass();
    while (!Modifier.isPublic(ex.getModifiers()) || EvoSuiteMock.class.isAssignableFrom(ex) ||
        ex.getCanonicalName().startsWith("com.sun.")) {
      ex = ex.getSuperclass();
    }
    return ex;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy