From 1a72decf557820f81281db768603c1a69ff82b90 Mon Sep 17 00:00:00 2001 From: Jonny Date: Fri, 28 Feb 2025 16:24:53 +0000 Subject: [PATCH] feat: remove unsaferunsync and integrate io in tests instead --- project.scala | 2 + src/test/wacc/examples.scala | 157 ++++++++++++++++++----------------- 2 files changed, 83 insertions(+), 76 deletions(-) diff --git a/project.scala b/project.scala index 6443ad8..86e8a44 100644 --- a/project.scala +++ b/project.scala @@ -11,6 +11,8 @@ //> using dep com.monovore::decline-effect::2.5.0 //> using dep com.github.scopt::scopt::4.1.0 //> using test.dep org.scalatest::scalatest::3.2.19 +//> using dep org.typelevel::cats-effect-testing-scalatest::1.6.0 + // sensible defaults for warnings and compiler checks //> using options -deprecation -unchecked -feature diff --git a/src/test/wacc/examples.scala b/src/test/wacc/examples.scala index 4fac462..cd79161 100644 --- a/src/test/wacc/examples.scala +++ b/src/test/wacc/examples.scala @@ -1,15 +1,19 @@ package wacc import org.scalatest.BeforeAndAfterAll -import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.Inspectors.forEvery +import org.scalatest.matchers.should.Matchers._ +import org.scalatest.freespec.AsyncFreeSpec +import cats.effect.testing.scalatest.AsyncIOSpec import java.io.File import sys.process._ import java.io.PrintStream import scala.io.Source -import cats.effect.unsafe.implicits.global +import cats.effect.IO +import wacc.{compile as compileWacc} + +class ParallelExamplesSpec extends AsyncFreeSpec with AsyncIOSpec with BeforeAndAfterAll { -class ParallelExamplesSpec extends AnyFlatSpec with BeforeAndAfterAll { val files = allWaccFiles("wacc-examples/valid").map { p => (p.toString, List(0)) @@ -24,95 +28,96 @@ class ParallelExamplesSpec extends AnyFlatSpec with BeforeAndAfterAll { (p.toString, List(100, 200)) } - // tests go here forEvery(files) { (filename, expectedResult) => val baseFilename = filename.stripSuffix(".wacc") given stdout: PrintStream = PrintStream(File(baseFilename + ".out")) - s"$filename" should "be compiled with correct result" in { - val result = compile(filename).unsafeRunSync() - assert(expectedResult.contains(result)) - } + s"$filename" - { + "should be compiled with correct result" in { + compileWacc(filename).map { result => + expectedResult should contain(result) + } + } - if (expectedResult == List(0)) it should "run with correct result" in { - if (fileIsDisallowedBackend(filename)) pending + if (expectedResult == List(0)) { + "should run with correct result" in { + if (fileIsDisallowedBackend(filename)) + IO.pure( + succeed + ) // TODO: remove when advanced tests removed. not sure how to "pending" this otherwise + else { + for { + contents <- IO(Source.fromFile(File(filename)).getLines.toList) + inputLine = extractInput(contents) + expectedOutput = extractOutput(contents) + expectedExit = extractExit(contents) - // Retrieve contents to get input and expected output + exit code - val contents = scala.io.Source.fromFile(File(filename)).getLines.toList - val inputLine = - contents - .find(_.matches("^# ?[Ii]nput:.*$")) - .map(_.split(":").last.strip + "\n") - .getOrElse("") - val outputLineIdx = contents.indexWhere(_.matches("^# ?[Oo]utput:.*$")) - val expectedOutput = - if (outputLineIdx == -1) "" - else - contents - .drop(outputLineIdx + 1) - .takeWhile(_.startsWith("#")) - .map(_.stripPrefix("#").stripLeading) - .mkString("\n") + asmFilename = baseFilename + ".s" + execFilename = baseFilename + gccResult <- IO(s"gcc -o $execFilename -z noexecstack $asmFilename".!) - val exitLineIdx = contents.indexWhere(_.matches("^# ?[Ee]xit:.*$")) - val expectedExit = - if (exitLineIdx == -1) 0 - else contents(exitLineIdx + 1).stripPrefix("#").strip.toInt + _ = assert(gccResult == 0) - // Assembly and link using gcc - val asmFilename = baseFilename + ".s" - val execFilename = baseFilename - val gccResult = s"gcc -o $execFilename -z noexecstack $asmFilename".! - assert(gccResult == 0) + stdout <- IO.pure(new StringBuilder) + process <- IO { + s"timeout 5s $execFilename" run ProcessIO( + in = w => { + w.write(inputLine.getBytes) + w.close() + }, + out = Source.fromInputStream(_).addString(stdout), + err = _ => () + ) + } - // Run the executable with the provided input - val stdout = new StringBuilder - val process = s"timeout 5s $execFilename" run ProcessIO( - in = w => { - w.write(inputLine.getBytes) - w.close() - }, - out = Source.fromInputStream(_).addString(stdout), - err = _ => () - ) + exitCode <- IO.pure(process.exitValue) - assert(process.exitValue == expectedExit) - assert( - stdout.toString - .replaceAll("0x[0-9a-f]+", "#addrs#") - .replaceAll("fatal error:.*", "#runtime_error#\u0000") - .takeWhile(_ != '\u0000') - == expectedOutput - ) + } yield { + exitCode shouldBe expectedExit + normalizeOutput(stdout.toString) shouldBe expectedOutput + } + } + } + } } } def allWaccFiles(dir: String): IndexedSeq[os.Path] = val d = java.io.File(dir) - os.walk(os.Path(d.getAbsolutePath)).filter { _.ext == "wacc" } + os.walk(os.Path(d.getAbsolutePath)).filter(_.ext == "wacc") + // TODO: eventually remove this I think def fileIsDisallowedBackend(filename: String): Boolean = Seq( - // format: off - // disable formatting to avoid binPack - "^.*wacc-examples/valid/advanced.*$", - // "^.*wacc-examples/valid/array.*$", - // "^.*wacc-examples/valid/basic/exit.*$", - // "^.*wacc-examples/valid/basic/skip.*$", - // "^.*wacc-examples/valid/expressions.*$", - // "^.*wacc-examples/valid/function/nested_functions.*$", - // "^.*wacc-examples/valid/function/simple_functions.*$", - // "^.*wacc-examples/valid/if.*$", - // "^.*wacc-examples/valid/IO/print.*$", - // "^.*wacc-examples/valid/IO/read.*$", - // "^.*wacc-examples/valid/IO/IOLoop.wacc.*$", - // "^.*wacc-examples/valid/IO/IOSequence.wacc.*$", - // "^.*wacc-examples/valid/pairs.*$", - //"^.*wacc-examples/valid/runtimeErr.*$", - // "^.*wacc-examples/valid/scope.*$", - // "^.*wacc-examples/valid/sequence.*$", - // "^.*wacc-examples/valid/variables.*$", - // "^.*wacc-examples/valid/while.*$", - // format: on - ).find(filename.matches).isDefined + "^.*wacc-examples/valid/advanced.*$" + ).exists(filename.matches) + + private def extractInput(contents: List[String]): String = + contents + .find(_.matches("^# ?[Ii]nput:.*$")) + .map(_.split(":").last.strip + "\n") + .getOrElse("") + + private def extractOutput(contents: List[String]): String = { + val outputLineIdx = contents.indexWhere(_.matches("^# ?[Oo]utput:.*$")) + if (outputLineIdx == -1) "" + else + contents + .drop(outputLineIdx + 1) + .takeWhile(_.startsWith("#")) + .map(_.stripPrefix("#").stripLeading) + .mkString("\n") + } + + private def extractExit(contents: List[String]): Int = { + val exitLineIdx = contents.indexWhere(_.matches("^# ?[Ee]xit:.*$")) + if (exitLineIdx == -1) 0 + else contents(exitLineIdx + 1).stripPrefix("#").strip.toInt + } + + private def normalizeOutput(output: String): String = + output + .replaceAll("0x[0-9a-f]+", "#addrs#") + .replaceAll("fatal error:.*", "#runtime_error#\u0000") + .takeWhile(_ != '\u0000') }