From 3089f6b6ce84833f669bb523718b20b6ad194105 Mon Sep 17 00:00:00 2001 From: Griffin Smith Date: Sun, 26 Jul 2020 21:11:32 -0400 Subject: feat(nix/buildLisp): Add abstraction for test suites Add support for explicitly specifying tests as part of a buildLisp program or library. Change-Id: I733213c1618f0fa60f645465560bce0522641efd Reviewed-on: https://cl.tvl.fyi/c/depot/+/1481 Tested-by: BuildkiteCI Reviewed-by: tazjin --- nix/buildLisp/README.md | 25 ++++++- nix/buildLisp/default.nix | 167 +++++++++++++++++++++++++++++++++------------- 2 files changed, 142 insertions(+), 50 deletions(-) (limited to 'nix/buildLisp') diff --git a/nix/buildLisp/README.md b/nix/buildLisp/README.md index 8e45f3479c..452f495deb 100644 --- a/nix/buildLisp/README.md +++ b/nix/buildLisp/README.md @@ -16,8 +16,6 @@ Nix-based ecosystem. This offers several advantages over ASDF: The project is still in its early stages and some important restrictions should be highlighted: -* There is no separate abstraction for tests at the moment (i.e. they - are built and run as programs) * Only SBCL is supported (though the plan is to add support for at least ABCL and Clozure CL, and maybe make it extensible) @@ -33,6 +31,7 @@ restrictions should be highlighted: | `srcs` | `list` | List of paths to source files | yes | | `deps` | `list` | List of dependencies | no | | `native` | `list` | List of native dependencies | no | + | `test` | see "Tests" | Specification for test suite | no | The output of invoking this is a directory containing a FASL file that is the concatenated result of all compiled sources. @@ -46,6 +45,7 @@ restrictions should be highlighted: | `deps` | `list` | List of dependencies | no | | `native` | `list` | List of native dependencies | no | | `main` | `string` | Entrypoint function | no | + | `test` | see "Tests" | Specification for test suite | no | The `main` parameter should be the name of a function and defaults to `${name}:main` (i.e. the *exported* `main` function of the @@ -71,6 +71,22 @@ restrictions should be highlighted: pre-loaded with all of that Lisp code and can be used as the host for e.g. Sly or SLIME. +## Tests + +Both `buildLisp.library` and `buildLisp.program` take an optional argument +`tests`, which has the following supported fields: + + | parameter | type | use | required? | + |--------------|--------------|-------------------------------|-----------| + | `name` | `string` | Name of the test suite | no | + | `expression` | `string` | Lisp expression to run tests | yes | + | `srcs` | `list` | List of paths to source files | no | + | `native` | `list` | List of native dependencies | no | + +the `expression` parameter should be a Lisp expression and will be evaluated +after loading all sources and dependencies (including library/program +dependencies). It must return a non-`NIL` value if the test suite has passed. + ## Example Using buildLisp could look like this: @@ -92,5 +108,10 @@ in buildLisp.program { name = "example"; deps = [ libExample ]; srcs = [ ./main.lisp ]; + tests = { + deps = [ lispPkgs.fiveam ]; + srcs = [ ./tests.lisp ]; + expression = "(fiveam:run!)"; + }; } ``` diff --git a/nix/buildLisp/default.nix b/nix/buildLisp/default.nix index 2ee635d4c1..98d6260870 100644 --- a/nix/buildLisp/default.nix +++ b/nix/buildLisp/default.nix @@ -60,6 +60,20 @@ let )) ''; + # 'genTestLisp' generates a Lisp file that loads all sources and deps and + # executes expression + genTestLisp = name: srcs: deps: expression: writeText "${name}.lisp" '' + ;; Dependencies + ${genLoadLisp deps} + + ;; Sources + ${lib.concatStringsSep "\n" (map (src: "(load \"${src}\")") srcs)} + + ;; Test expression + (unless ${expression} + (exit :code 1)) + ''; + # 'dependsOn' determines whether Lisp library 'b' depends on 'a'. dependsOn = a: b: builtins.elem a b.lispDeps; @@ -103,64 +117,121 @@ let overrideLisp = new: makeOverridable f (orig // (new orig)); }; + # 'testSuite' builds a Common Lisp test suite that loads all of srcs and deps, + # and then executes expression to check its result + testSuite = { name, expression, srcs, deps ? [], native ? [] }: + let + lispNativeDeps = allNative native deps; + lispDeps = allDeps deps; + in runCommandNoCC name { + LD_LIBRARY_PATH = lib.makeLibraryPath lispNativeDeps; + LANG = "C.UTF-8"; + } '' + echo "Running test suite ${name}" + + ${sbcl}/bin/sbcl --script ${genTestLisp name srcs deps expression} \ + | tee $out + + echo "Test suite ${name} succeeded" + ''; + # # Public API functions # # 'library' builds a list of Common Lisp files into a single FASL # which can then be loaded into SBCL. - library = { name, srcs, deps ? [], native ? [] }: - let - lispNativeDeps = (allNative native deps); - lispDeps = allDeps deps; - in runCommandNoCC "${name}-cllib" { - LD_LIBRARY_PATH = lib.makeLibraryPath lispNativeDeps; - LANG = "C.UTF-8"; - } '' - ${sbcl}/bin/sbcl --script ${genCompileLisp srcs lispDeps} - - echo "Compilation finished, assembling FASL files" - - # FASL files can be combined by simply concatenating them - # together, but it needs to be in the compilation order. - mkdir $out - - chmod +x cat_fasls - ./cat_fasls > $out/${name}.fasl - '' // { - inherit lispNativeDeps lispDeps; - lispName = name; - lispBinary = false; - }; + library = + { name + , srcs + , deps ? [] + , native ? [] + , tests ? null + }: + let + lispNativeDeps = (allNative native deps); + lispDeps = allDeps deps; + testDrv = if ! isNull tests + then testSuite { + name = tests.name or "${name}-test"; + srcs = srcs ++ (tests.srcs or []); + deps = deps ++ (tests.deps or []); + expression = tests.expression; + } + else null; + in runCommandNoCC "${name}-cllib" { + LD_LIBRARY_PATH = lib.makeLibraryPath lispNativeDeps; + LANG = "C.UTF-8"; + } '' + ${if ! isNull testDrv + then "echo 'Test ${testDrv} succeeded'" + else "echo 'No tests run'"} + ${sbcl}/bin/sbcl --script ${genCompileLisp srcs lispDeps} + + echo "Compilation finished, assembling FASL files" + + # FASL files can be combined by simply concatenating them + # together, but it needs to be in the compilation order. + mkdir $out + + chmod +x cat_fasls + ./cat_fasls > $out/${name}.fasl + '' // { + inherit lispNativeDeps lispDeps; + lispName = name; + lispBinary = false; + tests = testDrv; + }; # 'program' creates an executable containing a dumped image of the # specified sources and dependencies. - program = { name, main ? "${name}:main", srcs, deps ? [], native ? [] }: - let - lispDeps = allDeps deps; - libPath = lib.makeLibraryPath (allNative native lispDeps); - selfLib = library { - inherit name srcs native; - deps = lispDeps; + program = + { name + , main ? "${name}:main" + , srcs + , deps ? [] + , native ? [] + , tests ? null + }: + let + lispDeps = allDeps deps; + libPath = lib.makeLibraryPath (allNative native lispDeps); + selfLib = library { + inherit name srcs native; + deps = lispDeps; + }; + testDrv = if ! isNull tests + then testSuite { + name = tests.name or "${name}-test"; + srcs = + ( + srcs ++ (tests.srcs or [])); + deps = deps ++ (tests.deps or []); + expression = tests.expression; + } + else null; + in runCommandNoCC "${name}" { + nativeBuildInputs = [ makeWrapper ]; + LD_LIBRARY_PATH = libPath; + LANG = "C.UTF-8"; + } '' + ${if ! isNull testDrv + then "echo 'Test ${testDrv} succeeded'" + else ""} + mkdir -p $out/bin + + ${sbcl}/bin/sbcl --script ${ + genDumpLisp name main ([ selfLib ] ++ lispDeps) + } + + wrapProgram $out/bin/${name} --prefix LD_LIBRARY_PATH : "${libPath}" + '' // { + lispName = name; + lispDeps = [ selfLib ] ++ (tests.deps or []); + lispNativeDeps = native; + lispBinary = true; + tests = testDrv; }; - in runCommandNoCC "${name}" { - nativeBuildInputs = [ makeWrapper ]; - LD_LIBRARY_PATH = libPath; - LANG = "C.UTF-8"; - } '' - mkdir -p $out/bin - - ${sbcl}/bin/sbcl --script ${ - genDumpLisp name main ([ selfLib ] ++ lispDeps) - } - - wrapProgram $out/bin/${name} --prefix LD_LIBRARY_PATH : "${libPath}" - '' // { - lispName = name; - lispDeps = [ selfLib ]; - lispNativeDeps = native; - lispBinary = true; - }; # 'bundled' creates a "library" that calls 'require' on a built-in # package, such as any of SBCL's sb-* packages. -- cgit 1.4.1