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

Commit ec03bdc

Browse files
authored
Merge pull request #7 from tanishiking/testsuite
Setup a testing infrastructure
2 parents 72073ff + ccf1532 commit ec03bdc

File tree

11 files changed

+219
-57
lines changed

11 files changed

+219
-57
lines changed

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,19 @@ $ deno run --allow-read run.mjs
1717
- The WasmGC reference interpreter can be used to validate and convert between the binary and text form:
1818
- https://github.com/WebAssembly/gc/tree/main/interpreter
1919
- Use docker image for it https://github.com/tanishiking/wasmgc-docker
20+
21+
### Testing
22+
23+
Requires NodeJS >= 22 (for enough support of WasmGC).
24+
25+
```sh
26+
$ NVM_NODEJS_ORG_MIRROR=https://nodejs.org/download/nightly nvm install v22
27+
```
28+
29+
- `tests/test` will
30+
- Run `testSuite/run` to compile the Scala code under `test-suite` to WebAssembly
31+
- Run the WebAssembly binary using NodeJS
32+
- 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`.
33+
- When you add a test,
34+
- Add a file under `test-suite`
35+
- Add a test case to `cli/src/main/scala/TestSuites.scala` (`methodName` should be a exported function name).

build.sbt

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,15 @@ lazy val cli = project
99
scalaJSUseMainModuleInitializer := true,
1010
scalaJSLinkerConfig ~= {
1111
_.withModuleKind(ModuleKind.CommonJSModule),
12-
}
12+
},
13+
libraryDependencies ++= Seq(
14+
"org.scala-js" %%% "scala-js-macrotask-executor" % "1.1.1"
15+
),
16+
)
17+
.dependsOn(
18+
wasm,
19+
// tests // for TestSuites constant
1320
)
14-
.dependsOn(wasm)
1521

1622
lazy val wasm = project
1723
.in(file("wasm"))
@@ -21,7 +27,6 @@ lazy val wasm = project
2127
version := "0.1.0-SNAPSHOT",
2228
scalaVersion := scalaV,
2329
libraryDependencies ++= Seq(
24-
"org.scalameta" %% "munit" % "0.7.29" % Test,
2530
"org.scala-js" %%% "scalajs-linker" % "1.15.0"
2631
),
2732
scalaJSUseMainModuleInitializer := true,
@@ -41,14 +46,50 @@ lazy val sample = project
4146
import org.scalajs.jsenv.nodejs.NodeJSEnv
4247
val cp = Attributed
4348
.data((Compile / fullClasspath).value)
44-
// .filter { path =>
45-
// val pathStr = path.toString()
46-
// println(pathStr)
47-
// pathStr.contains("sample/target")
48-
// }
4949
.mkString(";")
50-
val env = Map("SCALAJS_CLASSPATH" -> cp, "SCALAJS_MODE" -> "sample")
50+
val env = Map(
51+
"SCALAJS_CLASSPATH" -> cp,
52+
"SCALAJS_MODE" -> "sample",
53+
)
54+
new NodeJSEnv(NodeJSEnv.Config().withEnv(env).withArgs(List("--enable-source-maps")))
55+
},
56+
Compile / jsEnvInput := (`cli` / Compile / jsEnvInput).value
57+
)
58+
59+
lazy val testSuite = project
60+
.in(file("test-suite"))
61+
.enablePlugins(ScalaJSPlugin)
62+
.settings(
63+
scalaVersion := scalaV,
64+
scalaJSUseMainModuleInitializer := true,
65+
Compile / jsEnv := {
66+
import org.scalajs.jsenv.nodejs.NodeJSEnv
67+
val cp = Attributed
68+
.data((Compile / fullClasspath).value)
69+
.mkString(";")
70+
val env = Map(
71+
"SCALAJS_CLASSPATH" -> cp,
72+
"SCALAJS_MODE" -> "testsuite",
73+
)
5174
new NodeJSEnv(NodeJSEnv.Config().withEnv(env).withArgs(List("--enable-source-maps")))
5275
},
5376
Compile / jsEnvInput := (`cli` / Compile / jsEnvInput).value
5477
)
78+
79+
lazy val tests = project
80+
.in(file("tests"))
81+
.enablePlugins(ScalaJSPlugin)
82+
.settings(
83+
scalaVersion := scalaV,
84+
libraryDependencies ++= Seq(
85+
"org.scalameta" %%% "munit" % "0.7.29" % Test,
86+
"org.scala-js" %%% "scala-js-macrotask-executor" % "1.1.1" % Test
87+
),
88+
scalaJSLinkerConfig ~= {
89+
_.withModuleKind(ModuleKind.CommonJSModule),
90+
},
91+
test := Def.sequential(
92+
(testSuite / Compile / run).toTask(""),
93+
(Test / test)
94+
).value
95+
).dependsOn(cli)

cli/src/main/scala/Main.scala

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,35 +2,68 @@ package cli
22

33
import scala.scalajs.js
44

5-
import scala.concurrent.ExecutionContext
65
import wasm.Compiler
76

7+
import org.scalajs.linker.interface.ModuleInitializer
8+
import org.scalajs.macrotaskexecutor.MacrotaskExecutor.Implicits._
9+
10+
import scala.concurrent.Future
11+
812
object Main {
9-
private implicit val ec: ExecutionContext = ExecutionContext.global
1013
def main(args: Array[String]): Unit = {
1114
val modeEnvVar = js.Dynamic.global.process.env.SCALAJS_MODE
12-
1315
val cpEnvVar = js.Dynamic.global.process.env.SCALAJS_CLASSPATH
16+
1417
val classpath = (cpEnvVar: Any) match {
1518
case cpEnvVar: String if cpEnvVar != "" =>
1619
cpEnvVar.split(';').toList
1720
case _ =>
1821
throw new IllegalArgumentException("The classpath was not provided.")
1922
}
20-
println(classpath)
21-
22-
val result = for {
23-
irFiles <- new CliReader(classpath).irFiles
24-
_ <- Compiler.compileIRFiles(irFiles)
25-
} yield {
26-
println("Module successfully initialized")
27-
()
23+
24+
val mode = (modeEnvVar: Any) match {
25+
case modeEnvVar if modeEnvVar == "testsuite" => "testsuite"
26+
case _ => "compile"
2827
}
28+
29+
val result =
30+
if (mode == "testsuite") {
31+
val className = TestSuites.suites.map(_.className)
32+
val moduleInitializers = className
33+
.map { clazz =>
34+
ModuleInitializer.mainMethod(clazz, "main")
35+
}
36+
.zip(className)
37+
38+
for {
39+
irFiles <- new CliReader(classpath).irFiles
40+
_ <- Future.sequence {
41+
moduleInitializers.map { case (moduleInitializer, className) =>
42+
Compiler.compileIRFiles(
43+
irFiles,
44+
List(moduleInitializer),
45+
s"$className"
46+
)
47+
}
48+
}
49+
} yield {
50+
println("Module successfully initialized")
51+
()
52+
}
53+
} else {
54+
for {
55+
irFiles <- new CliReader(classpath).irFiles
56+
_ <- Compiler.compileIRFiles(irFiles, Nil, s"output")
57+
} yield {
58+
println("Module successfully initialized")
59+
()
60+
}
61+
}
62+
2963
result.recover { case th: Throwable =>
3064
System.err.println("Module initialization failed:")
3165
th.printStackTrace()
3266
js.Dynamic.global.process.exit(1)
3367
}
34-
3568
}
3669
}

cli/src/main/scala/TestSuites.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package cli
2+
3+
object TestSuites {
4+
case class TestSuite(className: String, methodName: String)
5+
val suites = List(
6+
TestSuite("testsuite.core.simple.Simple", "simple"),
7+
TestSuite("testsuite.core.add.Add", "add")
8+
)
9+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package testsuite.core.add
2+
3+
import scala.scalajs.js.annotation._
4+
5+
object Add {
6+
def main(): Unit = { val _ = test() }
7+
@JSExportTopLevel("add")
8+
def test(): Boolean = {
9+
1 + 1 == 2
10+
}
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package testsuite.core.simple
2+
3+
import scala.scalajs.js.annotation._
4+
5+
object Simple {
6+
def main(): Unit = { val _ = test() }
7+
@JSExportTopLevel("simple")
8+
def test(): Boolean = {
9+
true
10+
}
11+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package tests
2+
3+
import scala.scalajs.js
4+
import scala.scalajs.js.annotation._
5+
import org.scalajs.macrotaskexecutor.MacrotaskExecutor.Implicits._
6+
7+
class CoreTests extends munit.FunSuite {
8+
cli.TestSuites.suites.map { suite =>
9+
test(suite.className) {
10+
val file = s"./target/${suite.className}.wasm"
11+
val wasmBuffer = FS.readFileSync(file)
12+
val wasmModule =
13+
js.Dynamic.global.WebAssembly.instantiate(wasmBuffer).asInstanceOf[js.Promise[js.Dynamic]]
14+
wasmModule.toFuture.map { module =>
15+
val testFunction =
16+
module.instance.exports
17+
.selectDynamic(suite.methodName)
18+
.asInstanceOf[js.Function0[Int]]
19+
assert(testFunction() == 1)
20+
}
21+
}
22+
}
23+
24+
}
25+
26+
private object FS {
27+
@js.native @JSImport("fs")
28+
def readFileSync(file: String): js.typedarray.Uint8Array = js.native
29+
}

wasm/src/main/scala/Compiler.scala

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import org.scalajs.ir.Trees._
88
import org.scalajs.ir.Types._
99

1010
import org.scalajs.linker.frontend.LinkerFrontendImpl
11-
import org.scalajs.linker.interface.IRFile
11+
import org.scalajs.linker.interface.{IRFile, ModuleInitializer}
1212
import org.scalajs.linker.standard.{LinkedClass, SymbolRequirement}
1313

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

2222
object Compiler {
23-
def compileIRFiles(irFiles: Seq[IRFile])(implicit ec: ExecutionContext): Future[Unit] = {
23+
def compileIRFiles(
24+
irFiles: Seq[IRFile],
25+
moduleInitializers: List[ModuleInitializer],
26+
outputName: String
27+
)(implicit ec: ExecutionContext): Future[Unit] = {
2428
val module = new WasmModule
2529
val builder = new WasmBuilder()
2630
implicit val context: WasmContext = new WasmContext(module)
27-
println("compiling")
31+
println("compiling... ")
2832

29-
val config = LinkerFrontendImpl.Config()
33+
val config = LinkerFrontendImpl
34+
.Config()
3035
.withOptimizer(false)
3136
val linkerFrontend = LinkerFrontendImpl(config)
3237

3338
val symbolRequirements = SymbolRequirement.factory("none").none()
34-
val logger = new ScalaConsoleLogger(Level.Error)
39+
val logger = new ScalaConsoleLogger(Level.Info)
3540

3641
for {
3742
patchedIRFiles <- LibraryPatches.patchIRFiles(irFiles)
38-
moduleSet <- linkerFrontend.link(patchedIRFiles, Nil, symbolRequirements, logger)
43+
moduleSet <- linkerFrontend.link(
44+
patchedIRFiles,
45+
moduleInitializers,
46+
symbolRequirements,
47+
logger
48+
)
3949
} yield {
4050
val onlyModule = moduleSet.modules.head
4151

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

4858
Preprocessor.preprocess(filteredClasses)(context)
59+
println("preprocessed")
4960
filteredClasses.foreach { clazz =>
5061
builder.transformClassDef(clazz)
5162
}
5263
onlyModule.topLevelExports.foreach { tle =>
5364
builder.transformTopLevelExport(tle)
5465
}
55-
val writer = new converters.WasmTextWriter()
56-
println(writer.write(module))
66+
val textOutput = new converters.WasmTextWriter().write(module)
67+
FS.writeFileSync(s"./target/$outputName.wat", textOutput.getBytes().toTypedArray)
5768

5869
val binaryOutput = new converters.WasmBinaryWriter(module).write()
59-
FS.writeFileSync("./target/output.wasm", binaryOutput.toTypedArray)
70+
FS.writeFileSync(s"./target/$outputName.wasm", binaryOutput.toTypedArray)
6071
}
6172
}
6273

@@ -112,7 +123,9 @@ object Compiler {
112123
::: clazz.jsConstructorDef.toList
113124
::: clazz.exportedMembers
114125
::: clazz.jsNativeMembers,
115-
"{", "", "}"
126+
"{",
127+
"",
128+
"}"
116129
)
117130
}
118131
}

wasm/src/main/scala/ir2wasm/TypeTransformer.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,9 @@ object TypeTransformer {
3535
t: IRTypes.Type
3636
)(implicit ctx: ReadOnlyWasmContext): List[Types.WasmType] =
3737
t match {
38-
case IRTypes.NoType => Nil
39-
case _ => List(transformType(t))
38+
case IRTypes.NoType => Nil
39+
case IRTypes.ClassType(className) if className == IRNames.BoxedUnitClass => Nil
40+
case _ => List(transformType(t))
4041
}
4142
def transformType(t: IRTypes.Type)(implicit ctx: ReadOnlyWasmContext): Types.WasmType =
4243
t match {

0 commit comments

Comments
 (0)