From 31981fc93a600f91ea8f9eef89fc5a05b56c36e1 Mon Sep 17 00:00:00 2001 From: Avimitin Date: Tue, 24 Oct 2023 13:07:20 +0800 Subject: [PATCH] [build system] build test case when TEST_CASE_DIR is unset This commit remove the caseBuild build task and let the verilatorEmulator automatically build test case when the test binary is not found. Signed-off-by: Avimitin --- build.sc | 56 +++++++++++++++++++++++++++++++++---------------- flake.nix | 6 ++---- tests/build.sc | 33 ----------------------------- tests/readme.md | 46 +++++++++++++++++++--------------------- 4 files changed, 61 insertions(+), 80 deletions(-) diff --git a/build.sc b/build.sc index a1be2ea702..2cc67ac661 100644 --- a/build.sc +++ b/build.sc @@ -355,18 +355,21 @@ trait Emulator } } -def testsOutDir = sys.env.get("TEST_CASE_DIR").map(os.Path(_)).getOrElse { - val tmpDir = os.temp.dir(dir = os.pwd / "out", prefix = "TEST_CASE_DIR", deleteOnExit = false) - os.makeDir(tmpDir / "configs") - tmpDir +def testsCaseDir = sys.env.get("TEST_CASE_DIR").map(os.Path(_)).getOrElse { + val testsCaseDir = os.pwd / "out" / "testCaseDir.dest" + if (!os.exists(testsCaseDir)) { + os.makeDir(testsCaseDir) + os.symlink(testsCaseDir / "configs", os.pwd / "tests" / "configs") + } + testsCaseDir } def testConfigs = os - .walk(testsOutDir / "configs") + .walk(testsCaseDir / "configs") .filter(_.ext == "json") .filter(p => !ujson.read(os.read(p)).obj("fp").bool) .map(_.baseName) def fpTestConfigs = os - .walk(testsOutDir / "configs") + .walk(testsCaseDir / "configs") .filter(_.ext == "json") .filter(p => ujson.read(os.read(p)).obj("fp").bool) .map(_.baseName) @@ -400,8 +403,22 @@ trait RunVerilatorEmulator def configDir: T[os.Path] = T(os.pwd / "run") def configFile: T[os.Path] = T(configDir() / s"$config.json") def runConfig: T[ujson.Value.Value] = T(ujson.read(os.read(configFile()))) - def testConfig: T[ujson.Value.Value] = T(ujson.read(os.read(testsOutDir / "configs" / s"$testTask.json"))) - def binPath: T[os.Path] = T(testsOutDir / os.RelPath(testConfig().obj("elf").obj("path").str)) + def testConfig: T[ujson.Value.Value] = T(ujson.read(os.read(testsCaseDir / "configs" / s"$testTask.json"))) + def binPath: T[os.Path] = T { + if (testConfig().obj.get("elf").isDefined) { + testsCaseDir / os.RelPath(testConfig().obj("elf").obj("path").str) + } else { + import scala.util.chaining._ + val testType = testConfig().obj("type").str + val testName = testConfig().obj("name").str + os.proc("mill", "-i", "show", s"$testType[$testName].elf") + .call(os.pwd / "tests") + .out + .text() + .pipe(ujson.read(_).str.split(":")(3)) + .pipe(os.Path(_)) + } + } def run = T { def wave: String = runConfig().obj.get("wave").map(_.str).getOrElse((T.dest / "wave").toString) @@ -677,29 +694,32 @@ trait SubsystemEmulator PathRef(buildDir().path / "emulator") } - def defaultCommandName = "elf" + override def defaultCommandName() = "elf" } object runSubsystemEmu extends mill.Cross[RunSubsystemEmu]( - os.walk(os.pwd / "tests" / "configs").filter(_.ext == "json").map(_.baseName).toSeq + os.walk(testsCaseDir).filter(_.ext == "json").map(_.baseName).toSeq ) trait RunSubsystemEmu extends Cross.Module[String] with TaskModule { override def defaultCommandName() = "run" val test: String = crossValue - def testConfigDir = T(os.pwd / "tests" / "configs" / s"$test.json") - def testConfig = T(ujson.read(os.read(testConfigDir()))) + def testConfig = T(ujson.read(os.read(testsCaseDir / "configs" / s"$test.json"))) def testName = T(testConfig().apply("name").str) def testType = T(testConfig().apply("type").str) import scala.util.chaining._ def testElf = T ( - os.proc(Seq("mill", "-i", "show", s"${testType()}[${testName()}].bin")) - .call(os.pwd / "tests") - .out - .text - .pipe(ujson.read(_).str.split(":")(3)) - .pipe(os.Path(_)) + if (testConfig().obj.get("elf").isDefined) { + testsCaseDir / os.RelPath(testConfig().obj("elf").obj("path").str) + } else { + os.proc(Seq("mill", "-i", "show", s"${testType()}[${testName()}].bin")) + .call(os.pwd / "tests") + .out + .text + .pipe(ujson.read(_).str.split(":")(3)) + .pipe(os.Path(_)) + } ) def run = T { diff --git a/flake.nix b/flake.nix index 91369158c2..4a19208953 100644 --- a/flake.nix +++ b/flake.nix @@ -87,11 +87,9 @@ CODEGEN_CFG_PATH = "${pkgs.rvv-codegen}/configs"; }; }; - # This devShell is used for running testcase + # This devShell is used only for running testcase testcase = mkLLVMShell { - # TODO: Currently, the emulator needs all the dependencies to run a test case , - # but most of them are used to get version information, so they should be cleaned up one day. - buildInputs = commonDeps ++ chiselDeps ++ testcaseDeps ++ emulatorDeps ++ [ pkgs.metals ]; + buildInputs = commonDeps ++ chiselDeps ++ emulatorDeps; env = { TEST_CASE_DIR = "${pkgs.rvv-testcase}"; diff --git a/tests/build.sc b/tests/build.sc index 230b34e8d4..991a4cf88a 100644 --- a/tests/build.sc +++ b/tests/build.sc @@ -174,36 +174,3 @@ trait AsmCase val config: String = crossValue override def moduleName = "asm" } - -object caseBuild - extends Cross[CaseBuilder]( - os.walk(os.pwd / "configs") - .filter(_.ext == "json") - .map(f => s"${ujson.read(os.read(f)).obj("name").str}-${ujson.read(os.read(f)).obj("type").str}") -) - -trait CaseBuilder - extends Cross.Module[String] { - val task: String = crossValue - def run = T { - // prepare - val outputDir = os.pwd / os.up / "tests-out" - os.remove.all(os.pwd / "out") - os.makeDir.all(outputDir) - val IndexedSeq(name, module) = task.split("-").toSeq - os.makeDir.all(outputDir / "cases" / module) - os.makeDir.all(outputDir / "configs") - - // build elf - val rawElfPath = os.proc("mill", "--no-server", "show", s"$module[$name].elf").call(os.pwd).out.text - val elfPath = os.Path(ujson.read(rawElfPath).str.split(":")(3)) - - // write elf path into test config - val origConfig = ujson.read(os.read(os.pwd / "configs" / s"$task.json")) - origConfig("elf") = ujson.Obj("path" -> s"cases/$module/${elfPath.last}") - - // install, override if file exists - os.move.into(elfPath, outputDir / "cases" / module, true) - os.write.over(outputDir / "configs" / s"$task.json", ujson.write(origConfig)) - } -} diff --git a/tests/readme.md b/tests/readme.md index bd9a67335d..d153f69175 100644 --- a/tests/readme.md +++ b/tests/readme.md @@ -24,14 +24,33 @@ You can run `nix develop .#testcase-bootstrap` to have this env set up for you. ## How to resolve all the tests ```bash -mill resolve _[_].elf +mill -i resolve _[_].elf # Get asm test only -mill resolve asm[_].elf +mill -i resolve asm[_].elf ``` ## How to run the test +```bash +nix develop .#testcase + +# List all runnable tests +mill -i resolve verilatorEmulator[v1024l8b2-test,_,debug].run + +# Run a specific test +mill -i verilatorEmulator[v1024l8b2-test,matmul-mlir,debug].run +``` + +## How to build single test and run it + +```bash +unset TEST_CASE_DIR +mill -i verilatorEmulator[$emulator,$YOUR_TEST,$debug].run +``` + +## How to manually rebuild all the tests + Test cases are run by an emulator specified in the `build.sc` file which located at the project root. `mill` will create a bunch of `verilatorEmulator[__]` objects by reading test file in env `TEST_CASE_DIR`. The `TEST_CASE_DIR` must point to a directory with the following layout: @@ -72,26 +91,3 @@ you can type `mill -i verilatorEmulator[v1024l8b2-test,matmul-mlir,debug].run` i To reduce all the tedious setup steps, you can use the provided `.#testcase` shell: -```bash -nix develop .#testcase -mill -i verilatorEmulator[v1024l8b2-test,matmul-mlir,debug].run -``` - -## How to build single test and run it - -Suppose you want to build and run the mlir/hello.mlir test: - -```bash -# build -pushd tests -nix develop .#testcase-bootstrap -mill --no-server caseBuild[hello-mlir].run -exit -popd - -# run -nix develop .#testcase -export TEST_CASE_DIR=$PWD/tests-out -mill --no-server verilatorEmulator[v1024l8b2-test,hello-mlir,debug].run -``` -