acr-cli: init at 0.14 (#359508)
[NixPkgs.git] / pkgs / test / texlive / default.nix
blob4efd858594fe2e5e2c076bbe6689807a2a6b72f0
1 { lib, stdenv, buildEnv, runCommand, fetchurl, file, texlive, writeShellScript, writeText, texliveInfraOnly, texliveConTeXt, texliveSmall, texliveMedium, texliveFull }:
3 rec {
5   mkTeXTest = lib.makeOverridable (
6     { name
7     , format
8     , text
9     , texLive ? texliveSmall
10     , options ? "-interaction=errorstopmode"
11     , preTest ? ""
12     , postTest ? ""
13     , ...
14     }@attrs: runCommand "texlive-test-tex-${name}"
15       ({
16         nativeBuildInputs = [ texLive ] ++ attrs.nativeBuildInputs or [ ];
17         text = builtins.toFile "${name}.tex" text;
18       } // builtins.removeAttrs attrs [ "nativeBuildInputs" "text" "texLive" ])
19       ''
20         export HOME="$(mktemp -d)"
21         mkdir "$out"
22         cd "$out"
23         cp "$text" "$name.tex"
24         ${preTest}
25         $format $options "$name.tex"
26         ${postTest}
27       ''
28   );
30   tlpdbNix = runCommand "texlive-test-tlpdb-nix" {
31     nixpkgsTlpdbNix = ../../tools/typesetting/tex/texlive/tlpdb.nix;
32     tlpdbNix = texlive.tlpdb.nix;
33   }
34   ''
35     mkdir -p "$out"
36     diff -u "''${nixpkgsTlpdbNix}" "''${tlpdbNix}" | tee "$out/tlpdb.nix.patch"
37   '';
39   # test two completely different font discovery mechanisms, both of which were once broken:
40   #  - lualatex uses its own luaotfload script (#220228)
41   #  - xelatex uses fontconfig (#228196)
42   opentype-fonts = lib.recurseIntoAttrs rec {
43     lualatex = mkTeXTest {
44       name = "opentype-fonts-lualatex";
45       format = "lualatex";
46       texLive = texliveSmall.withPackages (ps: [ ps.libertinus-fonts ]);
47       text = ''
48         \documentclass{article}
49         \usepackage{fontspec}
50         \setmainfont{Libertinus Serif}
51         \begin{document}
52           \LaTeX{} is great
53         \end{document}
54       '';
55     };
56     xelatex = lualatex.override {
57       name = "opentype-fonts-xelatex";
58       format = "xelatex";
59     };
60   };
62   chktex = runCommand "texlive-test-chktex" {
63     nativeBuildInputs = [
64       (texlive.withPackages (ps: [ ps.chktex ]))
65     ];
66     input = builtins.toFile "chktex-sample.tex" ''
67       \documentclass{article}
68       \begin{document}
69         \LaTeX is great
70       \end{document}
71     '';
72   } ''
73     # chktex is supposed to return 2 when it (successfully) finds warnings, but no errors,
74     # see http://git.savannah.nongnu.org/cgit/chktex.git/commit/?id=ec0fb9b58f02a62ff0bfec98b997208e9d7a5998
75     (set +e; chktex -v -nall -w1 "$input" 2>&1; [ $? = 2 ] || exit 1; set -e)  | tee "$out"
76     # also check that the output does indeed contain "One warning printed"
77     grep "One warning printed" "$out"
78   '';
80   context = mkTeXTest {
81     name = "texlive-test-context";
82     format = "context";
83     texLive = texliveConTeXt;
84     # check that the PDF has been created: we have hit cases of context
85     # failing with exit status 0 due to a misconfigured texlive
86     postTest = ''
87       if [[ ! -f "$name".pdf ]] ; then
88         echo "ConTeXt test failed: file '$name.pdf' not found"
89         exit 1
90       fi
91     '';
92     text = ''
93       \starttext
94       \startsection[title={ConTeXt test document}]
95         This is an {\em incredibly} simple ConTeXt document.
96       \stopsection
97       \stoptext
98     '';
99   };
101   dvipng = lib.recurseIntoAttrs {
102     # https://github.com/NixOS/nixpkgs/issues/75605
103     basic = runCommand "texlive-test-dvipng-basic" {
104       nativeBuildInputs = [ file texliveMedium ];
105       input = fetchurl {
106         name = "test_dvipng.tex";
107         url = "http://git.savannah.nongnu.org/cgit/dvipng.git/plain/test_dvipng.tex?id=b872753590a18605260078f56cbd6f28d39dc035";
108         sha256 = "1pjpf1jvwj2pv5crzdgcrzvbmn7kfmgxa39pcvskl4pa0c9hl88n";
109       };
110     } ''
111       cp "$input" ./document.tex
113       latex document.tex
114       dvipng -T tight -strict -picky document.dvi
115       for f in document*.png; do
116         file "$f" | tee output
117         grep PNG output
118       done
120       mkdir "$out"
121       mv document*.png "$out"/
122     '';
124     # test dvipng's limited capability to render postscript specials via GS
125     ghostscript = runCommand "texlive-test-ghostscript" {
126       nativeBuildInputs = [ file (texliveSmall.withPackages (ps: [ ps.dvipng ])) ];
127       input = builtins.toFile "postscript-sample.tex" ''
128         \documentclass{minimal}
129         \begin{document}
130           Ni
131           \special{ps:
132             newpath
133             0 0 moveto
134             7 7 rlineto
135             0 7 moveto
136             7 -7 rlineto
137             stroke
138             showpage
139           }
140         \end{document}
141       '';
142       gs_trap = writeShellScript "gs_trap.sh" ''
143         exit 1
144       '';
145     } ''
146       cp "$gs_trap" ./gs
147       export PATH=$PWD:$PATH
148       # check that the trap works
149       gs && exit 1
151       cp "$input" ./document.tex
153       latex document.tex
154       dvipng -T 1in,1in -strict -picky document.dvi
155       for f in document*.png; do
156         file "$f" | tee output
157         grep PNG output
158       done
160       mkdir "$out"
161       mv document*.png "$out"/
162     '';
163   };
165   # https://github.com/NixOS/nixpkgs/issues/75070
166   dvisvgm = runCommand "texlive-test-dvisvgm" {
167     nativeBuildInputs = [ file texliveMedium ];
168     input = builtins.toFile "dvisvgm-sample.tex" ''
169       \documentclass{article}
170       \begin{document}
171         mwe
172       \end{document}
173     '';
174   } ''
175     cp "$input" ./document.tex
177     latex document.tex
178     dvisvgm document.dvi -n -o document_dvi.svg
179     cat document_dvi.svg
180     file document_dvi.svg | grep SVG
182     pdflatex document.tex
183     dvisvgm -P document.pdf -n -o document_pdf.svg
184     cat document_pdf.svg
185     file document_pdf.svg | grep SVG
187     mkdir "$out"
188     mv document*.svg "$out"/
189   '';
191   texdoc = runCommand "texlive-test-texdoc" {
192     nativeBuildInputs = [
193       (texlive.withPackages (ps: [ ps.luatex ps.texdoc ps.texdoc.texdoc ]))
194     ];
195   } ''
196     texdoc --version
198     texdoc --debug --list texdoc | tee "$out"
199     grep texdoc.pdf "$out"
200   '';
202   # check that the default language is US English
203   defaultLanguage = lib.recurseIntoAttrs rec {
204     # language.def
205     etex = mkTeXTest {
206       name = "default-language-etex";
207       format = "etex";
208       text = ''
209         \catcode`\@=11
210         \ifnum\language=\lang@USenglish \message{[tests.texlive] Default language is US English.}
211         \else\errmessage{[tests.texlive] Error: default language is NOT US English.}\fi
212         \ifnum\language=0\message{[tests.texlive] Default language has id 0.}
213         \else\errmessage{[tests.texlive] Error: default language does NOT have id 0.}\fi
214         \bye
215       '';
216     };
217     # language.dat
218     latex = mkTeXTest {
219       name = "default-language-latex";
220       format = "latex";
221       text = ''
222         \makeatletter
223         \ifnum\language=\l@USenglish \GenericWarning{}{[tests.texlive] Default language is US English}
224         \else\GenericError{}{[tests.texlive] Error: default language is NOT US English}{}{}\fi
225         \ifnum\language=0\GenericWarning{}{[tests.texlive] Default language has id 0}
226         \else\GenericError{}{[tests.texlive] Error: default language does NOT have id 0}{}{}\fi
227         \stop
228       '';
229     };
230     # language.dat.lua
231     luatex = etex.override {
232       name = "default-language-luatex";
233       format = "luatex";
234     };
235   };
237   # check that all languages are available, including synonyms
238   allLanguages = let hyphenBase = texlive.pkgs.hyphen-base; texLive = texliveFull; in
239     lib.recurseIntoAttrs {
240       # language.def
241       etex = mkTeXTest {
242         name = "all-languages-etex";
243         format = "etex";
244         inherit hyphenBase texLive;
245         text = ''
246           \catcode`\@=11
247           \input kvsetkeys.sty
248           \def\CheckLang#1{
249             \ifcsname lang@#1\endcsname\message{[tests.texlive] Found language #1}
250             \else\errmessage{[tests.texlive] Error: missing language #1}\fi
251           }
252           \comma@parse{@texLanguages@}\CheckLang
253           \bye
254         '';
255         preTest = ''
256           texLanguages="$(sed -n -E 's/^\\addlanguage\s*\{([^}]+)\}.*$/\1/p' < "$hyphenBase"/tex/generic/config/language.def)"
257           texLanguages="''${texLanguages//$'\n'/,}"
258           substituteInPlace "$name.tex" --subst-var texLanguages
259         '';
260       };
261       # language.dat
262       latex = mkTeXTest {
263         name = "all-languages-latex";
264         format = "latex";
265         inherit hyphenBase texLive;
266         text = ''
267           \makeatletter
268           \@for\Lang:=italian,@texLanguages@\do{
269             \ifcsname l@\Lang\endcsname
270               \GenericWarning{}{[tests.texlive] Found language \Lang}
271             \else
272               \GenericError{}{[tests.texlive] Error: missing language \Lang}{}{}
273             \fi
274           }
275           \stop
276         '';
277         preTest = ''
278           texLanguages="$(sed -n -E 's/^([^%= \t]+).*$/\1/p' < "$hyphenBase"/tex/generic/config/language.dat)"
279           texLanguages="''${texLanguages//$'\n'/,}"
280           substituteInPlace "$name.tex" --subst-var texLanguages
281         '';
282       };
283       # language.dat.lua
284       luatex = mkTeXTest {
285         name = "all-languages-luatex";
286         format = "luatex";
287         inherit hyphenBase texLive;
288         text = ''
289           \directlua{
290             require('luatex-hyphen.lua')
291             langs = '@texLanguages@,'
292             texio.write('\string\n')
293             for l in langs:gmatch('([^,]+),') do
294               if luatexhyphen.lookupname(l) \string~= nil then
295                 texio.write('[tests.texlive] Found language '..l..'.\string\n')
296               else
297                 error('[tests.texlive] Error: missing language '..l..'.', 2)
298               end
299             end
300           }
301           \bye
302         '';
303         preTest = ''
304           texLanguages="$(sed -n -E 's/^.*\[("|'\''')(.*)("|'\''')].*$/\2/p' < "$hyphenBase"/tex/generic/config/language.dat.lua)"
305           texLanguages="''${texLanguages//$'\n'/,}"
306           substituteInPlace "$name.tex" --subst-var texLanguages
307         '';
308       };
309     };
311   # test that language files are generated as expected
312   hyphen-base = runCommand "texlive-test-hyphen-base" {
313     hyphenBase = texlive.pkgs.hyphen-base;
314     schemeFull = texliveFull;
315     schemeInfraOnly = texliveInfraOnly;
316   } ''
317     mkdir -p "$out"/{scheme-infraonly,scheme-full}
319     # create language files with no hyphenation patterns
320     cat "$hyphenBase"/tex/generic/config/language.us >language.dat
321     cat "$hyphenBase"/tex/generic/config/language.us.def >language.def
322     cat "$hyphenBase"/tex/generic/config/language.us.lua >language.dat.lua
324     cat >>language.dat.lua <<EOF
325     }
326     EOF
328     cat >>language.def <<EOF
329     %%% No changes may be made beyond this point.
331     \uselanguage {USenglish}             %%% This MUST be the last line of the file.
332     EOF
334     for fname in language.{dat,def,dat.lua} ; do
335       diff --ignore-matching-lines='^\(%\|--\) Generated by ' -u \
336         {"$hyphenBase","$schemeFull"/share/texmf-var}/tex/generic/config/"$fname" \
337         | tee "$out/scheme-full/$fname.patch"
338       diff --ignore-matching-lines='^\(%\|--\) Generated by ' -u \
339         {,"$schemeInfraOnly"/share/texmf-var/tex/generic/config/}"$fname" \
340         | tee "$out/scheme-infraonly/$fname.patch"
341     done
342   '';
344   # verify that the restricted mode gets enabled when
345   # needed (detected by checking if it disallows --gscmd)
346   repstopdf = runCommand "texlive-test-repstopdf" {
347     nativeBuildInputs = [ (texlive.withPackages (ps: [ ps.epstopdf ])) ];
348   } ''
349     ! (epstopdf --gscmd echo /dev/null 2>&1 || true) | grep forbidden >/dev/null
350     (repstopdf --gscmd echo /dev/null 2>&1 || true) | grep forbidden >/dev/null
351     mkdir "$out"
352   '';
354   # verify that the restricted mode gets enabled when
355   # needed (detected by checking if it disallows --gscmd)
356   rpdfcrop = runCommand "texlive-test-rpdfcrop" {
357     nativeBuildInputs = [ (texlive.withPackages (ps: [ ps.pdfcrop ])) ];
358   } ''
359     ! (pdfcrop --gscmd echo $(command -v pdfcrop) 2>&1 || true) | grep 'restricted mode' >/dev/null
360     (rpdfcrop --gscmd echo $(command -v pdfcrop) 2>&1 || true) | grep 'restricted mode' >/dev/null
361     mkdir "$out"
362   '';
364   # check that all binaries run successfully, in the following sense:
365   # (1) run --version, -v, --help, -h successfully; or
366   # (2) run --help, -h, or no argument with error code but show help text; or
367   # (3) run successfully on a test.tex or similar file
368   # we ignore the binaries that cannot be tested as above, and are either
369   # compiled binaries or trivial shell wrappers
370   binaries = let
371       # TODO known broken binaries
372       broken = [
373         # *.inc files in source container rather than run
374         "texaccents"
376         # 'Error initialising QuantumRenderer: no suitable pipeline found'
377         "tlcockpit"
378       ] ++ lib.optional stdenv.hostPlatform.isDarwin "epspdftk";  # wish shebang is a script, not a binary!
380       # (1) binaries requiring -v
381       shortVersion = [ "devnag" "diadia" "pmxchords" "ptex2pdf" "simpdftex" "ttf2afm" ];
382       # (1) binaries requiring --help or -h
383       help = [ "arlatex" "bundledoc" "cachepic" "checklistings" "dvipos" "extractres" "fig4latex" "fragmaster"
384         "kpsewhere" "latex-git-log" "ltxfileinfo" "mendex" "pdflatexpicscale" "perltex" "pn2pdf" "psbook" "psnup" "psresize" "purifyeps"
385         "simpdftex" "tex2xindy" "texluac" "texluajitc" "upmendex" "urlbst" "yplan" ];
386       shortHelp = [ "adhocfilelist" "authorindex" "bbl2bib" "bibdoiadd" "bibmradd" "biburl2doi" "bibzbladd" "bookshelf-listallfonts" "bookshelf-mkfontsel" "ctanupload"
387         "disdvi" "dvibook" "dviconcat" "getmapdl" "latex2man" "listings-ext.sh" "pygmentex" ];
388       # (2) binaries that return non-zero exit code even if correctly asked for help
389       ignoreExitCode = [ "authorindex" "bookshelf-listallfonts" "bookshelf-mkfontsel" "dvibook" "dviconcat" "dvipos" "extractres" "fig4latex" "fragmaster" "latex2man"
390         "latex-git-log" "listings-ext.sh" "psbook" "psnup" "psresize" "purifyeps" "tex2xindy"  "texluac"
391         "texluajitc" ];
392       # (2) binaries that print help on no argument, returning non-zero exit code
393       noArg = [ "a2ping" "bg5+latex" "bg5+pdflatex" "bg5latex" "bg5pdflatex" "cef5latex" "cef5pdflatex" "ceflatex"
394         "cefpdflatex" "cefslatex" "cefspdflatex" "chkdvifont" "dvi2fax" "dvired" "dviselect" "dvitodvi" "epsffit"
395         "findhyph" "gbklatex" "gbkpdflatex" "komkindex" "kpsepath" "listbib" "listings-ext" "mag" "mathspic" "mf2pt1"
396         "mk4ht" "mkt1font" "mkgrkindex" "musixflx" "pdf2ps" "pdfclose" "pdftosrc" "pdfxup" "pedigree" "pfb2pfa" "pk2bm"
397         "prepmx" "ps2pk" "psselect" "pstops" "rubibtex" "rubikrotation" "sjislatex" "sjispdflatex" "srcredact" "t4ht"
398         "teckit_compile" "tex4ht" "texdiff" "texdirflatten" "texplate" "tie" "ttf2kotexfont" "ttfdump" "vlna" "vpl2ovp"
399         "vpl2vpl" "yplan" ];
400       # (3) binaries requiring a .tex file
401       contextTest = [ "htcontext" ];
402       latexTest = [ "de-macro" "e2pall" "htlatex" "htxelatex" "makeindex" "pslatex" "rumakeindex" "tpic2pdftex"
403         "wordcount" "xhlatex" ];
404       texTest = [ "fontinst" "htmex" "httex" "httexi" "htxetex" ];
405       # tricky binaries or scripts that are obviously working but are hard to test
406       # (e.g. because they expect user input no matter the arguments)
407       # (printafm comes from ghostscript, not texlive)
408       ignored = [
409         # compiled binaries
410         "dt2dv" "dv2dt" "dvi2tty" "dvidvi" "dvispc" "otp2ocp" "outocp" "pmxab"
412         # GUI scripts that accept no argument or crash without a graphics server; please test manualy
413         "epspdftk" "texdoctk" "tlshell" "xasy"
415         # requires Cinderella, not open source and not distributed via Nixpkgs
416         "ketcindy"
417       ];
418       # binaries that need a combined scheme and cannot work standalone
419       needScheme = [
420         # pfarrei: require working kpse to find lua module
421         "a5toa4"
423         # bibexport: requires kpsewhich
424         "bibexport"
426         # crossrefware: require bibtexperllibs under TEXMFROOT
427         "bbl2bib" "bibdoiadd" "bibmradd" "biburl2doi" "bibzbladd" "checkcites" "ltx2crossrefxml"
429         # epstopdf: requires kpsewhich
430         "epstopdf" "repstopdf"
432         # requires kpsewhich
433         "memoize-extract.pl" "memoize-extract.py"
435         # require other texlive binaries in PATH
436         "allcm" "allec" "chkweb" "fontinst" "ht*" "installfont-tl" "kanji-config-updmap-sys" "kanji-config-updmap-user"
437         "kpse*" "latexfileversion" "mkocp" "mkofm" "mtxrunjit" "pdftex-quiet" "pslatex" "rumakeindex" "texconfig"
438         "texconfig-sys" "texexec" "texlinks" "texmfstart" "typeoutfileinfo" "wordcount" "xdvi" "xhlatex"
440         # misc luatex binaries searching for luatex in PATH
441         "citeproc-lua" "context" "contextjit" "ctanbib" "digestif" "epspdf" "l3build" "luafindfont" "luaotfload-tool"
442         "luatools" "make4ht" "pmxchords" "tex4ebook" "texblend" "texdoc" "texfindpkg" "texlogsieve" "xindex"
444         # requires full TEXMFROOT (e.g. for config)
445         "mktexfmt" "mktexmf" "mktexpk" "mktextfm" "psnup" "psresize" "pstops" "tlmgr" "updmap" "webquiz"
447         # texlive-scripts: requires texlive.infra's TeXLive::TLUtils under TEXMFROOT
448         "fmtutil" "fmtutil-sys" "fmtutil-user"
450         # texlive-scripts: not used in nixpkgs, need updmap in PATH
451         "updmap-sys" "updmap-user"
452       ];
454       # simple test files
455       contextTestTex = writeText "context-test.tex" ''
456         \starttext
457           A simple test file.
458         \stoptext
459       '';
460       latexTestTex = writeText "latex-test.tex" ''
461         \documentclass{article}
462         \begin{document}
463           A simple test file.
464         \end{document}
465       '';
466       texTestTex = writeText "tex-test.tex" ''
467         Hello.
468         \bye
469       '';
471       # link all binaries in single derivation
472       binPackages = lib.catAttrs "out" (lib.attrValues texlive.pkgs);
473       binaries = buildEnv { name = "texlive-binaries"; paths = binPackages; };
474     in
475     runCommand "texlive-test-binaries"
476       {
477         inherit binaries contextTestTex latexTestTex texTestTex;
478         texliveScheme = texliveFull;
479       }
480       ''
481         loadables="$(command -v bash)"
482         loadables="''${loadables%/bin/bash}/lib/bash"
483         enable -f "$loadables/realpath" realpath
484         mkdir -p "$out"
485         export HOME="$(mktemp -d)"
486         declare -i binCount=0 ignoredCount=0 brokenCount=0 failedCount=0
487         cp "$contextTestTex" context-test.tex
488         cp "$latexTestTex" latex-test.tex
489         cp "$texTestTex" tex-test.tex
491         testBin () {
492           path="$(realpath "$bin")"
493           path="''${path##*/}"
494           if [[ -z "$ignoreExitCode" ]] ; then
495             PATH="$path" "$bin" $args >"$out/$base.log" 2>&1
496             ret=$?
497             if [[ $ret == 0 ]] && grep -i 'command not found' "$out/$base.log" >/dev/null ; then
498               echo "command not found when running '$base''${args:+ $args}'"
499               return 1
500             fi
501             return $ret
502           else
503             PATH="$path" "$bin" $args >"$out/$base.log" 2>&1
504             ret=$?
505             if [[ $ret == 0 ]] && grep -i 'command not found' "$out/$base.log" >/dev/null ; then
506               echo "command not found when running '$base''${args:+ $args}'"
507               return 1
508             fi
509             if ! grep -Ei '(Example:|Options:|Syntax:|Usage:|improper command|SYNOPSIS)' "$out/$base.log" >/dev/null ; then
510               echo "did not find usage info when running '$base''${args:+ $args}'"
511               return $ret
512             fi
513           fi
514         }
516         for bin in "$binaries"/bin/* ; do
517           base="''${bin##*/}"
518           args=
519           ignoreExitCode=
520           binCount=$((binCount + 1))
522           # ignore non-executable files (such as context.lua)
523           if [[ ! -x "$bin" ]] ; then
524             ignoredCount=$((ignoredCount + 1))
525             continue
526           fi
528           case "$base" in
529             ${lib.concatStringsSep "|" ignored})
530               ignoredCount=$((ignoredCount + 1))
531               continue ;;
532             ${lib.concatStringsSep "|" broken})
533               brokenCount=$((brokenCount + 1))
534               continue ;;
535             ${lib.concatStringsSep "|" help})
536               args=--help ;;
537             ${lib.concatStringsSep "|" shortHelp})
538               args=-h ;;
539             ${lib.concatStringsSep "|" noArg})
540               ;;
541             ${lib.concatStringsSep "|" contextTest})
542               args=context-test.tex ;;
543             ${lib.concatStringsSep "|" latexTest})
544               args=latex-test.tex ;;
545             ${lib.concatStringsSep "|" texTest})
546               args=tex-test.tex ;;
547             ${lib.concatStringsSep "|" shortVersion})
548               args=-v ;;
549             ebong)
550               touch empty
551               args=empty ;;
552             ht)
553               args='latex latex-test.tex' ;;
554             pdf2dsc)
555               args='--help --help --help' ;;
556             typeoutfileinfo)
557               args=/dev/null ;;
558             *)
559               args=--version ;;
560           esac
562           case "$base" in
563             ${lib.concatStringsSep "|" (ignoreExitCode ++ noArg)})
564               ignoreExitCode=1 ;;
565           esac
567           case "$base" in
568             ${lib.concatStringsSep "|" needScheme})
569               bin="$texliveScheme/bin/$base"
570               if [[ ! -f "$bin" ]] ; then
571                 ignoredCount=$((ignoredCount + 1))
572                 continue
573               fi ;;
574           esac
576           if testBin ; then : ; else # preserve exit code
577             echo "failed '$base''${args:+ $args}' (exit code: $?)"
578             sed 's/^/  > /' < "$out/$base.log"
579             failedCount=$((failedCount + 1))
580           fi
581         done
583         echo "tested $binCount binaries: $ignoredCount ignored, $brokenCount broken, $failedCount failed"
584         [[ $failedCount = 0 ]]
585       '';
587   # check that all scripts have a Nix shebang
588   shebangs = let
589       binPackages = lib.catAttrs "out" (lib.attrValues texlive.pkgs);
590     in
591     runCommand "texlive-test-shebangs" { }
592       (''
593         echo "checking that all texlive scripts shebangs are in '$NIX_STORE'"
594         declare -i scriptCount=0 invalidCount=0
595       '' +
596       (lib.concatMapStrings
597         (pkg: ''
598           for bin in '${pkg.outPath}'/bin/* ; do
599             grep -I -q . "$bin" || continue  # ignore binary files
600             [[ -x "$bin" ]] || continue # ignore non-executable files (such as context.lua)
601             scriptCount=$((scriptCount + 1))
602             read -r cmdline < "$bin"
603             read -r interp <<< "$cmdline"
604             if [[ "$interp" != "#!$NIX_STORE"/* && "$interp" != "#! $NIX_STORE"/* ]] ; then
605               echo "error: non-nix shebang '$interp' in script '$bin'"
606               invalidCount=$((invalidCount + 1))
607             fi
608           done
609         '')
610         binPackages)
611       + ''
612         echo "checked $scriptCount scripts, found $invalidCount non-nix shebangs"
613         [[ $invalidCount -gt 0 ]] && exit 1
614         mkdir -p "$out"
615       ''
616       );
618   # verify that the precomputed licensing information in default.nix
619   # does indeed match the metadata of the individual packages.
620   #
621   # This is part of the test suite (and not the normal evaluation) to save
622   # time for "normal" evaluations. To be more in line with the other tests, this
623   # also builds a derivation, even though it is essentially an eval-time assertion.
624   licenses =
625     let
626         concatLicenses = builtins.foldl' (acc: el: if builtins.elem el acc then acc else acc ++ [ el ]);
627         # converts a license to its attribute name in lib.licenses
628         licenseToAttrName = license:
629           builtins.head (builtins.attrNames
630             (lib.filterAttrs (n: v: license == v) lib.licenses));
631         lt = (a: b: a < b);
633         savedLicenses = scheme: scheme.meta.license;
634         savedLicensesAttrNames = scheme: map licenseToAttrName (savedLicenses scheme);
636         correctLicenses = scheme: builtins.foldl'
637                 (acc: pkg: concatLicenses acc (lib.toList (pkg.meta.license or [])))
638                 []
639                 scheme.passthru.requiredTeXPackages;
640         correctLicensesAttrNames = scheme:
641           lib.sort lt
642             (map licenseToAttrName (correctLicenses scheme));
644         hasLicenseMismatch = scheme:
645           (lib.isDerivation scheme) &&
646           (savedLicensesAttrNames scheme) != (correctLicensesAttrNames scheme);
647         incorrectSchemes = lib.filterAttrs
648           (n: hasLicenseMismatch)
649           (texlive.combined // texlive.schemes);
650         prettyPrint = name: scheme:
651           ''
652             license info for ${name} is incorrect! Note that order is enforced.
653             saved: [ ${lib.concatStringsSep " " (savedLicensesAttrNames scheme)} ]
654             correct: [ ${lib.concatStringsSep " " (correctLicensesAttrNames scheme)} ]
655           '';
656         errorText = lib.concatStringsSep "\n\n" (lib.mapAttrsToList prettyPrint incorrectSchemes);
657       in
658         runCommand "texlive-test-license" {
659           inherit errorText;
660         }
661         (if (incorrectSchemes == {})
662         then "echo everything is fine! > $out"
663         else ''
664           echo "$errorText"
665           false
666         '');
668   # verify that all fixed hashes are present
669   # this is effectively an eval-time assertion, converted into a derivation for
670   # ease of testing
671   fixedHashes = let
672     fods = lib.concatMap
673       (p: lib.optional (p ? tex && lib.isDerivation p.tex) p.tex
674         ++ lib.optional (p ? texdoc) p.texdoc
675         ++ lib.optional (p ? texsource) p.texsource
676         ++ lib.optional (p ? tlpkg) p.tlpkg)
677       (lib.attrValues texlive.pkgs);
678     errorText = lib.concatMapStrings (p: lib.optionalString (! p ? outputHash) "${p.pname}-${p.tlOutputName} does not have a fixed output hash\n") fods;
679   in runCommand "texlive-test-fixed-hashes" {
680     inherit errorText;
681     passAsFile = [ "errorText" ];
682   } ''
683     if [[ -s "$errorTextPath" ]] ; then
684       cat "$errorTextPath"
685       echo Failed: some TeX Live packages do not have fixed output hashes. Please read UPGRADING.md for how to generate a new fixed-hashes.nix.
686       exit 1
687     else
688       touch "$out"
689     fi
690   '';