From 21762297a4072b5a25f2d528d5839633fa396133 Mon Sep 17 00:00:00 2001 From: Profpatsch Date: Fri, 21 Sep 2018 22:03:41 +0200 Subject: More execline experiments & testing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Improves the “execline experience” and adds some basic tests. The idea is that the final result doesn’t use coreutils and provides a feasible alternative to bash-based tooling. --- pkgs/profpatsch/default.nix | 59 ++++++++++++---- pkgs/profpatsch/execline/run-execline-tests.nix | 86 ++++++++++++++++++++++ pkgs/profpatsch/execline/run-execline.nix | 40 +++++++++-- pkgs/profpatsch/testing/default.nix | 94 +++++++++++++++++++++++++ 4 files changed, 257 insertions(+), 22 deletions(-) create mode 100644 pkgs/profpatsch/execline/run-execline-tests.nix create mode 100644 pkgs/profpatsch/testing/default.nix (limited to 'pkgs/profpatsch') diff --git a/pkgs/profpatsch/default.nix b/pkgs/profpatsch/default.nix index f14eb61c..5477b83a 100644 --- a/pkgs/profpatsch/default.nix +++ b/pkgs/profpatsch/default.nix @@ -3,6 +3,31 @@ let inherit (pkgs) callPackage; + # wrapper for execlineb that doesn’t need the execline commands + # in PATH to work (making them appear like “builtins”) + execlineb-with-builtins = + let eldir = "${pkgs.execline}/bin"; + in pkgs.writeScriptBin "execlineb" '' + #!${eldir}/execlineb -s0 + ${eldir}/define eldir ${eldir} + ''${eldir}/importas oldpath PATH + ''${eldir}/export PATH "''${eldir}:''${oldpath}" + ''${eldir}/execlineb $@ + ''; + + # TODO: use imports! + execlinebCommand = "${execlineb-with-builtins}/bin/execlineb"; + redirfdCommand = "${pkgs.execline}/bin/redirfd"; + importasCommand = "${pkgs.execline}/bin/importas"; + s6TouchCommand = "${pkgs.s6-portable-utils}/bin/s6-touch"; + s6EchoCommand = "${pkgs.s6-portable-utils}/bin/s6-echo"; + ifCommand = "${pkgs.execline}/bin/if"; + s6GrepCommand = "${pkgs.s6-portable-utils}/bin/s6-grep"; + s6CatCommand = "${pkgs.s6-portable-utils}/bin/s6-cat"; + s6TestCommand = "${pkgs.s6-portable-utils}/bin/s6-test"; + s6ChmodCommand = "${pkgs.s6-portable-utils}/bin/s6-chmod"; + execCommand = "${pkgs.execline}/bin/exec"; + in rec { backlight = callPackage ./backlight { inherit (pkgs.xorg) xbacklight; }; display-infos = callPackage ./display-infos {}; @@ -33,22 +58,26 @@ in rec { ]; }); - # wrapper for execlineb that doesn’t need the execline commands - # in PATH to work (making them appear like “builtins”) - execlineb-with-builtins = - let eldir = "${pkgs.execline}/bin"; - in pkgs.writeScriptBin "execlineb" '' - #!${eldir}/execlineb -s0 - ${eldir}/define eldir ${eldir} - ''${eldir}/importas oldpath PATH - ''${eldir}/export PATH "''${eldir}:''${oldpath}" - ''${eldir}/execlineb $@ - ''; + runExecline = + # todo: factor out calling tests + let + it = import ./execline/run-execline.nix { + inherit stdenv execlinebCommand redirfdCommand + importasCommand execCommand; + }; + tests = import ./execline/run-execline-tests.nix { + runExecline = it; + inherit (testing) drvSeqL; + inherit (pkgs) coreutils; + inherit stdenv ifCommand redirfdCommand s6CatCommand + s6GrepCommand importasCommand s6TouchCommand + s6TestCommand execlinebCommand s6ChmodCommand; + }; + in tests; + - runExecline = import ./execline/run-execline.nix { - inherit stdenv; - execlinebCommand = "${execlineb-with-builtins}/bin/execlineb"; - importasCommand = "${pkgs.execline}/bin/importas"; + testing = pkgs.callPackage ./testing { + inherit runExecline s6TouchCommand s6EchoCommand; }; symlink = pkgs.callPackage ./execline/symlink.nix { diff --git a/pkgs/profpatsch/execline/run-execline-tests.nix b/pkgs/profpatsch/execline/run-execline-tests.nix new file mode 100644 index 00000000..c2c4c23e --- /dev/null +++ b/pkgs/profpatsch/execline/run-execline-tests.nix @@ -0,0 +1,86 @@ +{ stdenv, drvSeqL, runExecline +, ifCommand, redirfdCommand, s6GrepCommand +, importasCommand, s6TouchCommand, s6CatCommand +, execlinebCommand, s6TestCommand, s6ChmodCommand +# https://www.mail-archive.com/skaware@list.skarnet.org/msg01256.html +, coreutils }: + +# TODO: run all of these locally! runExeclineLocal + +let + + # lol + writeScript = name: script: runExecline { + inherit name; + derivationArgs = { + inherit script; + passAsFile = [ "script" ]; + }; + execline = '' + importas -ui s scriptPath + importas -ui out out + foreground { + ${coreutils}/bin/mv $s $out + } + ${s6ChmodCommand} 0755 $out + ''; + }; + + # execline block of depth 1 + block = args: builtins.map (arg: " ${arg}") args ++ [ "" ]; + + # derivation that tests whether a given line exists + # in the given file. Does not use runExecline, because + # that should be tested after all. + fileHasLine = line: file: derivation { + name = "file-${file.name}-has-line"; + inherit (stdenv) system; + builder = ifCommand; + args = + (block [ + redirfdCommand "-r" "0" file # read file to stdin + s6GrepCommand "-F" "-q" line # and grep for the line + ]) + ++ [ + # if the block succeeded, touch $out + importasCommand "-ui" "out" "out" + s6TouchCommand "$out" + ]; + }; + + # basic test that touches out + basic = runExecline { + name = "basic"; + execline = '' + importas -ui out out + ${s6TouchCommand} $out + ''; + }; + + # whether the stdin argument works as intended + stdin = fileHasLine "foo" (runExecline { + name = "stdin"; + stdin = "foo\nbar\nfoo"; + execline = '' + importas -ui out out + # this pipes stdout of s6-cat to $out + # and s6-cat redirects from stdin to stdout + redirfd -w 1 $out ${s6CatCommand} + ''; + }); + + wrapWithVar = runExecline { + name = "wrap-with-var"; + builderWrapper = writeScript "var-wrapper" '' + #!${execlinebCommand} -S0 + export myvar myvalue $@ + ''; + execline = '' + importas -ui v myvar + if { ${s6TestCommand} myvalue = $v } + importas out out + ${s6TouchCommand} $out + ''; + }; + +in args: drvSeqL [ basic stdin wrapWithVar ] (runExecline args) diff --git a/pkgs/profpatsch/execline/run-execline.nix b/pkgs/profpatsch/execline/run-execline.nix index 40915f25..61c8c2e6 100644 --- a/pkgs/profpatsch/execline/run-execline.nix +++ b/pkgs/profpatsch/execline/run-execline.nix @@ -1,7 +1,12 @@ -{ stdenv, importasCommand, execlinebCommand }: +{ stdenv, importasCommand, execCommand, redirfdCommand, execlinebCommand }: { name -# the execline script +# the execline script as string , execline +# a string to pass as stdin to the execline script +, stdin ? "" +# a program wrapping the acutal execline invocation; +# should be in Bernstein-chaining style +, builderWrapper ? execCommand # additional arguments to pass to the derivation , derivationArgs ? {} }: @@ -18,20 +23,41 @@ derivation (derivationArgs // { # okay, `builtins.toFile` does not accept strings # that reference drv outputs. This means we need - # to pass the script as envvar; + # to pass the script and stdin as envvar; # this might clash with another passed envar, # so we give it a long & unique name _runExeclineScript = execline; - passAsFile = [ "_runExeclineScript" ] - ++ derivationArgs.passAsFile or []; + _runExeclineStdin = stdin; + passAsFile = [ + "_runExeclineScript" + "_runExeclineStdin" + ] ++ derivationArgs.passAsFile or []; + + # the default, exec acts as identity executable + builder = builderWrapper; - builder = importasCommand; args = [ + importasCommand # import script file as $script "-ui" # drop the envvar afterwards "script" # substitution name "_runExeclineScriptPath" # passed script file + + # TODO: can we scrap stdin via builderWrapper? + importasCommand # do the same for $stdin + "-ui" + "stdin" + "_runExeclineStdinPath" + + redirfdCommand # now we + "-r" # read the file + "0" # into the stdin of execlineb + "$stdin" # that was given via stdin + execlinebCommand # the actual invocation - "-P" # ignore command line arguments + # TODO: depending on the use-case, -S0 might not be enough + # in all use-cases, then a wrapper for execlineb arguments + # should be added (-P, -S, -s). + "-S0" # set $@ inside the execline script "-W" # die on syntax error "$script" # substituted by importas ]; diff --git a/pkgs/profpatsch/testing/default.nix b/pkgs/profpatsch/testing/default.nix new file mode 100644 index 00000000..55a141d9 --- /dev/null +++ b/pkgs/profpatsch/testing/default.nix @@ -0,0 +1,94 @@ +{ stdenv, runCommand, lib +, runExecline, s6TouchCommand, s6EchoCommand }: + +let + + /* Realize drvDep, then return drvOut if that succeds. + * This can be used to make drvOut depend on the + * build success of drvDep without making drvDep a + * dependency of drvOut + * => drvOut is not rebuilt if drvDep changes + */ + drvSeq = drvDep: drvOut: drvSeqL [drvDep] drvOut; + + /* TODO DOCS */ + drvSeqL = drvDeps: drvOut: let + drvOutOutputs = drvOut.outputs or ["out"]; + in + runCommand drvOut.name { + # we inherit all attributes in order to replicate + # the original derivation as much as possible + outputs = drvOutOutputs; + passthru = drvOut.drvAttrs; + preferLocalBuild = true; + allowSubstitutes = false; + # depend on drvDeps (by putting it in builder context) + inherit drvDeps; + } + # the outputs of the original derivation are replicated + # by creating a symlink to the old output path + (lib.concatMapStrings (output: '' + target=${lib.escapeShellArg drvOut.${output}} + # if the target is already a symlink, follow it until it’s not; + # this is done to prevent too many dereferences + target=$(readlink -e "$target") + # link to the output + ln -s "$target" "${"$"}${output}" + '') drvOutOutputs); + + /* Takes a derivation and an attribute set of + * test names to tests. + * Tests can be constructed by calling test + * functions like `bashTest` or `execlineTest`. + * They generally take scripts that + * is not sucessful and succeed otherwise. + */ + withTests = tests: drv: + assert lib.isDerivation drv; # drv needs to be a derivation! + let testDrvs = lib.mapAttrsToList + (testName: testFun: testFun { + drvName = "${drv.name}-test-${testName}"; + }) tests; + in drvSeqL testDrvs drv; + + # /* Constructs a test from a bash script. + # * The test will fail if the bash script exits + # * with an exit code other than 0. */ + # bashTest = testScript: { drvName }: + # runCommand drvName { + # preferLocalBuild = true; + # allowSubstitutes = false; + # } '' + # ${testScript} + # touch "$out" + # ''; + + # /* Constructs a test from an execline script. + # * The test will fail if the bash script exits + # * with an exit code other than 0. */ + # execlineTest = testScript: { drvName }: + # runExecline { + # name = drvName; + # execline = testScript; + # builderWrapper = runExecline { + # name = "touch-out"; + # execline = '' + # importas -i out out + # ifte + # # if $@ succeeds, $out is touched + # { ${s6TouchCommand} $out } + # # otherwise we return the exit code + # { importas exit ? + # ${s6EchoCommand} $exit } + # # condition + # $@ + # ''; + # derivationArgs = { + # preferLocalBuild = true; + # allowSubstitutes = false; + # }; + # }; + # }; + +in + { inherit drvSeq drvSeqL withTests; } -- cgit 1.4.1