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

org.backuity.matchete.AnyMatchers.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2013 Bruno Bieth
 *
 * 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.backuity.matchete

import scala.language.reflectiveCalls

trait Sized[T] {
  def size(t: T) : Int
}

trait MatcherComparator[-T] {
  def checkEqual(actual: T, expected : T)
}

trait AnyMatchers extends CoreMatcherSupport {

  implicit def seqComparator[T](implicit elemFormatter: Formatter[T], seqFormatter: Formatter[Seq[T]]) = new MatcherComparator[Seq[T]] {
    def checkEqual(actualSeq: Seq[T], expectedSeq: Seq[T]) {
      val expectedIt = expectedSeq.iterator
      val actualIt = actualSeq.iterator
      var idx = 1

      def failAtIndex(msg: String) = fail(s"${seqFormatter.format(actualSeq)} is not equal to ${seqFormatter.format(expectedSeq)}, at index $idx $msg")

      while(expectedIt.hasNext) {
        val expectedNext = expectedIt.next()
        if( ! actualIt.hasNext ) {
          failAtIndex(s"expected ${elemFormatter.format(expectedNext)} but got no element")
        } else {
          val actualNext = actualIt.next()
          if( expectedNext != actualNext ) {
            failAtIndex(s"expected ${elemFormatter.format(expectedNext)} but got ${elemFormatter.format(actualNext)}")
          }
        }
        idx += 1
      }
      if( actualIt.hasNext ) {
        failAtIndex(s"expected no element but got ${elemFormatter.format(actualIt.next())}")
      }
    }
  }

  implicit def setComparator[T](implicit elemFormatter: Formatter[T], setFormatter: Formatter[Set[T]] ) = new MatcherComparator[Set[T]] {
    def checkEqual(actualSet: Set[T], expectedSet: Set[T]) {
      if( actualSet != expectedSet ) {
        def failWithHint(msg: String) = fail(s"${setFormatter.format(actualSet)} is not equal to ${setFormatter.format(expectedSet)}, $msg")

        val missing = expectedSet -- actualSet
        val extra = actualSet -- expectedSet
        val missingMsg = if( missing.size > 1 ) {
            List(s"elements ${missing.mkString(", ")} are missing")
          } else if( missing.size == 1 ) {
            List(s"element ${missing.head} is missing")
          } else {
            Nil
          }
        val extraMsg = if( extra.size > 1 ) {
            List(s"elements ${extra.mkString(", ")} were not expected")
          } else if(extra.size == 1) {
            List(s"element ${extra.head} was not expected")
          } else Nil
        failWithHint((missingMsg ::: extraMsg).mkString(" and "))
      }
    }
  }

  implicit def arrayComparator[T](implicit arrayFormatter: Formatter[Array[T]]) = new MatcherComparator[Array[T]] {
    def checkEqual(actualArr: Array[T], expectedArr: Array[T]) {
      if( expectedArr.deep != actualArr.deep ) {
        fail(s"${arrayFormatter.format(actualArr)} is not equal to ${arrayFormatter.format(expectedArr)}")
      }
    }
  }

  implicit def stringComparator(implicit formatter: Formatter[String]) = new MatcherComparator[String] {
    def checkEqual(actual: String, expected: String) {
      failIfDifferentStrings(actual, expected, s"${formatter.format(actual)} is not equal to ${formatter.format(expected)}")
    }
  }

  implicit def anyComparator[T](implicit formatter: Formatter[T]) = new MatcherComparator[T] {
    def checkEqual(actual: T, expected: T) {
      failIf(actual != expected, s"${formatter.format(actual)} is not equal to ${formatter.format(expected)}")
    }
  }

  /** non-type safe equality, prefer must_== when possible */
  private def equal[T](prefix: String, expected: T)(implicit formatter: Formatter[T], comparator: MatcherComparator[T]) = new EagerMatcher[T]{
    def description = s"${prefix}equal to ${formatter.format(expected)}"

    def eagerCheck(actual: T) {
      comparator.checkEqual(actual, expected)
    }
  }

