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

org.scalactic.Differ.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2001-2016 Artima, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.scalactic

// SKIP-SCALATESTNATIVE-START
import org.scalactic.source.ObjectMeta
// SKIP-SCALATESTNATIVE-END

private[scalactic] trait Differ {

  def difference(a: Any, b: Any, prettifier: Prettifier): PrettyPair

}

private[scalactic] object Differ {

  def simpleClassName(v: Any): String = {
    val className = v.getClass.getName
    val lastIdxOfDot = className.lastIndexOf(".")
    val shortName =
      if (lastIdxOfDot >= 0)
        className.substring(lastIdxOfDot + 1)
      else
        className
    if (shortName == "$colon$colon")
      "List"
    else if (shortName.startsWith("Set$Set"))
      "Set"
    else if (shortName.startsWith("Map$Map"))
      "Map"
    else if (shortName.startsWith("Tuple"))
      shortName.takeWhile(_ != '$')
    else
      shortName
  }
}

private[scalactic] trait StringDiffer extends Differ {

  def difference(a: Any, b: Any, prettifier: Prettifier): PrettyPair = {
    def diffStrings(s: String, t: String): Tuple2[String, String] = {
      def findCommonPrefixLength(s: String, t: String): Int = {
        val max = s.length.min(t.length) // the maximum potential size of the prefix
        var i = 0
        var found = false
        while (i < max & !found) {
          found = (s.charAt(i) != t.charAt(i))
          if (!found)
            i = i + 1
        }
        i
      }
      def findCommonSuffixLength(s: String, t: String): Int = {
        val max = s.length.min(t.length) // the maximum potential size of the suffix
        var i = 0
        var found = false
        while (i < max & !found) {
          found = (s.charAt(s.length - 1 - i) != t.charAt(t.length - 1 - i))
          if (!found)
            i = i + 1
        }
        i
      }
      if (s != t) {
        val commonPrefixLength = findCommonPrefixLength(s, t)
        val commonSuffixLength = findCommonSuffixLength(s.substring(commonPrefixLength), t.substring(commonPrefixLength))
        val prefix = s.substring(0, commonPrefixLength)
        val suffix = if (s.length - commonSuffixLength < 0) "" else s.substring(s.length - commonSuffixLength)
        val sMiddleEnd = s.length - commonSuffixLength
        val tMiddleEnd = t.length - commonSuffixLength
        val sMiddle = s.substring(commonPrefixLength, sMiddleEnd)
        val tMiddle = t.substring(commonPrefixLength, tMiddleEnd)
        val MaxContext = 20
        val shortPrefix = if (commonPrefixLength > MaxContext) "..." + prefix.substring(prefix.length - MaxContext) else prefix
        val shortSuffix = if (commonSuffixLength > MaxContext) suffix.substring(0, MaxContext) + "..." else suffix
        (shortPrefix + "[" + sMiddle + "]" + shortSuffix, shortPrefix + "[" + tMiddle + "]" + shortSuffix)
      }
      else
        (s, t)
    }

    (a, b) match {
      case (aStr: String, bStr: String) =>
        val (aRes, bRes) = diffStrings(aStr, bStr)
        PrettyPair(
          prettifier(aRes),
          prettifier(bRes),
          Some(prettifier(aRes) + " -> " + prettifier(bRes))
        )

      case _ =>
        PrettyPair(
          prettifier(a),
          prettifier(b),
          None
        )
    }
  }
}

private[scalactic] object StringDiffer extends StringDiffer

private[scalactic] class GenSeqDiffer extends Differ {

  def difference(a: Any, b: Any, prettifier: Prettifier): PrettyPair = {
    (a, b) match {
      case (aSeq: scala.collection.GenSeq[_], bSeq: scala.collection.GenSeq[_]) =>
        val diffSet =
          ((0 until aSeq.length) flatMap { i =>
            val leftEl = aSeq(i)
            if (bSeq.isDefinedAt(i)) {
              val rightEl = bSeq(i)
              if (leftEl != rightEl)
                Some(i + ": " + leftEl + " -> " + rightEl)
              else
                None
            }
            else
              Some(i + ": " + leftEl + " -> ")
          }).toSet ++
            ((aSeq.length until bSeq.length) flatMap { i =>
              Some(i + ": -> " + bSeq(i))
            }).toSet

        val shortName = Differ.simpleClassName(aSeq)
        if (diffSet.isEmpty)
          PrettyPair(prettifier(a), prettifier(b), None)
        else
          PrettyPair(prettifier(a), prettifier(b), Some(shortName + "(" + diffSet.toList.sorted.mkString(", ") + ")"))

      case _ => PrettyPair(prettifier(a), prettifier(b), None)
    }
  }

}

private[scalactic] object GenSeqDiffer extends GenSeqDiffer

