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 <mail@tazj.in>
This commit is contained in:
Griffin Smith 2020-07-26 21:11:32 -04:00 committed by glittershark
parent d98f2ea68f
commit 3089f6b6ce
2 changed files with 139 additions and 47 deletions

View file

@ -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!)";
};
}
```

View file

@ -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}
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"
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
# 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;
};
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.