  def equalTo[T : Formatter : MatcherComparator](expected: T) = equal("", expected)
  def beEqualTo[T : Formatter : MatcherComparator](expected: T) = equal("be ", expected)
  def be_==[T : Formatter : MatcherComparator](expected : T) = equal("be ", expected)

  implicit val SizedString : Sized[String] = new Sized[String] { def size(s: String) = s.length }
  implicit def SizedArray[T] : Sized[Array[T]] = new Sized[Array[T]] { def size(a: Array[T]): Int = a.length }
  implicit def SizedStructural[T <: { def size : Int }] : Sized[T] = new Sized[T] { def size(s : T) = s.size }

//  implicit val StringFormatter : Formatter[String] = new Formatter[String] { def format(s: String) = s"'$s'" }
//  implicit def AnyFormatter[T] : Formatter[T] = new Formatter[T] { def format(any: T) = any.toString }

  def haveSize[T](size: Int)(implicit sized: Sized[T], formatter: Formatter[T]) = new EagerMatcher[T] {
    def description = s"have size $size"
    def eagerCheck(t: T) {
      val actualSize = sized.size(t)
      failIf( actualSize != size, s"${formatter.format(t)} has size $actualSize but expected size $size")
    }
  }

  def beEmpty[T <% Any { def isEmpty : Boolean }](implicit formatter: Formatter[T]) : Matcher[T] = be("empty") {
    case t if t.isEmpty =>
  }

  def beEq[T <: AnyRef](expected: T)(implicit formatter: Formatter[T]) = new EagerMatcher[T] {
    def description: String = "be eq " + expected

    private def format(obj: T) : String = s"${formatter.format(obj)} (${System.identityHashCode(obj)})"

    protected def eagerCheck(t : T) {
      failIf( t ne expected, s"${format(t)} is not eq ${format(expected)}" )
    }
  }

  def beNull[T <: AnyRef](implicit formatter: Formatter[T]) = new EagerMatcher[T] {
    protected def eagerCheck(t: T) {
      failIf(t != null, s"${formatter.format(t)} is not null")
    }
    def description: String = "be null"
  }

  // we need a manifest to be able to format the failure if the failing result is formatable
  def not[T](matcher: Matcher[T])(implicit formatter: Formatter[T], manifest: Manifest[T]) = new Matcher[T]{
    def description = "not " + matcher.description
    def check(t : => T) : Any = {
      def failWith(formattedEval: String) = fail(formattedEval + " should not " + matcher.description)

      val evalT = try {
        t
      } catch {
        case e : Throwable =>
          // let's see if our matcher can cope with that exception
          try {
            matcher.check { throw e }
          } catch {
            case matcherException if matcherException == e =>
              // matcher just forwarded the exception, let it pass through the not
              throw e

            case matcherException : Throwable =>
              // matcher failed with its own exception, that's correct
              return matcherException
          }

          // matcher can cope with `e`, but that's a `not` so fail it
          failWith(s"${e.getClass.getName}: ${e.getMessage}")
      }

      val eval = try {
        matcher.check(evalT)
      } catch {
        case e : Throwable => return e
      }

      if( eval == null ) {
        failWith("null")
      } else if( manifest.runtimeClass.isAssignableFrom(eval.getClass)) {
        failWith(formatter.format(eval.asInstanceOf[T]))
      } else {
        failWith(eval.toString)
      }
    }
  }

  def beA[T : Manifest] = matcher[Any](
    description = "be a " + manifest[T].runtimeClass.getCanonicalName,
    validate = (any: Any) => manifest[T].runtimeClass.isAssignableFrom(any.getClass),
    failureDescription = (any: Any) => s"$any is not a ${manifest[T].runtimeClass.getCanonicalName} it is a ${any.getClass.getCanonicalName}")

  def a[T : Manifest] = beA[T]
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy