Skip to content
This repository has been archived by the owner on Jul 12, 2024. It is now read-only.

Commit

Permalink
Merge pull request #7 from tanishiking/testsuite
Browse files Browse the repository at this point in the history
Setup a testing infrastructure
  • Loading branch information
tanishiking authored Mar 7, 2024
2 parents 72073ff + ccf1532 commit ec03bdc
Show file tree
Hide file tree
Showing 11 changed files with 219 additions and 57 deletions.
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,19 @@ $ deno run --allow-read run.mjs
- The WasmGC reference interpreter can be used to validate and convert between the binary and text form:
- https://github.com/WebAssembly/gc/tree/main/interpreter
- Use docker image for it https://github.com/tanishiking/wasmgc-docker

### Testing

Requires NodeJS >= 22 (for enough support of WasmGC).

```sh
$ NVM_NODEJS_ORG_MIRROR=https://nodejs.org/download/nightly nvm install v22
```

- `tests/test` will
- Run `testSuite/run` to compile the Scala code under `test-suite` to WebAssembly
- Run the WebAssembly binary using NodeJS
- Each Scala program in `test-suite` should have a function that has no arguments and return a Boolean value. The test passes if the function returns `true`.
- When you add a test,
- Add a file under `test-suite`
- Add a test case to `cli/src/main/scala/TestSuites.scala` (`methodName` should be a exported function name).
59 changes: 50 additions & 9 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,15 @@ lazy val cli = project
scalaJSUseMainModuleInitializer := true,
scalaJSLinkerConfig ~= {
_.withModuleKind(ModuleKind.CommonJSModule),
}
},
libraryDependencies ++= Seq(
"org.scala-js" %%% "scala-js-macrotask-executor" % "1.1.1"
),
)
.dependsOn(
wasm,
// tests // for TestSuites constant
)
.dependsOn(wasm)

lazy val wasm = project
.in(file("wasm"))
Expand All @@ -21,7 +27,6 @@ lazy val wasm = project
version := "0.1.0-SNAPSHOT",
scalaVersion := scalaV,
libraryDependencies ++= Seq(
"org.scalameta" %% "munit" % "0.7.29" % Test,
"org.scala-js" %%% "scalajs-linker" % "1.15.0"
),
scalaJSUseMainModuleInitializer := true,
Expand All @@ -41,14 +46,50 @@ lazy val sample = project
import org.scalajs.jsenv.nodejs.NodeJSEnv
val cp = Attributed
.data((Compile / fullClasspath).value)
// .filter { path =>
// val pathStr = path.toString()
// println(pathStr)
// pathStr.contains("sample/target")
// }
.mkString(";")
val env = Map("SCALAJS_CLASSPATH" -> cp, "SCALAJS_MODE" -> "sample")
val env = Map(
"SCALAJS_CLASSPATH" -> cp,
"SCALAJS_MODE" -> "sample",
)
new NodeJSEnv(NodeJSEnv.Config().withEnv(env).withArgs(List("--enable-source-maps")))
},
Compile / jsEnvInput := (`cli` / Compile / jsEnvInput).value
)

lazy val testSuite = project
.in(file("test-suite"))
.enablePlugins(ScalaJSPlugin)
.settings(
scalaVersion := scalaV,
scalaJSUseMainModuleInitializer := true,
Compile / jsEnv := {
import org.scalajs.jsenv.nodejs.NodeJSEnv
val cp = Attributed
.data((Compile / fullClasspath).value)
.mkString(";")
val env = Map(
"SCALAJS_CLASSPATH" -> cp,
"SCALAJS_MODE" -> "testsuite",
)
new NodeJSEnv(NodeJSEnv.Config().withEnv(env).withArgs(List("--enable-source-maps")))
},
Compile / jsEnvInput := (`cli` / Compile / jsEnvInput).value
)

