about summary refs log tree commit diff
path: root/nix/buildLisp
diff options
context:
space:
mode:
authorGriffin Smith <grfn@gws.fyi>2020-07-27T01·11-0400
committerglittershark <grfn@gws.fyi>2020-07-27T14·18+0000
commit3089f6b6ce84833f669bb523718b20b6ad194105 (patch)
treecfa8b5c0f93c3adaf82b30595718167b5efe0565 /nix/buildLisp
parentd98f2ea68fa9c73f1cc4acfdfb9a427ae3e47f63 (diff)
feat(nix/buildLisp): Add abstraction for test suites r/1492
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 <mail@tazj.in>
Diffstat (limited to 'nix/buildLisp')
-rw-r--r--nix/buildLisp/README.md25
-rw-r--r--nix/buildLisp/default.nix167
2 files changed, 142 insertions, 50 deletions
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<path>` | List of paths to source files | yes       |
   | `deps`    | `list<drv>`  | List of dependencies          | no        |
   | `native`  | `list<drv>`  | 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<drv>`  | List of dependencies          | no        |
   | `native`  | `list<drv>`  | 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<path>` | List of paths to source files | no        |
+  | `native`     | `list<drv>`  | 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.