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 The project is still in its early stages and some important
restrictions should be highlighted: 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 * Only SBCL is supported (though the plan is to add support for at
least ABCL and Clozure CL, and maybe make it extensible) 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 | | `srcs` | `list<path>` | List of paths to source files | yes |
| `deps` | `list<drv>` | List of dependencies | no | | `deps` | `list<drv>` | List of dependencies | no |
| `native` | `list<drv>` | List of native 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 The output of invoking this is a directory containing a FASL file
that is the concatenated result of all compiled sources. that is the concatenated result of all compiled sources.
@ -46,6 +45,7 @@ restrictions should be highlighted:
| `deps` | `list<drv>` | List of dependencies | no | | `deps` | `list<drv>` | List of dependencies | no |
| `native` | `list<drv>` | List of native dependencies | no | | `native` | `list<drv>` | List of native dependencies | no |
| `main` | `string` | Entrypoint function | 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 The `main` parameter should be the name of a function and defaults
to `${name}:main` (i.e. the *exported* `main` function of the 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 pre-loaded with all of that Lisp code and can be used as the host
for e.g. Sly or SLIME. 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 ## Example
Using buildLisp could look like this: Using buildLisp could look like this:
@ -92,5 +108,10 @@ in buildLisp.program {
name = "example"; name = "example";
deps = [ libExample ]; deps = [ libExample ];
srcs = [ ./main.lisp ]; 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' determines whether Lisp library 'b' depends on 'a'.
dependsOn = a: b: builtins.elem a b.lispDeps; dependsOn = a: b: builtins.elem a b.lispDeps;
@ -103,64 +117,121 @@ let
overrideLisp = new: makeOverridable f (orig // (new orig)); 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 # Public API functions
# #
# 'library' builds a list of Common Lisp files into a single FASL # 'library' builds a list of Common Lisp files into a single FASL
# which can then be loaded into SBCL. # which can then be loaded into SBCL.
library = { name, srcs, deps ? [], native ? [] }: library =
let { name
lispNativeDeps = (allNative native deps); , srcs
lispDeps = allDeps deps; , deps ? []
in runCommandNoCC "${name}-cllib" { , native ? []
LD_LIBRARY_PATH = lib.makeLibraryPath lispNativeDeps; , tests ? null
LANG = "C.UTF-8"; }:
} '' let
${sbcl}/bin/sbcl --script ${genCompileLisp srcs lispDeps} 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 # FASL files can be combined by simply concatenating them
# together, but it needs to be in the compilation order. # together, but it needs to be in the compilation order.
mkdir $out mkdir $out
chmod +x cat_fasls chmod +x cat_fasls
./cat_fasls > $out/${name}.fasl ./cat_fasls > $out/${name}.fasl
'' // { '' // {
inherit lispNativeDeps lispDeps; inherit lispNativeDeps lispDeps;
lispName = name; lispName = name;
lispBinary = false; lispBinary = false;
}; tests = testDrv;
};
# 'program' creates an executable containing a dumped image of the # 'program' creates an executable containing a dumped image of the
# specified sources and dependencies. # specified sources and dependencies.
program = { name, main ? "${name}:main", srcs, deps ? [], native ? [] }: program =
let { name
lispDeps = allDeps deps; , main ? "${name}:main"
libPath = lib.makeLibraryPath (allNative native lispDeps); , srcs
selfLib = library { , deps ? []
inherit name srcs native; , native ? []
deps = lispDeps; , 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 # 'bundled' creates a "library" that calls 'require' on a built-in
# package, such as any of SBCL's sb-* packages. # package, such as any of SBCL's sb-* packages.