package scalaprops import java.util.concurrent.TimeoutException import java.util.concurrent.atomic.AtomicBoolean import sbt.testing._ import scala.collection.mutable.ArrayBuffer import scala.util.control.NonFatal import scalaz._ final class ScalapropsTaskImpl( override val taskDef: TaskDef, testClassLoader: ClassLoader, args: Array[String], arguments: Arguments, results: ArrayBuffer[TestResult], status: TestStatus ) extends sbt.testing.Task { def execute(eventHandler: EventHandler, loggers: Array[Logger], continuation: Array[Task] => Unit): Unit = { continuation(execute(eventHandler, loggers)) } override def execute(eventHandler: EventHandler, loggers: Array[Logger]) = { val log = Scalaprops.logger(loggers) val testClassName = taskDef.fullyQualifiedName() val obj = ScalapropsRunner.getTestObject( fingerprint = taskDef.fingerprint, testClassName = testClassName, testClassLoader = testClassLoader ) val tests = ScalapropsRunner.findTests( fingerprint = taskDef.fingerprint, testClassName = testClassName, testClassLoader = testClassLoader, only = arguments.only, logger = log ) TestExecutorImpl.withExecutor(log){ executor => val result = ScalapropsTaskImpl.createTree( tests = tests, testClassName = testClassName, arguments = arguments, results = results, testStatus = status, eventHandler = eventHandler, log = log, obj = obj, fingerprint = taskDef.fingerprint(), executor = executor ) obj.listener.onFinishAll(obj, result, log) Array() } } override def tags() = Array() } object ScalapropsTaskImpl { private[this] val emptyThrowable = new OptionalThrowable def createTree( tests: Properties[_], testClassName: String, arguments: Arguments, results: ArrayBuffer[TestResult], testStatus: TestStatus, eventHandler: EventHandler, log: Logger, obj: Scalaprops, fingerprint: Fingerprint, executor: TestExecutor ): Tree[(Any, LazyOption[(Property, Param, ScalapropsEvent)])] = { tests.props.loc.cojoin.toTree.map { t => (t.tree.rootLabel +: t.parents.map(_._2)).map(_._1).reverse.mkString(".") -> t.tree.rootLabel }.map { case (fullName, (id, checkOpt)) => val name = id.toString (id: Any) -> (checkOpt match { case Maybe.Just(check) => LazyOption.lazySome { val cancel = new AtomicBoolean(false) val selector = new TestSelector(name) def event (status: Status, duration: Long, result0: Throwable \&/ CheckResult) = { status match { case Status.Success => testStatus.success.incrementAndGet() case Status.Error => testStatus.error.incrementAndGet() case Status.Failure => testStatus.failure.incrementAndGet() case Status.Ignored => testStatus.ignored.incrementAndGet() case Status.Pending | Status.Skipped | Status.Canceled => } val err = result0.a match { case Some(e) => new OptionalThrowable(e) case None => emptyThrowable } ScalapropsEvent(fullName.toString, fingerprint, selector, status, err, duration, result0) } val param = arguments.param.merge(check.paramEndo(obj.param)) val start = System.currentTimeMillis() val r = try { obj.listener.onStart(obj, name, check.prop, param, log) val r = executor.execute(param.timeout) { check.prop.check( param, { () => cancel.get || ((System.currentTimeMillis() - start) > param.timeout.toMillis) }, count => obj.listener.onCheck(obj, name, check.prop, param, log, count) ) } val duration = System.currentTimeMillis() - start obj.listener.onFinish(obj, name, check.prop, param, r, log) r match { case _: CheckResult.Proven | _: CheckResult.Passed => event(Status.Success, duration, \&/.That(r)) case _: CheckResult.Exhausted | _: CheckResult.Falsified => event(Status.Failure, duration, \&/.That(r)) case e: CheckResult.GenException => log.trace(e.exception) event(Status.Error, duration, \&/.Both(e.exception, r)) case e: CheckResult.PropException => log.trace(e.exception) event(Status.Error, duration, \&/.Both(e.exception, r)) case _: CheckResult.Timeout => event(Status.Error, duration, \&/.That(r)) case _: CheckResult.Ignored => event(Status.Ignored, duration, \&/.That(r)) } } catch { case e: TimeoutException => val duration = System.currentTimeMillis() - start log.trace(e) obj.listener.onError(obj, name, e, log) event(Status.Error, duration, \&/.This(e)) case NonFatal(e) => val duration = System.currentTimeMillis() - start log.trace(e) obj.listener.onError(obj, name, e, log) event(Status.Error, duration, \&/.This(e)) } finally { cancel.set(true) testStatus.all.incrementAndGet() } eventHandler.handle(r) results += TestResult( name = fullName, duration = r.duration, maxSize = param.maxSize, minSuccessful = param.minSuccessful ) (check.prop, param, r) } case Maybe.Empty() => LazyOption.lazyNone }) } } private[scalaprops] def filterTests( objName: String, tests: List[Properties[Any]], names: NonEmptyList[String], logger: Logger ): List[Properties[Any]] = { val set = Foldable[NonEmptyList].toSet(names) val actualTests: Set[String] = tests.map(_.id.toString)(collection.breakOut) set.filterNot(actualTests).foreach{ typo => logger.warn(s"""'${objName}.$typo' does not exists""") } tests.filter(p => set(p.id.toString)) } }