private[scalactic] class GenSetDiffer extends Differ {

  def difference(a: Any, b: Any, prettifier: Prettifier): PrettyPair = {
    (a, b) match {
      case (aSet: scala.collection.GenSet[Any], bSet: scala.collection.GenSet[Any]) =>
        val missingInRight = aSet.diff(bSet)
        val missingInLeft = bSet.diff(aSet)

        val shortName = Differ.simpleClassName(aSet)
        if (missingInLeft.isEmpty && missingInRight.isEmpty)
          PrettyPair(prettifier(a), prettifier(b), None)
        else {
          val diffList =
            List(
              if (missingInLeft.isEmpty) "" else "missingInLeft: [" + missingInLeft.mkString(", ") + "]",
              if (missingInRight.isEmpty) "" else "missingInRight: [" + missingInRight.mkString(", ") + "]"
            ).filter(_.nonEmpty)
          PrettyPair(prettifier(a), prettifier(b), Some(shortName + "(" + diffList.mkString(", ") + ")"))
        }

      case _ => PrettyPair(prettifier(a), prettifier(b), None)
    }
  }

}

private[scalactic] object GenSetDiffer extends GenSetDiffer

private[scalactic] class GenMapDiffer[K, V] extends Differ {

  def difference(a: Any, b: Any, prettifier: Prettifier): PrettyPair =
    (a, b) match {
      case (aMap: scala.collection.GenMap[K, V], bMap: scala.collection.GenMap[K, V]) =>
        val leftKeySet = aMap.keySet
        val rightKeySet = bMap.keySet
        val missingKeyInRight = leftKeySet.diff(rightKeySet)
        val missingKeyInLeft = rightKeySet.diff(leftKeySet)
        val intersectKeys = leftKeySet.intersect(rightKeySet)
        val diffSet =
          intersectKeys.flatMap { k =>
            val leftValue = aMap(k)
            val rightValue = bMap(k)
            if (leftValue != rightValue)
              Some(k + ": " + leftValue + " -> " + rightValue)
            else
              None
          }.toSet ++
            missingKeyInLeft.flatMap { k =>
              val rightValue = bMap(k)
              Option(k + ": -> " + rightValue)
            }.toSet ++
            missingKeyInRight.flatMap { k =>
              val leftValue = aMap(k)
              Option(k + ": " + leftValue + " -> ")
            }.toSet

        val shortName = Differ.simpleClassName(aMap)
        if (diffSet.isEmpty)
          PrettyPair(prettifier(a), prettifier(b), None)
        else
          PrettyPair(prettifier(a), prettifier(b), Some(shortName + "(" + diffSet.toList.sorted.mkString(", ") + ")"))

      case _ =>
        PrettyPair(prettifier(a), prettifier(b), None)
    }

}

private[scalactic] object GenMapDiffer extends GenMapDiffer[Any, Any]

// SKIP-SCALATESTNATIVE-START
private[scalactic] trait ObjectDiffer extends Differ {

  def difference(a: Any, b: Any, prettifier: Prettifier): PrettyPair = {
    import org.scalactic.source.ObjectMeta

    val leftMeta = ObjectMeta(a)
    val rightMeta = ObjectMeta(b)

    val diffSet =
      (leftMeta.fieldNames.flatMap { name =>
        val leftValue = leftMeta.value(name)
        try {
          if (rightMeta.hasField(name)) {
            val rightValue = rightMeta.value(name)
            if (leftValue != rightValue) {
              val nestedPair = AnyDiffer.difference(leftValue, rightValue, prettifier)
              nestedPair.analysis match {
                case Some(analysis) =>
                  Some(name + ": " + analysis)

                case None =>
                  Some(name + ": " + leftValue + " -> " + rightValue)
              }
            }
            else
              None
          }
          else
            Some(name + ": " + leftValue + " -> ")
        }
        catch {
          case iae: IllegalArgumentException =>
            None
        }
      }) ++
        rightMeta.fieldNames.filter(f => !leftMeta.fieldNames.contains(f)).flatMap { name =>
          val rightValue = rightMeta.value(name)
          Some(name + ": -> " + rightValue)
        }


    if (diffSet.isEmpty)
      PrettyPair(prettifier(a), prettifier(b), None)
    else {
      val shortName = Differ.simpleClassName(a)
      PrettyPair(prettifier(a), prettifier(b), Some(shortName + "(" + diffSet.toList.sorted.mkString(", ") + ")"))
    }
  }

}

private[scalactic] object ObjectDiffer extends ObjectDiffer
// SKIP-SCALATESTNATIVE-END

private[scalactic] class AnyDiffer extends Differ {

  def difference(a: Any, b: Any, prettifier: Prettifier): PrettyPair = {

    (a, b) match {
      case (s1: String, s2: String) => StringDiffer.difference(s1, s2, prettifier)
      case (s1: scala.collection.GenMap[Any, Any], s2: scala.collection.GenMap[Any, Any]) => GenMapDiffer.difference(s1, s2, prettifier)
      case (s1: scala.collection.GenSeq[_], s2: scala.collection.GenSeq[_]) => GenSeqDiffer.difference(s1, s2, prettifier)
      case (s1: scala.collection.GenSet[Any], s2: scala.collection.GenSet[Any]) => GenSetDiffer.difference(s1, s2, prettifier)
      // SKIP-SCALATESTNATIVE-START
      case (s1: Product, s2: Product) => ObjectDiffer.difference(s1, s2, prettifier)
      // SKIP-SCALATESTNATIVE-END
      case _ => PrettyPair(prettifier(a), prettifier(b), None)
    }
  }

}

private[scalactic] object AnyDiffer extends AnyDiffer




© 2015 - 2025 Weber Informatics LLC | Privacy Policy