lazy val tests = project
.in(file("tests"))
.enablePlugins(ScalaJSPlugin)
.settings(
scalaVersion := scalaV,
libraryDependencies ++= Seq(
"org.scalameta" %%% "munit" % "0.7.29" % Test,
"org.scala-js" %%% "scala-js-macrotask-executor" % "1.1.1" % Test
),
scalaJSLinkerConfig ~= {
_.withModuleKind(ModuleKind.CommonJSModule),
},
test := Def.sequential(
(testSuite / Compile / run).toTask(""),
(Test / test)
).value
).dependsOn(cli)
57 changes: 45 additions & 12 deletions cli/src/main/scala/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,68 @@ package cli

import scala.scalajs.js

import scala.concurrent.ExecutionContext
import wasm.Compiler

import org.scalajs.linker.interface.ModuleInitializer
import org.scalajs.macrotaskexecutor.MacrotaskExecutor.Implicits._

import scala.concurrent.Future

object Main {
private implicit val ec: ExecutionContext = ExecutionContext.global
def main(args: Array[String]): Unit = {
val modeEnvVar = js.Dynamic.global.process.env.SCALAJS_MODE

val cpEnvVar = js.Dynamic.global.process.env.SCALAJS_CLASSPATH

val classpath = (cpEnvVar: Any) match {
case cpEnvVar: String if cpEnvVar != "" =>
cpEnvVar.split(';').toList
case _ =>
throw new IllegalArgumentException("The classpath was not provided.")
}
println(classpath)

val result = for {
irFiles <- new CliReader(classpath).irFiles
_ <- Compiler.compileIRFiles(irFiles)
} yield {
println("Module successfully initialized")
()

val mode = (modeEnvVar: Any) match {
case modeEnvVar if modeEnvVar == "testsuite" => "testsuite"
case _ => "compile"
}

val result =
if (mode == "testsuite") {
val className = TestSuites.suites.map(_.className)
val moduleInitializers = className
.map { clazz =>
ModuleInitializer.mainMethod(clazz, "main")
}
.zip(className)

for {
irFiles <- new CliReader(classpath).irFiles
_ <- Future.sequence {
moduleInitializers.map { case (moduleInitializer, className) =>
Compiler.compileIRFiles(
irFiles,
List(moduleInitializer),
s"$className"
)
}
}
} yield {
println("Module successfully initialized")
()
}
} else {
for {
irFiles <- new CliReader(classpath).irFiles
_ <- Compiler.compileIRFiles(irFiles, Nil, s"output")
} yield {
println("Module successfully initialized")
()
}
}

result.recover { case th: Throwable =>
System.err.println("Module initialization failed:")
th.printStackTrace()
js.Dynamic.global.process.exit(1)
}

}
}
9 changes: 9 additions & 0 deletions cli/src/main/scala/TestSuites.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package cli

object TestSuites {
case class TestSuite(className: String, methodName: String)
val suites = List(
TestSuite("testsuite.core.simple.Simple", "simple"),
TestSuite("testsuite.core.add.Add", "add")
)
}
11 changes: 11 additions & 0 deletions test-suite/src/main/scala/testsuite/core/Add.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package testsuite.core.add

import scala.scalajs.js.annotation._

object Add {
def main(): Unit = { val _ = test() }
@JSExportTopLevel("add")
def test(): Boolean = {
1 + 1 == 2
}
}
11 changes: 11 additions & 0 deletions test-suite/src/main/scala/testsuite/core/Simple.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package testsuite.core.simple

import scala.scalajs.js.annotation._

object Simple {
def main(): Unit = { val _ = test() }
@JSExportTopLevel("simple")
def test(): Boolean = {
true
}
}
29 changes: 29 additions & 0 deletions tests/src/test/scala/tests/CoreTests.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package tests

import scala.scalajs.js
import scala.scalajs.js.annotation._
import org.scalajs.macrotaskexecutor.MacrotaskExecutor.Implicits._

class CoreTests extends munit.FunSuite {
cli.TestSuites.suites.map { suite =>
test(suite.className) {
val file = s"./target/${suite.className}.wasm"
val wasmBuffer = FS.readFileSync(file)
val wasmModule =
js.Dynamic.global.WebAssembly.instantiate(wasmBuffer).asInstanceOf[js.Promise[js.Dynamic]]
wasmModule.toFuture.map { module =>
val testFunction =
module.instance.exports
.selectDynamic(suite.methodName)
.asInstanceOf[js.Function0[Int]]
assert(testFunction() == 1)
}
}
}

}

private object FS {
@js.native @JSImport("fs")
def readFileSync(file: String): js.typedarray.Uint8Array = js.native
}
33 changes: 23 additions & 10 deletions wasm/src/main/scala/Compiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import org.scalajs.ir.Trees._
import org.scalajs.ir.Types._

import org.scalajs.linker.frontend.LinkerFrontendImpl
import org.scalajs.linker.interface.IRFile
import org.scalajs.linker.interface.{IRFile, ModuleInitializer}
import org.scalajs.linker.standard.{LinkedClass, SymbolRequirement}

import org.scalajs.logging.{Level, ScalaConsoleLogger}
Expand All @@ -20,22 +20,32 @@ import scala.scalajs.js.annotation._
import scala.scalajs.js.typedarray._

object Compiler {
def compileIRFiles(irFiles: Seq[IRFile])(implicit ec: ExecutionContext): Future[Unit] = {
def compileIRFiles(
irFiles: Seq[IRFile],
moduleInitializers: List[ModuleInitializer],
outputName: String
)(implicit ec: ExecutionContext): Future[Unit] = {
val module = new WasmModule
val builder = new WasmBuilder()
implicit val context: WasmContext = new WasmContext(module)
println("compiling")
println("compiling... ")

val config = LinkerFrontendImpl.Config()
val config = LinkerFrontendImpl
.Config()
.withOptimizer(false)
val linkerFrontend = LinkerFrontendImpl(config)

val symbolRequirements = SymbolRequirement.factory("none").none()
val logger = new ScalaConsoleLogger(Level.Error)
val logger = new ScalaConsoleLogger(Level.Info)

for {
patchedIRFiles <- LibraryPatches.patchIRFiles(irFiles)
moduleSet <- linkerFrontend.link(patchedIRFiles, Nil, symbolRequirements, logger)
moduleSet <- linkerFrontend.link(
patchedIRFiles,
moduleInitializers,
symbolRequirements,
logger
)
} yield {
val onlyModule = moduleSet.modules.head

Expand All @@ -46,17 +56,18 @@ object Compiler {
filteredClasses.sortBy(_.className).foreach(showLinkedClass(_))

Preprocessor.preprocess(filteredClasses)(context)
println("preprocessed")
filteredClasses.foreach { clazz =>
builder.transformClassDef(clazz)
}
onlyModule.topLevelExports.foreach { tle =>
builder.transformTopLevelExport(tle)
}
val writer = new converters.WasmTextWriter()
println(writer.write(module))
val textOutput = new converters.WasmTextWriter().write(module)
FS.writeFileSync(s"./target/$outputName.wat", textOutput.getBytes().toTypedArray)

val binaryOutput = new converters.WasmBinaryWriter(module).write()
FS.writeFileSync("./target/output.wasm", binaryOutput.toTypedArray)
FS.writeFileSync(s"./target/$outputName.wasm", binaryOutput.toTypedArray)
}
}

Expand Down Expand Up @@ -112,7 +123,9 @@ object Compiler {
::: clazz.jsConstructorDef.toList
::: clazz.exportedMembers
::: clazz.jsNativeMembers,
"{", "", "}"
"{",
"",
"}"
)
}
}
Expand Down
5 changes: 3 additions & 2 deletions wasm/src/main/scala/ir2wasm/TypeTransformer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ object TypeTransformer {
t: IRTypes.Type
)(implicit ctx: ReadOnlyWasmContext): List[Types.WasmType] =
t match {
case IRTypes.NoType => Nil
case _ => List(transformType(t))
case IRTypes.NoType => Nil
case IRTypes.ClassType(className) if className == IRNames.BoxedUnitClass => Nil
case _ => List(transformType(t))
}
def transformType(t: IRTypes.Type)(implicit ctx: ReadOnlyWasmContext): Types.WasmType =
t match {
Expand Down
Loading

0 comments on commit ec03bdc

Please sign in to comment.