package sbt import java.io.File import org.scalacheck.{Arbitrary, Gen, Prop, Properties} import Prop._ import Process._ object ProcessSpecification extends Properties("Process I/O") { implicit val exitCodeArb: Arbitrary[Array[Byte]] = Arbitrary( for(size <- Gen.choose(0, 10); l <- Gen.listOfN[Byte](size, Arbitrary.arbByte.arbitrary)) yield l.toArray ) /*property("Correct exit code") = forAll( (exitCode: Byte) => checkExit(exitCode)) property("#&& correct") = forAll( (exitCodes: Array[Byte]) => checkBinary(exitCodes)(_ #&& _)(_ && _)) property("#|| correct") = forAll( (exitCodes: Array[Byte]) => checkBinary(exitCodes)(_ #|| _)(_ || _)) property("### correct") = forAll( (exitCodes: Array[Byte]) => checkBinary(exitCodes)(_ ### _)( (x,latest) => latest))*/ property("Pipe to output file") = forAll( (data: Array[Byte]) => checkFileOut(data)) property("Pipe from input file") = forAll( (data: Array[Byte]) => checkFileIn(data)) property("Pipe to process") = forAll( (data: Array[Byte]) => checkPipe(data)) property("Pipe to process ignores input exit code") = forAll( (data: Array[Byte], code: Byte) => checkPipeExit(data, code)) property("Pipe from input file to bad process preserves correct exit code.") = forAll( (data: Array[Byte], code: Byte) => checkFileInExit(data, code)) property("Pipe to output file from bad process preserves correct exit code.") = forAll( (data: Array[Byte], code: Byte) => checkFileOutExit(data, code)) private def checkBinary(codes: Array[Byte])(reduceProcesses: (ProcessBuilder, ProcessBuilder) => ProcessBuilder)(reduceExit: (Boolean, Boolean) => Boolean) = { (codes.length > 1) ==> { val unsignedCodes = codes.map(unsigned) val exitCode = unsignedCodes.map(code => Process(process("sbt.exit " + code))).reduceLeft(reduceProcesses) ! val expectedExitCode = unsignedCodes.map(toBoolean).reduceLeft(reduceExit) toBoolean(exitCode) == expectedExitCode } } private def toBoolean(exitCode: Int) = exitCode == 0 private def checkExit(code: Byte) = { val exitCode = unsigned(code) (process("sbt.exit " + exitCode) !) == exitCode } private def checkFileOut(data: Array[Byte]) = { withData(data) { (temporaryFile, temporaryFile2) => val catCommand = process("sbt.cat " + temporaryFile.getAbsolutePath) catCommand #> temporaryFile2 } } private def checkFileIn(data: Array[Byte]) = { withData(data) { (temporaryFile, temporaryFile2) => val catCommand = process("sbt.cat") temporaryFile #> catCommand #> temporaryFile2 } } private def checkPipe(data: Array[Byte]) = { withData(data) { (temporaryFile, temporaryFile2) => val catCommand = process("sbt.cat") temporaryFile #> catCommand #| catCommand #> temporaryFile2 } } private def checkPipeExit(data: Array[Byte], code: Byte) = withTempFiles { (a,b) => IO.write(a, data) val catCommand = process("sbt.cat") val exitCommand = process(s"sbt.exit $code") val exit = (a #> exitCommand #| catCommand #> b).! (s"Exit code: $exit") |: (s"Output file length: ${b.length}") |: (exit == 0) && (b.length == 0) } private def checkFileOutExit(data: Array[Byte], exitCode: Byte) = withTempFiles { (a,b) => IO.write(a, data) val code = unsigned(exitCode) val command = process(s"sbt.exit $code") val exit = (a #> command #> b).! (s"Exit code: $exit, expected: $code") |: (s"Output file length: ${b.length}") |: (exit == code) && (b.length == 0) } private def checkFileInExit(data: Array[Byte], exitCode: Byte) = withTempFiles { (a,b) => IO.write(a, data) val code = unsigned(exitCode) val command = process(s"sbt.exit $code") val exit = (a #> command).! (s"Exit code: $exit, expected: $code") |: (exit == code) } private def temp() = File.createTempFile("sbt", "") private def withData(data: Array[Byte])(f: (File, File) => ProcessBuilder) = withTempFiles { (a, b) => IO.write(a, data) val process = f(a, b) ( process ! ) == 0 && sameFiles(a, b) } private def sameFiles(a: File, b: File) = IO.readBytes(a) sameElements IO.readBytes(b) private def withTempFiles[T](f: (File, File) => T): T = { val temporaryFile1 = temp() val temporaryFile2 = temp() try f(temporaryFile1, temporaryFile2) finally { temporaryFile1.delete() temporaryFile2.delete() } } private def unsigned(b: Int): Int = ((b: Int) +256) % 256 private def unsigned(b: Byte): Int = unsigned(b: Int) private def process(command: String) = { val ignore = echo // just for the compile dependency so that this test is rerun when TestedProcess.scala changes, not used otherwise val thisClasspath = List(getSource[Product], getSource[IO.type], getSource[SourceTag]).mkString(File.pathSeparator) "java -cp " + thisClasspath + " " + command } private def getSource[T : Manifest]: String = IO.classLocationFile[T].getAbsolutePath } private trait SourceTag