diff options
Diffstat (limited to 'tests')
29 files changed, 588 insertions, 71 deletions
diff --git a/tests/functional/build.sh b/tests/functional/build.sh index 356985a64..58fba83aa 100644 --- a/tests/functional/build.sh +++ b/tests/functional/build.sh @@ -144,13 +144,10 @@ test "$(<<<"$out" grep -E '^error:' | wc -l)" = 1 # --keep-going and FOD out="$(nix build -f fod-failing.nix -L 2>&1)" && status=0 || status=$? test "$status" = 1 -# one "hash mismatch" error, one "build of ... failed" -test "$(<<<"$out" grep -E '^error:' | wc -l)" = 2 -<<<"$out" grepQuiet -E "hash mismatch in fixed-output derivation '.*-x1\\.drv'" -<<<"$out" grepQuiet -vE "hash mismatch in fixed-output derivation '.*-x3\\.drv'" -<<<"$out" grepQuiet -vE "hash mismatch in fixed-output derivation '.*-x2\\.drv'" -<<<"$out" grepQuiet -E "likely URL: https://meow.puppy.forge/puppy.tar.gz" -<<<"$out" grepQuiet -vE "likely URL: https://kitty.forge/cat.tar.gz" +# at least one "hash mismatch" error, one "build of ... failed" +test "$(<<<"$out" grep -E '^error:' | wc -l)" -ge 2 +<<<"$out" grepQuiet -E "hash mismatch in fixed-output derivation '.*-x.\\.drv'" +<<<"$out" grepQuiet -E "likely URL: " <<<"$out" grepQuiet -E "error: build of '.*-x[1-4]\\.drv\\^out', '.*-x[1-4]\\.drv\\^out', '.*-x[1-4]\\.drv\\^out', '.*-x[1-4]\\.drv\\^out' failed" out="$(nix build -f fod-failing.nix -L x1 x2 x3 --keep-going 2>&1)" && status=0 || status=$? @@ -167,9 +164,9 @@ test "$(<<<"$out" grep -E '^error:' | wc -l)" = 4 out="$(nix build -f fod-failing.nix -L x4 2>&1)" && status=0 || status=$? test "$status" = 1 -test "$(<<<"$out" grep -E '^error:' | wc -l)" = 2 -<<<"$out" grepQuiet -E "error: 1 dependencies of derivation '.*-x4\\.drv' failed to build" -<<<"$out" grepQuiet -E "hash mismatch in fixed-output derivation '.*-x2\\.drv'" +test "$(<<<"$out" grep -E '^error:' | wc -l)" -ge 2 +<<<"$out" grepQuiet -E "error: [12] dependencies of derivation '.*-x4\\.drv' failed to build" +<<<"$out" grepQuiet -E "hash mismatch in fixed-output derivation '.*-x[23]\\.drv'" out="$(nix build -f fod-failing.nix -L x4 --keep-going 2>&1)" && status=0 || status=$? test "$status" = 1 diff --git a/tests/functional/common/vars-and-functions.sh.in b/tests/functional/common/vars-and-functions.sh.in index 451cf5383..98892f660 100644 --- a/tests/functional/common/vars-and-functions.sh.in +++ b/tests/functional/common/vars-and-functions.sh.in @@ -28,6 +28,7 @@ export NIX_REMOTE=${NIX_REMOTE_-} unset NIX_PATH export TEST_HOME=$TEST_ROOT/test-home export HOME=$TEST_HOME +export GIT_CONFIG_SYSTEM=/dev/null unset XDG_STATE_HOME unset XDG_DATA_HOME unset XDG_CONFIG_HOME diff --git a/tests/functional/dependencies.nix b/tests/functional/dependencies.nix index be1a7ae9a..0ede76b71 100644 --- a/tests/functional/dependencies.nix +++ b/tests/functional/dependencies.nix @@ -1,8 +1,7 @@ { hashInvalidator ? "" }: with import ./config.nix; -let { - +let input0 = mkDerivation { name = "dependencies-input-0"; buildCommand = "mkdir $out; echo foo > $out/bar"; @@ -32,17 +31,15 @@ let { outputHashAlgo = "sha256"; outputHash = "1dq9p0hnm1y75q2x40fws5887bq1r840hzdxak0a9djbwvx0b16d"; }; - - body = mkDerivation { - name = "dependencies-top"; - builder = ./dependencies.builder0.sh + "/FOOBAR/../."; - input1 = input1 + "/."; - input2 = "${input2}/."; - input1_drv = input1; - input2_drv = input2; - input0_drv = input0; - fod_input_drv = fod_input; - meta.description = "Random test package"; - }; - +in +mkDerivation { + name = "dependencies-top"; + builder = ./dependencies.builder0.sh + "/FOOBAR/../."; + input1 = input1 + "/."; + input2 = "${input2}/."; + input1_drv = input1; + input2_drv = input2; + input0_drv = input0; + fod_input_drv = fod_input; + meta.description = "Random test package"; } diff --git a/tests/functional/fetchGit.sh b/tests/functional/fetchGit.sh index 2c00facc2..492c57602 100644 --- a/tests/functional/fetchGit.sh +++ b/tests/functional/fetchGit.sh @@ -53,8 +53,17 @@ out=$(nix eval --impure --raw --expr "builtins.fetchGit { url = \"file://$repo\" [[ $status == 1 ]] [[ $out =~ 'Cannot find Git revision' ]] +# allow revs as refs (for 2.3 compat) [[ $(nix eval --raw --expr "builtins.readFile (builtins.fetchGit { url = \"file://$repo\"; rev = \"$devrev\"; allRefs = true; } + \"/differentbranch\")") = 'different file' ]] +rm -rf "$TEST_ROOT/test-home" +[[ $(nix eval --raw --expr "builtins.readFile (builtins.fetchGit { url = \"file://$repo\"; rev = \"$devrev\"; allRefs = true; } + \"/differentbranch\")") = 'different file' ]] + +rm -rf "$TEST_ROOT/test-home" +out=$(nix eval --raw --expr "builtins.readFile (builtins.fetchGit { url = \"file://$repo\"; rev = \"$devrev\"; ref = \"lolkek\"; } + \"/differentbranch\")" 2>&1) || status=$? +[[ $status == 1 ]] +[[ $out =~ 'Cannot find Git revision' ]] + # In pure eval mode, fetchGit without a revision should fail. [[ $(nix eval --impure --raw --expr "builtins.readFile (fetchGit \"file://$repo\" + \"/hello\")") = world ]] (! nix eval --raw --expr "builtins.readFile (fetchGit \"file://$repo\" + \"/hello\")") @@ -228,6 +237,12 @@ export _NIX_FORCE_HTTP=1 rev_tag1_nix=$(nix eval --impure --raw --expr "(builtins.fetchGit { url = \"file://$repo\"; ref = \"refs/tags/tag1\"; }).rev") rev_tag1=$(git -C $repo rev-parse refs/tags/tag1) [[ $rev_tag1_nix = $rev_tag1 ]] + +# Allow fetching tags w/o specifying refs/tags +rm -rf "$TEST_ROOT/test-home" +rev_tag1_nix_alt=$(nix eval --impure --raw --expr "(builtins.fetchGit { url = \"file://$repo\"; ref = \"tag1\"; }).rev") +[[ $rev_tag1_nix_alt = $rev_tag1 ]] + rev_tag2_nix=$(nix eval --impure --raw --expr "(builtins.fetchGit { url = \"file://$repo\"; ref = \"refs/tags/tag2\"; }).rev") rev_tag2=$(git -C $repo rev-parse refs/tags/tag2) [[ $rev_tag2_nix = $rev_tag2 ]] @@ -254,3 +269,33 @@ git -C "$repo" add hello .gitignore git -C "$repo" commit -m 'Bla1' cd "$repo" path11=$(nix eval --impure --raw --expr "(builtins.fetchGit ./.).outPath") + +# test behavior if both branch and tag with same name exist +repo="$TEST_ROOT/git" +rm -rf "$repo"/.git +git init "$repo" +git -C "$repo" config user.email "foobar@example.com" +git -C "$repo" config user.name "Foobar" + +touch "$repo"/test +echo "hello world" > "$repo"/test +git -C "$repo" checkout -b branch +git -C "$repo" add test + +git -C "$repo" commit -m "Init" + +git -C "$repo" tag branch + +echo "goodbye world" > "$repo"/test +git -C "$repo" add test +git -C "$repo" commit -m "Update test" + +path12=$(nix eval --impure --raw --expr "(builtins.fetchGit { url = \"file://$repo\"; ref = \"branch\"; }).outPath") +[[ "$(cat "$path12"/test)" =~ 'hello world' ]] +[[ "$(cat "$repo"/test)" =~ 'goodbye world' ]] + +path13=$(nix eval --impure --raw --expr "(builtins.fetchGit { url = \"file://$repo\"; ref = \"refs/heads/branch\"; }).outPath") +[[ "$(cat "$path13"/test)" =~ 'goodbye world' ]] + +path14=$(nix eval --impure --raw --expr "(builtins.fetchGit { url = \"file://$repo\"; ref = \"refs/tags/branch\"; }).outPath") +[[ "$path14" = "$path12" ]] diff --git a/tests/functional/fmt.sh b/tests/functional/fmt.sh index 3c1bd9989..7d6add9b6 100644 --- a/tests/functional/fmt.sh +++ b/tests/functional/fmt.sh @@ -26,7 +26,10 @@ cat << EOF > flake.nix }; } EOF -nix fmt ./file ./folder | grep 'Formatting: ./file ./folder' +# No arguments check +[[ "$(nix fmt)" = "Formatting(0):" ]] +# Argument forwarding check +nix fmt ./file ./folder | grep 'Formatting(2): ./file ./folder' nix flake check nix flake show | grep -P "package 'formatter'" diff --git a/tests/functional/fmt.simple.sh b/tests/functional/fmt.simple.sh index 03109a655..f655846ca 100755 --- a/tests/functional/fmt.simple.sh +++ b/tests/functional/fmt.simple.sh @@ -1,3 +1,3 @@ #!/usr/bin/env bash -echo Formatting: "${@}" +echo "Formatting(${#}):" "${@}" diff --git a/tests/functional/hash-check.nix b/tests/functional/hash-check.nix index f029f0cc9..6095bc57f 100644 --- a/tests/functional/hash-check.nix +++ b/tests/functional/hash-check.nix @@ -1,5 +1,4 @@ -let { - +let input1 = derivation { name = "dependencies-input-1"; system = "i086-msdos"; @@ -16,14 +15,12 @@ let { outputHashAlgo = "md5"; outputHash = "ffffffffffffffffffffffffffffffff"; }; - - body = derivation { - name = "dependencies"; - system = "i086-msdos"; - builder = "/bar/sh"; - args = ["-e" "-x" (./dummy + "/FOOBAR/../.")]; - input1 = input1 + "/."; - inherit input2; - }; - +in +derivation { + name = "dependencies"; + system = "i086-msdos"; + builder = "/bar/sh"; + args = ["-e" "-x" (./dummy + "/FOOBAR/../.")]; + input1 = input1 + "/."; + inherit input2; } diff --git a/tests/functional/lang.sh b/tests/functional/lang.sh index 59db10340..cec4f9352 100755 --- a/tests/functional/lang.sh +++ b/tests/functional/lang.sh @@ -41,7 +41,12 @@ badExitCode=0 for i in lang/parse-fail-*.nix; do echo "parsing $i (should fail)"; i=$(basename "$i" .nix) - if expectStderr 1 nix-instantiate --parse - < "lang/$i.nix" > "lang/$i.err" + + declare -a flags=() + if test -e "lang/$i.flags"; then + read -r -a flags < "lang/$i.flags" + fi + if expectStderr 1 nix-instantiate --parse "${flags[@]}" - < "lang/$i.nix" > "lang/$i.err" then diffAndAccept "$i" err err.exp else @@ -54,13 +59,12 @@ for i in lang/parse-okay-*.nix; do echo "parsing $i (should succeed)"; i=$(basename "$i" .nix) - if [ -e "lang/$i.flags" ]; then - extraArgs="$(cat "lang/$i.flags")" - else - extraArgs="" + declare -a flags=() + if test -e "lang/$i.flags"; then + read -r -a flags < "lang/$i.flags" fi if - expect 0 nix-instantiate --parse ${extraArgs-} - < "lang/$i.nix" \ + expect 0 nix-instantiate --parse "${flags[@]}" - < "lang/$i.nix" \ 1> "lang/$i.out" \ 2> "lang/$i.err" then @@ -77,13 +81,12 @@ for i in lang/eval-fail-*.nix; do echo "evaluating $i (should fail)"; i=$(basename "$i" .nix) - if [ -e "lang/$i.flags" ]; then - extraArgs="$(cat "lang/$i.flags")" - else - extraArgs="" + declare -a flags=() + if test -e "lang/$i.flags"; then + read -r -a flags < "lang/$i.flags" fi if - expectStderr 1 nix-instantiate --eval --strict --show-trace ${extraArgs-} "lang/$i.nix" \ + expectStderr 1 nix-instantiate --eval --strict --show-trace "${flags[@]}" "lang/$i.nix" \ | sed "s!$(pwd)!/pwd!g" > "lang/$i.err" then diffAndAccept "$i" err err.exp @@ -97,14 +100,13 @@ for i in lang/eval-okay-*.nix; do echo "evaluating $i (should succeed)"; i=$(basename "$i" .nix) - if [ -e "lang/$i.flags" ]; then - extraArgs="$(cat "lang/$i.flags")" - else - extraArgs="" + declare -a flags=() + if test -e "lang/$i.flags"; then + read -r -a flags < "lang/$i.flags" fi if test -e "lang/$i.exp.xml"; then - if expect 0 nix-instantiate --eval --xml --no-location --strict ${extraArgs-} \ + if expect 0 nix-instantiate --eval --xml --no-location --strict "${flags[@]}" \ "lang/$i.nix" > "lang/$i.out.xml" then diffAndAccept "$i" out.xml exp.xml @@ -113,11 +115,6 @@ for i in lang/eval-okay-*.nix; do badExitCode=1 fi elif test ! -e "lang/$i.exp-disabled"; then - declare -a flags=() - if test -e "lang/$i.flags"; then - read -r -a flags < "lang/$i.flags" - fi - if expect 0 env \ NIX_PATH=lang/dir3:lang/dir4 \ @@ -155,7 +152,7 @@ else echo '' echo 'You can rerun this test with:' echo '' - echo ' _NIX_TEST_ACCEPT=1 make tests/functional/lang.sh.test' + echo ' _NIX_TEST_ACCEPT=1 just test --suite installcheck -v functional-lang' echo '' echo 'to regenerate the files containing the expected output,' echo 'and then view the git diff to decide whether a change is' diff --git a/tests/functional/lang/eval-okay-ind-string.exp b/tests/functional/lang/eval-okay-ind-string.exp index 7862331fa..1531af0f4 100644 --- a/tests/functional/lang/eval-okay-ind-string.exp +++ b/tests/functional/lang/eval-okay-ind-string.exp @@ -1 +1 @@ -"This is an indented multi-line string\nliteral. An amount of whitespace at\nthe start of each line matching the minimum\nindentation of all lines in the string\nliteral together will be removed. Thus,\nin this case four spaces will be\nstripped from each line, even though\n THIS LINE is indented six spaces.\n\nAlso, empty lines don't count in the\ndetermination of the indentation level (the\nprevious empty line has indentation 0, but\nit doesn't matter).\nIf the string starts with whitespace\n followed by a newline, it's stripped, but\n that's not the case here. Two spaces are\n stripped because of the \" \" at the start. \nThis line is indented\na bit further.\nAnti-quotations, like so, are\nalso allowed.\n The \\ is not special here.\n' can be followed by any character except another ', e.g. 'x'.\nLikewise for $, e.g. $$ or $varName.\nBut ' followed by ' is special, as is $ followed by {.\nIf you want them, use anti-quotations: '', \${.\n Tabs are not interpreted as whitespace (since we can't guess\n what tab settings are intended), so don't use them.\n\tThis line starts with a space and a tab, so only one\n space will be stripped from each line.\nAlso note that if the last line (just before the closing ' ')\nconsists only of whitespace, it's ignored. But here there is\nsome non-whitespace stuff, so the line isn't removed. \nThis shows a hacky way to preserve an empty line after the start.\nBut there's no reason to do so: you could just repeat the empty\nline.\n Similarly you can force an indentation level,\n in this case to 2 spaces. This works because the anti-quote\n is significant (not whitespace).\nstart on network-interfaces\n\nstart script\n\n rm -f /var/run/opengl-driver\n ln -sf 123 /var/run/opengl-driver\n\n rm -f /var/log/slim.log\n \nend script\n\nenv SLIM_CFGFILE=abc\nenv SLIM_THEMESDIR=def\nenv FONTCONFIG_FILE=/etc/fonts/fonts.conf \t\t\t\t# !!! cleanup\nenv XKB_BINDIR=foo/bin \t\t\t\t# Needed for the Xkb extension.\nenv LD_LIBRARY_PATH=libX11/lib:libXext/lib:/usr/lib/ # related to xorg-sys-opengl - needed to load libglx for (AI)GLX support (for compiz)\n\nenv XORG_DRI_DRIVER_PATH=nvidiaDrivers/X11R6/lib/modules/drivers/ \n\nexec slim/bin/slim\nEscaping of ' followed by ': ''\nEscaping of $ followed by {: \${\nAnd finally to interpret \\n etc. as in a string: \n, \r, \t.\nfoo\n'bla'\nbar\ncut -d $'\\t' -f 1\nending dollar $$\n" +"This is an indented multi-line string\nliteral. An amount of whitespace at\nthe start of each line matching the minimum\nindentation of all lines in the string\nliteral together will be removed. Thus,\nin this case four spaces will be\nstripped from each line, even though\n THIS LINE is indented six spaces.\n\nAlso, empty lines don't count in the\ndetermination of the indentation level (the\nprevious empty line has indentation 0, but\nit doesn't matter).\nIf the string starts with whitespace\n followed by a newline, it's stripped, but\n that's not the case here. Two spaces are\n stripped because of the \" \" at the start. \nThis line is indented\na bit further.\nAnti-quotations, like so, are\nalso allowed.\n The \\ is not special here.\n' can be followed by any character except another ', e.g. 'x'.\nLikewise for $, e.g. $$ or $varName.\nBut ' followed by ' is special, as is $ followed by {.\nIf you want them, use anti-quotations: '', \${.\n Tabs are not interpreted as whitespace (since we can't guess\n what tab settings are intended), so don't use them.\n\tThis line starts with a space and a tab, so only one\n space will be stripped from each line.\nAlso note that if the last line (just before the closing ' ')\nconsists only of whitespace, it's ignored. But here there is\nsome non-whitespace stuff, so the line isn't removed. \nThis shows a hacky way to preserve an empty line after the start.\nBut there's no reason to do so: you could just repeat the empty\nline.\n Similarly you can force an indentation level,\n in this case to 2 spaces. This works because the anti-quote\n is significant (not whitespace).\nstart on network-interfaces\n\nstart script\n\n rm -f /var/run/opengl-driver\n ln -sf 123 /var/run/opengl-driver\n\n rm -f /var/log/slim.log\n \nend script\n\nenv SLIM_CFGFILE=abc\nenv SLIM_THEMESDIR=def\nenv FONTCONFIG_FILE=/etc/fonts/fonts.conf \t\t\t\t# !!! cleanup\nenv XKB_BINDIR=foo/bin \t\t\t\t# Needed for the Xkb extension.\nenv LD_LIBRARY_PATH=libX11/lib:libXext/lib:/usr/lib/ # related to xorg-sys-opengl - needed to load libglx for (AI)GLX support (for compiz)\n\nenv XORG_DRI_DRIVER_PATH=nvidiaDrivers/X11R6/lib/modules/drivers/ \n\nexec slim/bin/slim\nEscaping of ' followed by ': ''\nEscaping of $ followed by {: \${\nAnd finally to interpret \\n etc. as in a string: \n, \r, \t.\nfoo\n'bla'\nbar\ncut -d $'\\t' -f 1\nending dollar $$\n Lines without any indentation effectively disable the indentation\n stripping for the entire string:\n\n cat >$out/foo/data <<EOF\n lasjdöaxnasd\nasdom 12398\nä\"§Æẞ¢«»”alsd\nEOF\nEmpty lines with a bit of whitespace don't affect the indentation calculation:\n\nAnd empty lines with more whitespace will have whitespace in the string:\n \nUnless it's the last line:\n Indentation stripping\n must not be impressed by\nthe last line not being empty\t Nor by people\n weirdly mixing tabs\n\tand spaces\n\t" diff --git a/tests/functional/lang/eval-okay-ind-string.nix b/tests/functional/lang/eval-okay-ind-string.nix index 95d59b508..44df83458 100644 --- a/tests/functional/lang/eval-okay-ind-string.nix +++ b/tests/functional/lang/eval-okay-ind-string.nix @@ -64,6 +64,36 @@ let is significant (not whitespace). ''; + s18 = '' + Lines without any indentation effectively disable the indentation + stripping for the entire string: + + cat >$out/foo/data <<EOF + lasjdöaxnasd +asdom 12398 +ä"§Æẞ¢«»”alsd +EOF + ''; + + s19 = '' + Empty lines with a bit of whitespace don't affect the indentation calculation: + + And empty lines with more whitespace will have whitespace in the string: + + Unless it's the last line: + ''; + + s20 = '' + Indentation stripping + must not be impressed by + the last line not being empty''; + + s21 = '' + Nor by people + weirdly mixing tabs + and spaces + ''; + s10 = '' ''; @@ -125,4 +155,4 @@ let # Accept dollars at end of strings s17 = ''ending dollar $'' + ''$'' + "\n"; -in s1 + s2 + s3 + s4 + s5 + s6 + s7 + s8 + s9 + s10 + s11 + s12 + s13 + s14 + s15 + s16 + s17 +in s1 + s2 + s3 + s4 + s5 + s6 + s7 + s8 + s9 + s10 + s11 + s12 + s13 + s14 + s15 + s16 + s17 + s18 + s19 + s20 + s21 diff --git a/tests/functional/lang/parse-okay-ind-string.exp b/tests/functional/lang/parse-okay-ind-string.exp new file mode 100644 index 000000000..570785ee2 --- /dev/null +++ b/tests/functional/lang/parse-okay-ind-string.exp @@ -0,0 +1 @@ +(let s1 = "This is an indented multi-line string\nliteral. An amount of whitespace at\nthe start of each line matching the minimum\nindentation of all lines in the string\nliteral together will be removed. Thus,\nin this case four spaces will be\nstripped from each line, even though\n THIS LINE is indented six spaces.\n\nAlso, empty lines don't count in the\ndetermination of the indentation level (the\nprevious empty line has indentation 0, but\nit doesn't matter).\n"; s10 = ""; s11 = ""; s12 = ""; s13 = ("start on network-interfaces\n\nstart script\n\n rm -f /var/run/opengl-driver\n " + "${(if true then "ln -sf 123 /var/run/opengl-driver" else (if true then "ln -sf 456 /var/run/opengl-driver" else ""))}" + "\n\n rm -f /var/log/slim.log\n \nend script\n\nenv SLIM_CFGFILE=" + "abc" + "\nenv SLIM_THEMESDIR=" + "def" + "\nenv FONTCONFIG_FILE=/etc/fonts/fonts.conf \t\t\t\t# !!! cleanup\nenv XKB_BINDIR=" + "foo" + "/bin \t\t\t\t# Needed for the Xkb extension.\nenv LD_LIBRARY_PATH=" + "libX11" + "/lib:" + "libXext" + "/lib:/usr/lib/ # related to xorg-sys-opengl - needed to load libglx for (AI)GLX support (for compiz)\n\n" + "${(if true then ("env XORG_DRI_DRIVER_PATH=" + "nvidiaDrivers" + "/X11R6/lib/modules/drivers/") else (if true then ("env XORG_DRI_DRIVER_PATH=" + "mesa" + "/lib/modules/dri") else ""))}" + " \n\nexec " + "slim" + "/bin/slim\n"); s14 = "Escaping of ' followed by ': ''\nEscaping of $ followed by {: \${\nAnd finally to interpret \\n etc. as in a string: \n, \r, \t.\n"; s15 = (let x = "bla"; in ("foo\n'" + "${x}" + "'\nbar\n")); s16 = "cut -d $'\\t' -f 1\n"; s17 = (("ending dollar $" + "$") + "\n"); s18 = " Lines without any indentation effectively disable the indentation\n stripping for the entire string:\n\n cat >$out/foo/data <<EOF\n lasjdöaxnasd\nasdom 12398\nä\"§Æẞ¢«»”alsd\nEOF\n"; s19 = "Empty lines with a bit of whitespace don't affect the indentation calculation:\n\nAnd empty lines with more whitespace will have whitespace in the string:\n \nUnless it's the last line:\n"; s2 = "If the string starts with whitespace\n followed by a newline, it's stripped, but\n that's not the case here. Two spaces are\n stripped because of the \" \" at the start. \n"; s20 = " Indentation stripping\n must not be impressed by\nthe last line not being empty"; s21 = "\t Nor by people\n weirdly mixing tabs\n\tand spaces\n\t"; s3 = "This line is indented\na bit further.\n"; s4 = ("Anti-quotations, like " + "${(if true then "so" else "not so")}" + ", are\nalso allowed.\n"); s5 = (" The \\ is not special here.\n' can be followed by any character except another ', e.g. 'x'.\nLikewise for $, e.g. $$ or $varName.\nBut ' followed by ' is special, as is $ followed by {.\nIf you want them, use anti-quotations: " + "''" + ", " + "\${" + ".\n"); s6 = " Tabs are not interpreted as whitespace (since we can't guess\n what tab settings are intended), so don't use them.\n\tThis line starts with a space and a tab, so only one\n space will be stripped from each line.\n"; s7 = "Also note that if the last line (just before the closing ' ')\nconsists only of whitespace, it's ignored. But here there is\nsome non-whitespace stuff, so the line isn't removed. "; s8 = ("" + "\nThis shows a hacky way to preserve an empty line after the start.\nBut there's no reason to do so: you could just repeat the empty\nline.\n"); s9 = ("" + " Similarly you can force an indentation level,\n in this case to 2 spaces. This works because the anti-quote\n is significant (not whitespace).\n"); in ((((((((((((((((((((s1 + s2) + s3) + s4) + s5) + s6) + s7) + s8) + s9) + s10) + s11) + s12) + s13) + s14) + s15) + s16) + s17) + s18) + s19) + s20) + s21)) diff --git a/tests/functional/lang/parse-okay-ind-string.nix b/tests/functional/lang/parse-okay-ind-string.nix new file mode 120000 index 000000000..43864cd8c --- /dev/null +++ b/tests/functional/lang/parse-okay-ind-string.nix @@ -0,0 +1 @@ +eval-okay-ind-string.nix
\ No newline at end of file diff --git a/tests/functional/lang/parse-okay-regression-751.exp b/tests/functional/lang/parse-okay-regression-751.exp index e2ed886fe..0cbf55d49 100644 --- a/tests/functional/lang/parse-okay-regression-751.exp +++ b/tests/functional/lang/parse-okay-regression-751.exp @@ -1 +1 @@ -(let const = (a: "const"); in ((const { x = "q"; }))) +(let const = (a: "const"); in ("${(const { x = "q"; })}")) diff --git a/tests/functional/restricted.sh b/tests/functional/restricted.sh index 450674bd6..dd6386278 100644 --- a/tests/functional/restricted.sh +++ b/tests/functional/restricted.sh @@ -11,8 +11,8 @@ nix-instantiate --restrict-eval ./simple.nix -I src1=simple.nix -I src2=config.n (! nix-instantiate --restrict-eval --eval -E 'builtins.readFile ./simple.nix') nix-instantiate --restrict-eval --eval -E 'builtins.readFile ./simple.nix' -I src=../.. -(! nix-instantiate --restrict-eval --eval -E 'builtins.readDir ../../src/nix-channel') -nix-instantiate --restrict-eval --eval -E 'builtins.readDir ../../src/nix-channel' -I src=../../src +(! nix-instantiate --restrict-eval --eval -E 'builtins.readDir ../../src/legacy') +nix-instantiate --restrict-eval --eval -E 'builtins.readDir ../../src/legacy' -I src=../../src (! nix-instantiate --restrict-eval --eval -E 'let __nixPath = [ { prefix = "foo"; path = ./.; } ]; in <foo>') nix-instantiate --restrict-eval --eval -E 'let __nixPath = [ { prefix = "foo"; path = ./.; } ]; in <foo>' -I src=. diff --git a/tests/functional2/README.md b/tests/functional2/README.md new file mode 100644 index 000000000..b0440fb3a --- /dev/null +++ b/tests/functional2/README.md @@ -0,0 +1,24 @@ +# functional2 tests + +This uncreatively named test suite is a Pytest based replacement for the shell framework used to write traditional Nix integration tests. +Its primary goal is to make tests more concise, more self-contained, easier to write, and to produce better errors. + +## Goals + +- Eliminate implicit dependencies on files in the test directory as well as the requirement to copy the test files to the build directory as is currently hacked in the other functional test suite. + - You should be able to write a DirectoryTree of files for your test declaratively. +- Reduce the amount of global environment state being thrown around in the test suite. +- Make tests very concise and easy to reuse code for, and hopefully turn more of what is currently code into data. + - Provide rich ways of calling `nix` with pleasant syntax. + +## TODO: Intended features + +- [ ] Expect tests ([pytest-expect-test]) or snapshot tests ([pytest-insta]) or, likely, both! + We::jade prefer to have short output written in-line as it makes it greatly easier to read the tests, but pytest-expect doesn't allow for putting larger stuff in external files, so something else is necessary for those. +- [ ] Web server fixture: we don't test our network functionality because background processes are hard and this is simply goofy. + We could just test it. +- [ ] Nix daemon fixture. +- [x] Parallelism via pytest-xdist. + +[pytest-expect-test]: https://pypi.org/project/pytest-expect-test/ +[pytest-insta]: https://pypi.org/project/pytest-insta/ diff --git a/tests/functional2/__init__.py b/tests/functional2/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/tests/functional2/__init__.py diff --git a/tests/functional2/conftest.py b/tests/functional2/conftest.py new file mode 100644 index 000000000..406f97b75 --- /dev/null +++ b/tests/functional2/conftest.py @@ -0,0 +1,8 @@ +import pytest +from pathlib import Path +from .testlib import fixtures + + +@pytest.fixture +def nix(tmp_path: Path): + return fixtures.Nix(tmp_path) diff --git a/tests/functional2/meson.build b/tests/functional2/meson.build new file mode 100644 index 000000000..b18d7035d --- /dev/null +++ b/tests/functional2/meson.build @@ -0,0 +1,29 @@ +xdist_opts = [ + # auto number of workers, max 12 jobs + '-n', 'auto', '--maxprocesses=12', + # group tests by module or class; ensures that any setup work occurs as little as possible + '--dist=loadscope', +] + +# surprisingly, this actually works even if PATH is set to something before +# meson gets hold of it. neat! +functional2_env = environment() +functional2_env.prepend('PATH', bindir) + +test( + 'functional2', + python, + args : [ + '-m', 'pytest', + '-v', + xdist_opts, + meson.current_source_dir() + ], + env : functional2_env, + # FIXME: Although we can trivially use TAP here with pytest-tap, due to a meson bug, it is unusable. + # (failure output does not get displayed to the console. at all. someone should go fix it): + # https://github.com/mesonbuild/meson/issues/11185 + # protocol : 'tap', + suite : 'installcheck', + timeout : 300, +) diff --git a/tests/functional2/test_eval_trivial.py b/tests/functional2/test_eval_trivial.py new file mode 100644 index 000000000..8bde9d22c --- /dev/null +++ b/tests/functional2/test_eval_trivial.py @@ -0,0 +1,4 @@ +from .testlib.fixtures import Nix + +def test_trivial_addition(nix: Nix): + assert nix.eval('1 + 1').json() == 2 diff --git a/tests/functional2/testlib/__init__.py b/tests/functional2/testlib/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/tests/functional2/testlib/__init__.py diff --git a/tests/functional2/testlib/fixtures.py b/tests/functional2/testlib/fixtures.py new file mode 100644 index 000000000..bbaaae51d --- /dev/null +++ b/tests/functional2/testlib/fixtures.py @@ -0,0 +1,121 @@ +import os +import json +import subprocess +from typing import Any +from pathlib import Path +import dataclasses + + +@dataclasses.dataclass +class CommandResult: + cmd: list[str] + rc: int + """Return code""" + stderr: bytes + """Outputted stderr""" + stdout: bytes + """Outputted stdout""" + + def ok(self): + if self.rc != 0: + raise subprocess.CalledProcessError(returncode=self.rc, + cmd=self.cmd, + stderr=self.stderr, + output=self.stdout) + return self + + def json(self) -> Any: + self.ok() + return json.loads(self.stdout) + + +@dataclasses.dataclass +class NixSettings: + """Settings for invoking Nix""" + experimental_features: set[str] | None = None + + def feature(self, *names: str): + self.experimental_features = (self.experimental_features + or set()) | set(names) + return self + + def to_config(self) -> str: + config = '' + + def serialise(value): + if type(value) in {str, int}: + return str(value) + elif type(value) in {list, set}: + return ' '.join(str(e) for e in value) + else: + raise ValueError( + f'Value is unsupported in nix config: {value!r}') + + def field_may(name, value, serialiser=serialise): + nonlocal config + if value is not None: + config += f'{name} = {serialiser(value)}\n' + + field_may('experimental-features', self.experimental_features) + return config + + +@dataclasses.dataclass +class Nix: + test_root: Path + + def hermetic_env(self): + # mirroring vars-and-functions.sh + home = self.test_root / 'test-home' + home.mkdir(parents=True, exist_ok=True) + return { + 'NIX_STORE_DIR': self.test_root / 'store', + 'NIX_LOCALSTATE_DIR': self.test_root / 'var', + 'NIX_LOG_DIR': self.test_root / 'var/log/nix', + 'NIX_STATE_DIR': self.test_root / 'var/nix', + 'NIX_CONF_DIR': self.test_root / 'etc', + 'NIX_DAEMON_SOCKET_PATH': self.test_root / 'daemon-socket', + 'NIX_USER_CONF_FILES': '', + 'HOME': home, + } + + def make_env(self): + # We conservatively assume that people might want to successfully get + # some env through to the subprocess, so we override whatever is in the + # global env. + d = os.environ.copy() + d.update(self.hermetic_env()) + return d + + def call(self, cmd: list[str], extra_env: dict[str, str] = {}): + """ + Calls a process in the test environment. + """ + env = self.make_env() + env.update(extra_env) + proc = subprocess.Popen( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + cwd=self.test_root, + env=env, + ) + (stdout, stderr) = proc.communicate() + rc = proc.returncode + return CommandResult(cmd=cmd, rc=rc, stdout=stdout, stderr=stderr) + + def nix(self, + cmd: list[str], + settings: NixSettings = NixSettings(), + extra_env: dict[str, str] = {}): + extra_env = extra_env.copy() + extra_env.update({'NIX_CONFIG': settings.to_config()}) + return self.call(['nix', *cmd], extra_env) + + def eval( + self, expr: str, + settings: NixSettings = NixSettings()) -> CommandResult: + # clone due to reference-shenanigans + settings = dataclasses.replace(settings).feature('nix-command') + + return self.nix(['eval', '--json', '--expr', expr], settings=settings) diff --git a/tests/nixos/fetchurl.nix b/tests/nixos/fetchurl.nix index 63c639c31..97365d053 100644 --- a/tests/nixos/fetchurl.nix +++ b/tests/nixos/fetchurl.nix @@ -67,6 +67,9 @@ in out = machine.succeed("curl https://good/index.html") assert out == "hello world\n" + out = machine.succeed("cat ${badCert}/cert.pem > /tmp/cafile.pem; curl --cacert /tmp/cafile.pem https://bad/index.html") + assert out == "foobar\n" + # Fetching from a server with a trusted cert should work. machine.succeed("nix build --no-substitute --expr 'import <nix/fetchurl.nix> { url = \"https://good/index.html\"; hash = \"sha256-qUiQTy8PR5uPgZdpSzAYSw0u0cHNKh7A+4XSmaGSpEc=\"; }'") @@ -74,5 +77,8 @@ in err = machine.fail("nix build --no-substitute --expr 'import <nix/fetchurl.nix> { url = \"https://bad/index.html\"; hash = \"sha256-rsBwZF/lPuOzdjBZN2E08FjMM3JHyXit0Xi2zN+wAZ8=\"; }' 2>&1") print(err) assert "SSL certificate problem: self-signed certificate" in err or "SSL peer certificate or SSH remote key was not OK" in err + + # Fetching from a server with a trusted cert should work via environment variable override. + machine.succeed("NIX_SSL_CERT_FILE=/tmp/cafile.pem nix build --no-substitute --expr 'import <nix/fetchurl.nix> { url = \"https://bad/index.html\"; hash = \"sha256-rsBwZF/lPuOzdjBZN2E08FjMM3JHyXit0Xi2zN+wAZ8=\"; }'") ''; } diff --git a/tests/nixos/remote-builds-ssh-ng.nix b/tests/nixos/remote-builds-ssh-ng.nix index 8deb9a504..ec12f9066 100644 --- a/tests/nixos/remote-builds-ssh-ng.nix +++ b/tests/nixos/remote-builds-ssh-ng.nix @@ -97,7 +97,8 @@ in builder.wait_for_unit("sshd.service") out = client.fail("nix-build ${expr nodes.client 1} 2>&1") - assert "error: failed to start SSH connection to 'root@builder': Host key verification failed" in out, f"No host verification error in {out}" + assert "Host key verification failed." in out, f"No host verification error:\n{out}" + assert "warning: SSH to 'root@builder' failed, stdout first line: '''" in out, f"No details about which host:\n{out}" client.succeed(f"ssh -o StrictHostKeyChecking=no {builder.name} 'echo hello world' >&2") diff --git a/tests/unit/libmain/crash.cc b/tests/unit/libmain/crash.cc new file mode 100644 index 000000000..883dc39bd --- /dev/null +++ b/tests/unit/libmain/crash.cc @@ -0,0 +1,56 @@ +#include <gtest/gtest.h> +#include "crash-handler.hh" + +namespace nix { + +class OopsException : public std::exception +{ + const char * msg; + +public: + OopsException(const char * msg) : msg(msg) {} + const char * what() const noexcept override + { + return msg; + } +}; + +void causeCrashForTesting(std::function<void()> fixture) +{ + registerCrashHandler(); + std::cerr << "time to crash\n"; + try { + fixture(); + } catch (...) { + std::terminate(); + } +} + +TEST(CrashHandler, exceptionName) +{ + ASSERT_DEATH( + causeCrashForTesting([]() { throw OopsException{"lol oops"}; }), + "time to crash\nLix crashed.*OopsException: lol oops" + ); +} + +TEST(CrashHandler, unknownTerminate) +{ + ASSERT_DEATH( + causeCrashForTesting([]() { std::terminate(); }), + "time to crash\nLix crashed.*std::terminate\\(\\) called without exception" + ); +} + +TEST(CrashHandler, nonStdException) +{ + ASSERT_DEATH( + causeCrashForTesting([]() { + // NOLINTNEXTLINE(hicpp-exception-baseclass): intentional + throw 4; + }), + "time to crash\nLix crashed.*Unknown exception! Spooky\\." + ); +} + +} diff --git a/tests/unit/libstore/filetransfer.cc b/tests/unit/libstore/filetransfer.cc index 71e7392fc..fd4d326f0 100644 --- a/tests/unit/libstore/filetransfer.cc +++ b/tests/unit/libstore/filetransfer.cc @@ -150,6 +150,14 @@ TEST(FileTransfer, exceptionAbortsDownload) } } +TEST(FileTransfer, exceptionAbortsRead) +{ + auto [port, srv] = serveHTTP("200 ok", "content-length: 0\r\n", [] { return ""; }); + auto ft = makeFileTransfer(); + char buf[10] = ""; + ASSERT_THROW(ft->download(FileTransferRequest(fmt("http://[::1]:%d/index", port)))->read(buf, 10), EndOfFile); +} + TEST(FileTransfer, NOT_ON_DARWIN(reportsSetupErrors)) { auto [port, srv] = serveHTTP("404 not found", "", [] { return ""; }); diff --git a/tests/unit/libutil/async-collect.cc b/tests/unit/libutil/async-collect.cc new file mode 100644 index 000000000..770374d21 --- /dev/null +++ b/tests/unit/libutil/async-collect.cc @@ -0,0 +1,104 @@ +#include "async-collect.hh" + +#include <gtest/gtest.h> +#include <kj/array.h> +#include <kj/async.h> +#include <kj/exception.h> +#include <stdexcept> + +namespace nix { + +TEST(AsyncCollect, void) +{ + kj::EventLoop loop; + kj::WaitScope waitScope(loop); + + auto a = kj::newPromiseAndFulfiller<void>(); + auto b = kj::newPromiseAndFulfiller<void>(); + auto c = kj::newPromiseAndFulfiller<void>(); + auto d = kj::newPromiseAndFulfiller<void>(); + + auto collect = asyncCollect(kj::arr( + std::pair(1, std::move(a.promise)), + std::pair(2, std::move(b.promise)), + std::pair(3, std::move(c.promise)), + std::pair(4, std::move(d.promise)) + )); + + auto p = collect.next(); + ASSERT_FALSE(p.poll(waitScope)); + + // collection is ordered + c.fulfiller->fulfill(); + b.fulfiller->fulfill(); + + ASSERT_TRUE(p.poll(waitScope)); + ASSERT_EQ(p.wait(waitScope), 3); + + p = collect.next(); + ASSERT_TRUE(p.poll(waitScope)); + ASSERT_EQ(p.wait(waitScope), 2); + + p = collect.next(); + ASSERT_FALSE(p.poll(waitScope)); + + // exceptions propagate + a.fulfiller->rejectIfThrows([] { throw std::runtime_error("test"); }); + + p = collect.next(); + ASSERT_TRUE(p.poll(waitScope)); + ASSERT_THROW(p.wait(waitScope), kj::Exception); + + // first exception aborts collection + p = collect.next(); + ASSERT_TRUE(p.poll(waitScope)); + ASSERT_THROW(p.wait(waitScope), kj::Exception); +} + +TEST(AsyncCollect, nonVoid) +{ + kj::EventLoop loop; + kj::WaitScope waitScope(loop); + + auto a = kj::newPromiseAndFulfiller<int>(); + auto b = kj::newPromiseAndFulfiller<int>(); + auto c = kj::newPromiseAndFulfiller<int>(); + auto d = kj::newPromiseAndFulfiller<int>(); + + auto collect = asyncCollect(kj::arr( + std::pair(1, std::move(a.promise)), + std::pair(2, std::move(b.promise)), + std::pair(3, std::move(c.promise)), + std::pair(4, std::move(d.promise)) + )); + + auto p = collect.next(); + ASSERT_FALSE(p.poll(waitScope)); + + // collection is ordered + c.fulfiller->fulfill(1); + b.fulfiller->fulfill(2); + + ASSERT_TRUE(p.poll(waitScope)); + ASSERT_EQ(p.wait(waitScope), std::pair(3, 1)); + + p = collect.next(); + ASSERT_TRUE(p.poll(waitScope)); + ASSERT_EQ(p.wait(waitScope), std::pair(2, 2)); + + p = collect.next(); + ASSERT_FALSE(p.poll(waitScope)); + + // exceptions propagate + a.fulfiller->rejectIfThrows([] { throw std::runtime_error("test"); }); + + p = collect.next(); + ASSERT_TRUE(p.poll(waitScope)); + ASSERT_THROW(p.wait(waitScope), kj::Exception); + + // first exception aborts collection + p = collect.next(); + ASSERT_TRUE(p.poll(waitScope)); + ASSERT_THROW(p.wait(waitScope), kj::Exception); +} +} diff --git a/tests/unit/libutil/async-semaphore.cc b/tests/unit/libutil/async-semaphore.cc new file mode 100644 index 000000000..12b52885d --- /dev/null +++ b/tests/unit/libutil/async-semaphore.cc @@ -0,0 +1,74 @@ +#include "async-semaphore.hh" + +#include <gtest/gtest.h> +#include <kj/async.h> + +namespace nix { + +TEST(AsyncSemaphore, counting) +{ + kj::EventLoop loop; + kj::WaitScope waitScope(loop); + + AsyncSemaphore sem(2); + + ASSERT_EQ(sem.available(), 2); + ASSERT_EQ(sem.used(), 0); + + auto a = kj::evalNow([&] { return sem.acquire(); }); + ASSERT_EQ(sem.available(), 1); + ASSERT_EQ(sem.used(), 1); + auto b = kj::evalNow([&] { return sem.acquire(); }); + ASSERT_EQ(sem.available(), 0); + ASSERT_EQ(sem.used(), 2); + + auto c = kj::evalNow([&] { return sem.acquire(); }); + auto d = kj::evalNow([&] { return sem.acquire(); }); + + ASSERT_TRUE(a.poll(waitScope)); + ASSERT_TRUE(b.poll(waitScope)); + ASSERT_FALSE(c.poll(waitScope)); + ASSERT_FALSE(d.poll(waitScope)); + + a = nullptr; + ASSERT_TRUE(c.poll(waitScope)); + ASSERT_FALSE(d.poll(waitScope)); + + { + auto lock = b.wait(waitScope); + ASSERT_FALSE(d.poll(waitScope)); + } + + ASSERT_TRUE(d.poll(waitScope)); + + ASSERT_EQ(sem.available(), 0); + ASSERT_EQ(sem.used(), 2); + c = nullptr; + ASSERT_EQ(sem.available(), 1); + ASSERT_EQ(sem.used(), 1); + d = nullptr; + ASSERT_EQ(sem.available(), 2); + ASSERT_EQ(sem.used(), 0); +} + +TEST(AsyncSemaphore, cancelledWaiter) +{ + kj::EventLoop loop; + kj::WaitScope waitScope(loop); + + AsyncSemaphore sem(1); + + auto a = kj::evalNow([&] { return sem.acquire(); }); + auto b = kj::evalNow([&] { return sem.acquire(); }); + auto c = kj::evalNow([&] { return sem.acquire(); }); + + ASSERT_TRUE(a.poll(waitScope)); + ASSERT_FALSE(b.poll(waitScope)); + + b = nullptr; + a = nullptr; + + ASSERT_TRUE(c.poll(waitScope)); +} + +} diff --git a/tests/unit/libutil/compression.cc b/tests/unit/libutil/compression.cc index 8d181f53d..6dd8c96df 100644 --- a/tests/unit/libutil/compression.cc +++ b/tests/unit/libutil/compression.cc @@ -1,4 +1,5 @@ #include "compression.hh" +#include <cstddef> #include <gtest/gtest.h> namespace nix { @@ -147,7 +148,7 @@ TEST_P(PerTypeNonNullCompressionTest, truncatedValidInput) /* n.b. This also tests zero-length input, which is also invalid. * As of the writing of this comment, it returns empty output, but is * allowed to throw a compression error instead. */ - for (int i = 0; i < compressed.length(); ++i) { + for (size_t i = 0u; i < compressed.length(); ++i) { auto newCompressed = compressed.substr(compressed.length() - i); try { decompress(method, newCompressed); diff --git a/tests/unit/meson.build b/tests/unit/meson.build index 8ff0b5ec5..9db619c5d 100644 --- a/tests/unit/meson.build +++ b/tests/unit/meson.build @@ -12,7 +12,11 @@ # It's only ~200 lines; better to just refactor the tests themselves which we'll want to do anyway. default_test_env = { - 'ASAN_OPTIONS': 'detect_leaks=0:halt_on_error=1:abort_on_error=1:print_summary=1:dump_instruction_bytes=1' + 'ASAN_OPTIONS': 'detect_leaks=0:halt_on_error=1:abort_on_error=1:print_summary=1:dump_instruction_bytes=1', + # Prevents loading global configuration file in /etc/nix/nix.conf in tests 😱 + 'NIX_CONF_DIR': '/var/empty', + # Prevent loading user configuration files in tests + 'NIX_USER_CONF_FILES': '', } libutil_test_support_sources = files( @@ -39,6 +43,8 @@ liblixutil_test_support = declare_dependency( ) libutil_tests_sources = files( + 'libutil/async-collect.cc', + 'libutil/async-semaphore.cc', 'libutil/canon-path.cc', 'libutil/checked-arithmetic.cc', 'libutil/chunked-vector.cc', @@ -76,6 +82,7 @@ libutil_tester = executable( liblixexpr_mstatic, liblixutil_test_support, nlohmann_json, + kj, ], cpp_pch : cpp_pch, ) @@ -252,7 +259,7 @@ test( env : default_test_env + { # No special meaning here, it's just a file laying around that is unlikely to go anywhere # any time soon. - '_NIX_TEST_UNIT_DATA': meson.project_source_root() / 'src/nix-env/buildenv.nix', + '_NIX_TEST_UNIT_DATA': meson.project_source_root() / 'src/legacy/buildenv.nix', # Use a temporary home directory for the unit tests. # Otherwise, /homeless-shelter is created in the single-user sandbox, and functional tests will fail. # TODO(alois31): handle TMPDIR properly (meson can't, and setting HOME in the test is too late)… @@ -262,9 +269,14 @@ test( protocol : 'gtest', ) +libmain_tests_sources = files( + 'libmain/crash.cc', + 'libmain/progress-bar.cc', +) + libmain_tester = executable( 'liblixmain-tests', - files('libmain/progress-bar.cc'), + libmain_tests_sources, dependencies : [ liblixmain, liblixexpr, |