From a3a51763f918d53278db8d16d87f7adbca77e105 Mon Sep 17 00:00:00 2001 From: Andreas Rammhold Date: Tue, 17 Dec 2019 21:15:53 +0100 Subject: [PATCH] buildRustCrate: add `buildTests` flag to tell rustc to build tests instead of binaries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This helps us instruct rustc to build tests instead of binaries. The actual build will then ONLY produce test executables. This is a first step towards having rust crate tests within nixpkgs. We default back to only a single output in test cases since that is the only reasonable thing to do here. Producing libraries or binaries in addition to tests would theoretically be feasible but usually generates different dependency trees. It is very common to have some libraries in `[dev-depdendencies]` within Cargo.toml just for your tests. To not start mixing things up going with a dedicated derivation for the test build sounds like the best choice for now. To use this you must provide a proper test dependency chain to `buildRustCrate` (as you would usually do with your non-test inputs). And then set the `buildTests` attribute to `true`. The derivation will then contain all tests that were built in `$out/tests`. All common test patterns and directories should be supported and tested by this change. Below is an example how you would run a single test from the derivation. This commit contains some more examples in the `buildRustCrateTests` attribute set that might be helpful. ``` let drv = buildRustCrate { … buildTests true; }; in runCommand "test-my-crate" {} '' touch $out exec ${drv}/tests/my-test '' ``` --- .../rust/build-rust-crate/build-crate.nix | 34 ++++- .../rust/build-rust-crate/default.nix | 20 +-- .../rust/build-rust-crate/install-crate.nix | 23 +++- .../rust/build-rust-crate/lib.sh | 27 +++- .../rust/build-rust-crate/test/default.nix | 119 ++++++++++++++++-- 5 files changed, 196 insertions(+), 27 deletions(-) diff --git a/pkgs/build-support/rust/build-rust-crate/build-crate.nix b/pkgs/build-support/rust/build-rust-crate/build-crate.nix index 9f1930fa62b6..4e2e2af1aa77 100644 --- a/pkgs/build-support/rust/build-rust-crate/build-crate.nix +++ b/pkgs/build-support/rust/build-rust-crate/build-crate.nix @@ -4,6 +4,7 @@ crateFeatures, crateRenames, libName, release, libPath, crateType, metadata, crateBin, hasCrateBin, extraRustcOpts, verbose, colors, + buildTests }: let @@ -30,6 +31,7 @@ baseRustcOpts ); + build_bin = if buildTests then "build_bin_test" else "build_bin"; in '' runHook preBuild ${echo_build_heading colors} @@ -48,14 +50,18 @@ setup_link_paths if [[ -e "$LIB_PATH" ]]; then - build_lib $LIB_PATH + build_lib "$LIB_PATH" + ${lib.optionalString buildTests ''build_lib_test "$LIB_PATH"''} elif [[ -e src/lib.rs ]]; then build_lib src/lib.rs + ${lib.optionalString buildTests "build_lib_test src/lib.rs"} elif [[ -e "src/$LIB_NAME.rs" ]]; then build_lib src/$LIB_NAME.rs + ${lib.optionalString buildTests ''build_lib_test "src/$LIB_NAME.rs"''} fi + ${lib.optionalString (lib.length crateBin > 0) (lib.concatMapStringsSep "\n" (bin: '' mkdir -p target/bin BIN_NAME='${bin.name or crateName}' @@ -65,19 +71,39 @@ '' else '' BIN_PATH='${bin.path}' ''} - build_bin "$BIN_NAME" "$BIN_PATH" + ${build_bin} "$BIN_NAME" "$BIN_PATH" '') crateBin)} + ${lib.optionalString buildTests '' + # When tests are enabled build all the files in the `tests` directory as + # test binaries. + if [ -d tests ]; then + # find all the .rs files (or symlinks to those) in the tests directory, no subdirectories + find tests -maxdepth 1 \( -type f -o -type l \) -a -name '*.rs' -print0 | while IFS= read -r -d ''' file; do + mkdir -p target/bin + build_bin_test_file "$file" + done + + # find all the subdirectories of tests/ that contain a main.rs file as + # that is also a test according to cargo + find tests/ -mindepth 1 -maxdepth 2 \( -type f -o -type l \) -a -name 'main.rs' -print0 | while IFS= read -r -d ''' file; do + mkdir -p target/bin + build_bin_test_file "$file" + done + + fi + ''} + # If crateBin is empty and hasCrateBin is not set then we must try to # detect some kind of bin target based on some files that might exist. ${lib.optionalString (lib.length crateBin == 0 && !hasCrateBin) '' if [[ -e src/main.rs ]]; then mkdir -p target/bin - build_bin ${crateName} src/main.rs + ${build_bin} ${crateName} src/main.rs fi for i in src/bin/*.rs; do #*/ mkdir -p target/bin - build_bin "$(basename $i .rs)" "$i" + ${build_bin} "$(basename $i .rs)" "$i" done ''} # Remove object files to avoid "wrong ELF type" diff --git a/pkgs/build-support/rust/build-rust-crate/default.nix b/pkgs/build-support/rust/build-rust-crate/default.nix index d5d6bf30b7c3..fb95c435380d 100644 --- a/pkgs/build-support/rust/build-rust-crate/default.nix +++ b/pkgs/build-support/rust/build-rust-crate/default.nix @@ -39,12 +39,12 @@ let inherit lib stdenv echo_build_heading noisily mkRustcDepArgs rust; }; - installCrate = import ./install-crate.nix; + installCrate = import ./install-crate.nix { inherit stdenv; }; in crate_: lib.makeOverridable ({ rust, release, verbose, features, buildInputs, crateOverrides, dependencies, buildDependencies, crateRenames, - extraRustcOpts, + extraRustcOpts, buildTests, preUnpack, postUnpack, prePatch, patches, postPatch, preConfigure, postConfigure, preBuild, postBuild, preInstall, postInstall }: @@ -59,6 +59,7 @@ let crate = crate_ // (lib.attrByPath [ crate_.crateName ] (attr: {}) crateOverr extraDerivationAttrs = lib.filterAttrs (n: v: ! lib.elem n processedAttrs) crate; buildInputs_ = buildInputs; extraRustcOpts_ = extraRustcOpts; + buildTests_ = buildTests; # take a list of crates that we depend on and override them to fit our overrides, rustc, release, … makeDependencies = map (dep: lib.getLib (dep.override { inherit release verbose crateOverrides; })); @@ -78,7 +79,7 @@ stdenv.mkDerivation (rec { crate.src else fetchCrate { inherit (crate) crateName version sha256; }; - name = "rust_${crate.crateName}-${crate.version}"; + name = "rust_${crate.crateName}-${crate.version}${lib.optionalString buildTests "-test"}"; depsBuildBuild = [ rust stdenv.cc ]; buildInputs = (crate.buildInputs or []) ++ buildInputs_; dependencies = makeDependencies dependencies_; @@ -122,6 +123,8 @@ stdenv.mkDerivation (rec { ++ extraRustcOpts_ ++ (lib.optional (edition != null) "--edition ${edition}"); + buildTests = buildTests_; + configurePhase = configureCrate { inherit crateName buildDependencies completeDeps completeBuildDeps crateDescription crateFeatures crateRenames libName build workspace_member release libPath crateVersion @@ -132,12 +135,14 @@ stdenv.mkDerivation (rec { inherit crateName dependencies crateFeatures crateRenames libName release libPath crateType metadata hasCrateBin crateBin verbose colors - extraRustcOpts; + extraRustcOpts buildTests; }; - installPhase = installCrate crateName metadata; + installPhase = installCrate crateName metadata buildTests; - outputs = [ "out" "lib" ]; - outputDev = [ "lib" ]; + # depending on the test setting we are either producing something with bins + # and libs or just test binaries + outputs = if buildTests then [ "out" ] else [ "out" "lib" ]; + outputDev = if buildTests then [ "out" ] else [ "lib" ]; } // extraDerivationAttrs )) { @@ -162,4 +167,5 @@ stdenv.mkDerivation (rec { dependencies = crate_.dependencies or []; buildDependencies = crate_.buildDependencies or []; crateRenames = crate_.crateRenames or {}; + buildTests = crate_.buildTests or false; } diff --git a/pkgs/build-support/rust/build-rust-crate/install-crate.nix b/pkgs/build-support/rust/build-rust-crate/install-crate.nix index 934c3a031764..5ba7b69bedc5 100644 --- a/pkgs/build-support/rust/build-rust-crate/install-crate.nix +++ b/pkgs/build-support/rust/build-rust-crate/install-crate.nix @@ -1,5 +1,6 @@ -crateName: metadata: -'' +{ stdenv }: +crateName: metadata: buildTests: +if !buildTests then '' runHook preInstall # always create $out even if we do not have binaries. We are detecting binary targets during compilation, if those are missing there is no way to only have $lib mkdir $out @@ -28,5 +29,23 @@ crateName: metadata: cp -P target/bin/* $out/bin # */ fi fi + runHook postInstall +'' else +# for tests we just put them all in the output. No execution. +'' + runHook preInstall + + mkdir -p $out/tests + if [ -e target/bin ]; then + find target/bin/ -type f -executable -exec cp {} $out/tests \; + fi + if [ -e target/lib ]; then + find target/lib/ -type f \! -name '*.rlib' \ + -a \! -name '*${stdenv.hostPlatform.extensions.sharedLibrary}' \ + -a \! -name '*.d' \ + -executable \ + -print0 | xargs --no-run-if-empty --null install --target $out/tests; + fi + runHook postInstall '' diff --git a/pkgs/build-support/rust/build-rust-crate/lib.sh b/pkgs/build-support/rust/build-rust-crate/lib.sh index 24c712468ea6..d4d9317496f5 100644 --- a/pkgs/build-support/rust/build-rust-crate/lib.sh +++ b/pkgs/build-support/rust/build-rust-crate/lib.sh @@ -13,6 +13,7 @@ build_lib() { $BUILD_OUT_DIR \ $EXTRA_BUILD \ $EXTRA_FEATURES \ + $EXTRA_RUSTC_FLAGS \ --color $colors EXTRA_LIB=" --extern $CRATE_NAME=target/lib/lib$CRATE_NAME-$metadata.rlib" @@ -22,9 +23,10 @@ build_lib() { } build_bin() { - crate_name=$1 - crate_name_=$(echo $crate_name | tr '-' '_') - main_file="" + local crate_name=$1 + local crate_name_=$(echo $crate_name | tr '-' '_') + local main_file="" + if [[ ! -z $2 ]]; then main_file=$2 fi @@ -43,6 +45,7 @@ build_bin() { $BUILD_OUT_DIR \ $EXTRA_BUILD \ $EXTRA_FEATURES \ + $EXTRA_RUSTC_FLAGS \ --color ${colors} \ if [ "$crate_name_" != "$crate_name" ]; then @@ -50,6 +53,24 @@ build_bin() { fi } +build_lib_test() { + local file="$1" + EXTRA_RUSTC_FLAGS="--test $EXTRA_RUSTC_FLAGS" build_lib "$1" "$2" +} + +build_bin_test() { + local crate="$1" + local file="$2" + EXTRA_RUSTC_FLAGS="--test $EXTRA_RUSTC_FLAGS" build_bin "$1" "$2" +} + +build_bin_test_file() { + local file="$1" + local derived_crate_name="${file//\//_}" + derived_crate_name="${derived_crate_name%.rs}" + build_bin_test "$derived_crate_name" "$file" +} + setup_link_paths() { EXTRA_LIB="" if [[ -e target/link_ ]]; then diff --git a/pkgs/build-support/rust/build-rust-crate/test/default.nix b/pkgs/build-support/rust/build-rust-crate/test/default.nix index 4a90cf442a4d..f0f1ed4d1ebf 100644 --- a/pkgs/build-support/rust/build-rust-crate/test/default.nix +++ b/pkgs/build-support/rust/build-rust-crate/test/default.nix @@ -29,10 +29,30 @@ let } ''; + mkTestFile = name: functionName: mkFile name '' + #[cfg(test)] + #[test] + fn ${functionName}() { + assert!(true); + } + ''; + mkTestFileWithMain = name: functionName: mkFile name '' + #[cfg(test)] + #[test] + fn ${functionName}() { + assert!(true); + } + + fn main() {} + ''; + + mkLib = name: mkFile name "pub fn test() -> i32 { return 23; }"; mkTest = crateArgs: let - crate = mkCrate crateArgs; + crate = mkCrate (builtins.removeAttrs crateArgs ["expectedTestOutput"]); + hasTests = crateArgs.buildTests or false; + expectedTestOutputs = crateArgs.expectedTestOutputs or null; binaries = map (v: ''"${v.name}"'') (crateArgs.crateBin or []); isLib = crateArgs ? libName || crateArgs ? libPath; crateName = crateArgs.crateName or "nixtestcrate"; @@ -44,16 +64,28 @@ let src = mkBinExtern "src/main.rs" libName; }; - in runCommand "run-buildRustCrate-${crateName}-test" { - nativeBuildInputs = [ crate ]; - } '' - ${lib.concatStringsSep "\n" binaries} - ${lib.optionalString isLib '' - test -e ${crate}/lib/*.rlib || exit 1 - ${libTestBinary}/bin/run-test-${crateName} - ''} - touch $out - ''; + in + assert expectedTestOutputs != null -> hasTests; + assert hasTests -> expectedTestOutputs != null; + + runCommand "run-buildRustCrate-${crateName}-test" { + nativeBuildInputs = [ crate ]; + } (if !hasTests then '' + ${lib.concatStringsSep "\n" binaries} + ${lib.optionalString isLib '' + test -e ${crate}/lib/*.rlib || exit 1 + ${libTestBinary}/bin/run-test-${crateName} + ''} + touch $out + '' else '' + for file in ${crate}/tests/*; do + $file 2>&1 >> $out + done + set -e + ${lib.concatMapStringsSep "\n" (o: "grep '${o}' $out || { echo 'output \"${o}\" not found in:'; cat $out; exit 23; }") expectedTestOutputs} + '' + ); + in rec { tests = let @@ -85,6 +117,71 @@ let dependencies = [ (mkCrate { crateName = "foo"; libName = "foolib"; src = mkLib "src/lib.rs"; }) ]; crateRenames = { "foo" = "foo_renamed"; }; }; + rustLibTestsDefault = { + src = mkTestFile "src/lib.rs" "baz"; + buildTests = true; + expectedTestOutputs = [ "test baz ... ok" ]; + }; + rustLibTestsCustomLibName = { + libName = "test_lib"; + src = mkTestFile "src/test_lib.rs" "foo"; + buildTests = true; + expectedTestOutputs = [ "test foo ... ok" ]; + }; + rustLibTestsCustomLibPath = { + libPath = "src/test_path.rs"; + src = mkTestFile "src/test_path.rs" "bar"; + buildTests = true; + expectedTestOutputs = [ "test bar ... ok" ]; + }; + rustLibTestsCustomLibPathWithTests = { + libPath = "src/test_path.rs"; + src = symlinkJoin { + name = "rust-lib-tests-custom-lib-path-with-tests-dir"; + paths = [ + (mkTestFile "src/test_path.rs" "bar") + (mkTestFile "tests/something.rs" "something") + ]; + }; + buildTests = true; + expectedTestOutputs = [ + "test bar ... ok" + "test something ... ok" + ]; + }; + rustBinTestsCombined = { + src = symlinkJoin { + name = "rust-bin-tests-combined"; + paths = [ + (mkTestFileWithMain "src/main.rs" "src_main") + (mkTestFile "tests/foo.rs" "tests_foo") + (mkTestFile "tests/bar.rs" "tests_bar") + ]; + }; + buildTests = true; + expectedTestOutputs = [ + "test src_main ... ok" + "test tests_foo ... ok" + "test tests_bar ... ok" + ]; + }; + rustBinTestsSubdirCombined = { + src = symlinkJoin { + name = "rust-bin-tests-subdir-combined"; + paths = [ + (mkTestFileWithMain "src/main.rs" "src_main") + (mkTestFile "tests/foo/main.rs" "tests_foo") + (mkTestFile "tests/bar/main.rs" "tests_bar") + ]; + }; + buildTests = true; + expectedTestOutputs = [ + "test src_main ... ok" + "test tests_foo ... ok" + "test tests_bar ... ok" + ]; + }; + }; brotliCrates = (callPackage ./brotli-crates.nix {}); in lib.mapAttrs (key: value: mkTest (value // lib.optionalAttrs (!value?crateName) { crateName = key; })) cases // {