package xsbt.boot

import org.scalacheck._
import Prop._
import java.io.File
import sbt.IO.withTemporaryDirectory

/**
 * These mainly test that things work in the uncontested case and that no OverlappingFileLockExceptions occur.
 * There is no real locking testing, just the coordination of locking.
 */
object LocksTest extends Properties("Locks") {
  property("Lock in nonexisting directory") = spec {
    withTemporaryDirectory { dir =>
      val lockFile = new File(dir, "doesntexist/lock")
      Locks(lockFile, callTrue)
    }
  }

  property("Uncontested re-entrant lock") = spec {
    withTemporaryDirectory { dir =>
      val lockFile = new File(dir, "lock")
      Locks(lockFile, callLocked(lockFile)) &&
        Locks(lockFile, callLocked(lockFile))
    }
  }

  property("Uncontested double lock") = spec {
    withTemporaryDirectory { dir =>
      val lockFileA = new File(dir, "lockA")
      val lockFileB = new File(dir, "lockB")
      Locks(lockFileA, callLocked(lockFileB)) &&
        Locks(lockFileB, callLocked(lockFileA))
    }
  }

  property("Contested single lock") = spec {
    withTemporaryDirectory { dir =>
      val lockFile = new File(dir, "lock")
      forkFold(2000) { i => Locks(lockFile, callTrue) }
    }
  }

  private def spec(f: => Boolean): Prop = Prop { _ => Result(if (f) True else False) }

  private def call[T](impl: => T) = new java.util.concurrent.Callable[T] { def call = impl }
  private def callLocked(lockFile: File) = call { Locks(lockFile, callTrue) }
  private lazy val callTrue = call { true }

  private def forkFold(n: Int)(impl: Int => Boolean): Boolean =
    (true /: forkWait(n)(impl))(_ && _)
  private def forkWait(n: Int)(impl: Int => Boolean): Iterable[Boolean] =
    {
      import scala.concurrent.Future
      import scala.concurrent.ExecutionContext.Implicits.global
      import scala.concurrent.Await
      import scala.concurrent.duration.Duration.Inf
      // TODO - Don't wait forever...
      val futures = (0 until n).map { i => Future { impl(i) } }
      futures.toList.map(f => Await.result(f, Inf))
    }
}